diff options
5 files changed, 148 insertions, 97 deletions
diff --git a/services/surfaceflinger/RenderEngine/Description.cpp b/services/surfaceflinger/RenderEngine/Description.cpp index 5e79e7a129..323bdb2b41 100644 --- a/services/surfaceflinger/RenderEngine/Description.cpp +++ b/services/surfaceflinger/RenderEngine/Description.cpp @@ -73,8 +73,4 @@ void Description::setOutputTransferFunction(TransferFunction transferFunction) { mOutputTransferFunction = transferFunction; } -void Description::enableToneMapping(bool enable) { - mToneMappingEnabled = enable; -} - } /* namespace android */ diff --git a/services/surfaceflinger/RenderEngine/Description.h b/services/surfaceflinger/RenderEngine/Description.h index 75c1981326..5854ba49d1 100644 --- a/services/surfaceflinger/RenderEngine/Description.h +++ b/services/surfaceflinger/RenderEngine/Description.h @@ -51,12 +51,11 @@ public: LINEAR, SRGB, ST2084, + HLG, // Hybrid Log-Gamma for HDR. }; void setInputTransferFunction(TransferFunction transferFunction); void setOutputTransferFunction(TransferFunction transferFunction); - void enableToneMapping(bool enable); - private: friend class Program; friend class ProgramCache; @@ -84,9 +83,6 @@ private: // transfer functions for the input/output TransferFunction mInputTransferFunction = TransferFunction::LINEAR; TransferFunction mOutputTransferFunction = TransferFunction::LINEAR; - - // tone-map the color - bool mToneMappingEnabled = false; }; } /* namespace android */ diff --git a/services/surfaceflinger/RenderEngine/GLES20RenderEngine.cpp b/services/surfaceflinger/RenderEngine/GLES20RenderEngine.cpp index 1efe0ac5de..6e0fa329f1 100644 --- a/services/surfaceflinger/RenderEngine/GLES20RenderEngine.cpp +++ b/services/surfaceflinger/RenderEngine/GLES20RenderEngine.cpp @@ -316,7 +316,12 @@ void GLES20RenderEngine::drawMesh(const Mesh& mesh) { wideColorState.setColorMatrix(mState.getColorMatrix() * mBt2020ToDisplayP3); wideColorState.setInputTransferFunction(Description::TransferFunction::ST2084); wideColorState.setOutputTransferFunction(Description::TransferFunction::SRGB); - wideColorState.enableToneMapping(true); + break; + case Dataspace::BT2020_HLG: + case Dataspace::BT2020_ITU_HLG: + wideColorState.setColorMatrix(mState.getColorMatrix() * mBt2020ToDisplayP3); + wideColorState.setInputTransferFunction(Description::TransferFunction::HLG); + wideColorState.setOutputTransferFunction(Description::TransferFunction::SRGB); break; default: // treat all other dataspaces as sRGB diff --git a/services/surfaceflinger/RenderEngine/ProgramCache.cpp b/services/surfaceflinger/RenderEngine/ProgramCache.cpp index 6a34981174..d1887ee925 100644 --- a/services/surfaceflinger/RenderEngine/ProgramCache.cpp +++ b/services/surfaceflinger/RenderEngine/ProgramCache.cpp @@ -143,6 +143,9 @@ ProgramCache::Key ProgramCache::computeKey(const Description& description) { case Description::TransferFunction::ST2084: needs.set(Key::INPUT_TF_MASK, Key::INPUT_TF_ST2084); break; + case Description::TransferFunction::HLG: + needs.set(Key::INPUT_TF_MASK, Key::INPUT_TF_HLG); + break; } switch (description.mOutputTransferFunction) { @@ -156,10 +159,10 @@ ProgramCache::Key ProgramCache::computeKey(const Description& description) { case Description::TransferFunction::ST2084: needs.set(Key::OUTPUT_TF_MASK, Key::OUTPUT_TF_ST2084); break; + case Description::TransferFunction::HLG: + needs.set(Key::OUTPUT_TF_MASK, Key::OUTPUT_TF_HLG); + break; } - - needs.set(Key::TONE_MAPPING_MASK, - description.mToneMappingEnabled ? Key::TONE_MAPPING_ON : Key::TONE_MAPPING_OFF); } return needs; @@ -220,6 +223,8 @@ String8 ProgramCache::generateFragmentShader(const Key& needs) { if (needs.hasColorMatrix()) { fs << "uniform mat4 colorMatrix;"; + // Generate EOTF that converts signal values to relative display light, + // both normalized to [0, 1]. switch (needs.getInputTF()) { case Key::INPUT_TF_LINEAR: default: @@ -259,8 +264,123 @@ String8 ProgramCache::generateFragmentShader(const Key& needs) { } )__SHADER__"; break; + case Key::INPUT_TF_HLG: + fs << R"__SHADER__( + highp float EOTF_channel(const highp float channel) { + const highp float a = 0.17883277; + const highp float b = 0.28466892; + const highp float c = 0.55991073; + return channel <= 0.5 ? channel * channel / 3.0 : + (exp((channel - c) / a) + b) / 12.0; + } + + vec3 EOTF(const highp vec3 color) { + return vec3(EOTF_channel(color.r), EOTF_channel(color.g), + EOTF_channel(color.b)); + } + )__SHADER__"; + break; } + fs << R"__SHADER__( + highp float CalculateY(const highp vec3 color) { + // BT2020 standard uses the unadjusted KR = 0.2627, + // KB = 0.0593 luminance interpretation for RGB conversion. + return color.r * 0.262700 + color.g * 0.677998 + + color.b * 0.059302; + } + )__SHADER__"; + + // Generate OOTF that modifies the relative display light. + switch(needs.getInputTF()) { + case Key::INPUT_TF_ST2084: + fs << R"__SHADER__( + highp vec3 OOTF(const highp vec3 color) { + const float maxLumi = 10000.0; + const float maxMasteringLumi = 1000.0; + const float maxContentLumi = 1000.0; + const float maxInLumi = min(maxMasteringLumi, maxContentLumi); + const float maxOutLumi = 500.0; + + // Calculate Y value in XYZ color space. + float colorY = CalculateY(color); + + // convert to nits first + float nits = colorY * maxLumi; + + // clamp to max input luminance + nits = clamp(nits, 0.0, maxInLumi); + + // scale [0.0, maxInLumi] to [0.0, maxOutLumi] + if (maxInLumi <= maxOutLumi) { + nits *= maxOutLumi / maxInLumi; + } else { + // three control points + const float x0 = 10.0; + const float y0 = 17.0; + const float x1 = maxOutLumi * 0.75; + const float y1 = x1; + const float x2 = x1 + (maxInLumi - x1) / 2.0; + const float y2 = y1 + (maxOutLumi - y1) * 0.75; + + // horizontal distances between the last three control points + const float h12 = x2 - x1; + const float h23 = maxInLumi - x2; + // tangents at the last three control points + const float m1 = (y2 - y1) / h12; + const float m3 = (maxOutLumi - y2) / h23; + const float m2 = (m1 + m3) / 2.0; + + if (nits < x0) { + // scale [0.0, x0] to [0.0, y0] linearly + const float slope = y0 / x0; + nits *= slope; + } else if (nits < x1) { + // scale [x0, x1] to [y0, y1] linearly + const 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; + } + } + + // convert back to [0.0, 1.0] + float targetY = nits / maxOutLumi; + return color * (targetY / max(1e-6, colorY)); + } + )__SHADER__"; + break; + case Key::INPUT_TF_HLG: + fs << R"__SHADER__( + highp vec3 OOTF(const highp vec3 color) { + const float maxOutLumi = 500.0; + const float gamma = 1.2 + 0.42 * log(maxOutLumi / 1000.0) / log(10.0); + // The formula is: + // alpha * pow(Y, gamma - 1.0) * color + beta; + // where alpha is 1.0, beta is 0.0 as recommended in + // Rec. ITU-R BT.2100-1 TABLE 5. + return pow(CalculateY(color), gamma - 1.0) * color; + } + )__SHADER__"; + break; + default: + fs << R"__SHADER__( + highp vec3 OOTF(const highp vec3 color) { + return color; + } + )__SHADER__"; + } + + // Generate OETF that converts relative display light to signal values, + // both normalized to [0, 1] switch (needs.getOutputTF()) { case Key::OUTPUT_TF_LINEAR: default: @@ -301,84 +421,22 @@ String8 ProgramCache::generateFragmentShader(const Key& needs) { } )__SHADER__"; break; - } - - if (needs.hasToneMapping()) { - fs << R"__SHADER__( - float CalculateY(const vec3 color) { - // BT2020 standard uses the unadjusted KR = 0.2627, - // KB = 0.0593 luminance interpretation for RGB conversion. - return color.r * 0.262700 + color.g * 0.677998 + - color.b * 0.059302; - } - vec3 ToneMap(const vec3 color) { - const float maxLumi = 10000.0; - const float maxMasteringLumi = 1000.0; - const float maxContentLumi = 1000.0; - const float maxInLumi = min(maxMasteringLumi, maxContentLumi); - const float maxOutLumi = 500.0; - - // Calculate Y value in XYZ color space. - float colorY = CalculateY(color); - - // convert to nits first - float nits = colorY * maxLumi; - - // clamp to max input luminance - nits = clamp(nits, 0.0, maxInLumi); - - // scale [0.0, maxInLumi] to [0.0, maxOutLumi] - if (maxInLumi <= maxOutLumi) { - nits *= maxOutLumi / maxInLumi; - } else { - // three control points - const float x0 = 10.0; - const float y0 = 17.0; - const float x1 = maxOutLumi * 0.75; - const float y1 = x1; - const float x2 = x1 + (maxInLumi - x1) / 2.0; - const float y2 = y1 + (maxOutLumi - y1) * 0.75; - - // horizontal distances between the last three control points - const float h12 = x2 - x1; - const float h23 = maxInLumi - x2; - // tangents at the last three control points - const float m1 = (y2 - y1) / h12; - const float m3 = (maxOutLumi - y2) / h23; - const float m2 = (m1 + m3) / 2.0; - - if (nits < x0) { - // scale [0.0, x0] to [0.0, y0] linearly - const float slope = y0 / x0; - nits *= slope; - } else if (nits < x1) { - // scale [x0, x1] to [y0, y1] linearly - const 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; - } + case Key::OUTPUT_TF_HLG: + fs << R"__SHADER__( + highp float OETF_channel(const highp float channel) { + const highp float a = 0.17883277; + const highp float b = 0.28466892; + const highp float c = 0.55991073; + return channel <= 1.0 / 12.0 ? sqrt(3.0 * channel) : + a * log(12.0 * channel - b) + c; } - // convert back to [0.0, 1.0] - float targetY = nits / maxOutLumi; - return color * (targetY / max(1e-6, colorY)); - } - )__SHADER__"; - } else { - fs << R"__SHADER__( - vec3 ToneMap(const vec3 color) { - return color; - } - )__SHADER__"; + vec3 OETF(const highp vec3 color) { + return vec3(OETF_channel(color.r), OETF_channel(color.g), + OETF_channel(color.b)); + } + )__SHADER__"; + break; } } @@ -411,7 +469,7 @@ String8 ProgramCache::generateFragmentShader(const Key& needs) { // avoid divide by 0 by adding 0.5/256 to the alpha channel fs << "gl_FragColor.rgb = gl_FragColor.rgb / (gl_FragColor.a + 0.0019);"; } - fs << "vec4 transformed = colorMatrix * vec4(ToneMap(EOTF(gl_FragColor.rgb)), 1);"; + fs << "vec4 transformed = colorMatrix * vec4(OOTF(EOTF(gl_FragColor.rgb)), 1);"; // the transformation from a wider colorspace to a narrower one can // result in >1.0 or <0.0 pixel values fs << "transformed.rgb = clamp(transformed.rgb, 0.0, 1.0);"; diff --git a/services/surfaceflinger/RenderEngine/ProgramCache.h b/services/surfaceflinger/RenderEngine/ProgramCache.h index dcc8cc601b..f67e1324d1 100644 --- a/services/surfaceflinger/RenderEngine/ProgramCache.h +++ b/services/surfaceflinger/RenderEngine/ProgramCache.h @@ -81,19 +81,16 @@ public: INPUT_TF_LINEAR = 0 << INPUT_TF_SHIFT, INPUT_TF_SRGB = 1 << INPUT_TF_SHIFT, INPUT_TF_ST2084 = 2 << INPUT_TF_SHIFT, + INPUT_TF_HLG = 3 << INPUT_TF_SHIFT, OUTPUT_TF_SHIFT = 8, OUTPUT_TF_MASK = 3 << OUTPUT_TF_SHIFT, OUTPUT_TF_LINEAR = 0 << OUTPUT_TF_SHIFT, OUTPUT_TF_SRGB = 1 << OUTPUT_TF_SHIFT, OUTPUT_TF_ST2084 = 2 << OUTPUT_TF_SHIFT, + OUTPUT_TF_HLG = 3 << OUTPUT_TF_SHIFT, - TONE_MAPPING_SHIFT = 10, - TONE_MAPPING_MASK = 1 << TONE_MAPPING_SHIFT, - TONE_MAPPING_OFF = 0 << TONE_MAPPING_SHIFT, - TONE_MAPPING_ON = 1 << TONE_MAPPING_SHIFT, - - Y410_BT2020_SHIFT = 11, + Y410_BT2020_SHIFT = 10, Y410_BT2020_MASK = 1 << Y410_BT2020_SHIFT, Y410_BT2020_OFF = 0 << Y410_BT2020_SHIFT, Y410_BT2020_ON = 1 << Y410_BT2020_SHIFT, @@ -115,7 +112,6 @@ public: inline bool hasColorMatrix() const { return (mKey & COLOR_MATRIX_MASK) == COLOR_MATRIX_ON; } inline int getInputTF() const { return (mKey & INPUT_TF_MASK); } inline int getOutputTF() const { return (mKey & OUTPUT_TF_MASK); } - inline bool hasToneMapping() const { return (mKey & TONE_MAPPING_MASK) == TONE_MAPPING_ON; } inline bool isY410BT2020() const { return (mKey & Y410_BT2020_MASK) == Y410_BT2020_ON; } // this is the definition of a friend function -- not a method of class Needs |