Use BT2100 OOTF for HLG...
...which it turns out we already do, except we apply a tone-map for
HDR10 after scaling the luminance. Don't apply the HDR10 tone-map, and
instead linearly normalize to max display luminance.
Furthermore, adjust the gamma used in the default HLG OOTF in
libshaders to take into account current display luminance according to
the BT2100 spec, which says that the OOTF gamma should be adjusted if
the effective luminance differs from 1000 nits
Bug: 208933319
Test: librenderengine_test
Test: libtonemap_test
Test: HLG and PQ test videos on youtube
Change-Id: I622096ad387420ce4769f6f080b8756cd57baa7d
diff --git a/libs/renderengine/tests/RenderEngineTest.cpp b/libs/renderengine/tests/RenderEngineTest.cpp
index 612a0aa..e197150 100644
--- a/libs/renderengine/tests/RenderEngineTest.cpp
+++ b/libs/renderengine/tests/RenderEngineTest.cpp
@@ -49,6 +49,50 @@
namespace android {
namespace renderengine {
+namespace {
+
+double EOTF_PQ(double channel) {
+ float m1 = (2610.0 / 4096.0) / 4.0;
+ float m2 = (2523.0 / 4096.0) * 128.0;
+ float c1 = (3424.0 / 4096.0);
+ float c2 = (2413.0 / 4096.0) * 32.0;
+ float c3 = (2392.0 / 4096.0) * 32.0;
+
+ float tmp = std::pow(std::clamp(channel, 0.0, 1.0), 1.0 / m2);
+ tmp = std::fmax(tmp - c1, 0.0) / (c2 - c3 * tmp);
+ return std::pow(tmp, 1.0 / m1);
+}
+
+vec3 EOTF_PQ(vec3 color) {
+ return vec3(EOTF_PQ(color.r), EOTF_PQ(color.g), EOTF_PQ(color.b));
+}
+
+double EOTF_HLG(double channel) {
+ const float a = 0.17883277;
+ const float b = 0.28466892;
+ const float c = 0.55991073;
+ return channel <= 0.5 ? channel * channel / 3.0 : (exp((channel - c) / a) + b) / 12.0;
+}
+
+vec3 EOTF_HLG(vec3 color) {
+ return vec3(EOTF_HLG(color.r), EOTF_HLG(color.g), EOTF_HLG(color.b));
+}
+
+double OETF_sRGB(double channel) {
+ return channel <= 0.0031308 ? channel * 12.92 : (pow(channel, 1.0 / 2.4) * 1.055) - 0.055;
+}
+
+int sign(float in) {
+ return in >= 0.0 ? 1 : -1;
+}
+
+vec3 OETF_sRGB(vec3 linear) {
+ return vec3(sign(linear.r) * OETF_sRGB(linear.r), sign(linear.g) * OETF_sRGB(linear.g),
+ sign(linear.b) * OETF_sRGB(linear.b));
+}
+
+} // namespace
+
class RenderEngineFactory {
public:
virtual ~RenderEngineFactory() = default;
@@ -598,6 +642,12 @@
const renderengine::ShadowSettings& shadow,
const ubyte4& backgroundColor);
+ // Tonemaps grey values from sourceDataspace -> Display P3 and checks that GPU and CPU
+ // implementations are identical Also implicitly checks that the injected tonemap shader
+ // compiles
+ void tonemap(ui::Dataspace sourceDataspace, std::function<vec3(vec3)> eotf,
+ std::function<vec3(vec3, float)> scaleOotf);
+
void initializeRenderEngine();
std::unique_ptr<renderengine::RenderEngine> mRE;
@@ -1418,6 +1468,119 @@
invokeDraw(settings, layers);
}
+void RenderEngineTest::tonemap(ui::Dataspace sourceDataspace, std::function<vec3(vec3)> eotf,
+ std::function<vec3(vec3, float)> scaleOotf) {
+ constexpr int32_t kGreyLevels = 256;
+
+ const auto rect = Rect(0, 0, kGreyLevels, 1);
+
+ constexpr float kMaxLuminance = 750.f;
+ constexpr float kCurrentLuminanceNits = 500.f;
+ const renderengine::DisplaySettings display{
+ .physicalDisplay = rect,
+ .clip = rect,
+ .maxLuminance = kMaxLuminance,
+ .currentLuminanceNits = kCurrentLuminanceNits,
+ .outputDataspace = ui::Dataspace::DISPLAY_P3,
+ };
+
+ auto buf = std::make_shared<
+ renderengine::impl::
+ ExternalTexture>(new GraphicBuffer(kGreyLevels, 1, HAL_PIXEL_FORMAT_RGBA_8888,
+ 1,
+ GRALLOC_USAGE_SW_READ_OFTEN |
+ GRALLOC_USAGE_SW_WRITE_OFTEN |
+ GRALLOC_USAGE_HW_RENDER |
+ GRALLOC_USAGE_HW_TEXTURE,
+ "input"),
+ *mRE,
+ renderengine::impl::ExternalTexture::Usage::READABLE |
+ renderengine::impl::ExternalTexture::Usage::WRITEABLE);
+ ASSERT_EQ(0, buf->getBuffer()->initCheck());
+ {
+ uint8_t* pixels;
+ buf->getBuffer()->lock(GRALLOC_USAGE_SW_READ_OFTEN | GRALLOC_USAGE_SW_WRITE_OFTEN,
+ reinterpret_cast<void**>(&pixels));
+
+ uint8_t color = 0;
+ for (int32_t j = 0; j < buf->getBuffer()->getHeight(); j++) {
+ uint8_t* dest = pixels + (buf->getBuffer()->getStride() * j * 4);
+ for (int32_t i = 0; i < buf->getBuffer()->getWidth(); i++) {
+ dest[0] = color;
+ dest[1] = color;
+ dest[2] = color;
+ dest[3] = 255;
+ color++;
+ dest += 4;
+ }
+ }
+ buf->getBuffer()->unlock();
+ }
+
+ mBuffer = std::make_shared<
+ renderengine::impl::
+ ExternalTexture>(new GraphicBuffer(kGreyLevels, 1, HAL_PIXEL_FORMAT_RGBA_8888,
+ 1,
+ GRALLOC_USAGE_SW_READ_OFTEN |
+ GRALLOC_USAGE_SW_WRITE_OFTEN |
+ GRALLOC_USAGE_HW_RENDER |
+ GRALLOC_USAGE_HW_TEXTURE,
+ "output"),
+ *mRE,
+ renderengine::impl::ExternalTexture::Usage::READABLE |
+ renderengine::impl::ExternalTexture::Usage::WRITEABLE);
+ ASSERT_EQ(0, mBuffer->getBuffer()->initCheck());
+
+ const renderengine::LayerSettings layer{.geometry.boundaries = rect.toFloatRect(),
+ .source =
+ renderengine::PixelSource{
+ .buffer =
+ renderengine::Buffer{
+ .buffer =
+ std::move(buf),
+ .usePremultipliedAlpha =
+ true,
+ },
+ },
+ .alpha = 1.0f,
+ .sourceDataspace = sourceDataspace};
+
+ std::vector<renderengine::LayerSettings> layers{layer};
+ invokeDraw(display, layers);
+
+ ColorSpace displayP3 = ColorSpace::DisplayP3();
+ ColorSpace bt2020 = ColorSpace::BT2020();
+
+ tonemap::Metadata metadata{.displayMaxLuminance = 750.0f};
+
+ auto generator = [=](Point location) {
+ const double normColor = static_cast<double>(location.x) / (kGreyLevels - 1);
+ const vec3 rgb = vec3(normColor, normColor, normColor);
+
+ const vec3 linearRGB = eotf(rgb);
+
+ const vec3 xyz = bt2020.getRGBtoXYZ() * linearRGB;
+
+ const vec3 scaledXYZ = scaleOotf(xyz, kCurrentLuminanceNits);
+ const double gain =
+ tonemap::getToneMapper()
+ ->lookupTonemapGain(static_cast<aidl::android::hardware::graphics::common::
+ Dataspace>(sourceDataspace),
+ static_cast<aidl::android::hardware::graphics::common::
+ Dataspace>(
+ ui::Dataspace::DISPLAY_P3),
+ scaleOotf(linearRGB, kCurrentLuminanceNits), scaledXYZ,
+ metadata);
+ const vec3 normalizedXYZ = scaledXYZ * gain / metadata.displayMaxLuminance;
+
+ const vec3 targetRGB = OETF_sRGB(displayP3.getXYZtoRGB() * normalizedXYZ) * 255;
+ return ubyte4(static_cast<uint8_t>(targetRGB.r), static_cast<uint8_t>(targetRGB.g),
+ static_cast<uint8_t>(targetRGB.b), 255);
+ };
+
+ expectBufferColor(Rect(kGreyLevels, 1), generator, 2);
+}
+
INSTANTIATE_TEST_SUITE_P(PerRenderEngineType, RenderEngineTest,
testing::Values(std::make_shared<GLESRenderEngineFactory>(),
std::make_shared<GLESCMRenderEngineFactory>(),
@@ -2412,155 +2575,47 @@
}
}
-double EOTF_PQ(double channel) {
- float m1 = (2610.0 / 4096.0) / 4.0;
- float m2 = (2523.0 / 4096.0) * 128.0;
- float c1 = (3424.0 / 4096.0);
- float c2 = (2413.0 / 4096.0) * 32.0;
- float c3 = (2392.0 / 4096.0) * 32.0;
-
- float tmp = std::pow(std::clamp(channel, 0.0, 1.0), 1.0 / m2);
- tmp = std::fmax(tmp - c1, 0.0) / (c2 - c3 * tmp);
- return std::pow(tmp, 1.0 / m1);
-}
-
-vec3 EOTF_PQ(vec3 color) {
- return vec3(EOTF_PQ(color.r), EOTF_PQ(color.g), EOTF_PQ(color.b));
-}
-
-double OETF_sRGB(double channel) {
- return channel <= 0.0031308 ? channel * 12.92 : (pow(channel, 1.0 / 2.4) * 1.055) - 0.055;
-}
-
-int sign(float in) {
- return in >= 0.0 ? 1 : -1;
-}
-
-vec3 OETF_sRGB(vec3 linear) {
- return vec3(sign(linear.r) * OETF_sRGB(linear.r), sign(linear.g) * OETF_sRGB(linear.g),
- sign(linear.b) * OETF_sRGB(linear.b));
-}
-
TEST_P(RenderEngineTest, test_tonemapPQMatches) {
if (!GetParam()->useColorManagement()) {
- return;
+ GTEST_SKIP();
}
if (GetParam()->type() == renderengine::RenderEngine::RenderEngineType::GLES) {
- return;
+ GTEST_SKIP();
}
initializeRenderEngine();
- constexpr int32_t kGreyLevels = 256;
+ tonemap(
+ static_cast<ui::Dataspace>(HAL_DATASPACE_STANDARD_BT2020 |
+ HAL_DATASPACE_TRANSFER_ST2084 | HAL_DATASPACE_RANGE_FULL),
+ [](vec3 color) { return EOTF_PQ(color); },
+ [](vec3 color, float) {
+ static constexpr float kMaxPQLuminance = 10000.f;
+ return color * kMaxPQLuminance;
+ });
+}
- const auto rect = Rect(0, 0, kGreyLevels, 1);
- const renderengine::DisplaySettings display{
- .physicalDisplay = rect,
- .clip = rect,
- .maxLuminance = 750.0f,
- .outputDataspace = ui::Dataspace::DISPLAY_P3,
- };
-
- auto buf = std::make_shared<
- renderengine::impl::
- ExternalTexture>(new GraphicBuffer(kGreyLevels, 1, HAL_PIXEL_FORMAT_RGBA_8888,
- 1,
- GRALLOC_USAGE_SW_READ_OFTEN |
- GRALLOC_USAGE_SW_WRITE_OFTEN |
- GRALLOC_USAGE_HW_RENDER |
- GRALLOC_USAGE_HW_TEXTURE,
- "input"),
- *mRE,
- renderengine::impl::ExternalTexture::Usage::READABLE |
- renderengine::impl::ExternalTexture::Usage::WRITEABLE);
- ASSERT_EQ(0, buf->getBuffer()->initCheck());
-
- {
- uint8_t* pixels;
- buf->getBuffer()->lock(GRALLOC_USAGE_SW_READ_OFTEN | GRALLOC_USAGE_SW_WRITE_OFTEN,
- reinterpret_cast<void**>(&pixels));
-
- uint8_t color = 0;
- for (int32_t j = 0; j < buf->getBuffer()->getHeight(); j++) {
- uint8_t* dest = pixels + (buf->getBuffer()->getStride() * j * 4);
- for (int32_t i = 0; i < buf->getBuffer()->getWidth(); i++) {
- dest[0] = color;
- dest[1] = color;
- dest[2] = color;
- dest[3] = 255;
- color++;
- dest += 4;
- }
- }
- buf->getBuffer()->unlock();
+TEST_P(RenderEngineTest, test_tonemapHLGMatches) {
+ if (!GetParam()->useColorManagement()) {
+ GTEST_SKIP();
}
- mBuffer = std::make_shared<
- renderengine::impl::
- ExternalTexture>(new GraphicBuffer(kGreyLevels, 1, HAL_PIXEL_FORMAT_RGBA_8888,
- 1,
- GRALLOC_USAGE_SW_READ_OFTEN |
- GRALLOC_USAGE_SW_WRITE_OFTEN |
- GRALLOC_USAGE_HW_RENDER |
- GRALLOC_USAGE_HW_TEXTURE,
- "output"),
- *mRE,
- renderengine::impl::ExternalTexture::Usage::READABLE |
- renderengine::impl::ExternalTexture::Usage::WRITEABLE);
- ASSERT_EQ(0, mBuffer->getBuffer()->initCheck());
+ if (GetParam()->type() == renderengine::RenderEngine::RenderEngineType::GLES) {
+ GTEST_SKIP();
+ }
- const renderengine::LayerSettings layer{
- .geometry.boundaries = rect.toFloatRect(),
- .source =
- renderengine::PixelSource{
- .buffer =
- renderengine::Buffer{
- .buffer = std::move(buf),
- .usePremultipliedAlpha = true,
- },
- },
- .alpha = 1.0f,
- .sourceDataspace = static_cast<ui::Dataspace>(HAL_DATASPACE_STANDARD_BT2020 |
- HAL_DATASPACE_TRANSFER_ST2084 |
- HAL_DATASPACE_RANGE_FULL),
- };
+ initializeRenderEngine();
- std::vector<renderengine::LayerSettings> layers{layer};
- invokeDraw(display, layers);
-
- ColorSpace displayP3 = ColorSpace::DisplayP3();
- ColorSpace bt2020 = ColorSpace::BT2020();
-
- tonemap::Metadata metadata{.displayMaxLuminance = 750.0f};
-
- auto generator = [=](Point location) {
- const double normColor = static_cast<double>(location.x) / (kGreyLevels - 1);
- const vec3 rgb = vec3(normColor, normColor, normColor);
-
- const vec3 linearRGB = EOTF_PQ(rgb);
-
- static constexpr float kMaxPQLuminance = 10000.f;
- const vec3 xyz = bt2020.getRGBtoXYZ() * linearRGB * kMaxPQLuminance;
- const double gain =
- tonemap::getToneMapper()
- ->lookupTonemapGain(static_cast<aidl::android::hardware::graphics::common::
- Dataspace>(
- HAL_DATASPACE_STANDARD_BT2020 |
- HAL_DATASPACE_TRANSFER_ST2084 |
- HAL_DATASPACE_RANGE_FULL),
- static_cast<aidl::android::hardware::graphics::common::
- Dataspace>(
- ui::Dataspace::DISPLAY_P3),
- linearRGB * 10000.0, xyz, metadata);
- const vec3 scaledXYZ = xyz * gain / metadata.displayMaxLuminance;
-
- const vec3 targetRGB = OETF_sRGB(displayP3.getXYZtoRGB() * scaledXYZ) * 255;
- return ubyte4(static_cast<uint8_t>(targetRGB.r), static_cast<uint8_t>(targetRGB.g),
- static_cast<uint8_t>(targetRGB.b), 255);
- };
-
- expectBufferColor(Rect(kGreyLevels, 1), generator, 2);
+ tonemap(
+ static_cast<ui::Dataspace>(HAL_DATASPACE_STANDARD_BT2020 | HAL_DATASPACE_TRANSFER_HLG |
+ HAL_DATASPACE_RANGE_FULL),
+ [](vec3 color) { return EOTF_HLG(color); },
+ [](vec3 color, float currentLuminaceNits) {
+ static constexpr float kMaxHLGLuminance = 1000.f;
+ static const float kHLGGamma = 1.2 + 0.42 * std::log10(currentLuminaceNits / 1000);
+ return color * kMaxHLGLuminance * std::pow(color.y, kHLGGamma - 1);
+ });
}
TEST_P(RenderEngineTest, r8_behaves_as_mask) {