Loading libs/renderengine/tests/RenderEngineTest.cpp +190 −135 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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; Loading Loading @@ -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>(), Loading Loading @@ -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)); TEST_P(RenderEngineTest, test_tonemapPQMatches) { if (!GetParam()->useColorManagement()) { GTEST_SKIP(); } double OETF_sRGB(double channel) { return channel <= 0.0031308 ? channel * 12.92 : (pow(channel, 1.0 / 2.4) * 1.055) - 0.055; if (GetParam()->type() == renderengine::RenderEngine::RenderEngineType::GLES) { GTEST_SKIP(); } int sign(float in) { return in >= 0.0 ? 1 : -1; } initializeRenderEngine(); 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)); 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; }); } TEST_P(RenderEngineTest, test_tonemapPQMatches) { TEST_P(RenderEngineTest, test_tonemapHLGMatches) { 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)); 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 = 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 | tonemap( static_cast<ui::Dataspace>(HAL_DATASPACE_STANDARD_BT2020 | HAL_DATASPACE_TRANSFER_HLG | 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); [](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) { Loading libs/shaders/shaders.cpp +39 −22 Original line number Diff line number Diff line Loading @@ -18,6 +18,7 @@ #include <tonemap/tonemap.h> #include <cmath> #include <optional> #include <math/mat4.h> Loading @@ -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"( Loading Loading @@ -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; Loading @@ -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"( Loading @@ -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; Loading Loading @@ -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; Loading @@ -240,7 +245,7 @@ static void generateLuminanceNormalizationForOOTF(ui::Dataspace outputDataspace, } } static void generateOOTF(ui::Dataspace inputDataspace, ui::Dataspace outputDataspace, void generateOOTF(ui::Dataspace inputDataspace, ui::Dataspace outputDataspace, std::string& shader) { shader.append(tonemap::getToneMapper() ->generateTonemapGainShaderSkSL(toAidlDataspace(inputDataspace), Loading @@ -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"( Loading Loading @@ -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) { Loading Loading @@ -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(); Loading @@ -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 Loading @@ -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, Loading @@ -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 Loading libs/tonemap/include/tonemap/tonemap.h +0 −2 Original line number Diff line number Diff line Loading @@ -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; }; Loading libs/tonemap/tests/tonemap_test.cpp +13 −1 Original line number Diff line number Diff line Loading @@ -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:: Loading @@ -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 libs/tonemap/tonemap.cpp +48 −53 File changed.Preview size limit exceeded, changes collapsed. Show changes Loading
libs/renderengine/tests/RenderEngineTest.cpp +190 −135 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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; Loading Loading @@ -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>(), Loading Loading @@ -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)); TEST_P(RenderEngineTest, test_tonemapPQMatches) { if (!GetParam()->useColorManagement()) { GTEST_SKIP(); } double OETF_sRGB(double channel) { return channel <= 0.0031308 ? channel * 12.92 : (pow(channel, 1.0 / 2.4) * 1.055) - 0.055; if (GetParam()->type() == renderengine::RenderEngine::RenderEngineType::GLES) { GTEST_SKIP(); } int sign(float in) { return in >= 0.0 ? 1 : -1; } initializeRenderEngine(); 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)); 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; }); } TEST_P(RenderEngineTest, test_tonemapPQMatches) { TEST_P(RenderEngineTest, test_tonemapHLGMatches) { 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)); 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 = 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 | tonemap( static_cast<ui::Dataspace>(HAL_DATASPACE_STANDARD_BT2020 | HAL_DATASPACE_TRANSFER_HLG | 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); [](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) { Loading
libs/shaders/shaders.cpp +39 −22 Original line number Diff line number Diff line Loading @@ -18,6 +18,7 @@ #include <tonemap/tonemap.h> #include <cmath> #include <optional> #include <math/mat4.h> Loading @@ -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"( Loading Loading @@ -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; Loading @@ -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"( Loading @@ -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; Loading Loading @@ -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; Loading @@ -240,7 +245,7 @@ static void generateLuminanceNormalizationForOOTF(ui::Dataspace outputDataspace, } } static void generateOOTF(ui::Dataspace inputDataspace, ui::Dataspace outputDataspace, void generateOOTF(ui::Dataspace inputDataspace, ui::Dataspace outputDataspace, std::string& shader) { shader.append(tonemap::getToneMapper() ->generateTonemapGainShaderSkSL(toAidlDataspace(inputDataspace), Loading @@ -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"( Loading Loading @@ -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) { Loading Loading @@ -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(); Loading @@ -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 Loading @@ -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, Loading @@ -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 Loading
libs/tonemap/include/tonemap/tonemap.h +0 −2 Original line number Diff line number Diff line Loading @@ -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; }; Loading
libs/tonemap/tests/tonemap_test.cpp +13 −1 Original line number Diff line number Diff line Loading @@ -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:: Loading @@ -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
libs/tonemap/tonemap.cpp +48 −53 File changed.Preview size limit exceeded, changes collapsed. Show changes