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

Commit 196b0f20 authored by Alec Mouri's avatar Alec Mouri
Browse files

Tweak libtonemap's CPU interface to support batching.

This is in response to feedback that utilizing the tonemap api to
generate a LUT may require more CPU cycles as common parameters
describing the tone-mapping curve must be regenerated. Support this by
taking in a list of colors, rather than a single color, so that a LUT
can be one-shot computed.

Bug: 200310159
Test: librenderengine_test
Change-Id: I4b9ef8ef6bd95eb25aedd2b16268dc6e58828208
parent aedf10c2
Loading
Loading
Loading
Loading
+8 −2
Original line number Diff line number Diff line
@@ -1562,15 +1562,21 @@ void RenderEngineTest::tonemap(ui::Dataspace sourceDataspace, std::function<vec3
        const vec3 xyz = bt2020.getRGBtoXYZ() * linearRGB;

        const vec3 scaledXYZ = scaleOotf(xyz, kCurrentLuminanceNits);
        const double gain =
        const auto gains =
                tonemap::getToneMapper()
                        ->lookupTonemapGain(static_cast<aidl::android::hardware::graphics::common::
                                                                Dataspace>(sourceDataspace),
                                            static_cast<aidl::android::hardware::graphics::common::
                                                                Dataspace>(
                                                    ui::Dataspace::DISPLAY_P3),
                                            scaleOotf(linearRGB, kCurrentLuminanceNits), scaledXYZ,
                                            {tonemap::
                                                     Color{.linearRGB =
                                                                   scaleOotf(linearRGB,
                                                                             kCurrentLuminanceNits),
                                                           .xyz = scaledXYZ}},
                                            metadata);
        EXPECT_EQ(1, gains.size());
        const double gain = gains.front();
        const vec3 normalizedXYZ = scaledXYZ * gain / metadata.displayMaxLuminance;

        const vec3 targetRGB = OETF_sRGB(displayP3.getXYZtoRGB() * normalizedXYZ) * 255;
+12 −3
Original line number Diff line number Diff line
@@ -48,6 +48,14 @@ struct Metadata {
    float contentMaxLuminance = 0.0;
};

// Utility class containing pre-processed conversions for a particular color
struct Color {
    // RGB color in linear space
    vec3 linearRGB;
    // CIE 1931 XYZ representation of the color
    vec3 xyz;
};

class ToneMapper {
public:
    virtual ~ToneMapper() {}
@@ -108,10 +116,11 @@ public:
    // described by destinationDataspace. To compute the gain, the input colors are provided by
    // linearRGB, which is the RGB colors in linear space. The colors in XYZ space are also
    // provided. Metadata is also provided for helping to compute the tonemapping curve.
    virtual double lookupTonemapGain(
    using Gain = double;
    virtual std::vector<Gain> lookupTonemapGain(
            aidl::android::hardware::graphics::common::Dataspace sourceDataspace,
            aidl::android::hardware::graphics::common::Dataspace destinationDataspace,
            vec3 linearRGB, vec3 xyz, const Metadata& metadata) = 0;
            const std::vector<Color>& colors, const Metadata& metadata) = 0;
};

// Retrieves a tonemapper instance.
+206 −197
Original line number Diff line number Diff line
@@ -236,12 +236,17 @@ public:
        return uniforms;
    }

    double lookupTonemapGain(
    std::vector<Gain> lookupTonemapGain(
            aidl::android::hardware::graphics::common::Dataspace sourceDataspace,
            aidl::android::hardware::graphics::common::Dataspace destinationDataspace,
            vec3 /* linearRGB */, vec3 xyz, const Metadata& metadata) override {
            const std::vector<Color>& colors, const Metadata& metadata) override {
        std::vector<Gain> gains;
        gains.reserve(colors.size());

        for (const auto [_, xyz] : colors) {
            if (xyz.y <= 0.0) {
            return 1.0;
                gains.push_back(1.0);
                continue;
            }
            const int32_t sourceDataspaceInt = static_cast<int32_t>(sourceDataspace);
            const int32_t destinationDataspaceInt = static_cast<int32_t>(destinationDataspace);
@@ -255,9 +260,9 @@ public:
                            targetNits = 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.
                            // 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.
                            targetNits = std::clamp(xyz.y, 0.0f, 1000.0f);
                            break;
                        default:
@@ -299,7 +304,8 @@ public:
                                                    (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
                                    // scale [x2, maxInLumi] to [y2, maxOutLumi] using Hermite
                                    // interp
                                    double t = (targetNits - x2) / h23;
                                    targetNits = (y2 * (1.0 + 2.0 * t) + h23 * m2 * t) * (1.0 - t) *
                                                    (1.0 - t) +
@@ -317,8 +323,8 @@ public:
                        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.
                            // Here we use a polynomial curve to map from [0, displayMaxLuminance]
                            // onto [0, maxOutLumi] which is hard-coded to be 3000 nits.
                            const double maxOutLumi = 3000.0;

                            double x0 = 5.0;
@@ -364,8 +370,9 @@ public:
                            break;
                    }
            }

        return targetNits / xyz.y;
            gains.push_back(targetNits / xyz.y);
        }
        return gains;
    }
};

