|  | /* | 
|  | * 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 <shaders/shaders.h> | 
|  |  | 
|  | #include <tonemap/tonemap.h> | 
|  |  | 
|  | #include <cmath> | 
|  | #include <optional> | 
|  |  | 
|  | #include <math/mat4.h> | 
|  | #include <system/graphics-base-v1.0.h> | 
|  | #include <ui/ColorSpace.h> | 
|  |  | 
|  | namespace android::shaders { | 
|  |  | 
|  | namespace { | 
|  |  | 
|  | aidl::android::hardware::graphics::common::Dataspace toAidlDataspace(ui::Dataspace dataspace) { | 
|  | return static_cast<aidl::android::hardware::graphics::common::Dataspace>(dataspace); | 
|  | } | 
|  |  | 
|  | 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_SMPTE_170M: | 
|  | shader.append(R"( | 
|  |  | 
|  | float EOTF_sRGB(float srgb) { | 
|  | return srgb <= 0.08125 ? srgb / 4.50 : pow((srgb + 0.099) / 1.099, 1 / 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"( | 
|  |  | 
|  | 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; | 
|  | } | 
|  | } | 
|  |  | 
|  | 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]) | 
|  | 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; | 
|  | } | 
|  | )"); | 
|  | 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; | 
|  | } | 
|  | )"); | 
|  | break; | 
|  | default: | 
|  | shader.append(R"( | 
|  | float3 NormalizeLuminance(float3 xyz) { | 
|  | return xyz / in_libtonemap_displayMaxLuminance; | 
|  | } | 
|  | )"); | 
|  | break; | 
|  | } | 
|  | } | 
|  |  | 
|  | 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); | 
|  | } | 
|  | )"); | 
|  | } | 
|  |  | 
|  | 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_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"( | 
|  | 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; | 
|  | } | 
|  | } | 
|  |  | 
|  | 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; | 
|  | } | 
|  | )"); | 
|  | } | 
|  |  | 
|  | // please keep in sync with toSkColorSpace function in renderengine/skia/ColorSpaces.cpp | 
|  | ColorSpace toColorSpace(ui::Dataspace dataspace) { | 
|  | switch (dataspace & HAL_DATASPACE_STANDARD_MASK) { | 
|  | case HAL_DATASPACE_STANDARD_BT709: | 
|  | return ColorSpace::sRGB(); | 
|  | case HAL_DATASPACE_STANDARD_DCI_P3: | 
|  | return ColorSpace::DisplayP3(); | 
|  | case HAL_DATASPACE_STANDARD_BT2020: | 
|  | case HAL_DATASPACE_STANDARD_BT2020_CONSTANT_LUMINANCE: | 
|  | return ColorSpace::BT2020(); | 
|  | 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(); | 
|  | } | 
|  | } | 
|  |  | 
|  | 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; | 
|  | } | 
|  |  | 
|  | } // namespace | 
|  |  | 
|  | 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; | 
|  | } | 
|  |  | 
|  | // Generates a list of uniforms to set on the LinearEffect shader above. | 
|  | std::vector<tonemap::ShaderUniform> buildLinearEffectUniforms( | 
|  | const LinearEffect& linearEffect, const mat4& colorTransform, float maxDisplayLuminance, | 
|  | float currentDisplayLuminanceNits, float maxLuminance, AHardwareBuffer* buffer, | 
|  | aidl::android::hardware::graphics::composer3::RenderIntent renderIntent) { | 
|  | std::vector<tonemap::ShaderUniform> uniforms; | 
|  |  | 
|  | 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>(mat4())}); | 
|  | uniforms.push_back( | 
|  | {.name = "in_xyzToRgb", .value = buildUniformValue<mat4>(colorTransform)}); | 
|  | } else { | 
|  | ColorSpace inputColorSpace = toColorSpace(inputDataspace); | 
|  | ColorSpace outputColorSpace = toColorSpace(linearEffect.outputDataspace); | 
|  | uniforms.push_back({.name = "in_rgbToXyz", | 
|  | .value = buildUniformValue<mat4>(mat4(inputColorSpace.getRGBtoXYZ()))}); | 
|  | uniforms.push_back({.name = "in_xyzToRgb", | 
|  | .value = buildUniformValue<mat4>( | 
|  | 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, | 
|  | .currentDisplayLuminance = currentDisplayLuminanceNits > 0 | 
|  | ? currentDisplayLuminanceNits | 
|  | : maxDisplayLuminance, | 
|  | .buffer = buffer, | 
|  | .renderIntent = renderIntent}; | 
|  |  | 
|  | for (const auto uniform : tonemap::getToneMapper()->generateShaderSkSLUniforms(metadata)) { | 
|  | uniforms.push_back(uniform); | 
|  | } | 
|  |  | 
|  | return uniforms; | 
|  | } | 
|  |  | 
|  | } // namespace android::shaders |