diff options
-rw-r--r-- | libs/renderengine/Android.bp | 6 | ||||
-rw-r--r-- | libs/renderengine/benchmark/Android.bp | 8 | ||||
-rw-r--r-- | libs/renderengine/skia/filters/LinearEffect.cpp | 198 | ||||
-rw-r--r-- | libs/renderengine/tests/Android.bp | 6 | ||||
-rw-r--r-- | libs/tonemap/Android.bp | 37 | ||||
-rw-r--r-- | libs/tonemap/OWNERS | 4 | ||||
-rw-r--r-- | libs/tonemap/TEST_MAPPING | 10 | ||||
-rw-r--r-- | libs/tonemap/include/tonemap/tonemap.h | 103 | ||||
-rw-r--r-- | libs/tonemap/tests/Android.bp | 38 | ||||
-rw-r--r-- | libs/tonemap/tests/tonemap_test.cpp | 76 | ||||
-rw-r--r-- | libs/tonemap/tonemap.cpp | 256 | ||||
-rw-r--r-- | services/surfaceflinger/Android.bp | 1 | ||||
-rw-r--r-- | services/surfaceflinger/CompositionEngine/Android.bp | 1 | ||||
-rw-r--r-- | services/surfaceflinger/tests/unittests/Android.bp | 1 |
14 files changed, 579 insertions, 166 deletions
diff --git a/libs/renderengine/Android.bp b/libs/renderengine/Android.bp index a62d2b9f85..ecfaef8928 100644 --- a/libs/renderengine/Android.bp +++ b/libs/renderengine/Android.bp @@ -40,6 +40,10 @@ cc_defaults { "libui", "libutils", ], + + static_libs: [ + "libtonemap", + ], local_include_dirs: ["include"], export_include_dirs: ["include"], } @@ -97,7 +101,7 @@ filegroup { "skia/filters/GaussianBlurFilter.cpp", "skia/filters/KawaseBlurFilter.cpp", "skia/filters/LinearEffect.cpp", - "skia/filters/StretchShaderFactory.cpp" + "skia/filters/StretchShaderFactory.cpp", ], } diff --git a/libs/renderengine/benchmark/Android.bp b/libs/renderengine/benchmark/Android.bp index 5968399544..baa50544b7 100644 --- a/libs/renderengine/benchmark/Android.bp +++ b/libs/renderengine/benchmark/Android.bp @@ -23,7 +23,10 @@ package { cc_benchmark { name: "librenderengine_bench", - defaults: ["skia_deps", "surfaceflinger_defaults"], + defaults: [ + "skia_deps", + "surfaceflinger_defaults", + ], srcs: [ "main.cpp", "Codec.cpp", @@ -32,6 +35,7 @@ cc_benchmark { ], static_libs: [ "librenderengine", + "libtonemap", ], cflags: [ "-DLOG_TAG=\"RenderEngineBench\"", @@ -50,5 +54,5 @@ cc_benchmark { "libutils", ], - data: [ "resources/*"], + data: ["resources/*"], } diff --git a/libs/renderengine/skia/filters/LinearEffect.cpp b/libs/renderengine/skia/filters/LinearEffect.cpp index 73dadef166..53136e40b7 100644 --- a/libs/renderengine/skia/filters/LinearEffect.cpp +++ b/libs/renderengine/skia/filters/LinearEffect.cpp @@ -19,6 +19,7 @@ #define ATRACE_TAG ATRACE_TAG_GRAPHICS #include <SkString.h> +#include <tonemap/tonemap.h> #include <utils/Trace.h> #include <optional> @@ -32,6 +33,11 @@ namespace android { namespace renderengine { namespace skia { +static aidl::android::hardware::graphics::common::Dataspace toAidlDataspace( + ui::Dataspace dataspace) { + return static_cast<aidl::android::hardware::graphics::common::Dataspace>(dataspace); +} + static void generateEOTF(ui::Dataspace dataspace, SkString& shader) { switch (dataspace & HAL_DATASPACE_TRANSFER_MASK) { case HAL_DATASPACE_TRANSFER_ST2084: @@ -127,159 +133,13 @@ static void generateLuminanceScalesForOOTF(ui::Dataspace inputDataspace, SkStrin default: shader.append(R"( float3 ScaleLuminance(float3 xyz) { - return xyz * in_inputMaxLuminance; + return xyz * in_libtonemap_inputMaxLuminance; } )"); break; } } -static void generateToneMapInterpolation(ui::Dataspace inputDataspace, - ui::Dataspace outputDataspace, SkString& shader) { - switch (inputDataspace & HAL_DATASPACE_TRANSFER_MASK) { - case HAL_DATASPACE_TRANSFER_ST2084: - case HAL_DATASPACE_TRANSFER_HLG: - switch (outputDataspace & HAL_DATASPACE_TRANSFER_MASK) { - case HAL_DATASPACE_TRANSFER_ST2084: - shader.append(R"( - float3 ToneMap(float3 xyz) { - return xyz; - } - )"); - break; - case HAL_DATASPACE_TRANSFER_HLG: - // PQ has a wider luminance range (10,000 nits vs. 1,000 nits) than HLG, so - // we'll clamp the luminance range in case we're mapping from PQ input to HLG - // output. - shader.append(R"( - float3 ToneMap(float3 xyz) { - return clamp(xyz, 0.0, 1000.0); - } - )"); - break; - default: - // Here we're mapping from HDR to SDR content, so interpolate using a Hermitian - // polynomial onto the smaller luminance range. - shader.append(R"( - float3 ToneMap(float3 xyz) { - float maxInLumi = in_inputMaxLuminance; - float maxOutLumi = in_displayMaxLuminance; - - float nits = xyz.y; - - // 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 (maxInLumi <= maxOutLumi) { - return xyz; - } else { - - // three control points - const float x0 = 10.0; - const float y0 = 17.0; - float x1 = maxOutLumi * 0.75; - float y1 = x1; - float x2 = x1 + (maxInLumi - x1) / 2.0; - float y2 = y1 + (maxOutLumi - y1) * 0.75; - - // horizontal distances between the last three control points - float h12 = x2 - x1; - float h23 = maxInLumi - x2; - // tangents at the last three control points - float m1 = (y2 - y1) / h12; - float m3 = (maxOutLumi - y2) / h23; - float m2 = (m1 + m3) / 2.0; - - if (nits < x0) { - // scale [0.0, x0] to [0.0, y0] linearly - float slope = y0 / x0; - return xyz * slope; - } else if (nits < x1) { - // scale [x0, x1] to [y0, y1] linearly - float slope = (y1 - y0) / (x1 - x0); - nits = y0 + (nits - x0) * slope; - } else if (nits < x2) { - // scale [x1, x2] to [y1, y2] using Hermite interp - float t = (nits - x1) / h12; - nits = (y1 * (1.0 + 2.0 * t) + h12 * m1 * t) * (1.0 - t) * (1.0 - t) + - (y2 * (3.0 - 2.0 * t) + h12 * m2 * (t - 1.0)) * t * t; - } else { - // scale [x2, maxInLumi] to [y2, maxOutLumi] using Hermite interp - float t = (nits - x2) / h23; - nits = (y2 * (1.0 + 2.0 * t) + h23 * m2 * t) * (1.0 - t) * (1.0 - t) + - (maxOutLumi * (3.0 - 2.0 * t) + h23 * m3 * (t - 1.0)) * t * t; - } - } - - // color.y is greater than x0 and is thus non-zero - return xyz * (nits / xyz.y); - } - )"); - break; - } - break; - default: - switch (outputDataspace & HAL_DATASPACE_TRANSFER_MASK) { - case HAL_DATASPACE_TRANSFER_ST2084: - case HAL_DATASPACE_TRANSFER_HLG: - // 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. - shader.append(R"( - float3 ToneMap(float3 xyz) { - const float maxOutLumi = 3000.0; - - const float x0 = 5.0; - const float y0 = 2.5; - float x1 = in_displayMaxLuminance * 0.7; - float y1 = maxOutLumi * 0.15; - float x2 = in_displayMaxLuminance * 0.9; - float y2 = maxOutLumi * 0.45; - float x3 = in_displayMaxLuminance; - float y3 = maxOutLumi; - - float c1 = y1 / 3.0; - float c2 = y2 / 2.0; - float c3 = y3 / 1.5; - - float nits = xyz.y; - - if (nits <= x0) { - // scale [0.0, x0] to [0.0, y0] linearly - float slope = y0 / x0; - return xyz * slope; - } else if (nits <= x1) { - // scale [x0, x1] to [y0, y1] using a curve - float t = (nits - x0) / (x1 - x0); - nits = (1.0 - t) * (1.0 - t) * y0 + 2.0 * (1.0 - t) * t * c1 + t * t * y1; - } else if (nits <= x2) { - // scale [x1, x2] to [y1, y2] using a curve - float t = (nits - x1) / (x2 - x1); - nits = (1.0 - t) * (1.0 - t) * y1 + 2.0 * (1.0 - t) * t * c2 + t * t * y2; - } else { - // scale [x2, x3] to [y2, y3] using a curve - float t = (nits - x2) / (x3 - x2); - nits = (1.0 - t) * (1.0 - t) * y2 + 2.0 * (1.0 - t) * t * c3 + t * t * y3; - } - - // xyz.y is greater than x0 and is thus non-zero - return xyz * (nits / xyz.y); - } - )"); - break; - default: - // For completeness, this is tone-mapping from SDR to SDR, where this is just a - // no-op. - shader.append(R"( - float3 ToneMap(float3 xyz) { - return xyz; - } - )"); - break; - } - break; - } -} - // Normalizes from absolute light back to relative light (maps from [0, maxNits] back to [0, 1]) static void generateLuminanceNormalizationForOOTF(ui::Dataspace outputDataspace, SkString& shader) { switch (outputDataspace & HAL_DATASPACE_TRANSFER_MASK) { @@ -300,7 +160,7 @@ static void generateLuminanceNormalizationForOOTF(ui::Dataspace outputDataspace, default: shader.append(R"( float3 NormalizeLuminance(float3 xyz) { - return xyz / in_displayMaxLuminance; + return xyz / in_libtonemap_displayMaxLuminance; } )"); break; @@ -309,19 +169,22 @@ static void generateLuminanceNormalizationForOOTF(ui::Dataspace outputDataspace, static void generateOOTF(ui::Dataspace inputDataspace, ui::Dataspace outputDataspace, SkString& shader) { - // Input uniforms - shader.append(R"( - uniform float in_displayMaxLuminance; - uniform float in_inputMaxLuminance; - )"); + shader.append(tonemap::getToneMapper() + ->generateTonemapGainShaderSkSL(toAidlDataspace(inputDataspace), + toAidlDataspace(outputDataspace)) + .c_str()); generateLuminanceScalesForOOTF(inputDataspace, shader); - generateToneMapInterpolation(inputDataspace, outputDataspace, shader); generateLuminanceNormalizationForOOTF(outputDataspace, shader); shader.append(R"( - float3 OOTF(float3 xyz) { - return NormalizeLuminance(ToneMap(ScaleLuminance(xyz))); + float3 OOTF(float3 linearRGB, float3 xyz) { + float3 scaledLinearRGB = ScaleLuminance(linearRGB); + float3 scaledXYZ = ScaleLuminance(xyz); + + float gain = libtonemap_LookupTonemapGain(scaledLinearRGB, scaledXYZ); + + return NormalizeLuminance(scaledXYZ * gain); } )"); } @@ -399,7 +262,9 @@ static void generateEffectiveOOTF(bool undoPremultipliedAlpha, SkString& shader) )"); } shader.append(R"( - c.rgb = OETF(ToRGB(OOTF(ToXYZ(EOTF(c.rgb))))); + float3 linearRGB = EOTF(c.rgb); + float3 xyz = ToXYZ(linearRGB); + c.rgb = OETF(ToRGB(OOTF(linearRGB, xyz))); )"); if (undoPremultipliedAlpha) { shader.append(R"( @@ -465,11 +330,20 @@ sk_sp<SkShader> createLinearEffectShader(sk_sp<SkShader> shader, const LinearEff colorTransform * mat4(outputColorSpace.getXYZtoRGB()); } - effectBuilder.uniform("in_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 - effectBuilder.uniform("in_inputMaxLuminance") = - maxLuminance > 0 ? maxLuminance : maxDisplayLuminance; + 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}; + + const auto uniforms = tonemap::getToneMapper()->generateShaderSkSLUniforms(metadata); + + for (const auto& uniform : uniforms) { + effectBuilder.uniform(uniform.name.c_str()).set(uniform.value.data(), uniform.value.size()); + } + return effectBuilder.makeShader(nullptr, false); } diff --git a/libs/renderengine/tests/Android.bp b/libs/renderengine/tests/Android.bp index d0e19dd4e2..52b6c8ff72 100644 --- a/libs/renderengine/tests/Android.bp +++ b/libs/renderengine/tests/Android.bp @@ -23,7 +23,10 @@ package { cc_test { name: "librenderengine_test", - defaults: ["skia_deps", "surfaceflinger_defaults"], + defaults: [ + "skia_deps", + "surfaceflinger_defaults", + ], test_suites: ["device-tests"], srcs: [ "RenderEngineTest.cpp", @@ -36,6 +39,7 @@ cc_test { "libgmock", "librenderengine", "librenderengine_mocks", + "libtonemap", ], shared_libs: [ diff --git a/libs/tonemap/Android.bp b/libs/tonemap/Android.bp new file mode 100644 index 0000000000..231a342852 --- /dev/null +++ b/libs/tonemap/Android.bp @@ -0,0 +1,37 @@ +// 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. + +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_library_static { + name: "libtonemap", + vendor_available: true, + + export_include_dirs: ["include"], + local_include_dirs: ["include"], + + shared_libs: [ + "android.hardware.graphics.common-V3-ndk", + ], + srcs: [ + "tonemap.cpp", + ], +} diff --git a/libs/tonemap/OWNERS b/libs/tonemap/OWNERS new file mode 100644 index 0000000000..6d91da3bd2 --- /dev/null +++ b/libs/tonemap/OWNERS @@ -0,0 +1,4 @@ +alecmouri@google.com +jreck@google.com +sallyqi@google.com +scroggo@google.com
\ No newline at end of file diff --git a/libs/tonemap/TEST_MAPPING b/libs/tonemap/TEST_MAPPING new file mode 100644 index 0000000000..00f83baaa9 --- /dev/null +++ b/libs/tonemap/TEST_MAPPING @@ -0,0 +1,10 @@ +{ + "presubmit": [ + { + "name": "librenderengine_test" + }, + { + "name": "libtonemap_test" + } + ] +} diff --git a/libs/tonemap/include/tonemap/tonemap.h b/libs/tonemap/include/tonemap/tonemap.h new file mode 100644 index 0000000000..d350e160ca --- /dev/null +++ b/libs/tonemap/include/tonemap/tonemap.h @@ -0,0 +1,103 @@ +/* + * 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. + */ + +#pragma once + +#include <aidl/android/hardware/graphics/common/Dataspace.h> + +#include <string> +#include <vector> + +namespace android::tonemap { + +// Describes a shader uniform +// The shader uniform is intended to be passed into a SkRuntimeShaderBuilder, i.e.: +// +// SkRuntimeShaderBuilder builder; +// builder.uniform(<uniform name>).set(<uniform value>.data(), <uniform value>.size()); +struct ShaderUniform { + // The name of the uniform, used for binding into a shader. + // The shader must contain a uniform whose name matches this. + std::string name; + + // The value for the uniform, which should be bound to the uniform identified by <name> + std::vector<uint8_t> value; +}; + +// 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. +struct Metadata { + float displayMaxLuminance = 0.0; + float contentMaxLuminance = 0.0; +}; + +class ToneMapper { +public: + virtual ~ToneMapper() {} + // Constructs a tonemap shader whose shader language is SkSL + // + // The returned shader string *must* contain a function with the following signature: + // float libtonemap_LookupTonemapGain(vec3 linearRGB, vec3 xyz); + // + // The arguments are: + // * linearRGB is the absolute nits of the RGB pixels in linear space + // * xyz is linearRGB converted into XYZ + // + // libtonemap_LookupTonemapGain() returns a float representing the amount by which to scale the + // absolute nits of the pixels. This function may be plugged into any existing SkSL shader, and + // is expected to look something like this: + // + // vec3 rgb = ...; + // // apply the EOTF based on the incoming dataspace to convert to linear nits. + // vec3 linearRGB = applyEOTF(rgb); + // // apply a RGB->XYZ matrix float3 + // vec3 xyz = toXYZ(linearRGB); + // // Scale the luminance based on the content standard + // vec3 absoluteRGB = ScaleLuminance(linearRGB); + // vec3 absoluteXYZ = ScaleLuminance(xyz); + // float gain = libtonemap_LookupTonemapGain(absoluteRGB, absoluteXYZ); + // // Normalize the luminance back down to a [0, 1] range + // xyz = NormalizeLuminance(absoluteXYZ * gain); + // // apply a XYZ->RGB matrix and apply the output OETf. + // vec3 finalColor = applyOETF(ToRGB(xyz)); + // ... + // + // Helper methods in this shader should be prefixed with "libtonemap_". Accordingly, libraries + // which consume this shader must *not* contain any methods prefixed with "libtonemap_" to + // guarantee that there are no conflicts in name resolution. + virtual std::string generateTonemapGainShaderSkSL( + aidl::android::hardware::graphics::common::Dataspace sourceDataspace, + aidl::android::hardware::graphics::common::Dataspace destinationDataspace) = 0; + + // Constructs uniform descriptions that correspond to those that are generated for the tonemap + // shader. Uniforms must be prefixed with "in_libtonemap_". Libraries which consume this shader + // must not bind any new uniforms that begin with this prefix. + // + // Downstream shaders may assume the existence of the uniform in_libtonemap_displayMaxLuminance + // and in_libtonemap_inputMaxLuminance, in order to assist with scaling and normalizing + // luminance as described in the documentation for generateTonemapGainShaderSkSL(). That is, + // shaders plugging in a tone-mapping shader returned by generateTonemapGainShaderSkSL() may + // assume that there are predefined floats in_libtonemap_displayMaxLuminance and + // in_libtonemap_inputMaxLuminance inside of the body of the tone-mapping shader. + virtual std::vector<ShaderUniform> generateShaderSkSLUniforms(const Metadata& metadata) = 0; +}; + +// Retrieves a tonemapper instance. +// This instance is globally constructed. +ToneMapper* getToneMapper(); + +} // namespace android::tonemap
\ No newline at end of file diff --git a/libs/tonemap/tests/Android.bp b/libs/tonemap/tests/Android.bp new file mode 100644 index 0000000000..e58d519224 --- /dev/null +++ b/libs/tonemap/tests/Android.bp @@ -0,0 +1,38 @@ +// 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. + +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: "libtonemap_test", + test_suites: ["device-tests"], + srcs: [ + "tonemap_test.cpp", + ], + shared_libs: [ + "android.hardware.graphics.common-V3-ndk", + ], + static_libs: [ + "libgmock", + "libgtest", + "libtonemap", + ], +} diff --git a/libs/tonemap/tests/tonemap_test.cpp b/libs/tonemap/tests/tonemap_test.cpp new file mode 100644 index 0000000000..7a7958f58f --- /dev/null +++ b/libs/tonemap/tests/tonemap_test.cpp @@ -0,0 +1,76 @@ +/* + * 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 <gmock/gmock.h> +#include <gtest/gtest.h> +#include <tonemap/tonemap.h> +#include <cmath> + +namespace android { + +using testing::HasSubstr; + +struct TonemapTest : public ::testing::Test {}; + +TEST_F(TonemapTest, generateShaderSkSLUniforms_containsDefaultUniforms) { + static const constexpr float kDisplayMaxLuminance = 1.f; + static const constexpr float kContentMaxLuminance = 2.f; + tonemap::Metadata metadata{.displayMaxLuminance = kDisplayMaxLuminance, + .contentMaxLuminance = kContentMaxLuminance}; + const auto uniforms = tonemap::getToneMapper()->generateShaderSkSLUniforms(metadata); + + ASSERT_EQ(1, std::count_if(uniforms.cbegin(), uniforms.cend(), [](const auto& data) { + return data.name == "in_libtonemap_displayMaxLuminance"; + })); + ASSERT_EQ(1, std::count_if(uniforms.cbegin(), uniforms.cend(), [](const auto& data) { + return data.name == "in_libtonemap_inputMaxLuminance"; + })); + + // Smoke check that metadata values are "real", specifically that they're non-zero and actually + // numbers. This is to help avoid shaders using these uniforms from dividing by zero or other + // catastrophic errors. + const auto& displayLum = std::find_if(uniforms.cbegin(), uniforms.cend(), [](const auto& data) { + return data.name == "in_libtonemap_displayMaxLuminance"; + })->value; + + float displayLumFloat = 0.f; + std::memcpy(&displayLumFloat, displayLum.data(), displayLum.size()); + EXPECT_FALSE(std::isnan(displayLumFloat)); + EXPECT_GT(displayLumFloat, 0); + + const auto& contentLum = std::find_if(uniforms.cbegin(), uniforms.cend(), [](const auto& data) { + return data.name == "in_libtonemap_inputMaxLuminance"; + })->value; + + float contentLumFloat = 0.f; + std::memcpy(&contentLumFloat, contentLum.data(), contentLum.size()); + EXPECT_FALSE(std::isnan(contentLumFloat)); + EXPECT_GT(contentLumFloat, 0); +} + +TEST_F(TonemapTest, generateTonemapGainShaderSkSL_containsEntryPoint) { + const auto shader = + tonemap::getToneMapper() + ->generateTonemapGainShaderSkSL(aidl::android::hardware::graphics::common:: + Dataspace::BT2020_ITU_PQ, + 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 new file mode 100644 index 0000000000..350bca427c --- /dev/null +++ b/libs/tonemap/tonemap.cpp @@ -0,0 +1,256 @@ +/* + * 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 <tonemap/tonemap.h> + +#include <cstdint> +#include <mutex> +#include <type_traits> + +namespace android::tonemap { + +namespace { + +// Flag containing the variant of tone map algorithm to use. +enum class ToneMapAlgorithm { + AndroidO, // Default algorithm in place since Android O, +}; + +static const constexpr auto kToneMapAlgorithm = ToneMapAlgorithm::AndroidO; + +static const constexpr auto kTransferMask = + static_cast<int32_t>(aidl::android::hardware::graphics::common::Dataspace::TRANSFER_MASK); +static const constexpr auto kTransferST2084 = + static_cast<int32_t>(aidl::android::hardware::graphics::common::Dataspace::TRANSFER_ST2084); +static const constexpr auto kTransferHLG = + static_cast<int32_t>(aidl::android::hardware::graphics::common::Dataspace::TRANSFER_HLG); + +template <typename T, std::enable_if_t<std::is_trivially_copyable<T>::value, bool> = true> +std::vector<uint8_t> buildUniformValue(T value) { + std::vector<uint8_t> result; + result.resize(sizeof(value)); + std::memcpy(result.data(), &value, sizeof(value)); + return result; +} + +class ToneMapperO : public ToneMapper { +public: + std::string generateTonemapGainShaderSkSL( + aidl::android::hardware::graphics::common::Dataspace sourceDataspace, + aidl::android::hardware::graphics::common::Dataspace destinationDataspace) override { + const int32_t sourceDataspaceInt = static_cast<int32_t>(sourceDataspace); + const int32_t destinationDataspaceInt = static_cast<int32_t>(destinationDataspace); + + std::string program; + // Define required uniforms + program.append(R"( + uniform float in_libtonemap_displayMaxLuminance; + uniform float in_libtonemap_inputMaxLuminance; + )"); + switch (sourceDataspaceInt & kTransferMask) { + case kTransferST2084: + case kTransferHLG: + switch (destinationDataspaceInt & kTransferMask) { + case kTransferST2084: + program.append(R"( + float libtonemap_ToneMapTargetNits(vec3 xyz) { + return xyz.y; + } + )"); + break; + case kTransferHLG: + // PQ has a wider luminance range (10,000 nits vs. 1,000 nits) than HLG, so + // we'll clamp the luminance range in case we're mapping from PQ input to + // HLG output. + program.append(R"( + float libtonemap_ToneMapTargetNits(vec3 xyz) { + return clamp(xyz.y, 0.0, 1000.0); + } + )"); + break; + default: + // 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_ToneMapTargetNits(vec3 xyz) { + float maxInLumi = in_libtonemap_inputMaxLuminance; + float maxOutLumi = in_libtonemap_displayMaxLuminance; + + float nits = xyz.y; + + // 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 (maxInLumi <= maxOutLumi) { + return xyz.y; + } else { + + // three control points + const float x0 = 10.0; + const float y0 = 17.0; + float x1 = maxOutLumi * 0.75; + float y1 = x1; + float x2 = x1 + (maxInLumi - x1) / 2.0; + float y2 = y1 + (maxOutLumi - y1) * 0.75; + + // horizontal distances between the last three + // control points + float h12 = x2 - x1; + float h23 = maxInLumi - x2; + // tangents at the last three control points + float m1 = (y2 - y1) / h12; + float m3 = (maxOutLumi - y2) / h23; + float m2 = (m1 + m3) / 2.0; + + if (nits < x0) { + // scale [0.0, x0] to [0.0, y0] linearly + float slope = y0 / x0; + return nits * slope; + } else if (nits < x1) { + // scale [x0, x1] to [y0, y1] linearly + float slope = (y1 - y0) / (x1 - x0); + nits = y0 + (nits - x0) * slope; + } else if (nits < x2) { + // scale [x1, x2] to [y1, y2] using Hermite interp + float t = (nits - x1) / h12; + nits = (y1 * (1.0 + 2.0 * t) + h12 * m1 * t) * + (1.0 - t) * (1.0 - t) + + (y2 * (3.0 - 2.0 * t) + + h12 * m2 * (t - 1.0)) * t * t; + } else { + // scale [x2, maxInLumi] to [y2, maxOutLumi] using + // Hermite interp + float t = (nits - x2) / h23; + nits = (y2 * (1.0 + 2.0 * t) + h23 * m2 * t) * + (1.0 - t) * (1.0 - t) + (maxOutLumi * + (3.0 - 2.0 * t) + h23 * m3 * + (t - 1.0)) * t * t; + } + } + + return nits; + } + )"); + break; + } + break; + default: + switch (destinationDataspaceInt & kTransferMask) { + case kTransferST2084: + case kTransferHLG: + // 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. + program.append(R"( + float libtonemap_ToneMapTargetNits(vec3 xyz) { + const float maxOutLumi = 3000.0; + + const float x0 = 5.0; + const float y0 = 2.5; + float x1 = in_libtonemap_displayMaxLuminance * 0.7; + float y1 = maxOutLumi * 0.15; + float x2 = in_libtonemap_displayMaxLuminance * 0.9; + float y2 = maxOutLumi * 0.45; + float x3 = in_libtonemap_displayMaxLuminance; + float y3 = maxOutLumi; + + float c1 = y1 / 3.0; + float c2 = y2 / 2.0; + float c3 = y3 / 1.5; + + float nits = xyz.y; + + if (nits <= x0) { + // scale [0.0, x0] to [0.0, y0] linearly + float slope = y0 / x0; + return nits * slope; + } else if (nits <= x1) { + // scale [x0, x1] to [y0, y1] using a curve + float t = (nits - x0) / (x1 - x0); + nits = (1.0 - t) * (1.0 - t) * y0 + + 2.0 * (1.0 - t) * t * c1 + t * t * y1; + } else if (nits <= x2) { + // scale [x1, x2] to [y1, y2] using a curve + float t = (nits - x1) / (x2 - x1); + nits = (1.0 - t) * (1.0 - t) * y1 + + 2.0 * (1.0 - t) * t * c2 + t * t * y2; + } else { + // scale [x2, x3] to [y2, y3] using a curve + float t = (nits - x2) / (x3 - x2); + nits = (1.0 - t) * (1.0 - t) * y2 + + 2.0 * (1.0 - t) * t * c3 + t * t * y3; + } + + return nits; + } + )"); + break; + default: + // For completeness, this is tone-mapping from SDR to SDR, where this is + // just a no-op. + program.append(R"( + float libtonemap_ToneMapTargetNits(vec3 xyz) { + return xyz.y; + } + )"); + break; + } + break; + } + + program.append(R"( + float libtonemap_LookupTonemapGain(vec3 linearRGB, vec3 xyz) { + if (xyz.y <= 0.0) { + return 1.0; + } + return libtonemap_ToneMapTargetNits(xyz) / xyz.y; + } + )"); + return program; + } + + std::vector<ShaderUniform> generateShaderSkSLUniforms(const Metadata& metadata) override { + std::vector<ShaderUniform> uniforms; + + uniforms.reserve(2); + + uniforms.push_back({.name = "in_libtonemap_displayMaxLuminance", + .value = buildUniformValue<float>(metadata.displayMaxLuminance)}); + uniforms.push_back({.name = "in_libtonemap_inputMaxLuminance", + .value = buildUniformValue<float>(metadata.contentMaxLuminance)}); + + return uniforms; + } +}; + +} // namespace + +ToneMapper* getToneMapper() { + static std::once_flag sOnce; + static std::unique_ptr<ToneMapper> sToneMapper; + + std::call_once(sOnce, [&] { + switch (kToneMapAlgorithm) { + case ToneMapAlgorithm::AndroidO: + sToneMapper = std::unique_ptr<ToneMapper>(new ToneMapperO()); + break; + } + }); + + return sToneMapper.get(); +} + +} // namespace android::tonemap
\ No newline at end of file diff --git a/services/surfaceflinger/Android.bp b/services/surfaceflinger/Android.bp index 56b8374eb0..6691575d32 100644 --- a/services/surfaceflinger/Android.bp +++ b/services/surfaceflinger/Android.bp @@ -79,6 +79,7 @@ cc_defaults { "libperfetto_client_experimental", "librenderengine", "libserviceutils", + "libtonemap", "libtrace_proto", "libaidlcommonsupport", ], diff --git a/services/surfaceflinger/CompositionEngine/Android.bp b/services/surfaceflinger/CompositionEngine/Android.bp index 83b4a2552b..aefc014062 100644 --- a/services/surfaceflinger/CompositionEngine/Android.bp +++ b/services/surfaceflinger/CompositionEngine/Android.bp @@ -38,6 +38,7 @@ cc_defaults { static_libs: [ "libmath", "librenderengine", + "libtonemap", "libtrace_proto", "libaidlcommonsupport", ], diff --git a/services/surfaceflinger/tests/unittests/Android.bp b/services/surfaceflinger/tests/unittests/Android.bp index f152cedf46..1ac568066e 100644 --- a/services/surfaceflinger/tests/unittests/Android.bp +++ b/services/surfaceflinger/tests/unittests/Android.bp @@ -142,6 +142,7 @@ cc_test { "libtimestats", "libtimestats_atoms_proto", "libtimestats_proto", + "libtonemap", "libtrace_proto", "perfetto_trace_protos", ], |