From 492c85c613ac94495c567465ad82b147b4526fb3 Mon Sep 17 00:00:00 2001 From: Alec Mouri Date: Fri, 19 Nov 2021 15:58:10 -0800 Subject: Introduce libshaders static library This library is for exposing system-only shader implementations in SkSL. Notably: libshaders depends on the vendor available libtonemap, but libtonemap is vendor available as it is a single source of truth for tonemap operations, whereas libshaders is not meant to be shared with the vendor partition. Rather, the intent is to allow for sharing of SkSL between librenderengine and libhwui. Bug: 200309590 Ignore-AOSP-First: Introduces internal-only library Test: builds Change-Id: I611b79eb3addd15528f0cdb70e9f2f3d62473ec1 --- libs/shaders/shaders.cpp | 361 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 361 insertions(+) create mode 100644 libs/shaders/shaders.cpp (limited to 'libs/shaders/shaders.cpp') diff --git a/libs/shaders/shaders.cpp b/libs/shaders/shaders.cpp new file mode 100644 index 0000000000..ee2d4a46a5 --- /dev/null +++ b/libs/shaders/shaders.cpp @@ -0,0 +1,361 @@ +/* + * Copyright 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#include + +#include + +#include +#include +#include + +namespace android::shaders { + +static aidl::android::hardware::graphics::common::Dataspace toAidlDataspace( + ui::Dataspace dataspace) { + return static_cast(dataspace); +} + +static void generateEOTF(ui::Dataspace dataspace, std::string& shader) { + switch (dataspace & HAL_DATASPACE_TRANSFER_MASK) { + case HAL_DATASPACE_TRANSFER_ST2084: + shader.append(R"( + + float3 EOTF(float3 color) { + 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; + + float3 tmp = pow(clamp(color, 0.0, 1.0), 1.0 / float3(m2)); + tmp = max(tmp - c1, 0.0) / (c2 - c3 * tmp); + return pow(tmp, 1.0 / float3(m1)); + } + )"); + break; + case HAL_DATASPACE_TRANSFER_HLG: + shader.append(R"( + float EOTF_channel(float 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; + } + + float3 EOTF(float3 color) { + return float3(EOTF_channel(color.r), EOTF_channel(color.g), + EOTF_channel(color.b)); + } + )"); + break; + case HAL_DATASPACE_TRANSFER_LINEAR: + shader.append(R"( + float3 EOTF(float3 color) { + return color; + } + )"); + break; + case HAL_DATASPACE_TRANSFER_SRGB: + default: + shader.append(R"( + + float EOTF_sRGB(float srgb) { + return srgb <= 0.04045 ? srgb / 12.92 : pow((srgb + 0.055) / 1.055, 2.4); + } + + float3 EOTF_sRGB(float3 srgb) { + return float3(EOTF_sRGB(srgb.r), EOTF_sRGB(srgb.g), EOTF_sRGB(srgb.b)); + } + + float3 EOTF(float3 srgb) { + return sign(srgb.rgb) * EOTF_sRGB(abs(srgb.rgb)); + } + )"); + break; + } +} + +static void generateXYZTransforms(std::string& shader) { + shader.append(R"( + uniform float4x4 in_rgbToXyz; + uniform float4x4 in_xyzToRgb; + float3 ToXYZ(float3 rgb) { + return (in_rgbToXyz * float4(rgb, 1.0)).rgb; + } + + float3 ToRGB(float3 xyz) { + return clamp((in_xyzToRgb * float4(xyz, 1.0)).rgb, 0.0, 1.0); + } + )"); +} + +// 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) { + switch (inputDataspace & HAL_DATASPACE_TRANSFER_MASK) { + case HAL_DATASPACE_TRANSFER_ST2084: + shader.append(R"( + float3 ScaleLuminance(float3 xyz) { + return xyz * 10000.0; + } + )"); + break; + case HAL_DATASPACE_TRANSFER_HLG: + shader.append(R"( + float3 ScaleLuminance(float3 xyz) { + return xyz * 1000.0 * pow(xyz.y, 0.2); + } + )"); + break; + default: + switch (outputDataspace & HAL_DATASPACE_TRANSFER_MASK) { + case HAL_DATASPACE_TRANSFER_ST2084: + case HAL_DATASPACE_TRANSFER_HLG: + // SDR -> HDR tonemap + shader.append(R"( + float3 ScaleLuminance(float3 xyz) { + return xyz * in_libtonemap_inputMaxLuminance; + } + )"); + break; + default: + // Input and output are both SDR, so no tone-mapping is expected so + // no-op the luminance normalization. + shader.append(R"( + float3 ScaleLuminance(float3 xyz) { + return xyz * in_libtonemap_displayMaxLuminance; + } + )"); + break; + } + } +} + +// Normalizes from absolute light back to relative light (maps from [0, maxNits] back to [0, 1]) +static void generateLuminanceNormalizationForOOTF(ui::Dataspace outputDataspace, + std::string& shader) { + switch (outputDataspace & HAL_DATASPACE_TRANSFER_MASK) { + case HAL_DATASPACE_TRANSFER_ST2084: + shader.append(R"( + float3 NormalizeLuminance(float3 xyz) { + return xyz / 10000.0; + } + )"); + break; + case HAL_DATASPACE_TRANSFER_HLG: + shader.append(R"( + float3 NormalizeLuminance(float3 xyz) { + return xyz / 1000.0 * pow(xyz.y / 1000.0, -0.2 / 1.2); + } + )"); + break; + default: + shader.append(R"( + float3 NormalizeLuminance(float3 xyz) { + return xyz / in_libtonemap_displayMaxLuminance; + } + )"); + break; + } +} + +static void generateOOTF(ui::Dataspace inputDataspace, ui::Dataspace outputDataspace, + std::string& shader) { + shader.append(tonemap::getToneMapper() + ->generateTonemapGainShaderSkSL(toAidlDataspace(inputDataspace), + toAidlDataspace(outputDataspace)) + .c_str()); + + generateLuminanceScalesForOOTF(inputDataspace, outputDataspace, shader); + generateLuminanceNormalizationForOOTF(outputDataspace, shader); + + shader.append(R"( + float3 OOTF(float3 linearRGB, float3 xyz) { + float3 scaledLinearRGB = ScaleLuminance(linearRGB); + float3 scaledXYZ = ScaleLuminance(xyz); + + float gain = libtonemap_LookupTonemapGain(scaledLinearRGB, scaledXYZ); + + return NormalizeLuminance(scaledXYZ * gain); + } + )"); +} + +static void generateOETF(ui::Dataspace dataspace, std::string& shader) { + switch (dataspace & HAL_DATASPACE_TRANSFER_MASK) { + case HAL_DATASPACE_TRANSFER_ST2084: + shader.append(R"( + + float3 OETF(float3 xyz) { + 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; + + float3 tmp = pow(xyz, float3(m1)); + tmp = (c1 + c2 * tmp) / (1.0 + c3 * tmp); + return pow(tmp, float3(m2)); + } + )"); + break; + case HAL_DATASPACE_TRANSFER_HLG: + shader.append(R"( + float OETF_channel(float channel) { + 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; + } + + float3 OETF(float3 linear) { + return float3(OETF_channel(linear.r), OETF_channel(linear.g), + OETF_channel(linear.b)); + } + )"); + break; + case HAL_DATASPACE_TRANSFER_LINEAR: + shader.append(R"( + float3 OETF(float3 linear) { + return linear; + } + )"); + break; + case HAL_DATASPACE_TRANSFER_SRGB: + default: + shader.append(R"( + float OETF_sRGB(float linear) { + return linear <= 0.0031308 ? + linear * 12.92 : (pow(linear, 1.0 / 2.4) * 1.055) - 0.055; + } + + float3 OETF_sRGB(float3 linear) { + return float3(OETF_sRGB(linear.r), OETF_sRGB(linear.g), OETF_sRGB(linear.b)); + } + + float3 OETF(float3 linear) { + return sign(linear.rgb) * OETF_sRGB(abs(linear.rgb)); + } + )"); + break; + } +} + +static void generateEffectiveOOTF(bool undoPremultipliedAlpha, std::string& shader) { + shader.append(R"( + uniform shader child; + half4 main(float2 xy) { + float4 c = float4(child.eval(xy)); + )"); + if (undoPremultipliedAlpha) { + shader.append(R"( + c.rgb = c.rgb / (c.a + 0.0019); + )"); + } + shader.append(R"( + float3 linearRGB = EOTF(c.rgb); + float3 xyz = ToXYZ(linearRGB); + c.rgb = OETF(ToRGB(OOTF(linearRGB, xyz))); + )"); + if (undoPremultipliedAlpha) { + shader.append(R"( + c.rgb = c.rgb * (c.a + 0.0019); + )"); + } + shader.append(R"( + return c; + } + )"); +} +static ColorSpace toColorSpace(ui::Dataspace dataspace) { + switch (dataspace & HAL_DATASPACE_STANDARD_MASK) { + case HAL_DATASPACE_STANDARD_BT709: + return ColorSpace::sRGB(); + break; + case HAL_DATASPACE_STANDARD_DCI_P3: + return ColorSpace::DisplayP3(); + break; + case HAL_DATASPACE_STANDARD_BT2020: + return ColorSpace::BT2020(); + break; + default: + return ColorSpace::sRGB(); + break; + } +} + +std::string buildLinearEffectSkSL(const LinearEffect& linearEffect) { + std::string shaderString; + generateEOTF(linearEffect.fakeInputDataspace == ui::Dataspace::UNKNOWN + ? linearEffect.inputDataspace + : linearEffect.fakeInputDataspace, + shaderString); + generateXYZTransforms(shaderString); + generateOOTF(linearEffect.inputDataspace, linearEffect.outputDataspace, shaderString); + generateOETF(linearEffect.outputDataspace, shaderString); + generateEffectiveOOTF(linearEffect.undoPremultipliedAlpha, shaderString); + return shaderString; +} + +template ::value, bool> = true> +std::vector buildUniformValue(T value) { + std::vector 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 buildLinearEffectUniforms(const LinearEffect& linearEffect, + const mat4& colorTransform, + float maxDisplayLuminance, + float maxLuminance) { + std::vector uniforms; + if (linearEffect.inputDataspace == linearEffect.outputDataspace) { + uniforms.push_back({.name = "in_rgbToXyz", .value = buildUniformValue(mat4())}); + uniforms.push_back( + {.name = "in_xyzToRgb", .value = buildUniformValue(colorTransform)}); + } else { + ColorSpace inputColorSpace = toColorSpace(linearEffect.inputDataspace); + ColorSpace outputColorSpace = toColorSpace(linearEffect.outputDataspace); + uniforms.push_back({.name = "in_rgbToXyz", + .value = buildUniformValue(mat4(inputColorSpace.getRGBtoXYZ()))}); + uniforms.push_back({.name = "in_xyzToRgb", + .value = buildUniformValue( + colorTransform * mat4(outputColorSpace.getXYZtoRGB()))}); + } + + tonemap::Metadata metadata{.displayMaxLuminance = maxDisplayLuminance, + // 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 + // uncalibrated displays + .contentMaxLuminance = + maxLuminance > 0 ? maxLuminance : maxDisplayLuminance}; + + for (const auto uniform : tonemap::getToneMapper()->generateShaderSkSLUniforms(metadata)) { + uniforms.push_back(uniform); + } + + return uniforms; +} + +} // namespace android::shaders \ No newline at end of file -- cgit v1.2.3-59-g8ed1b From 2019fd2341d4ca14b898018260b5dde57ef14b57 Mon Sep 17 00:00:00 2001 From: Sally Qi Date: Mon, 22 Nov 2021 10:19:04 -0800 Subject: Enable more ColorSpaces in RenderEngine Bug: 200307628 Test: librenderengine_test pass Change-Id: I81d9e86e8a826a74d65ce378d01ab55496cdd944 --- libs/renderengine/skia/ColorSpaces.cpp | 21 +++ libs/renderengine/tests/RenderEngineTest.cpp | 194 +++++++++++++++++++++++++++ libs/shaders/shaders.cpp | 144 +++++++++++++++++++- 3 files changed, 355 insertions(+), 4 deletions(-) (limited to 'libs/shaders/shaders.cpp') diff --git a/libs/renderengine/skia/ColorSpaces.cpp b/libs/renderengine/skia/ColorSpaces.cpp index ff4d348f0d..f367a84946 100644 --- a/libs/renderengine/skia/ColorSpaces.cpp +++ b/libs/renderengine/skia/ColorSpaces.cpp @@ -20,6 +20,7 @@ namespace android { namespace renderengine { namespace skia { +// please keep in sync with hwui/utils/Color.cpp sk_sp toSkColorSpace(ui::Dataspace dataspace) { skcms_Matrix3x3 gamut; switch (dataspace & HAL_DATASPACE_STANDARD_MASK) { @@ -32,6 +33,17 @@ sk_sp toSkColorSpace(ui::Dataspace dataspace) { case HAL_DATASPACE_STANDARD_DCI_P3: gamut = SkNamedGamut::kDisplayP3; break; + case HAL_DATASPACE_STANDARD_ADOBE_RGB: + gamut = SkNamedGamut::kAdobeRGB; + break; + case HAL_DATASPACE_STANDARD_BT601_625: + case HAL_DATASPACE_STANDARD_BT601_625_UNADJUSTED: + case HAL_DATASPACE_STANDARD_BT601_525: + case HAL_DATASPACE_STANDARD_BT601_525_UNADJUSTED: + case HAL_DATASPACE_STANDARD_BT2020_CONSTANT_LUMINANCE: + case HAL_DATASPACE_STANDARD_BT470M: + case HAL_DATASPACE_STANDARD_FILM: + case HAL_DATASPACE_STANDARD_UNSPECIFIED: default: gamut = SkNamedGamut::kSRGB; break; @@ -42,10 +54,19 @@ sk_sp toSkColorSpace(ui::Dataspace dataspace) { return SkColorSpace::MakeRGB(SkNamedTransferFn::kLinear, gamut); case HAL_DATASPACE_TRANSFER_SRGB: return SkColorSpace::MakeRGB(SkNamedTransferFn::kSRGB, gamut); + case HAL_DATASPACE_TRANSFER_GAMMA2_2: + return SkColorSpace::MakeRGB({2.2f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f}, gamut); + case HAL_DATASPACE_TRANSFER_GAMMA2_6: + return SkColorSpace::MakeRGB({2.6f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f}, gamut); + case HAL_DATASPACE_TRANSFER_GAMMA2_8: + return SkColorSpace::MakeRGB({2.8f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f}, gamut); case HAL_DATASPACE_TRANSFER_ST2084: return SkColorSpace::MakeRGB(SkNamedTransferFn::kPQ, gamut); + case HAL_DATASPACE_TRANSFER_SMPTE_170M: + return SkColorSpace::MakeRGB(SkNamedTransferFn::kRec2020, gamut); case HAL_DATASPACE_TRANSFER_HLG: return SkColorSpace::MakeRGB(SkNamedTransferFn::kHLG, gamut); + case HAL_DATASPACE_TRANSFER_UNSPECIFIED: default: return SkColorSpace::MakeRGB(SkNamedTransferFn::kSRGB, gamut); } diff --git a/libs/renderengine/tests/RenderEngineTest.cpp b/libs/renderengine/tests/RenderEngineTest.cpp index 5bc08ac089..82590638a8 100644 --- a/libs/renderengine/tests/RenderEngineTest.cpp +++ b/libs/renderengine/tests/RenderEngineTest.cpp @@ -503,6 +503,18 @@ public: template void fillBufferColorTransform(); + template + void fillBufferWithColorTransformAndSourceDataspace(const ui::Dataspace sourceDataspace); + + template + void fillBufferColorTransformAndSourceDataspace(); + + template + void fillBufferWithColorTransformAndOutputDataspace(const ui::Dataspace outputDataspace); + + template + void fillBufferColorTransformAndOutputDataspace(); + template void fillBufferWithColorTransformZeroLayerAlpha(); @@ -880,12 +892,104 @@ void RenderEngineTest::fillBufferWithColorTransform() { invokeDraw(settings, layers); } +template +void RenderEngineTest::fillBufferWithColorTransformAndSourceDataspace( + const ui::Dataspace sourceDataspace) { + renderengine::DisplaySettings settings; + settings.physicalDisplay = fullscreenRect(); + settings.clip = Rect(1, 1); + settings.outputDataspace = ui::Dataspace::V0_SRGB_LINEAR; + + std::vector layers; + + renderengine::LayerSettings layer; + layer.sourceDataspace = sourceDataspace; + layer.geometry.boundaries = Rect(1, 1).toFloatRect(); + SourceVariant::fillColor(layer, 0.5f, 0.25f, 0.125f, this); + layer.alpha = 1.0f; + + // construct a fake color matrix + // annihilate green and blue channels + settings.colorTransform = mat4::scale(vec4(0.9f, 0, 0, 1)); + // set red channel to red + green + layer.colorTransform = mat4(1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1); + + layer.alpha = 1.0f; + layer.geometry.boundaries = Rect(1, 1).toFloatRect(); + + layers.push_back(layer); + + invokeDraw(settings, layers); +} + template void RenderEngineTest::fillBufferColorTransform() { fillBufferWithColorTransform(); expectBufferColor(fullscreenRect(), 172, 0, 0, 255, 1); } +template +void RenderEngineTest::fillBufferColorTransformAndSourceDataspace() { + unordered_map dataspaceToColorMap; + dataspaceToColorMap[ui::Dataspace::V0_BT709] = {172, 0, 0, 255}; + dataspaceToColorMap[ui::Dataspace::BT2020] = {172, 0, 0, 255}; + dataspaceToColorMap[ui::Dataspace::ADOBE_RGB] = {172, 0, 0, 255}; + ui::Dataspace customizedDataspace = static_cast( + ui::Dataspace::STANDARD_BT709 | ui::Dataspace::TRANSFER_GAMMA2_2 | + ui::Dataspace::RANGE_FULL); + dataspaceToColorMap[customizedDataspace] = {172, 0, 0, 255}; + for (const auto& [sourceDataspace, color] : dataspaceToColorMap) { + fillBufferWithColorTransformAndSourceDataspace(sourceDataspace); + expectBufferColor(fullscreenRect(), color.r, color.g, color.b, color.a, 1); + } +} + +template +void RenderEngineTest::fillBufferWithColorTransformAndOutputDataspace( + const ui::Dataspace outputDataspace) { + renderengine::DisplaySettings settings; + settings.physicalDisplay = fullscreenRect(); + settings.clip = Rect(1, 1); + settings.outputDataspace = outputDataspace; + + std::vector layers; + + renderengine::LayerSettings layer; + layer.sourceDataspace = ui::Dataspace::V0_SCRGB_LINEAR; + layer.geometry.boundaries = Rect(1, 1).toFloatRect(); + SourceVariant::fillColor(layer, 0.5f, 0.25f, 0.125f, this); + layer.alpha = 1.0f; + + // construct a fake color matrix + // annihilate green and blue channels + settings.colorTransform = mat4::scale(vec4(0.9f, 0, 0, 1)); + // set red channel to red + green + layer.colorTransform = mat4(1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1); + + layer.alpha = 1.0f; + layer.geometry.boundaries = Rect(1, 1).toFloatRect(); + + layers.push_back(layer); + + invokeDraw(settings, layers); +} + +template +void RenderEngineTest::fillBufferColorTransformAndOutputDataspace() { + unordered_map dataspaceToColorMap; + dataspaceToColorMap[ui::Dataspace::V0_BT709] = {202, 0, 0, 255}; + dataspaceToColorMap[ui::Dataspace::BT2020] = {192, 0, 0, 255}; + dataspaceToColorMap[ui::Dataspace::ADOBE_RGB] = {202, 0, 0, 255}; + ui::Dataspace customizedDataspace = static_cast( + ui::Dataspace::STANDARD_BT709 | ui::Dataspace::TRANSFER_GAMMA2_6 | + ui::Dataspace::RANGE_FULL); + dataspaceToColorMap[customizedDataspace] = {202, 0, 0, 255}; + for (const auto& [outputDataspace, color] : dataspaceToColorMap) { + fillBufferWithColorTransformAndOutputDataspace(outputDataspace); + expectBufferColor(fullscreenRect(), color.r, color.g, color.b, color.a, 1); + } +} + template void RenderEngineTest::fillBufferWithColorTransformZeroLayerAlpha() { renderengine::DisplaySettings settings; @@ -1427,6 +1531,36 @@ TEST_P(RenderEngineTest, drawLayers_fillBufferColorTransform_colorSource) { fillBufferColorTransform(); } +TEST_P(RenderEngineTest, drawLayers_fillBufferColorTransform_sourceDataspace) { + const auto& renderEngineFactory = GetParam(); + // skip for non color management + if (!renderEngineFactory->useColorManagement()) { + return; + } + // skip for GLESRenderEngine + if (renderEngineFactory->type() != renderengine::RenderEngine::RenderEngineType::GLES) { + return; + } + + initializeRenderEngine(); + fillBufferColorTransformAndSourceDataspace(); +} + +TEST_P(RenderEngineTest, drawLayers_fillBufferColorTransform_outputDataspace) { + const auto& renderEngineFactory = GetParam(); + // skip for non color management + if (!renderEngineFactory->useColorManagement()) { + return; + } + // skip for GLESRenderEngine + if (renderEngineFactory->type() != renderengine::RenderEngine::RenderEngineType::GLES) { + return; + } + + initializeRenderEngine(); + fillBufferColorTransformAndOutputDataspace(); +} + TEST_P(RenderEngineTest, drawLayers_fillBufferRoundedCorners_colorSource) { initializeRenderEngine(); fillBufferWithRoundedCorners(); @@ -1507,6 +1641,36 @@ TEST_P(RenderEngineTest, drawLayers_fillBufferColorTransform_opaqueBufferSource) fillBufferColorTransform>(); } +TEST_P(RenderEngineTest, drawLayers_fillBufferColorTransformAndSourceDataspace_opaqueBufferSource) { + const auto& renderEngineFactory = GetParam(); + // skip for non color management + if (!renderEngineFactory->useColorManagement()) { + return; + } + // skip for GLESRenderEngine + if (renderEngineFactory->type() != renderengine::RenderEngine::RenderEngineType::GLES) { + return; + } + + initializeRenderEngine(); + fillBufferColorTransformAndSourceDataspace>(); +} + +TEST_P(RenderEngineTest, drawLayers_fillBufferColorTransformAndOutputDataspace_opaqueBufferSource) { + const auto& renderEngineFactory = GetParam(); + // skip for non color management + if (!renderEngineFactory->useColorManagement()) { + return; + } + // skip for GLESRenderEngine + if (renderEngineFactory->type() != renderengine::RenderEngine::RenderEngineType::GLES) { + return; + } + + initializeRenderEngine(); + fillBufferColorTransformAndOutputDataspace>(); +} + TEST_P(RenderEngineTest, drawLayers_fillBufferRoundedCorners_opaqueBufferSource) { initializeRenderEngine(); fillBufferWithRoundedCorners>(); @@ -1587,6 +1751,36 @@ TEST_P(RenderEngineTest, drawLayers_fillBufferColorTransform_bufferSource) { fillBufferColorTransform>(); } +TEST_P(RenderEngineTest, drawLayers_fillBufferColorTransformAndSourceDataspace_bufferSource) { + const auto& renderEngineFactory = GetParam(); + // skip for non color management + if (!renderEngineFactory->useColorManagement()) { + return; + } + // skip for GLESRenderEngine + if (renderEngineFactory->type() != renderengine::RenderEngine::RenderEngineType::GLES) { + return; + } + + initializeRenderEngine(); + fillBufferColorTransformAndSourceDataspace>(); +} + +TEST_P(RenderEngineTest, drawLayers_fillBufferColorTransformAndOutputDataspace_bufferSource) { + const auto& renderEngineFactory = GetParam(); + // skip for non color management + if (!renderEngineFactory->useColorManagement()) { + return; + } + // skip for GLESRenderEngine + if (renderEngineFactory->type() != renderengine::RenderEngine::RenderEngineType::GLES) { + return; + } + + initializeRenderEngine(); + fillBufferColorTransformAndOutputDataspace>(); +} + TEST_P(RenderEngineTest, drawLayers_fillBufferRoundedCorners_bufferSource) { initializeRenderEngine(); fillBufferWithRoundedCorners>(); diff --git a/libs/shaders/shaders.cpp b/libs/shaders/shaders.cpp index ee2d4a46a5..6019c4ac28 100644 --- a/libs/shaders/shaders.cpp +++ b/libs/shaders/shaders.cpp @@ -72,6 +72,70 @@ static void generateEOTF(ui::Dataspace dataspace, std::string& shader) { } )"); break; + case HAL_DATASPACE_TRANSFER_SMPTE_170M: + shader.append(R"( + + float EOTF_sRGB(float srgb) { + return srgb <= 0.08125 ? srgb / 4.50 : pow((srgb + 0.099) / 1.099, 0.45); + } + + float3 EOTF_sRGB(float3 srgb) { + return float3(EOTF_sRGB(srgb.r), EOTF_sRGB(srgb.g), EOTF_sRGB(srgb.b)); + } + + float3 EOTF(float3 srgb) { + return sign(srgb.rgb) * EOTF_sRGB(abs(srgb.rgb)); + } + )"); + break; + case HAL_DATASPACE_TRANSFER_GAMMA2_2: + shader.append(R"( + + float EOTF_sRGB(float srgb) { + return pow(srgb, 2.2); + } + + float3 EOTF_sRGB(float3 srgb) { + return float3(EOTF_sRGB(srgb.r), EOTF_sRGB(srgb.g), EOTF_sRGB(srgb.b)); + } + + float3 EOTF(float3 srgb) { + return sign(srgb.rgb) * EOTF_sRGB(abs(srgb.rgb)); + } + )"); + break; + case HAL_DATASPACE_TRANSFER_GAMMA2_6: + shader.append(R"( + + float EOTF_sRGB(float srgb) { + return pow(srgb, 2.6); + } + + float3 EOTF_sRGB(float3 srgb) { + return float3(EOTF_sRGB(srgb.r), EOTF_sRGB(srgb.g), EOTF_sRGB(srgb.b)); + } + + float3 EOTF(float3 srgb) { + return sign(srgb.rgb) * EOTF_sRGB(abs(srgb.rgb)); + } + )"); + break; + case HAL_DATASPACE_TRANSFER_GAMMA2_8: + shader.append(R"( + + float EOTF_sRGB(float srgb) { + return pow(srgb, 2.8); + } + + float3 EOTF_sRGB(float3 srgb) { + return float3(EOTF_sRGB(srgb.r), EOTF_sRGB(srgb.g), EOTF_sRGB(srgb.b)); + } + + float3 EOTF(float3 srgb) { + return sign(srgb.rgb) * EOTF_sRGB(abs(srgb.rgb)); + } + )"); + break; case HAL_DATASPACE_TRANSFER_SRGB: default: shader.append(R"( @@ -239,6 +303,67 @@ static void generateOETF(ui::Dataspace dataspace, std::string& shader) { } )"); break; + case HAL_DATASPACE_TRANSFER_SMPTE_170M: + shader.append(R"( + float OETF_sRGB(float linear) { + return linear <= 0.018 ? + linear * 4.50 : (pow(linear, 0.45) * 1.099) - 0.099; + } + + float3 OETF_sRGB(float3 linear) { + return float3(OETF_sRGB(linear.r), OETF_sRGB(linear.g), OETF_sRGB(linear.b)); + } + + float3 OETF(float3 linear) { + return sign(linear.rgb) * OETF_sRGB(abs(linear.rgb)); + } + )"); + break; + case HAL_DATASPACE_TRANSFER_GAMMA2_2: + shader.append(R"( + float OETF_sRGB(float linear) { + return pow(linear, (1.0 / 2.2)); + } + + float3 OETF_sRGB(float3 linear) { + return float3(OETF_sRGB(linear.r), OETF_sRGB(linear.g), OETF_sRGB(linear.b)); + } + + float3 OETF(float3 linear) { + return sign(linear.rgb) * OETF_sRGB(abs(linear.rgb)); + } + )"); + break; + case HAL_DATASPACE_TRANSFER_GAMMA2_6: + shader.append(R"( + float OETF_sRGB(float linear) { + return pow(linear, (1.0 / 2.6)); + } + + float3 OETF_sRGB(float3 linear) { + return float3(OETF_sRGB(linear.r), OETF_sRGB(linear.g), OETF_sRGB(linear.b)); + } + + float3 OETF(float3 linear) { + return sign(linear.rgb) * OETF_sRGB(abs(linear.rgb)); + } + )"); + break; + case HAL_DATASPACE_TRANSFER_GAMMA2_8: + shader.append(R"( + float OETF_sRGB(float linear) { + return pow(linear, (1.0 / 2.8)); + } + + float3 OETF_sRGB(float3 linear) { + return float3(OETF_sRGB(linear.r), OETF_sRGB(linear.g), OETF_sRGB(linear.b)); + } + + float3 OETF(float3 linear) { + return sign(linear.rgb) * OETF_sRGB(abs(linear.rgb)); + } + )"); + break; case HAL_DATASPACE_TRANSFER_SRGB: default: shader.append(R"( @@ -285,20 +410,31 @@ 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) { switch (dataspace & HAL_DATASPACE_STANDARD_MASK) { case HAL_DATASPACE_STANDARD_BT709: return ColorSpace::sRGB(); - break; case HAL_DATASPACE_STANDARD_DCI_P3: return ColorSpace::DisplayP3(); - break; case HAL_DATASPACE_STANDARD_BT2020: + case HAL_DATASPACE_STANDARD_BT2020_CONSTANT_LUMINANCE: return ColorSpace::BT2020(); - break; + case HAL_DATASPACE_STANDARD_ADOBE_RGB: + return ColorSpace::AdobeRGB(); + // TODO(b/208290320): BT601 format and variants return different primaries + case HAL_DATASPACE_STANDARD_BT601_625: + case HAL_DATASPACE_STANDARD_BT601_625_UNADJUSTED: + case HAL_DATASPACE_STANDARD_BT601_525: + case HAL_DATASPACE_STANDARD_BT601_525_UNADJUSTED: + // TODO(b/208290329): BT407M format returns different primaries + case HAL_DATASPACE_STANDARD_BT470M: + // TODO(b/208290904): FILM format returns different primaries + case HAL_DATASPACE_STANDARD_FILM: + case HAL_DATASPACE_STANDARD_UNSPECIFIED: default: return ColorSpace::sRGB(); - break; } } -- cgit v1.2.3-59-g8ed1b From b21d94e6b46a269e9960118c7c8d61ce984aea3e Mon Sep 17 00:00:00 2001 From: Alec Mouri Date: Thu, 13 Jan 2022 17:44:10 -0800 Subject: Supply extra brightness parameters to RenderEngine A future CL will update the HLG->SDR tonemapping algorithm to consider current display brightness, as recommended by BT2100. In preparation for this: * Fix an issue where maxLuminance was using the current display brightness if supplied from DisplayManager instead of the max luminance * Add currentDisplayBrightnessNits to the RenderEngine interface to support the current brightness * Plumb current display brightness all the way to libtonemap, where nothing uses it (yet) Bug: 206035964 Test: libcompositionengine_test Change-Id: I3e9f0fdb23fbb08c50e4733e5a16bcd20948d750 --- .../include/renderengine/DisplaySettings.h | 3 + libs/renderengine/skia/SkiaGLRenderEngine.cpp | 1 + libs/renderengine/skia/filters/LinearEffect.cpp | 7 +- libs/renderengine/skia/filters/LinearEffect.h | 3 +- libs/shaders/include/shaders/shaders.h | 1 + libs/shaders/shaders.cpp | 2 + libs/tonemap/include/tonemap/tonemap.h | 4 + .../CompositionEngine/src/Output.cpp | 4 +- .../CompositionEngine/tests/OutputTest.cpp | 94 +++++++++++++++++----- 9 files changed, 94 insertions(+), 25 deletions(-) (limited to 'libs/shaders/shaders.cpp') diff --git a/libs/renderengine/include/renderengine/DisplaySettings.h b/libs/renderengine/include/renderengine/DisplaySettings.h index b4cab39bd9..2c51ccd334 100644 --- a/libs/renderengine/include/renderengine/DisplaySettings.h +++ b/libs/renderengine/include/renderengine/DisplaySettings.h @@ -43,6 +43,9 @@ struct DisplaySettings { // Maximum luminance pulled from the display's HDR capabilities. float maxLuminance = 1.0f; + // Current luminance of the display + float currentLuminanceNits = -1.f; + // Output dataspace that will be populated if wide color gamut is used, or // DataSpace::UNKNOWN otherwise. ui::Dataspace outputDataspace = ui::Dataspace::UNKNOWN; diff --git a/libs/renderengine/skia/SkiaGLRenderEngine.cpp b/libs/renderengine/skia/SkiaGLRenderEngine.cpp index cc90946753..063ce67f83 100644 --- a/libs/renderengine/skia/SkiaGLRenderEngine.cpp +++ b/libs/renderengine/skia/SkiaGLRenderEngine.cpp @@ -656,6 +656,7 @@ sk_sp SkiaGLRenderEngine::createRuntimeEffectShader( parameters.layerDimmingRatio, 1.f)); return createLinearEffectShader(parameters.shader, effect, runtimeEffect, colorTransform, parameters.display.maxLuminance, + parameters.display.currentLuminanceNits, parameters.layer.source.buffer.maxLuminanceNits); } return parameters.shader; diff --git a/libs/renderengine/skia/filters/LinearEffect.cpp b/libs/renderengine/skia/filters/LinearEffect.cpp index 36305aeea8..6077c2e7da 100644 --- a/libs/renderengine/skia/filters/LinearEffect.cpp +++ b/libs/renderengine/skia/filters/LinearEffect.cpp @@ -44,14 +44,15 @@ sk_sp createLinearEffectShader(sk_sp shader, const shaders::LinearEffect& linearEffect, sk_sp runtimeEffect, const mat4& colorTransform, float maxDisplayLuminance, - float maxLuminance) { + float currentDisplayLuminanceNits, float maxLuminance) { ATRACE_CALL(); SkRuntimeShaderBuilder effectBuilder(runtimeEffect); effectBuilder.child("child") = shader; - const auto uniforms = shaders::buildLinearEffectUniforms(linearEffect, colorTransform, - maxDisplayLuminance, maxLuminance); + const auto uniforms = + shaders::buildLinearEffectUniforms(linearEffect, colorTransform, maxDisplayLuminance, + currentDisplayLuminanceNits, maxLuminance); for (const auto& uniform : uniforms) { effectBuilder.uniform(uniform.name.c_str()).set(uniform.value.data(), uniform.value.size()); diff --git a/libs/renderengine/skia/filters/LinearEffect.h b/libs/renderengine/skia/filters/LinearEffect.h index 8eb6670150..e0a556b11a 100644 --- a/libs/renderengine/skia/filters/LinearEffect.h +++ b/libs/renderengine/skia/filters/LinearEffect.h @@ -37,13 +37,14 @@ sk_sp buildRuntimeEffect(const shaders::LinearEffect& linearEff // matrix transforming from linear XYZ to linear RGB immediately before OETF. // We also provide additional HDR metadata upon creating the shader: // * The max display luminance is the max luminance of the physical display in nits +// * The current luminance of the physical display in nits // * The max luminance is provided as the max luminance for the buffer, either from the SMPTE 2086 // or as the max light level from the CTA 861.3 standard. sk_sp createLinearEffectShader(sk_sp inputShader, const shaders::LinearEffect& linearEffect, sk_sp runtimeEffect, const mat4& colorTransform, float maxDisplayLuminance, - float maxLuminance); + float currentDisplayLuminanceNits, float maxLuminance); } // namespace skia } // namespace renderengine } // namespace android diff --git a/libs/shaders/include/shaders/shaders.h b/libs/shaders/include/shaders/shaders.h index 712a27a3eb..43828ccd05 100644 --- a/libs/shaders/include/shaders/shaders.h +++ b/libs/shaders/include/shaders/shaders.h @@ -100,6 +100,7 @@ std::string buildLinearEffectSkSL(const LinearEffect& linearEffect); std::vector buildLinearEffectUniforms(const LinearEffect& linearEffect, const mat4& colorTransform, float maxDisplayLuminance, + float currentDisplayLuminanceNits, float maxLuminance); } // namespace android::shaders diff --git a/libs/shaders/shaders.cpp b/libs/shaders/shaders.cpp index 6019c4ac28..4d88d5d417 100644 --- a/libs/shaders/shaders.cpp +++ b/libs/shaders/shaders.cpp @@ -463,6 +463,7 @@ std::vector buildUniformValue(T value) { std::vector buildLinearEffectUniforms(const LinearEffect& linearEffect, const mat4& colorTransform, float maxDisplayLuminance, + float currentDisplayLuminanceNits, float maxLuminance) { std::vector uniforms; if (linearEffect.inputDataspace == linearEffect.outputDataspace) { @@ -480,6 +481,7 @@ std::vector buildLinearEffectUniforms(const LinearEffect } 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 bd7b72d186..6233e6c418 100644 --- a/libs/tonemap/include/tonemap/tonemap.h +++ b/libs/tonemap/include/tonemap/tonemap.h @@ -42,7 +42,11 @@ struct ShaderUniform { // This metadata should not be used for manipulating the source code of the shader program directly, // as otherwise caching by other system of these shaders may break. 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/services/surfaceflinger/CompositionEngine/src/Output.cpp b/services/surfaceflinger/CompositionEngine/src/Output.cpp index 192ee047ee..162d84ef31 100644 --- a/services/surfaceflinger/CompositionEngine/src/Output.cpp +++ b/services/surfaceflinger/CompositionEngine/src/Output.cpp @@ -1059,9 +1059,11 @@ std::optional Output::composeSurfaces( // If we have a valid current display brightness use that, otherwise fall back to the // display's max desired - clientCompositionDisplay.maxLuminance = outputState.displayBrightnessNits > 0.f + clientCompositionDisplay.currentLuminanceNits = outputState.displayBrightnessNits > 0.f ? outputState.displayBrightnessNits : mDisplayColorProfile->getHdrCapabilities().getDesiredMaxLuminance(); + clientCompositionDisplay.maxLuminance = + mDisplayColorProfile->getHdrCapabilities().getDesiredMaxLuminance(); clientCompositionDisplay.targetLuminanceNits = outputState.clientTargetWhitePointNits; // Compute the global color transform matrix. diff --git a/services/surfaceflinger/CompositionEngine/tests/OutputTest.cpp b/services/surfaceflinger/CompositionEngine/tests/OutputTest.cpp index f7c75337ff..c2235a26ea 100644 --- a/services/surfaceflinger/CompositionEngine/tests/OutputTest.cpp +++ b/services/surfaceflinger/CompositionEngine/tests/OutputTest.cpp @@ -3069,6 +3069,8 @@ struct OutputComposeSurfacesTest : public testing::Test { static constexpr float kDefaultMaxLuminance = 0.9f; static constexpr float kDefaultAvgLuminance = 0.7f; static constexpr float kDefaultMinLuminance = 0.1f; + static constexpr float kUnknownLuminance = -1.f; + static constexpr float kDisplayLuminance = 80.f; static constexpr float kClientTargetLuminanceNits = 200.f; static const Rect kDefaultOutputFrame; @@ -3101,6 +3103,7 @@ const Rect OutputComposeSurfacesTest::kDefaultOutputDestinationClip{1013, 1014, const mat4 OutputComposeSurfacesTest::kDefaultColorTransformMat{mat4() * 0.5f}; const compositionengine::CompositionRefreshArgs OutputComposeSurfacesTest::kDefaultRefreshArgs; const Region OutputComposeSurfacesTest::kDebugRegion{Rect{100, 101, 102, 103}}; + const HdrCapabilities OutputComposeSurfacesTest:: kHdrCapabilities{{}, OutputComposeSurfacesTest::kDefaultMaxLuminance, @@ -3403,6 +3406,14 @@ struct OutputComposeSurfacesTest_UsesExpectedDisplaySettings : public OutputComp auto andIfUsesHdr(bool used) { EXPECT_CALL(*getInstance()->mDisplayColorProfile, hasWideColorGamut()) .WillOnce(Return(used)); + return nextState(); + } + }; + + struct OutputWithDisplayBrightnessNits + : public CallOrderStateMachineHelper { + auto withDisplayBrightnessNits(float nits) { + getInstance()->mOutput.mState.displayBrightnessNits = nits; return nextState(); } }; @@ -3434,11 +3445,34 @@ struct OutputComposeSurfacesTest_UsesExpectedDisplaySettings : public OutputComp TEST_F(OutputComposeSurfacesTest_UsesExpectedDisplaySettings, forHdrMixedComposition) { verify().ifMixedCompositionIs(true) .andIfUsesHdr(true) + .withDisplayBrightnessNits(kUnknownLuminance) + .andIfSkipColorTransform(false) + .thenExpectDisplaySettingsUsed({.physicalDisplay = kDefaultOutputDestinationClip, + .clip = kDefaultOutputViewport, + .maxLuminance = kDefaultMaxLuminance, + .currentLuminanceNits = kDefaultMaxLuminance, + .outputDataspace = kDefaultOutputDataspace, + .colorTransform = mat4(), + .orientation = kDefaultOutputOrientationFlags, + .targetLuminanceNits = kClientTargetLuminanceNits}) + .execute() + .expectAFenceWasReturned(); +} + +TEST_F(OutputComposeSurfacesTest_UsesExpectedDisplaySettings, + forHdrMixedCompositionWithDisplayBrightness) { + verify().ifMixedCompositionIs(true) + .andIfUsesHdr(true) + .withDisplayBrightnessNits(kDisplayLuminance) .andIfSkipColorTransform(false) - .thenExpectDisplaySettingsUsed({kDefaultOutputDestinationClip, kDefaultOutputViewport, - kDefaultMaxLuminance, kDefaultOutputDataspace, mat4(), - kDefaultOutputOrientationFlags, - kClientTargetLuminanceNits}) + .thenExpectDisplaySettingsUsed({.physicalDisplay = kDefaultOutputDestinationClip, + .clip = kDefaultOutputViewport, + .maxLuminance = kDefaultMaxLuminance, + .currentLuminanceNits = kDisplayLuminance, + .outputDataspace = kDefaultOutputDataspace, + .colorTransform = mat4(), + .orientation = kDefaultOutputOrientationFlags, + .targetLuminanceNits = kClientTargetLuminanceNits}) .execute() .expectAFenceWasReturned(); } @@ -3446,11 +3480,16 @@ TEST_F(OutputComposeSurfacesTest_UsesExpectedDisplaySettings, forHdrMixedComposi TEST_F(OutputComposeSurfacesTest_UsesExpectedDisplaySettings, forNonHdrMixedComposition) { verify().ifMixedCompositionIs(true) .andIfUsesHdr(false) + .withDisplayBrightnessNits(kUnknownLuminance) .andIfSkipColorTransform(false) - .thenExpectDisplaySettingsUsed({kDefaultOutputDestinationClip, kDefaultOutputViewport, - kDefaultMaxLuminance, kDefaultOutputDataspace, mat4(), - kDefaultOutputOrientationFlags, - kClientTargetLuminanceNits}) + .thenExpectDisplaySettingsUsed({.physicalDisplay = kDefaultOutputDestinationClip, + .clip = kDefaultOutputViewport, + .maxLuminance = kDefaultMaxLuminance, + .currentLuminanceNits = kDefaultMaxLuminance, + .outputDataspace = kDefaultOutputDataspace, + .colorTransform = mat4(), + .orientation = kDefaultOutputOrientationFlags, + .targetLuminanceNits = kClientTargetLuminanceNits}) .execute() .expectAFenceWasReturned(); } @@ -3458,11 +3497,16 @@ TEST_F(OutputComposeSurfacesTest_UsesExpectedDisplaySettings, forNonHdrMixedComp TEST_F(OutputComposeSurfacesTest_UsesExpectedDisplaySettings, forHdrOnlyClientComposition) { verify().ifMixedCompositionIs(false) .andIfUsesHdr(true) + .withDisplayBrightnessNits(kUnknownLuminance) .andIfSkipColorTransform(false) - .thenExpectDisplaySettingsUsed( - {kDefaultOutputDestinationClip, kDefaultOutputViewport, kDefaultMaxLuminance, - kDefaultOutputDataspace, kDefaultColorTransformMat, - kDefaultOutputOrientationFlags, kClientTargetLuminanceNits}) + .thenExpectDisplaySettingsUsed({.physicalDisplay = kDefaultOutputDestinationClip, + .clip = kDefaultOutputViewport, + .maxLuminance = kDefaultMaxLuminance, + .currentLuminanceNits = kDefaultMaxLuminance, + .outputDataspace = kDefaultOutputDataspace, + .colorTransform = kDefaultColorTransformMat, + .orientation = kDefaultOutputOrientationFlags, + .targetLuminanceNits = kClientTargetLuminanceNits}) .execute() .expectAFenceWasReturned(); } @@ -3470,11 +3514,16 @@ TEST_F(OutputComposeSurfacesTest_UsesExpectedDisplaySettings, forHdrOnlyClientCo TEST_F(OutputComposeSurfacesTest_UsesExpectedDisplaySettings, forNonHdrOnlyClientComposition) { verify().ifMixedCompositionIs(false) .andIfUsesHdr(false) + .withDisplayBrightnessNits(kUnknownLuminance) .andIfSkipColorTransform(false) - .thenExpectDisplaySettingsUsed( - {kDefaultOutputDestinationClip, kDefaultOutputViewport, kDefaultMaxLuminance, - kDefaultOutputDataspace, kDefaultColorTransformMat, - kDefaultOutputOrientationFlags, kClientTargetLuminanceNits}) + .thenExpectDisplaySettingsUsed({.physicalDisplay = kDefaultOutputDestinationClip, + .clip = kDefaultOutputViewport, + .maxLuminance = kDefaultMaxLuminance, + .currentLuminanceNits = kDefaultMaxLuminance, + .outputDataspace = kDefaultOutputDataspace, + .colorTransform = kDefaultColorTransformMat, + .orientation = kDefaultOutputOrientationFlags, + .targetLuminanceNits = kClientTargetLuminanceNits}) .execute() .expectAFenceWasReturned(); } @@ -3483,11 +3532,16 @@ TEST_F(OutputComposeSurfacesTest_UsesExpectedDisplaySettings, usesExpectedDisplaySettingsForHdrOnlyClientCompositionWithSkipClientTransform) { verify().ifMixedCompositionIs(false) .andIfUsesHdr(true) + .withDisplayBrightnessNits(kUnknownLuminance) .andIfSkipColorTransform(true) - .thenExpectDisplaySettingsUsed({kDefaultOutputDestinationClip, kDefaultOutputViewport, - kDefaultMaxLuminance, kDefaultOutputDataspace, mat4(), - kDefaultOutputOrientationFlags, - kClientTargetLuminanceNits}) + .thenExpectDisplaySettingsUsed({.physicalDisplay = kDefaultOutputDestinationClip, + .clip = kDefaultOutputViewport, + .maxLuminance = kDefaultMaxLuminance, + .currentLuminanceNits = kDefaultMaxLuminance, + .outputDataspace = kDefaultOutputDataspace, + .colorTransform = mat4(), + .orientation = kDefaultOutputOrientationFlags, + .targetLuminanceNits = kClientTargetLuminanceNits}) .execute() .expectAFenceWasReturned(); } -- cgit v1.2.3-59-g8ed1b From 5a49372252e8ef08387800effe6d39196f274027 Mon Sep 17 00:00:00 2001 From: Alec Mouri Date: Wed, 26 Jan 2022 16:43:02 -0800 Subject: 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 --- libs/renderengine/tests/RenderEngineTest.cpp | 325 ++++++++++++++++----------- libs/shaders/shaders.cpp | 61 +++-- libs/tonemap/include/tonemap/tonemap.h | 2 - libs/tonemap/tests/tonemap_test.cpp | 14 +- libs/tonemap/tonemap.cpp | 101 ++++----- 5 files changed, 290 insertions(+), 213 deletions(-) (limited to 'libs/shaders/shaders.cpp') 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 eotf, + std::function scaleOotf); + void initializeRenderEngine(); std::unique_ptr mRE; @@ -1418,6 +1468,119 @@ void RenderEngineTest::drawShadowWithoutCaster(const FloatRect& castingBounds, invokeDraw(settings, layers); } +void RenderEngineTest::tonemap(ui::Dataspace sourceDataspace, std::function eotf, + std::function 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(&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 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(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(sourceDataspace), + static_cast( + 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(targetRGB.r), static_cast(targetRGB.g), + static_cast(targetRGB.b), 255); + }; + + expectBufferColor(Rect(kGreyLevels, 1), generator, 2); +} + INSTANTIATE_TEST_SUITE_P(PerRenderEngineType, RenderEngineTest, testing::Values(std::make_shared(), std::make_shared(), @@ -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(&pixels)); + tonemap( + static_cast(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(HAL_DATASPACE_STANDARD_BT2020 | - HAL_DATASPACE_TRANSFER_ST2084 | - HAL_DATASPACE_RANGE_FULL), - }; - - std::vector 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(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( - HAL_DATASPACE_STANDARD_BT2020 | - HAL_DATASPACE_TRANSFER_ST2084 | - HAL_DATASPACE_RANGE_FULL), - static_cast( - 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(targetRGB.r), static_cast(targetRGB.g), - static_cast(targetRGB.b), 255); - }; + initializeRenderEngine(); - expectBufferColor(Rect(kGreyLevels, 1), generator, 2); + tonemap( + static_cast(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 +#include #include #include @@ -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(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 ::value, bool> = true> +std::vector buildUniformValue(T value) { + std::vector 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 ::value, bool> = true> -std::vector buildUniformValue(T value) { - std::vector 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 buildLinearEffectUniforms(const LinearEffect& linearEffect, const mat4& colorTransform, @@ -480,8 +492,13 @@ std::vector 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(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; -- cgit v1.2.3-59-g8ed1b From 1a3c5456dd9e05642c3d0f388102c511e80b5108 Mon Sep 17 00:00:00 2001 From: Alec Mouri Date: Fri, 4 Mar 2022 22:44:51 +0000 Subject: Add AHardwareBuffer as a tone-mapping input. This is to allow for partners to take gralloc4 metadata into account for their tone-mapping operation. Bug: 212641375 Test: builds Change-Id: Id20291fc1a1a0350a7fff0a8e703f242c68d2b28 --- libs/renderengine/skia/SkiaGLRenderEngine.cpp | 6 +++++- libs/renderengine/skia/filters/LinearEffect.cpp | 7 ++++--- libs/renderengine/skia/filters/LinearEffect.h | 5 ++++- libs/shaders/Android.bp | 2 ++ libs/shaders/include/shaders/shaders.h | 3 ++- libs/shaders/shaders.cpp | 8 +++++--- libs/tonemap/Android.bp | 2 ++ libs/tonemap/include/tonemap/tonemap.h | 12 ++++++++++++ libs/tonemap/tests/Android.bp | 1 + 9 files changed, 37 insertions(+), 9 deletions(-) (limited to 'libs/shaders/shaders.cpp') diff --git a/libs/renderengine/skia/SkiaGLRenderEngine.cpp b/libs/renderengine/skia/SkiaGLRenderEngine.cpp index b467b3538d..5e5618b9aa 100644 --- a/libs/renderengine/skia/SkiaGLRenderEngine.cpp +++ b/libs/renderengine/skia/SkiaGLRenderEngine.cpp @@ -654,10 +654,14 @@ sk_sp SkiaGLRenderEngine::createRuntimeEffectShader( colorTransform *= mat4::scale(vec4(parameters.layerDimmingRatio, parameters.layerDimmingRatio, parameters.layerDimmingRatio, 1.f)); + const auto targetBuffer = parameters.layer.source.buffer.buffer; + const auto graphicBuffer = targetBuffer ? targetBuffer->getBuffer() : nullptr; + const auto hardwareBuffer = graphicBuffer ? graphicBuffer->toAHardwareBuffer() : nullptr; return createLinearEffectShader(parameters.shader, effect, runtimeEffect, colorTransform, parameters.display.maxLuminance, parameters.display.currentLuminanceNits, - parameters.layer.source.buffer.maxLuminanceNits); + parameters.layer.source.buffer.maxLuminanceNits, + hardwareBuffer); } return parameters.shader; } diff --git a/libs/renderengine/skia/filters/LinearEffect.cpp b/libs/renderengine/skia/filters/LinearEffect.cpp index a46329d9fc..d479606f2b 100644 --- a/libs/renderengine/skia/filters/LinearEffect.cpp +++ b/libs/renderengine/skia/filters/LinearEffect.cpp @@ -44,7 +44,8 @@ sk_sp createLinearEffectShader(sk_sp shader, const shaders::LinearEffect& linearEffect, sk_sp runtimeEffect, const mat4& colorTransform, float maxDisplayLuminance, - float currentDisplayLuminanceNits, float maxLuminance) { + float currentDisplayLuminanceNits, float maxLuminance, + AHardwareBuffer* buffer) { ATRACE_CALL(); SkRuntimeShaderBuilder effectBuilder(runtimeEffect); @@ -52,7 +53,7 @@ sk_sp createLinearEffectShader(sk_sp shader, const auto uniforms = shaders::buildLinearEffectUniforms(linearEffect, colorTransform, maxDisplayLuminance, - currentDisplayLuminanceNits, maxLuminance); + currentDisplayLuminanceNits, maxLuminance, buffer); for (const auto& uniform : uniforms) { effectBuilder.uniform(uniform.name.c_str()).set(uniform.value.data(), uniform.value.size()); @@ -63,4 +64,4 @@ sk_sp createLinearEffectShader(sk_sp shader, } // namespace skia } // namespace renderengine -} // namespace android \ No newline at end of file +} // namespace android diff --git a/libs/renderengine/skia/filters/LinearEffect.h b/libs/renderengine/skia/filters/LinearEffect.h index e0a556b11a..26bae3b5f0 100644 --- a/libs/renderengine/skia/filters/LinearEffect.h +++ b/libs/renderengine/skia/filters/LinearEffect.h @@ -40,11 +40,14 @@ sk_sp buildRuntimeEffect(const shaders::LinearEffect& linearEff // * The current luminance of the physical display in nits // * The max luminance is provided as the max luminance for the buffer, either from the SMPTE 2086 // or as the max light level from the CTA 861.3 standard. +// * An AHardwareBuffer for implementations that support gralloc4 metadata for +// communicating any HDR metadata. sk_sp createLinearEffectShader(sk_sp inputShader, const shaders::LinearEffect& linearEffect, sk_sp runtimeEffect, const mat4& colorTransform, float maxDisplayLuminance, - float currentDisplayLuminanceNits, float maxLuminance); + float currentDisplayLuminanceNits, float maxLuminance, + AHardwareBuffer* buffer); } // namespace skia } // namespace renderengine } // namespace android diff --git a/libs/shaders/Android.bp b/libs/shaders/Android.bp index 1cd143e98d..2f8bf49c8a 100644 --- a/libs/shaders/Android.bp +++ b/libs/shaders/Android.bp @@ -30,9 +30,11 @@ cc_library_static { shared_libs: [ "android.hardware.graphics.common-V3-ndk", "android.hardware.graphics.common@1.2", + "libnativewindow", ], static_libs: [ + "libarect", "libmath", "libtonemap", "libui-types", diff --git a/libs/shaders/include/shaders/shaders.h b/libs/shaders/include/shaders/shaders.h index 43828ccd05..4ec7594901 100644 --- a/libs/shaders/include/shaders/shaders.h +++ b/libs/shaders/include/shaders/shaders.h @@ -101,6 +101,7 @@ std::vector buildLinearEffectUniforms(const LinearEffect const mat4& colorTransform, float maxDisplayLuminance, float currentDisplayLuminanceNits, - float maxLuminance); + float maxLuminance, + AHardwareBuffer* buffer = nullptr); } // namespace android::shaders diff --git a/libs/shaders/shaders.cpp b/libs/shaders/shaders.cpp index 03da3ecd62..0d77519ab7 100644 --- a/libs/shaders/shaders.cpp +++ b/libs/shaders/shaders.cpp @@ -476,7 +476,8 @@ std::vector buildLinearEffectUniforms(const LinearEffect const mat4& colorTransform, float maxDisplayLuminance, float currentDisplayLuminanceNits, - float maxLuminance) { + float maxLuminance, + AHardwareBuffer* buffer) { std::vector uniforms; if (linearEffect.inputDataspace == linearEffect.outputDataspace) { uniforms.push_back({.name = "in_rgbToXyz", .value = buildUniformValue(mat4())}); @@ -504,7 +505,8 @@ std::vector buildLinearEffectUniforms(const LinearEffect // This will be the case for eg screenshots in addition to // uncalibrated displays .contentMaxLuminance = - maxLuminance > 0 ? maxLuminance : maxDisplayLuminance}; + maxLuminance > 0 ? maxLuminance : maxDisplayLuminance, + .buffer = buffer}; for (const auto uniform : tonemap::getToneMapper()->generateShaderSkSLUniforms(metadata)) { uniforms.push_back(uniform); @@ -513,4 +515,4 @@ std::vector buildLinearEffectUniforms(const LinearEffect return uniforms; } -} // namespace android::shaders \ No newline at end of file +} // namespace android::shaders diff --git a/libs/tonemap/Android.bp b/libs/tonemap/Android.bp index 99d1b22717..dc55586a1d 100644 --- a/libs/tonemap/Android.bp +++ b/libs/tonemap/Android.bp @@ -30,9 +30,11 @@ cc_library_static { shared_libs: [ "android.hardware.graphics.common-V3-ndk", "liblog", + "libnativewindow", ], static_libs: [ + "libarect", "libmath", ], diff --git a/libs/tonemap/include/tonemap/tonemap.h b/libs/tonemap/include/tonemap/tonemap.h index 9fba642b31..e380323dd3 100644 --- a/libs/tonemap/include/tonemap/tonemap.h +++ b/libs/tonemap/include/tonemap/tonemap.h @@ -17,6 +17,7 @@ #pragma once #include +#include #include #include @@ -44,8 +45,19 @@ struct ShaderUniform { struct Metadata { // The maximum luminance of the display in nits float displayMaxLuminance = 0.0; + // The maximum luminance of the content in nits float contentMaxLuminance = 0.0; + + // Reference to an AHardwareBuffer. + // Devices that support gralloc 4.0 and higher may attach metadata onto a + // particular frame's buffer, including metadata used by HDR-standards like + // SMPTE 2086 or SMPTE 2094-40. + // Note that this parameter may be optional if there is no hardware buffer + // available, for instance if the source content is generated from a GL + // texture that does not have associated metadata. As such, implementations + // must support nullptr. + AHardwareBuffer* buffer = nullptr; }; // Utility class containing pre-processed conversions for a particular color diff --git a/libs/tonemap/tests/Android.bp b/libs/tonemap/tests/Android.bp index d519482fe3..26a1d79058 100644 --- a/libs/tonemap/tests/Android.bp +++ b/libs/tonemap/tests/Android.bp @@ -32,6 +32,7 @@ cc_test { ], shared_libs: [ "android.hardware.graphics.common-V3-ndk", + "libnativewindow", ], static_libs: [ "libmath", -- cgit v1.2.3-59-g8ed1b From 7a577450e536aa1e99f229a0cb3d3531c82e8a8d Mon Sep 17 00:00:00 2001 From: Alec Mouri Date: Fri, 4 Mar 2022 23:41:38 +0000 Subject: Push HLG OOTF down to libtonemap. Usage of current display brightness may be vendor-configured when the display brightness is very low, so keep the OOTF in libtonemap as part of the reference implementation. Concretely, this means that: * The BT2100 recommended OOTF for HLG->output format is moved from ScaleLuminance in libshaders to be the first part of the tonemapping operator in libtonemap * The inverse OOTF for input format->HLG is moved from NormalizeLuminance in libshaders to the end of the tonemapping operator in libtonemp * Current display brightness is only taken into account in the default tonemapping for Android T. The historic tonemapper does not take into account current display brightness, as it treats the "nominal peak brightness" of the display as 1000 nits instead of the current brightness. Also add a default lower-bound for using the current display brightness, because not having a bound looks really terrible on existing shipping devices Bug: 208933319 Test: builds Test: HLG test video looks okay Test: HDR10 test video didn't break Change-Id: I4f489c68f635a8ecc4d497b98c32e91c297d0765 --- libs/renderengine/tests/RenderEngineTest.cpp | 3 +- libs/shaders/shaders.cpp | 21 ++---- libs/tonemap/include/tonemap/tonemap.h | 3 + libs/tonemap/tonemap.cpp | 103 +++++++++++++++++++++++---- 4 files changed, 100 insertions(+), 30 deletions(-) (limited to 'libs/shaders/shaders.cpp') diff --git a/libs/renderengine/tests/RenderEngineTest.cpp b/libs/renderengine/tests/RenderEngineTest.cpp index ec1bd470b5..38ae2fd4ed 100644 --- a/libs/renderengine/tests/RenderEngineTest.cpp +++ b/libs/renderengine/tests/RenderEngineTest.cpp @@ -2619,8 +2619,7 @@ TEST_P(RenderEngineTest, test_tonemapHLGMatches) { [](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); + return color * kMaxHLGLuminance; }); } diff --git a/libs/shaders/shaders.cpp b/libs/shaders/shaders.cpp index 0d77519ab7..5935589fdf 100644 --- a/libs/shaders/shaders.cpp +++ b/libs/shaders/shaders.cpp @@ -185,9 +185,8 @@ void generateLuminanceScalesForOOTF(ui::Dataspace inputDataspace, ui::Dataspace break; case HAL_DATASPACE_TRANSFER_HLG: shader.append(R"( - uniform float in_hlgGamma; float3 ScaleLuminance(float3 xyz) { - return xyz * 1000.0 * pow(xyz.y, in_hlgGamma - 1); + return xyz * 1000.0; } )"); break; @@ -228,10 +227,8 @@ 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, (1 - in_hlgGamma) / (in_hlgGamma)); + return xyz / 1000.0; } )"); break; @@ -451,11 +448,6 @@ std::vector buildUniformValue(T 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) { @@ -493,12 +485,6 @@ std::vector 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(computeHlgGamma(currentDisplayLuminanceNits))}); - } - tonemap::Metadata metadata{.displayMaxLuminance = maxDisplayLuminance, // If the input luminance is unknown, use display luminance (aka, // no-op any luminance changes) @@ -506,6 +492,9 @@ std::vector buildLinearEffectUniforms(const LinearEffect // uncalibrated displays .contentMaxLuminance = maxLuminance > 0 ? maxLuminance : maxDisplayLuminance, + .currentDisplayLuminance = currentDisplayLuminanceNits > 0 + ? currentDisplayLuminanceNits + : maxDisplayLuminance, .buffer = buffer}; for (const auto uniform : tonemap::getToneMapper()->generateShaderSkSLUniforms(metadata)) { diff --git a/libs/tonemap/include/tonemap/tonemap.h b/libs/tonemap/include/tonemap/tonemap.h index e380323dd3..c51016db56 100644 --- a/libs/tonemap/include/tonemap/tonemap.h +++ b/libs/tonemap/include/tonemap/tonemap.h @@ -49,6 +49,9 @@ struct Metadata { // The maximum luminance of the content in nits float contentMaxLuminance = 0.0; + // The current brightness of the display in nits + float currentDisplayLuminance = 0.0; + // Reference to an AHardwareBuffer. // Devices that support gralloc 4.0 and higher may attach metadata onto a // particular frame's buffer, including metadata used by HDR-standards like diff --git a/libs/tonemap/tonemap.cpp b/libs/tonemap/tonemap.cpp index c4f46bd97c..19e1eea12d 100644 --- a/libs/tonemap/tonemap.cpp +++ b/libs/tonemap/tonemap.cpp @@ -48,6 +48,22 @@ std::vector buildUniformValue(T value) { return result; } +// Refer to BT2100-2 +float computeHlgGamma(float currentDisplayBrightnessNits) { + // BT 2100-2's recommendation for taking into account the nominal max + // brightness of the display does not work when the current brightness is + // very low. For instance, the gamma becomes negative when the current + // brightness is between 1 and 2 nits, which would be a bad experience in a + // dark environment. Furthermore, BT2100-2 recommends applying + // channel^(gamma - 1) as its OOTF, which means that when the current + // brightness is lower than 335 nits then channel * channel^(gamma - 1) > + // channel, which makes dark scenes very bright. As a workaround for those + // problems, lower-bound the brightness to 500 nits. + constexpr float minBrightnessNits = 500.f; + currentDisplayBrightnessNits = std::max(minBrightnessNits, currentDisplayBrightnessNits); + return 1.2 + 0.42 * std::log10(currentDisplayBrightnessNits / 1000); +} + class ToneMapperO : public ToneMapper { public: std::string generateTonemapGainShaderSkSL( @@ -79,11 +95,27 @@ public: // HLG output. program.append(R"( float libtonemap_ToneMapTargetNits(vec3 xyz) { - return clamp(xyz.y, 0.0, 1000.0); + float nits = clamp(xyz.y, 0.0, 1000.0); + return nits * pow(nits / 1000.0, -0.2 / 1.2); } )"); break; default: + // HLG follows BT2100, but this tonemapping version + // does not take into account current display brightness + if ((sourceDataspaceInt & kTransferMask) == kTransferHLG) { + program.append(R"( + float libtonemap_applyBaseOOTFGain(float nits) { + return pow(nits, 0.2); + } + )"); + } else { + program.append(R"( + float libtonemap_applyBaseOOTFGain(float nits) { + return 1.0; + } + )"); + } // Here we're mapping from HDR to SDR content, so interpolate using a // Hermitian polynomial onto the smaller luminance range. program.append(R"( @@ -91,6 +123,8 @@ public: float maxInLumi = in_libtonemap_inputMaxLuminance; float maxOutLumi = in_libtonemap_displayMaxLuminance; + xyz = xyz * libtonemap_applyBaseOOTFGain(xyz.y); + float nits = xyz.y; // if the max input luminance is less than what we can @@ -153,6 +187,21 @@ public: switch (destinationDataspaceInt & kTransferMask) { case kTransferST2084: case kTransferHLG: + // HLG follows BT2100, but this tonemapping version + // does not take into account current display brightness + if ((destinationDataspaceInt & kTransferMask) == kTransferHLG) { + program.append(R"( + float libtonemap_applyBaseOOTFGain(float nits) { + return pow(nits / 1000.0, -0.2 / 1.2); + } + )"); + } else { + program.append(R"( + float libtonemap_applyBaseOOTFGain(float nits) { + return 1.0; + } + )"); + } // Map from SDR onto an HDR output buffer // Here we use a polynomial curve to map from [0, displayMaxLuminance] onto // [0, maxOutLumi] which is hard-coded to be 3000 nits. @@ -178,7 +227,7 @@ public: if (nits <= x0) { // scale [0.0, x0] to [0.0, y0] linearly float slope = y0 / x0; - return nits * slope; + nits = nits * slope; } else if (nits <= x1) { // scale [x0, x1] to [y0, y1] using a curve float t = (nits - x0) / (x1 - x0); @@ -196,7 +245,7 @@ public: 2.0 * (1.0 - t) * t * c3 + t * t * y3; } - return nits; + return nits * libtonemap_applyBaseOOTFGain(nits); } )"); break; @@ -264,12 +313,17 @@ public: // so we'll clamp the luminance range in case we're mapping from PQ // input to HLG output. targetNits = std::clamp(xyz.y, 0.0f, 1000.0f); + targetNits *= std::pow(targetNits / 1000.f, -0.2 / 1.2); break; default: // Here we're mapping from HDR to SDR content, so interpolate using a // Hermitian polynomial onto the smaller luminance range. targetNits = xyz.y; + + if ((sourceDataspaceInt & kTransferMask) == kTransferHLG) { + targetNits *= std::pow(targetNits, 0.2); + } // if the max input luminance is less than what we can output then // no tone mapping is needed as all color values will be in range. if (metadata.contentMaxLuminance > metadata.displayMaxLuminance) { @@ -362,6 +416,10 @@ public: targetNits = (1.0 - t) * (1.0 - t) * y2 + 2.0 * (1.0 - t) * t * c3 + t * t * y3; } + + if ((destinationDataspaceInt & kTransferMask) == kTransferHLG) { + targetNits *= std::pow(targetNits / 1000.0, -0.2 / 1.2); + } } break; default: // For completeness, this is tone-mapping from SDR to SDR, where this is @@ -411,6 +469,7 @@ public: program.append(R"( uniform float in_libtonemap_displayMaxLuminance; uniform float in_libtonemap_inputMaxLuminance; + uniform float in_libtonemap_hlgGamma; )"); switch (sourceDataspaceInt & kTransferMask) { case kTransferST2084: @@ -428,7 +487,10 @@ public: // HLG output. program.append(R"( float libtonemap_ToneMapTargetNits(float maxRGB) { - return clamp(maxRGB, 0.0, 1000.0); + float nits = clamp(maxRGB, 0.0, 1000.0); + float gamma = (1 - in_libtonemap_hlgGamma) + / in_libtonemap_hlgGamma; + return nits * pow(nits / 1000.0, gamma); } )"); break; @@ -497,8 +559,15 @@ public: break; case kTransferHLG: switch (destinationDataspaceInt & kTransferMask) { - // HLG -> HDR does not tone-map at all + // HLG uses the OOTF from BT 2100. case kTransferST2084: + program.append(R"( + float libtonemap_ToneMapTargetNits(float maxRGB) { + return maxRGB + * pow(maxRGB / 1000.0, in_libtonemap_hlgGamma - 1); + } + )"); + break; case kTransferHLG: program.append(R"( float libtonemap_ToneMapTargetNits(float maxRGB) { @@ -507,13 +576,14 @@ public: )"); 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] + // Follow BT 2100 and 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; + return maxRGB + * pow(maxRGB / 1000.0, in_libtonemap_hlgGamma - 1) + * in_libtonemap_displayMaxLuminance / 1000.0; } )"); break; @@ -545,11 +615,14 @@ public: // Hardcode the max content luminance to a "reasonable" level static const constexpr float kContentMaxLuminance = 4000.f; std::vector uniforms; - uniforms.reserve(2); + uniforms.reserve(3); uniforms.push_back({.name = "in_libtonemap_displayMaxLuminance", .value = buildUniformValue(metadata.displayMaxLuminance)}); uniforms.push_back({.name = "in_libtonemap_inputMaxLuminance", .value = buildUniformValue(kContentMaxLuminance)}); + uniforms.push_back({.name = "in_libtonemap_hlgGamma", + .value = buildUniformValue( + computeHlgGamma(metadata.currentDisplayLuminance))}); return uniforms; } @@ -580,6 +653,8 @@ public: const double slope2 = (y2 - y1) / (greyNorm2 - greyNorm1); const double slope3 = (y3 - y2) / (greyNorm3 - greyNorm2); + const double hlgGamma = computeHlgGamma(metadata.currentDisplayLuminance); + for (const auto [linearRGB, _] : colors) { double maxRGB = std::max({linearRGB.r, linearRGB.g, linearRGB.b}); @@ -603,6 +678,7 @@ public: // so we'll clamp the luminance range in case we're mapping from PQ // input to HLG output. targetNits = std::clamp(maxRGB, 0.0, 1000.0); + targetNits *= pow(targetNits / 1000.0, (1 - hlgGamma) / (hlgGamma)); break; default: targetNits = maxRGB; @@ -630,11 +706,14 @@ public: case kTransferHLG: switch (destinationDataspaceInt & kTransferMask) { case kTransferST2084: + targetNits = maxRGB * pow(maxRGB / 1000.0, hlgGamma - 1); + break; case kTransferHLG: targetNits = maxRGB; break; default: - targetNits = maxRGB * metadata.displayMaxLuminance / 1000.0; + targetNits = maxRGB * pow(maxRGB / 1000.0, hlgGamma - 1) * + metadata.displayMaxLuminance / 1000.0; break; } break; -- cgit v1.2.3-59-g8ed1b From fcedb9ca3fa14607451ca24f32539a36f2261912 Mon Sep 17 00:00:00 2001 From: Alec Mouri Date: Mon, 11 Apr 2022 20:02:17 +0000 Subject: Support RenderIntents in libtonemap. Add RenderIntent as a supported uniform which is needed as some devices may perform contrast enhancements intended for SDR content which must be compensated for pre-blend for HDR content. Bug: 227779465 Test: builds Change-Id: Id74277e727d73cb9e371c37a83bef805e66271f4 --- .../include/renderengine/DisplaySettings.h | 5 + libs/renderengine/skia/SkiaGLRenderEngine.cpp | 2 +- libs/renderengine/skia/filters/LinearEffect.cpp | 14 +-- libs/renderengine/skia/filters/LinearEffect.h | 13 +-- libs/shaders/Android.bp | 1 + libs/shaders/include/shaders/shaders.h | 11 +-- libs/shaders/shaders.cpp | 13 ++- libs/tonemap/Android.bp | 1 + libs/tonemap/include/tonemap/tonemap.h | 14 ++- libs/tonemap/tests/Android.bp | 1 + .../CompositionEngine/src/Output.cpp | 3 + .../CompositionEngine/tests/OutputTest.cpp | 103 ++++++++++++++++----- services/surfaceflinger/SurfaceFlinger.cpp | 5 + 13 files changed, 136 insertions(+), 50 deletions(-) (limited to 'libs/shaders/shaders.cpp') diff --git a/libs/renderengine/include/renderengine/DisplaySettings.h b/libs/renderengine/include/renderengine/DisplaySettings.h index bf506448f6..a5e0879ce5 100644 --- a/libs/renderengine/include/renderengine/DisplaySettings.h +++ b/libs/renderengine/include/renderengine/DisplaySettings.h @@ -17,6 +17,7 @@ #pragma once #include +#include #include #include @@ -73,6 +74,10 @@ struct DisplaySettings { // Configures when dimming should be applied for each layer. aidl::android::hardware::graphics::composer3::DimmingStage dimmingStage = aidl::android::hardware::graphics::composer3::DimmingStage::NONE; + + // Configures the rendering intent of the output display. This is used for tonemapping. + aidl::android::hardware::graphics::composer3::RenderIntent renderIntent = + aidl::android::hardware::graphics::composer3::RenderIntent::TONE_MAP_COLORIMETRIC; }; static inline bool operator==(const DisplaySettings& lhs, const DisplaySettings& rhs) { diff --git a/libs/renderengine/skia/SkiaGLRenderEngine.cpp b/libs/renderengine/skia/SkiaGLRenderEngine.cpp index a77a798d0b..76ae2fc38c 100644 --- a/libs/renderengine/skia/SkiaGLRenderEngine.cpp +++ b/libs/renderengine/skia/SkiaGLRenderEngine.cpp @@ -662,7 +662,7 @@ sk_sp SkiaGLRenderEngine::createRuntimeEffectShader( parameters.display.maxLuminance, parameters.display.currentLuminanceNits, parameters.layer.source.buffer.maxLuminanceNits, - hardwareBuffer); + hardwareBuffer, parameters.display.renderIntent); } return parameters.shader; } diff --git a/libs/renderengine/skia/filters/LinearEffect.cpp b/libs/renderengine/skia/filters/LinearEffect.cpp index d479606f2b..f7dcd3a6e1 100644 --- a/libs/renderengine/skia/filters/LinearEffect.cpp +++ b/libs/renderengine/skia/filters/LinearEffect.cpp @@ -40,12 +40,11 @@ sk_sp buildRuntimeEffect(const shaders::LinearEffect& linearEff return shader; } -sk_sp createLinearEffectShader(sk_sp shader, - const shaders::LinearEffect& linearEffect, - sk_sp runtimeEffect, - const mat4& colorTransform, float maxDisplayLuminance, - float currentDisplayLuminanceNits, float maxLuminance, - AHardwareBuffer* buffer) { +sk_sp createLinearEffectShader( + sk_sp shader, const shaders::LinearEffect& linearEffect, + sk_sp runtimeEffect, const mat4& colorTransform, float maxDisplayLuminance, + float currentDisplayLuminanceNits, float maxLuminance, AHardwareBuffer* buffer, + aidl::android::hardware::graphics::composer3::RenderIntent renderIntent) { ATRACE_CALL(); SkRuntimeShaderBuilder effectBuilder(runtimeEffect); @@ -53,7 +52,8 @@ sk_sp createLinearEffectShader(sk_sp shader, const auto uniforms = shaders::buildLinearEffectUniforms(linearEffect, colorTransform, maxDisplayLuminance, - currentDisplayLuminanceNits, maxLuminance, buffer); + currentDisplayLuminanceNits, maxLuminance, buffer, + renderIntent); for (const auto& uniform : uniforms) { effectBuilder.uniform(uniform.name.c_str()).set(uniform.value.data(), uniform.value.size()); diff --git a/libs/renderengine/skia/filters/LinearEffect.h b/libs/renderengine/skia/filters/LinearEffect.h index 26bae3b5f0..3c66c513d9 100644 --- a/libs/renderengine/skia/filters/LinearEffect.h +++ b/libs/renderengine/skia/filters/LinearEffect.h @@ -42,12 +42,13 @@ sk_sp buildRuntimeEffect(const shaders::LinearEffect& linearEff // or as the max light level from the CTA 861.3 standard. // * An AHardwareBuffer for implementations that support gralloc4 metadata for // communicating any HDR metadata. -sk_sp createLinearEffectShader(sk_sp inputShader, - const shaders::LinearEffect& linearEffect, - sk_sp runtimeEffect, - const mat4& colorTransform, float maxDisplayLuminance, - float currentDisplayLuminanceNits, float maxLuminance, - AHardwareBuffer* buffer); +// * A RenderIntent that communicates the downstream renderintent for a physical display, for image +// quality compensation. +sk_sp createLinearEffectShader( + sk_sp inputShader, const shaders::LinearEffect& linearEffect, + sk_sp runtimeEffect, const mat4& colorTransform, float maxDisplayLuminance, + float currentDisplayLuminanceNits, float maxLuminance, AHardwareBuffer* buffer, + aidl::android::hardware::graphics::composer3::RenderIntent renderIntent); } // namespace skia } // namespace renderengine } // namespace android diff --git a/libs/shaders/Android.bp b/libs/shaders/Android.bp index 2f8bf49c8a..6b936de0ec 100644 --- a/libs/shaders/Android.bp +++ b/libs/shaders/Android.bp @@ -29,6 +29,7 @@ cc_library_static { shared_libs: [ "android.hardware.graphics.common-V3-ndk", + "android.hardware.graphics.composer3-V1-ndk", "android.hardware.graphics.common@1.2", "libnativewindow", ], diff --git a/libs/shaders/include/shaders/shaders.h b/libs/shaders/include/shaders/shaders.h index 4ec7594901..2a4a370078 100644 --- a/libs/shaders/include/shaders/shaders.h +++ b/libs/shaders/include/shaders/shaders.h @@ -97,11 +97,10 @@ struct LinearEffectHasher { std::string buildLinearEffectSkSL(const LinearEffect& linearEffect); // Generates a list of uniforms to set on the LinearEffect shader above. -std::vector buildLinearEffectUniforms(const LinearEffect& linearEffect, - const mat4& colorTransform, - float maxDisplayLuminance, - float currentDisplayLuminanceNits, - float maxLuminance, - AHardwareBuffer* buffer = nullptr); +std::vector buildLinearEffectUniforms( + const LinearEffect& linearEffect, const mat4& colorTransform, float maxDisplayLuminance, + float currentDisplayLuminanceNits, float maxLuminance, AHardwareBuffer* buffer = nullptr, + aidl::android::hardware::graphics::composer3::RenderIntent renderIntent = + aidl::android::hardware::graphics::composer3::RenderIntent::TONE_MAP_COLORIMETRIC); } // namespace android::shaders diff --git a/libs/shaders/shaders.cpp b/libs/shaders/shaders.cpp index 5935589fdf..f0d45c2123 100644 --- a/libs/shaders/shaders.cpp +++ b/libs/shaders/shaders.cpp @@ -464,12 +464,10 @@ std::string buildLinearEffectSkSL(const LinearEffect& linearEffect) { } // Generates a list of uniforms to set on the LinearEffect shader above. -std::vector buildLinearEffectUniforms(const LinearEffect& linearEffect, - const mat4& colorTransform, - float maxDisplayLuminance, - float currentDisplayLuminanceNits, - float maxLuminance, - AHardwareBuffer* buffer) { +std::vector buildLinearEffectUniforms( + const LinearEffect& linearEffect, const mat4& colorTransform, float maxDisplayLuminance, + float currentDisplayLuminanceNits, float maxLuminance, AHardwareBuffer* buffer, + aidl::android::hardware::graphics::composer3::RenderIntent renderIntent) { std::vector uniforms; if (linearEffect.inputDataspace == linearEffect.outputDataspace) { uniforms.push_back({.name = "in_rgbToXyz", .value = buildUniformValue(mat4())}); @@ -495,7 +493,8 @@ std::vector buildLinearEffectUniforms(const LinearEffect .currentDisplayLuminance = currentDisplayLuminanceNits > 0 ? currentDisplayLuminanceNits : maxDisplayLuminance, - .buffer = buffer}; + .buffer = buffer, + .renderIntent = renderIntent}; for (const auto uniform : tonemap::getToneMapper()->generateShaderSkSLUniforms(metadata)) { uniforms.push_back(uniform); diff --git a/libs/tonemap/Android.bp b/libs/tonemap/Android.bp index dc55586a1d..37c98240c5 100644 --- a/libs/tonemap/Android.bp +++ b/libs/tonemap/Android.bp @@ -29,6 +29,7 @@ cc_library_static { shared_libs: [ "android.hardware.graphics.common-V3-ndk", + "android.hardware.graphics.composer3-V1-ndk", "liblog", "libnativewindow", ], diff --git a/libs/tonemap/include/tonemap/tonemap.h b/libs/tonemap/include/tonemap/tonemap.h index c51016db56..852fc87c57 100644 --- a/libs/tonemap/include/tonemap/tonemap.h +++ b/libs/tonemap/include/tonemap/tonemap.h @@ -17,6 +17,7 @@ #pragma once #include +#include #include #include @@ -41,7 +42,7 @@ struct ShaderUniform { // Describes metadata which may be used for constructing the shader uniforms. // This metadata should not be used for manipulating the source code of the shader program directly, -// as otherwise caching by other system of these shaders may break. +// as otherwise caching by other parts of the system using these shaders may break. struct Metadata { // The maximum luminance of the display in nits float displayMaxLuminance = 0.0; @@ -61,6 +62,17 @@ struct Metadata { // texture that does not have associated metadata. As such, implementations // must support nullptr. AHardwareBuffer* buffer = nullptr; + + // RenderIntent of the destination display. + // Non-colorimetric render-intents may be defined in order to take advantage of the full display + // gamut. Various contrast-enhancement mechanisms may be employed on SDR content as a result, + // which means that HDR content may need to be compensated in order to achieve correct blending + // behavior. This default is effectively optional - the display render intent may not be + // available to clients such as HWUI which are display-agnostic. For those clients, tone-map + // colorimetric may be assumed so that the luminance range may be converted to the correct range + // based on the output dataspace. + aidl::android::hardware::graphics::composer3::RenderIntent renderIntent = + aidl::android::hardware::graphics::composer3::RenderIntent::TONE_MAP_COLORIMETRIC; }; // Utility class containing pre-processed conversions for a particular color diff --git a/libs/tonemap/tests/Android.bp b/libs/tonemap/tests/Android.bp index 26a1d79058..58851b41be 100644 --- a/libs/tonemap/tests/Android.bp +++ b/libs/tonemap/tests/Android.bp @@ -32,6 +32,7 @@ cc_test { ], shared_libs: [ "android.hardware.graphics.common-V3-ndk", + "android.hardware.graphics.composer3-V1-ndk", "libnativewindow", ], static_libs: [ diff --git a/services/surfaceflinger/CompositionEngine/src/Output.cpp b/services/surfaceflinger/CompositionEngine/src/Output.cpp index e99b70ff1e..004e0717d2 100644 --- a/services/surfaceflinger/CompositionEngine/src/Output.cpp +++ b/services/surfaceflinger/CompositionEngine/src/Output.cpp @@ -1177,6 +1177,9 @@ std::optional Output::composeSurfaces( clientCompositionDisplay.targetLuminanceNits = outputState.clientTargetBrightness * outputState.displayBrightnessNits; clientCompositionDisplay.dimmingStage = outputState.clientTargetDimmingStage; + clientCompositionDisplay.renderIntent = + static_cast( + outputState.renderIntent); // Compute the global color transform matrix. clientCompositionDisplay.colorTransform = outputState.colorTransformMatrix; diff --git a/services/surfaceflinger/CompositionEngine/tests/OutputTest.cpp b/services/surfaceflinger/CompositionEngine/tests/OutputTest.cpp index 31a89af013..42c8b37710 100644 --- a/services/surfaceflinger/CompositionEngine/tests/OutputTest.cpp +++ b/services/surfaceflinger/CompositionEngine/tests/OutputTest.cpp @@ -3711,6 +3711,16 @@ struct OutputComposeSurfacesTest_UsesExpectedDisplaySettings : public OutputComp auto withDimmingStage( aidl::android::hardware::graphics::composer3::DimmingStage dimmingStage) { getInstance()->mOutput.mState.clientTargetDimmingStage = dimmingStage; + return nextState(); + } + }; + + struct OutputWithRenderIntent + : public CallOrderStateMachineHelper { + auto withRenderIntent( + aidl::android::hardware::graphics::composer3::RenderIntent renderIntent) { + getInstance()->mOutput.mState.renderIntent = + static_cast(renderIntent); return nextState(); } }; @@ -3744,6 +3754,8 @@ TEST_F(OutputComposeSurfacesTest_UsesExpectedDisplaySettings, forHdrMixedComposi .andIfUsesHdr(true) .withDisplayBrightnessNits(kUnknownLuminance) .withDimmingStage(aidl::android::hardware::graphics::composer3::DimmingStage::LINEAR) + .withRenderIntent( + aidl::android::hardware::graphics::composer3::RenderIntent::COLORIMETRIC) .andIfSkipColorTransform(false) .thenExpectDisplaySettingsUsed( {.physicalDisplay = kDefaultOutputDestinationClip, @@ -3756,7 +3768,9 @@ TEST_F(OutputComposeSurfacesTest_UsesExpectedDisplaySettings, forHdrMixedComposi .orientation = kDefaultOutputOrientationFlags, .targetLuminanceNits = kClientTargetLuminanceNits, .dimmingStage = - aidl::android::hardware::graphics::composer3::DimmingStage::LINEAR}) + aidl::android::hardware::graphics::composer3::DimmingStage::LINEAR, + .renderIntent = aidl::android::hardware::graphics::composer3::RenderIntent:: + COLORIMETRIC}) .execute() .expectAFenceWasReturned(); } @@ -3767,7 +3781,8 @@ TEST_F(OutputComposeSurfacesTest_UsesExpectedDisplaySettings, .andIfUsesHdr(true) .withDisplayBrightnessNits(kDisplayLuminance) .withDimmingStage(aidl::android::hardware::graphics::composer3::DimmingStage::LINEAR) - + .withRenderIntent( + aidl::android::hardware::graphics::composer3::RenderIntent::COLORIMETRIC) .andIfSkipColorTransform(false) .thenExpectDisplaySettingsUsed( {.physicalDisplay = kDefaultOutputDestinationClip, @@ -3780,7 +3795,9 @@ TEST_F(OutputComposeSurfacesTest_UsesExpectedDisplaySettings, .orientation = kDefaultOutputOrientationFlags, .targetLuminanceNits = kClientTargetLuminanceNits, .dimmingStage = - aidl::android::hardware::graphics::composer3::DimmingStage::LINEAR}) + aidl::android::hardware::graphics::composer3::DimmingStage::LINEAR, + .renderIntent = aidl::android::hardware::graphics::composer3::RenderIntent:: + COLORIMETRIC}) .execute() .expectAFenceWasReturned(); } @@ -3792,19 +3809,49 @@ TEST_F(OutputComposeSurfacesTest_UsesExpectedDisplaySettings, .withDisplayBrightnessNits(kUnknownLuminance) .withDimmingStage( aidl::android::hardware::graphics::composer3::DimmingStage::GAMMA_OETF) + .withRenderIntent( + aidl::android::hardware::graphics::composer3::RenderIntent::COLORIMETRIC) + .andIfSkipColorTransform(false) + .thenExpectDisplaySettingsUsed( + {.physicalDisplay = kDefaultOutputDestinationClip, + .clip = kDefaultOutputViewport, + .maxLuminance = kDefaultMaxLuminance, + .currentLuminanceNits = kDefaultMaxLuminance, + .outputDataspace = kDefaultOutputDataspace, + .colorTransform = kDefaultColorTransformMat, + .deviceHandlesColorTransform = true, + .orientation = kDefaultOutputOrientationFlags, + .targetLuminanceNits = kClientTargetLuminanceNits, + .dimmingStage = + aidl::android::hardware::graphics::composer3::DimmingStage::GAMMA_OETF, + .renderIntent = aidl::android::hardware::graphics::composer3::RenderIntent:: + COLORIMETRIC}) + .execute() + .expectAFenceWasReturned(); +} +TEST_F(OutputComposeSurfacesTest_UsesExpectedDisplaySettings, + forHdrMixedCompositionWithRenderIntent) { + verify().ifMixedCompositionIs(true) + .andIfUsesHdr(true) + .withDisplayBrightnessNits(kUnknownLuminance) + .withDimmingStage(aidl::android::hardware::graphics::composer3::DimmingStage::LINEAR) + .withRenderIntent(aidl::android::hardware::graphics::composer3::RenderIntent::ENHANCE) .andIfSkipColorTransform(false) - .thenExpectDisplaySettingsUsed({.physicalDisplay = kDefaultOutputDestinationClip, - .clip = kDefaultOutputViewport, - .maxLuminance = kDefaultMaxLuminance, - .currentLuminanceNits = kDefaultMaxLuminance, - .outputDataspace = kDefaultOutputDataspace, - .colorTransform = kDefaultColorTransformMat, - .deviceHandlesColorTransform = true, - .orientation = kDefaultOutputOrientationFlags, - .targetLuminanceNits = kClientTargetLuminanceNits, - .dimmingStage = aidl::android::hardware::graphics:: - composer3::DimmingStage::GAMMA_OETF}) + .thenExpectDisplaySettingsUsed( + {.physicalDisplay = kDefaultOutputDestinationClip, + .clip = kDefaultOutputViewport, + .maxLuminance = kDefaultMaxLuminance, + .currentLuminanceNits = kDefaultMaxLuminance, + .outputDataspace = kDefaultOutputDataspace, + .colorTransform = kDefaultColorTransformMat, + .deviceHandlesColorTransform = true, + .orientation = kDefaultOutputOrientationFlags, + .targetLuminanceNits = kClientTargetLuminanceNits, + .dimmingStage = + aidl::android::hardware::graphics::composer3::DimmingStage::LINEAR, + .renderIntent = + aidl::android::hardware::graphics::composer3::RenderIntent::ENHANCE}) .execute() .expectAFenceWasReturned(); } @@ -3814,7 +3861,8 @@ TEST_F(OutputComposeSurfacesTest_UsesExpectedDisplaySettings, forNonHdrMixedComp .andIfUsesHdr(false) .withDisplayBrightnessNits(kUnknownLuminance) .withDimmingStage(aidl::android::hardware::graphics::composer3::DimmingStage::LINEAR) - + .withRenderIntent( + aidl::android::hardware::graphics::composer3::RenderIntent::COLORIMETRIC) .andIfSkipColorTransform(false) .thenExpectDisplaySettingsUsed( {.physicalDisplay = kDefaultOutputDestinationClip, @@ -3827,7 +3875,9 @@ TEST_F(OutputComposeSurfacesTest_UsesExpectedDisplaySettings, forNonHdrMixedComp .orientation = kDefaultOutputOrientationFlags, .targetLuminanceNits = kClientTargetLuminanceNits, .dimmingStage = - aidl::android::hardware::graphics::composer3::DimmingStage::LINEAR}) + aidl::android::hardware::graphics::composer3::DimmingStage::LINEAR, + .renderIntent = aidl::android::hardware::graphics::composer3::RenderIntent:: + COLORIMETRIC}) .execute() .expectAFenceWasReturned(); } @@ -3837,7 +3887,8 @@ TEST_F(OutputComposeSurfacesTest_UsesExpectedDisplaySettings, forHdrOnlyClientCo .andIfUsesHdr(true) .withDisplayBrightnessNits(kUnknownLuminance) .withDimmingStage(aidl::android::hardware::graphics::composer3::DimmingStage::LINEAR) - + .withRenderIntent( + aidl::android::hardware::graphics::composer3::RenderIntent::COLORIMETRIC) .andIfSkipColorTransform(false) .thenExpectDisplaySettingsUsed( {.physicalDisplay = kDefaultOutputDestinationClip, @@ -3850,7 +3901,9 @@ TEST_F(OutputComposeSurfacesTest_UsesExpectedDisplaySettings, forHdrOnlyClientCo .orientation = kDefaultOutputOrientationFlags, .targetLuminanceNits = kClientTargetLuminanceNits, .dimmingStage = - aidl::android::hardware::graphics::composer3::DimmingStage::LINEAR}) + aidl::android::hardware::graphics::composer3::DimmingStage::LINEAR, + .renderIntent = aidl::android::hardware::graphics::composer3::RenderIntent:: + COLORIMETRIC}) .execute() .expectAFenceWasReturned(); } @@ -3860,7 +3913,8 @@ TEST_F(OutputComposeSurfacesTest_UsesExpectedDisplaySettings, forNonHdrOnlyClien .andIfUsesHdr(false) .withDisplayBrightnessNits(kUnknownLuminance) .withDimmingStage(aidl::android::hardware::graphics::composer3::DimmingStage::LINEAR) - + .withRenderIntent( + aidl::android::hardware::graphics::composer3::RenderIntent::COLORIMETRIC) .andIfSkipColorTransform(false) .thenExpectDisplaySettingsUsed( {.physicalDisplay = kDefaultOutputDestinationClip, @@ -3873,7 +3927,9 @@ TEST_F(OutputComposeSurfacesTest_UsesExpectedDisplaySettings, forNonHdrOnlyClien .orientation = kDefaultOutputOrientationFlags, .targetLuminanceNits = kClientTargetLuminanceNits, .dimmingStage = - aidl::android::hardware::graphics::composer3::DimmingStage::LINEAR}) + aidl::android::hardware::graphics::composer3::DimmingStage::LINEAR, + .renderIntent = aidl::android::hardware::graphics::composer3::RenderIntent:: + COLORIMETRIC}) .execute() .expectAFenceWasReturned(); } @@ -3884,7 +3940,8 @@ TEST_F(OutputComposeSurfacesTest_UsesExpectedDisplaySettings, .andIfUsesHdr(true) .withDisplayBrightnessNits(kUnknownLuminance) .withDimmingStage(aidl::android::hardware::graphics::composer3::DimmingStage::LINEAR) - + .withRenderIntent( + aidl::android::hardware::graphics::composer3::RenderIntent::COLORIMETRIC) .andIfSkipColorTransform(true) .thenExpectDisplaySettingsUsed( {.physicalDisplay = kDefaultOutputDestinationClip, @@ -3897,7 +3954,9 @@ TEST_F(OutputComposeSurfacesTest_UsesExpectedDisplaySettings, .orientation = kDefaultOutputOrientationFlags, .targetLuminanceNits = kClientTargetLuminanceNits, .dimmingStage = - aidl::android::hardware::graphics::composer3::DimmingStage::LINEAR}) + aidl::android::hardware::graphics::composer3::DimmingStage::LINEAR, + .renderIntent = aidl::android::hardware::graphics::composer3::RenderIntent:: + COLORIMETRIC}) .execute() .expectAFenceWasReturned(); } diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp index 115dc6435b..498f37ce4c 100644 --- a/services/surfaceflinger/SurfaceFlinger.cpp +++ b/services/surfaceflinger/SurfaceFlinger.cpp @@ -146,6 +146,7 @@ #include #include +#include #undef NO_THREAD_SAFETY_ANALYSIS #define NO_THREAD_SAFETY_ANALYSIS \ @@ -6668,6 +6669,7 @@ std::shared_future SurfaceFlinger::renderScree captureResults.buffer = buffer->getBuffer(); auto dataspace = renderArea.getReqDataSpace(); auto parent = renderArea.getParentLayer(); + auto renderIntent = RenderIntent::TONE_MAP_COLORIMETRIC; if ((dataspace == ui::Dataspace::UNKNOWN) && (parent != nullptr)) { Mutex::Autolock lock(mStateLock); auto display = findDisplay([layerStack = parent->getLayerStack()](const auto& display) { @@ -6680,6 +6682,7 @@ std::shared_future SurfaceFlinger::renderScree const ui::ColorMode colorMode = display->getCompositionDisplay()->getState().colorMode; dataspace = pickDataspaceFromColorMode(colorMode); + renderIntent = display->getCompositionDisplay()->getState().renderIntent; } captureResults.capturedDataspace = dataspace; @@ -6701,6 +6704,8 @@ std::shared_future SurfaceFlinger::renderScree clientCompositionDisplay.outputDataspace = dataspace; clientCompositionDisplay.maxLuminance = DisplayDevice::sDefaultMaxLumiance; + clientCompositionDisplay.renderIntent = + static_cast(renderIntent); const float colorSaturation = grayscale ? 0 : 1; clientCompositionDisplay.colorTransform = calculateColorMatrix(colorSaturation); -- cgit v1.2.3-59-g8ed1b From 575fb073170e38103ba3f18663ab25dadad45353 Mon Sep 17 00:00:00 2001 From: Sally Qi Date: Mon, 9 May 2022 12:34:52 -0700 Subject: Fix shader calculation equations that causes SDR grayscale mess-up. Bug: 219698906 Test: play YouTube videos with Focus Mode on Change-Id: Ic1d3ef72778da4e69d33c11dc11872356b856a41 --- libs/shaders/shaders.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'libs/shaders/shaders.cpp') diff --git a/libs/shaders/shaders.cpp b/libs/shaders/shaders.cpp index f0d45c2123..62745dc8d5 100644 --- a/libs/shaders/shaders.cpp +++ b/libs/shaders/shaders.cpp @@ -78,7 +78,7 @@ void generateEOTF(ui::Dataspace dataspace, std::string& shader) { shader.append(R"( float EOTF_sRGB(float srgb) { - return srgb <= 0.08125 ? srgb / 4.50 : pow((srgb + 0.099) / 1.099, 0.45); + return srgb <= 0.08125 ? srgb / 4.50 : pow((srgb + 0.099) / 1.099, 1 / 0.45); } float3 EOTF_sRGB(float3 srgb) { -- cgit v1.2.3-59-g8ed1b From 6ba7f2b4870df952ff8bb470fd52d91d9f206ae6 Mon Sep 17 00:00:00 2001 From: Alec Mouri Date: Thu, 2 Jun 2022 22:55:05 +0000 Subject: Use the correct dataspace in shaders.cpp This improves the quality of HDR->SDR tone-mapping in TextureView by removing an incorrect gamut mapping which was causing undesired hue shift. The technical detail is that in TextureView, skia will color manage the input image to the destination colorspace prior to tone-mapping. The tone-mapping shader library compensates when authoring the shader to use the correct EOTF when re-decoding the image, but did not compensate when binding uniforms. This patch compensates accordingly so that the correct gamut->gamut mapping is applied. This patch adds a brief test suite for libshaders to verify that the correct color gamut matrices are being selected. Bug: 234355355 Test: Observe accurate HLG colors in Photos and Instagram where TextureView is used for media playback. Test: libshaders_test Change-Id: I801349cfe1780880a55528fd7e91ff1ac553281b --- libs/shaders/TEST_MAPPING | 10 ++++ libs/shaders/shaders.cpp | 9 +++- libs/shaders/tests/Android.bp | 48 ++++++++++++++++++ libs/shaders/tests/shaders_test.cpp | 98 +++++++++++++++++++++++++++++++++++++ 4 files changed, 163 insertions(+), 2 deletions(-) create mode 100644 libs/shaders/TEST_MAPPING create mode 100644 libs/shaders/tests/Android.bp create mode 100644 libs/shaders/tests/shaders_test.cpp (limited to 'libs/shaders/shaders.cpp') diff --git a/libs/shaders/TEST_MAPPING b/libs/shaders/TEST_MAPPING new file mode 100644 index 0000000000..ad6514d201 --- /dev/null +++ b/libs/shaders/TEST_MAPPING @@ -0,0 +1,10 @@ +{ + "presubmit": [ + { + "name": "librenderengine_test" + }, + { + "name": "libshaders_test" + } + ] +} diff --git a/libs/shaders/shaders.cpp b/libs/shaders/shaders.cpp index 62745dc8d5..f80e93f6f8 100644 --- a/libs/shaders/shaders.cpp +++ b/libs/shaders/shaders.cpp @@ -469,12 +469,17 @@ std::vector buildLinearEffectUniforms( float currentDisplayLuminanceNits, float maxLuminance, AHardwareBuffer* buffer, aidl::android::hardware::graphics::composer3::RenderIntent renderIntent) { std::vector uniforms; - if (linearEffect.inputDataspace == linearEffect.outputDataspace) { + + const ui::Dataspace inputDataspace = linearEffect.fakeInputDataspace == ui::Dataspace::UNKNOWN + ? linearEffect.inputDataspace + : linearEffect.fakeInputDataspace; + + if (inputDataspace == linearEffect.outputDataspace) { uniforms.push_back({.name = "in_rgbToXyz", .value = buildUniformValue(mat4())}); uniforms.push_back( {.name = "in_xyzToRgb", .value = buildUniformValue(colorTransform)}); } else { - ColorSpace inputColorSpace = toColorSpace(linearEffect.inputDataspace); + ColorSpace inputColorSpace = toColorSpace(inputDataspace); ColorSpace outputColorSpace = toColorSpace(linearEffect.outputDataspace); uniforms.push_back({.name = "in_rgbToXyz", .value = buildUniformValue(mat4(inputColorSpace.getRGBtoXYZ()))}); diff --git a/libs/shaders/tests/Android.bp b/libs/shaders/tests/Android.bp new file mode 100644 index 0000000000..cf671bcb7a --- /dev/null +++ b/libs/shaders/tests/Android.bp @@ -0,0 +1,48 @@ +// Copyright 2022 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package { + // See: http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // all of the 'license_kinds' from "frameworks_native_license" + // to get the below license kinds: + // SPDX-license-identifier-Apache-2.0 + default_applicable_licenses: ["frameworks_native_license"], +} + +cc_test { + name: "libshaders_test", + test_suites: ["device-tests"], + srcs: [ + "shaders_test.cpp", + ], + header_libs: [ + "libtonemap_headers", + ], + shared_libs: [ + "android.hardware.graphics.common-V3-ndk", + "android.hardware.graphics.composer3-V1-ndk", + "android.hardware.graphics.common@1.2", + "libnativewindow", + ], + static_libs: [ + "libarect", + "libgmock", + "libgtest", + "libmath", + "libshaders", + "libtonemap", + "libui-types", + ], +} diff --git a/libs/shaders/tests/shaders_test.cpp b/libs/shaders/tests/shaders_test.cpp new file mode 100644 index 0000000000..d45fb246c7 --- /dev/null +++ b/libs/shaders/tests/shaders_test.cpp @@ -0,0 +1,98 @@ +/* + * Copyright 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "shaders/shaders.h" +#include +#include +#include +#include +#include +#include + +namespace android { + +using testing::Contains; +using testing::HasSubstr; + +struct ShadersTest : public ::testing::Test {}; + +namespace { + +MATCHER_P2(UniformEq, name, value, "") { + return arg.name == name && arg.value == value; +} + +template ::value, bool> = true> +std::vector buildUniformValue(T value) { + std::vector result; + result.resize(sizeof(value)); + std::memcpy(result.data(), &value, sizeof(value)); + return result; +} + +} // namespace + +TEST_F(ShadersTest, buildLinearEffectUniforms_selectsNoOpGamutMatrices) { + shaders::LinearEffect effect = + shaders::LinearEffect{.inputDataspace = ui::Dataspace::V0_SRGB_LINEAR, + .outputDataspace = ui::Dataspace::V0_SRGB_LINEAR, + .fakeInputDataspace = ui::Dataspace::UNKNOWN}; + + mat4 colorTransform = mat4::scale(vec4(.9, .9, .9, 1.)); + auto uniforms = + shaders::buildLinearEffectUniforms(effect, colorTransform, 1.f, 1.f, 1.f, nullptr, + aidl::android::hardware::graphics::composer3:: + RenderIntent::COLORIMETRIC); + EXPECT_THAT(uniforms, Contains(UniformEq("in_rgbToXyz", buildUniformValue(mat4())))); + EXPECT_THAT(uniforms, + Contains(UniformEq("in_xyzToRgb", buildUniformValue(colorTransform)))); +} + +TEST_F(ShadersTest, buildLinearEffectUniforms_selectsGamutTransformMatrices) { + shaders::LinearEffect effect = + shaders::LinearEffect{.inputDataspace = ui::Dataspace::V0_SRGB, + .outputDataspace = ui::Dataspace::DISPLAY_P3, + .fakeInputDataspace = ui::Dataspace::UNKNOWN}; + + ColorSpace inputColorSpace = ColorSpace::sRGB(); + ColorSpace outputColorSpace = ColorSpace::DisplayP3(); + auto uniforms = + shaders::buildLinearEffectUniforms(effect, mat4(), 1.f, 1.f, 1.f, nullptr, + aidl::android::hardware::graphics::composer3:: + RenderIntent::COLORIMETRIC); + EXPECT_THAT(uniforms, + Contains(UniformEq("in_rgbToXyz", + buildUniformValue(mat4(inputColorSpace.getRGBtoXYZ()))))); + EXPECT_THAT(uniforms, + Contains(UniformEq("in_xyzToRgb", + buildUniformValue(mat4(outputColorSpace.getXYZtoRGB()))))); +} + +TEST_F(ShadersTest, buildLinearEffectUniforms_respectsFakeInputDataspace) { + shaders::LinearEffect effect = + shaders::LinearEffect{.inputDataspace = ui::Dataspace::V0_SRGB, + .outputDataspace = ui::Dataspace::DISPLAY_P3, + .fakeInputDataspace = ui::Dataspace::DISPLAY_P3}; + + auto uniforms = + shaders::buildLinearEffectUniforms(effect, mat4(), 1.f, 1.f, 1.f, nullptr, + aidl::android::hardware::graphics::composer3:: + RenderIntent::COLORIMETRIC); + EXPECT_THAT(uniforms, Contains(UniformEq("in_rgbToXyz", buildUniformValue(mat4())))); + EXPECT_THAT(uniforms, Contains(UniformEq("in_xyzToRgb", buildUniformValue(mat4())))); +} + +} // namespace android -- cgit v1.2.3-59-g8ed1b