/* * 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 #include namespace android::shaders { namespace { aidl::android::hardware::graphics::common::Dataspace toAidlDataspace(ui::Dataspace dataspace) { return static_cast(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 ::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 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 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; 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(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, .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