Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 22a98637 authored by Alec Mouri's avatar Alec Mouri Committed by Automerger Merge Worker
Browse files

Merge "Push HLG OOTF down to libtonemap." into tm-dev am: 07ff07a9

Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/native/+/17088127

Change-Id: I998d08e0d47712beb17bd89d0a28452dc0b4e6f5
parents b55060ce 07ff07a9
Loading
Loading
Loading
Loading
+1 −2
Original line number Diff line number Diff line
@@ -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;
            });
}

+5 −16
Original line number Diff line number Diff line
@@ -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<uint8_t> 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<tonemap::ShaderUniform> 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<float>(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<tonemap::ShaderUniform> 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)) {
+3 −0
Original line number Diff line number Diff line
@@ -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
+91 −12
Original line number Diff line number Diff line
@@ -48,6 +48,22 @@ std::vector<uint8_t> 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<ShaderUniform> uniforms;
        uniforms.reserve(2);
        uniforms.reserve(3);
        uniforms.push_back({.name = "in_libtonemap_displayMaxLuminance",
                            .value = buildUniformValue<float>(metadata.displayMaxLuminance)});
        uniforms.push_back({.name = "in_libtonemap_inputMaxLuminance",
                            .value = buildUniformValue<float>(kContentMaxLuminance)});
        uniforms.push_back({.name = "in_libtonemap_hlgGamma",
                            .value = buildUniformValue<float>(
                                    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;