summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Alec Mouri <alecmouri@google.com> 2022-01-26 16:43:02 -0800
committer Alec Mouri <alecmouri@google.com> 2022-02-01 00:25:41 +0000
commit5a49372252e8ef08387800effe6d39196f274027 (patch)
tree00733a2feb02dd8b067cca7730a3f8d5286a570a
parentd029092f6bdba62f9fc9d6e0c180b0bb0b0d1111 (diff)
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
-rw-r--r--libs/renderengine/tests/RenderEngineTest.cpp325
-rw-r--r--libs/shaders/shaders.cpp61
-rw-r--r--libs/tonemap/include/tonemap/tonemap.h2
-rw-r--r--libs/tonemap/tests/tonemap_test.cpp14
-rw-r--r--libs/tonemap/tonemap.cpp101
5 files changed, 290 insertions, 213 deletions
diff --git a/libs/renderengine/tests/RenderEngineTest.cpp b/libs/renderengine/tests/RenderEngineTest.cpp
index 612a0aabdc..e197150afe 100644
--- a/libs/renderengine/tests/RenderEngineTest.cpp
+++ b/libs/renderengine/tests/RenderEngineTest.cpp
@@ -49,6 +49,50 @@ constexpr bool WRITE_BUFFER_TO_FILE_ON_FAILURE = false;
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 @@ public:
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 @@ void RenderEngineTest::drawShadowWithoutCaster(const FloatRect& castingBounds,
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 @@ TEST_P(RenderEngineTest, test_isOpaque) {
}
}
-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;
-
- 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));
+ 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;
+ });
+}
- 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());
-
- 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),
- };
-
- 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;
+ if (GetParam()->type() == renderengine::RenderEngine::RenderEngineType::GLES) {
+ GTEST_SKIP();
+ }
- 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);
- };
+ initializeRenderEngine();
- 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) {
diff --git a/libs/shaders/shaders.cpp b/libs/shaders/shaders.cpp
index 4d88d5d417..03da3ecd62 100644
--- a/libs/shaders/shaders.cpp
+++ b/libs/shaders/shaders.cpp
@@ -18,6 +18,7 @@
#include <tonemap/tonemap.h>
+#include <cmath>
#include <optional>
#include <math/mat4.h>
@@ -26,12 +27,13 @@
namespace android::shaders {
-static aidl::android::hardware::graphics::common::Dataspace toAidlDataspace(
- ui::Dataspace dataspace) {
+namespace {
+
+aidl::android::hardware::graphics::common::Dataspace toAidlDataspace(ui::Dataspace dataspace) {
return static_cast<aidl::android::hardware::graphics::common::Dataspace>(dataspace);
}
-static void generateEOTF(ui::Dataspace dataspace, std::string& shader) {
+void generateEOTF(ui::Dataspace dataspace, std::string& shader) {
switch (dataspace & HAL_DATASPACE_TRANSFER_MASK) {
case HAL_DATASPACE_TRANSFER_ST2084:
shader.append(R"(
@@ -156,7 +158,7 @@ static void generateEOTF(ui::Dataspace dataspace, std::string& shader) {
}
}
-static void generateXYZTransforms(std::string& shader) {
+void generateXYZTransforms(std::string& shader) {
shader.append(R"(
uniform float4x4 in_rgbToXyz;
uniform float4x4 in_xyzToRgb;
@@ -171,8 +173,8 @@ static void generateXYZTransforms(std::string& shader) {
}
// Conversion from relative light to absolute light (maps from [0, 1] to [0, maxNits])
-static void generateLuminanceScalesForOOTF(ui::Dataspace inputDataspace,
- ui::Dataspace outputDataspace, std::string& shader) {
+void generateLuminanceScalesForOOTF(ui::Dataspace inputDataspace, ui::Dataspace outputDataspace,
+ std::string& shader) {
switch (inputDataspace & HAL_DATASPACE_TRANSFER_MASK) {
case HAL_DATASPACE_TRANSFER_ST2084:
shader.append(R"(
@@ -183,8 +185,9 @@ static void generateLuminanceScalesForOOTF(ui::Dataspace inputDataspace,
break;
case HAL_DATASPACE_TRANSFER_HLG:
shader.append(R"(
+ uniform float in_hlgGamma;
float3 ScaleLuminance(float3 xyz) {
- return xyz * 1000.0 * pow(xyz.y, 0.2);
+ return xyz * 1000.0 * pow(xyz.y, in_hlgGamma - 1);
}
)");
break;
@@ -225,8 +228,10 @@ static void generateLuminanceNormalizationForOOTF(ui::Dataspace outputDataspace,
break;
case HAL_DATASPACE_TRANSFER_HLG:
shader.append(R"(
+ uniform float in_hlgGamma;
float3 NormalizeLuminance(float3 xyz) {
- return xyz / 1000.0 * pow(xyz.y / 1000.0, -0.2 / 1.2);
+ return xyz / 1000.0 *
+ pow(xyz.y / 1000.0, (1 - in_hlgGamma) / (in_hlgGamma));
}
)");
break;
@@ -240,8 +245,8 @@ static void generateLuminanceNormalizationForOOTF(ui::Dataspace outputDataspace,
}
}
-static void generateOOTF(ui::Dataspace inputDataspace, ui::Dataspace outputDataspace,
- std::string& shader) {
+void generateOOTF(ui::Dataspace inputDataspace, ui::Dataspace outputDataspace,
+ std::string& shader) {
shader.append(tonemap::getToneMapper()
->generateTonemapGainShaderSkSL(toAidlDataspace(inputDataspace),
toAidlDataspace(outputDataspace))
@@ -262,7 +267,7 @@ static void generateOOTF(ui::Dataspace inputDataspace, ui::Dataspace outputDatas
)");
}
-static void generateOETF(ui::Dataspace dataspace, std::string& shader) {
+void generateOETF(ui::Dataspace dataspace, std::string& shader) {
switch (dataspace & HAL_DATASPACE_TRANSFER_MASK) {
case HAL_DATASPACE_TRANSFER_ST2084:
shader.append(R"(
@@ -384,7 +389,7 @@ static void generateOETF(ui::Dataspace dataspace, std::string& shader) {
}
}
-static void generateEffectiveOOTF(bool undoPremultipliedAlpha, std::string& shader) {
+void generateEffectiveOOTF(bool undoPremultipliedAlpha, std::string& shader) {
shader.append(R"(
uniform shader child;
half4 main(float2 xy) {
@@ -412,7 +417,7 @@ static void generateEffectiveOOTF(bool undoPremultipliedAlpha, std::string& shad
}
// please keep in sync with toSkColorSpace function in renderengine/skia/ColorSpaces.cpp
-static ColorSpace toColorSpace(ui::Dataspace dataspace) {
+ColorSpace toColorSpace(ui::Dataspace dataspace) {
switch (dataspace & HAL_DATASPACE_STANDARD_MASK) {
case HAL_DATASPACE_STANDARD_BT709:
return ColorSpace::sRGB();
@@ -438,6 +443,21 @@ static ColorSpace toColorSpace(ui::Dataspace dataspace) {
}
}
+template <typename T, std::enable_if_t<std::is_trivially_copyable<T>::value, bool> = true>
+std::vector<uint8_t> buildUniformValue(T value) {
+ std::vector<uint8_t> result;
+ result.resize(sizeof(value));
+ std::memcpy(result.data(), &value, sizeof(value));
+ return result;
+}
+
+// Refer to BT2100-2
+float computeHlgGamma(float currentDisplayBrightnessNits) {
+ return 1.2 + 0.42 * std::log10(currentDisplayBrightnessNits / 1000);
+}
+
+} // namespace
+
std::string buildLinearEffectSkSL(const LinearEffect& linearEffect) {
std::string shaderString;
generateEOTF(linearEffect.fakeInputDataspace == ui::Dataspace::UNKNOWN
@@ -451,14 +471,6 @@ std::string buildLinearEffectSkSL(const LinearEffect& linearEffect) {
return shaderString;
}
-template <typename T, std::enable_if_t<std::is_trivially_copyable<T>::value, bool> = true>
-std::vector<uint8_t> buildUniformValue(T value) {
- std::vector<uint8_t> result;
- result.resize(sizeof(value));
- std::memcpy(result.data(), &value, sizeof(value));
- return result;
-}
-
// Generates a list of uniforms to set on the LinearEffect shader above.
std::vector<tonemap::ShaderUniform> buildLinearEffectUniforms(const LinearEffect& linearEffect,
const mat4& colorTransform,
@@ -480,8 +492,13 @@ std::vector<tonemap::ShaderUniform> buildLinearEffectUniforms(const LinearEffect
colorTransform * mat4(outputColorSpace.getXYZtoRGB()))});
}
+ if ((linearEffect.inputDataspace & HAL_DATASPACE_TRANSFER_MASK) == HAL_DATASPACE_TRANSFER_HLG) {
+ uniforms.push_back(
+ {.name = "in_hlgGamma",
+ .value = buildUniformValue<float>(computeHlgGamma(currentDisplayLuminanceNits))});
+ }
+
tonemap::Metadata metadata{.displayMaxLuminance = maxDisplayLuminance,
- .currentDisplayLuminanceNits = currentDisplayLuminanceNits,
// If the input luminance is unknown, use display luminance (aka,
// no-op any luminance changes)
// This will be the case for eg screenshots in addition to
diff --git a/libs/tonemap/include/tonemap/tonemap.h b/libs/tonemap/include/tonemap/tonemap.h
index 6233e6c418..b9abf8cd52 100644
--- a/libs/tonemap/include/tonemap/tonemap.h
+++ b/libs/tonemap/include/tonemap/tonemap.h
@@ -44,8 +44,6 @@ struct ShaderUniform {
struct Metadata {
// The maximum luminance of the display in nits
float displayMaxLuminance = 0.0;
- // The current luminance of the display in nits
- float currentDisplayLuminanceNits = 0.0;
// The maximum luminance of the content in nits
float contentMaxLuminance = 0.0;
};
diff --git a/libs/tonemap/tests/tonemap_test.cpp b/libs/tonemap/tests/tonemap_test.cpp
index 7a7958f58f..1d46482627 100644
--- a/libs/tonemap/tests/tonemap_test.cpp
+++ b/libs/tonemap/tests/tonemap_test.cpp
@@ -61,7 +61,7 @@ TEST_F(TonemapTest, generateShaderSkSLUniforms_containsDefaultUniforms) {
EXPECT_GT(contentLumFloat, 0);
}
-TEST_F(TonemapTest, generateTonemapGainShaderSkSL_containsEntryPoint) {
+TEST_F(TonemapTest, generateTonemapGainShaderSkSL_containsEntryPointForPQ) {
const auto shader =
tonemap::getToneMapper()
->generateTonemapGainShaderSkSL(aidl::android::hardware::graphics::common::
@@ -73,4 +73,16 @@ TEST_F(TonemapTest, generateTonemapGainShaderSkSL_containsEntryPoint) {
EXPECT_THAT(shader, HasSubstr("float libtonemap_LookupTonemapGain(vec3 linearRGB, vec3 xyz)"));
}
+TEST_F(TonemapTest, generateTonemapGainShaderSkSL_containsEntryPointForHLG) {
+ const auto shader =
+ tonemap::getToneMapper()
+ ->generateTonemapGainShaderSkSL(aidl::android::hardware::graphics::common::
+ Dataspace::BT2020_ITU_HLG,
+ aidl::android::hardware::graphics::common::
+ Dataspace::DISPLAY_P3);
+
+ // Other tests such as librenderengine_test will plug in the shader to check compilation.
+ EXPECT_THAT(shader, HasSubstr("float libtonemap_LookupTonemapGain(vec3 linearRGB, vec3 xyz)"));
+}
+
} // namespace android
diff --git a/libs/tonemap/tonemap.cpp b/libs/tonemap/tonemap.cpp
index c2372fe828..bc0a884ee4 100644
--- a/libs/tonemap/tonemap.cpp
+++ b/libs/tonemap/tonemap.cpp
@@ -407,7 +407,6 @@ public:
)");
switch (sourceDataspaceInt & kTransferMask) {
case kTransferST2084:
- case kTransferHLG:
switch (destinationDataspaceInt & kTransferMask) {
case kTransferST2084:
program.append(R"(
@@ -428,39 +427,22 @@ public:
break;
default:
- switch (sourceDataspaceInt & kTransferMask) {
- case kTransferST2084:
- program.append(R"(
- float libtonemap_OETFTone(float channel) {
- channel = channel / 10000.0;
- 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 = pow(channel, float(m1));
- tmp = (c1 + c2 * tmp) / (1.0 + c3 * tmp);
- return pow(tmp, float(m2));
- }
- )");
- break;
- case kTransferHLG:
- program.append(R"(
- float libtonemap_OETFTone(float channel) {
- channel = channel / 1000.0;
- const float a = 0.17883277;
- const float b = 0.28466892;
- const float c = 0.55991073;
- return channel <= 1.0 / 12.0 ? sqrt(3.0 * channel) :
- a * log(12.0 * channel - b) + c;
- }
- )");
- break;
- }
// Here we're mapping from HDR to SDR content, so interpolate using a
// Hermitian polynomial onto the smaller luminance range.
program.append(R"(
+ float libtonemap_OETFTone(float channel) {
+ channel = channel / 10000.0;
+ 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 = pow(channel, float(m1));
+ tmp = (c1 + c2 * tmp) / (1.0 + c3 * tmp);
+ return pow(tmp, float(m2));
+ }
+
float libtonemap_ToneMapTargetNits(float maxRGB) {
float maxInLumi = in_libtonemap_inputMaxLuminance;
float maxOutLumi = in_libtonemap_displayMaxLuminance;
@@ -508,6 +490,30 @@ public:
break;
}
break;
+ case kTransferHLG:
+ switch (destinationDataspaceInt & kTransferMask) {
+ // HLG -> HDR does not tone-map at all
+ case kTransferST2084:
+ case kTransferHLG:
+ program.append(R"(
+ float libtonemap_ToneMapTargetNits(float maxRGB) {
+ return maxRGB;
+ }
+ )");
+ break;
+ default:
+ // libshaders follows BT2100 OOTF, but with a nominal peak display luminance
+ // of 1000 nits. Renormalize to max display luminance if we're tone-mapping
+ // down to SDR, as libshaders normalizes all SDR output from [0,
+ // maxDisplayLumins] -> [0, 1]
+ program.append(R"(
+ float libtonemap_ToneMapTargetNits(float maxRGB) {
+ return maxRGB * in_libtonemap_displayMaxLuminance / 1000.0;
+ }
+ )");
+ break;
+ }
+ break;
default:
// Inverse tone-mapping and SDR-SDR mapping is not supported.
program.append(R"(
@@ -558,7 +564,6 @@ public:
double targetNits = 0.0;
switch (sourceDataspaceInt & kTransferMask) {
case kTransferST2084:
- case kTransferHLG:
switch (destinationDataspaceInt & kTransferMask) {
case kTransferST2084:
targetNits = maxRGB;
@@ -587,19 +592,9 @@ public:
double x2 = x1 + (x3 - x1) * 4.0 / 17.0;
double y2 = maxOutLumi * 0.9;
- double greyNorm1 = 0.0;
- double greyNorm2 = 0.0;
- double greyNorm3 = 0.0;
-
- if ((sourceDataspaceInt & kTransferMask) == kTransferST2084) {
- greyNorm1 = OETF_ST2084(x1);
- greyNorm2 = OETF_ST2084(x2);
- greyNorm3 = OETF_ST2084(x3);
- } else if ((sourceDataspaceInt & kTransferMask) == kTransferHLG) {
- greyNorm1 = OETF_HLG(x1);
- greyNorm2 = OETF_HLG(x2);
- greyNorm3 = OETF_HLG(x3);
- }
+ const double greyNorm1 = OETF_ST2084(x1);
+ const double greyNorm2 = OETF_ST2084(x2);
+ const double greyNorm3 = OETF_ST2084(x3);
double slope2 = (y2 - y1) / (greyNorm2 - greyNorm1);
double slope3 = (y3 - y2) / (greyNorm3 - greyNorm2);
@@ -613,12 +608,7 @@ public:
break;
}
- double greyNits = 0.0;
- if ((sourceDataspaceInt & kTransferMask) == kTransferST2084) {
- greyNits = OETF_ST2084(targetNits);
- } else if ((sourceDataspaceInt & kTransferMask) == kTransferHLG) {
- greyNits = OETF_HLG(targetNits);
- }
+ const double greyNits = OETF_ST2084(targetNits);
if (greyNits <= greyNorm2) {
targetNits = (greyNits - greyNorm2) * slope2 + y2;
@@ -630,15 +620,20 @@ public:
break;
}
break;
- default:
+ case kTransferHLG:
switch (destinationDataspaceInt & kTransferMask) {
case kTransferST2084:
case kTransferHLG:
- default:
targetNits = maxRGB;
break;
+ default:
+ targetNits = maxRGB * metadata.displayMaxLuminance / 1000.0;
+ break;
}
break;
+ default:
+ targetNits = maxRGB;
+ break;
}
return targetNits / maxRGB;