@@ -427,8 +434,6 @@ public:
                        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_OETFTone(float channel) {
                                    channel = channel / 10000.0;
@@ -548,14 +553,39 @@ public:
        return uniforms;
    }

    double lookupTonemapGain(
    std::vector<Gain> lookupTonemapGain(
            aidl::android::hardware::graphics::common::Dataspace sourceDataspace,
            aidl::android::hardware::graphics::common::Dataspace destinationDataspace,
            vec3 linearRGB, vec3 /* xyz */, const Metadata& metadata) override {
            const std::vector<Color>& colors, const Metadata& metadata) override {
        std::vector<Gain> gains;
        gains.reserve(colors.size());

        // Precompute constants for HDR->SDR tonemapping parameters
        constexpr double maxInLumi = 4000;
        const double maxOutLumi = metadata.displayMaxLuminance;

        const double x1 = maxOutLumi * 0.65;
        const double y1 = x1;

        const double x3 = maxInLumi;
        const double y3 = maxOutLumi;

        const double x2 = x1 + (x3 - x1) * 4.0 / 17.0;
        const double y2 = maxOutLumi * 0.9;

        const double greyNorm1 = OETF_ST2084(x1);
        const double greyNorm2 = OETF_ST2084(x2);
        const double greyNorm3 = OETF_ST2084(x3);

        const double slope2 = (y2 - y1) / (greyNorm2 - greyNorm1);
        const double slope3 = (y3 - y2) / (greyNorm3 - greyNorm2);

        for (const auto [linearRGB, _] : colors) {
            double maxRGB = std::max({linearRGB.r, linearRGB.g, linearRGB.b});

            if (maxRGB <= 0.0) {
            return 1.0;
                gains.push_back(1.0);
                continue;
            }

            const int32_t sourceDataspaceInt = static_cast<int32_t>(sourceDataspace);
@@ -569,36 +599,13 @@ public:
                            targetNits = maxRGB;
                            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.
                            // 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.
                            targetNits = std::clamp(maxRGB, 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.

                        double maxInLumi = 4000;
                        double maxOutLumi = metadata.displayMaxLuminance;

                            targetNits = maxRGB;

                        double x1 = maxOutLumi * 0.65;
                        double y1 = x1;

                        double x3 = maxInLumi;
                        double y3 = maxOutLumi;

                        double x2 = x1 + (x3 - x1) * 4.0 / 17.0;
                        double y2 = maxOutLumi * 0.9;

                        const double greyNorm1 = OETF_ST2084(x1);
                        const double greyNorm2 = OETF_ST2084(x2);
                        const double greyNorm3 = OETF_ST2084(x3);

                        double slope2 = (y2 - y1) / (greyNorm2 - greyNorm1);
                        double slope3 = (y3 - y2) / (greyNorm3 - greyNorm2);

                            if (targetNits < x1) {
                                break;
                            }
@@ -636,7 +643,9 @@ public:
                    break;
            }

        return targetNits / maxRGB;
            gains.push_back(targetNits / maxRGB);
        }
        return gains;
    }
};