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

Commit cc2c8964 authored by TreeHugger Robot's avatar TreeHugger Robot Committed by Android (Google) Code Review
Browse files

Merge "Add CPU implementation for tone-mapping curves."

parents d8a9fc04 4049b530
Loading
Loading
Loading
Loading
+1 −1
Original line number Diff line number Diff line
@@ -104,7 +104,7 @@ static void generateXYZTransforms(SkString& shader) {
        uniform float4x4 in_rgbToXyz;
        uniform float4x4 in_xyzToRgb;
        float3 ToXYZ(float3 rgb) {
            return clamp((in_rgbToXyz * float4(rgb, 1.0)).rgb, 0.0, 1.0);
            return (in_rgbToXyz * float4(rgb, 1.0)).rgb;
        }

        float3 ToRGB(float3 xyz) {
+195 −22
Original line number Diff line number Diff line
@@ -27,6 +27,9 @@
#include <renderengine/ExternalTexture.h>
#include <renderengine/RenderEngine.h>
#include <sync/sync.h>
#include <system/graphics-base-v1.0.h>
#include <tonemap/tonemap.h>
#include <ui/ColorSpace.h>
#include <ui/PixelFormat.h>

#include <chrono>
@@ -282,6 +285,13 @@ public:

    void expectBufferColor(const Rect& rect, uint8_t r, uint8_t g, uint8_t b, uint8_t a,
                           uint8_t tolerance = 0) {
        auto generator = [=](Point) { return ubyte4(r, g, b, a); };
        expectBufferColor(rect, generator, tolerance);
    }

    using ColorGenerator = std::function<ubyte4(Point location)>;

    void expectBufferColor(const Rect& rect, ColorGenerator generator, uint8_t tolerance = 0) {
        auto colorCompare = [tolerance](const uint8_t* colorA, const uint8_t* colorB) {
            auto colorBitCompare = [tolerance](uint8_t a, uint8_t b) {
                uint8_t tmp = a >= b ? a - b : b - a;
@@ -290,10 +300,10 @@ public:
            return std::equal(colorA, colorA + 4, colorB, colorBitCompare);
        };

        expectBufferColor(rect, r, g, b, a, colorCompare);
        expectBufferColor(rect, generator, colorCompare);
    }

    void expectBufferColor(const Rect& region, uint8_t r, uint8_t g, uint8_t b, uint8_t a,
    void expectBufferColor(const Rect& region, ColorGenerator generator,
                           std::function<bool(const uint8_t* a, const uint8_t* b)> colorCompare) {
        uint8_t* pixels;
        mBuffer->getBuffer()->lock(GRALLOC_USAGE_SW_READ_OFTEN | GRALLOC_USAGE_SW_WRITE_OFTEN,
@@ -304,19 +314,22 @@ public:
            const uint8_t* src = pixels +
                    (mBuffer->getBuffer()->getStride() * (region.top + j) + region.left) * 4;
            for (int32_t i = 0; i < region.getWidth(); i++) {
                const uint8_t expected[4] = {r, g, b, a};
                bool equal = colorCompare(src, expected);
                EXPECT_TRUE(equal)
                const auto location = Point(region.left + i, region.top + j);
                const ubyte4 colors = generator(location);
                const uint8_t expected[4] = {colors.r, colors.g, colors.b, colors.a};
                bool colorMatches = colorCompare(src, expected);
                EXPECT_TRUE(colorMatches)
                        << GetParam()->name().c_str() << ": "
                        << "pixel @ (" << region.left + i << ", " << region.top + j << "): "
                        << "expected (" << static_cast<uint32_t>(r) << ", "
                        << static_cast<uint32_t>(g) << ", " << static_cast<uint32_t>(b) << ", "
                        << static_cast<uint32_t>(a) << "), "
                        << "pixel @ (" << location.x << ", " << location.y << "): "
                        << "expected (" << static_cast<uint32_t>(colors.r) << ", "
                        << static_cast<uint32_t>(colors.g) << ", "
                        << static_cast<uint32_t>(colors.b) << ", "
                        << static_cast<uint32_t>(colors.a) << "), "
                        << "got (" << static_cast<uint32_t>(src[0]) << ", "
                        << static_cast<uint32_t>(src[1]) << ", " << static_cast<uint32_t>(src[2])
                        << ", " << static_cast<uint32_t>(src[3]) << ")";
                src += 4;
                if (!equal && ++fails >= maxFails) {
                if (!colorMatches && ++fails >= maxFails) {
                    break;
                }
            }
@@ -328,10 +341,11 @@ public:
    }

    void expectAlpha(const Rect& rect, uint8_t a) {
        auto generator = [=](Point) { return ubyte4(0, 0, 0, a); };
        auto colorCompare = [](const uint8_t* colorA, const uint8_t* colorB) {
            return colorA[3] == colorB[3];
        };
        expectBufferColor(rect, 0.0f /* r */, 0.0f /*g */, 0.0f /* b */, a, colorCompare);
        expectBufferColor(rect, generator, colorCompare);
    }

    void expectShadowColor(const renderengine::LayerSettings& castingLayer,
@@ -1099,7 +1113,7 @@ void RenderEngineTest::fillRedBufferTextureTransform() {
    layer.source.buffer.buffer = buf;
    layer.source.buffer.textureName = texName;
    // Transform coordinates to only be inside the red quadrant.
    layer.source.buffer.textureTransform = mat4::scale(vec4(0.2, 0.2, 1, 1));
    layer.source.buffer.textureTransform = mat4::scale(vec4(0.2f, 0.2f, 1.f, 1.f));
    layer.alpha = 1.0f;
    layer.geometry.boundaries = Rect(1, 1).toFloatRect();

@@ -1281,7 +1295,8 @@ TEST_P(RenderEngineTest, drawLayers_withoutBuffers_withColorTransform) {
    settings.clip = fullscreenRect();

    // 255, 255, 255, 255 is full opaque white.
    const ubyte4 backgroundColor(255.f, 255.f, 255.f, 255.f);
    const ubyte4 backgroundColor(static_cast<uint8_t>(255), static_cast<uint8_t>(255),
                                 static_cast<uint8_t>(255), static_cast<uint8_t>(255));
    // Create layer with given color.
    renderengine::LayerSettings bgLayer;
    bgLayer.sourceDataspace = ui::Dataspace::V0_SRGB_LINEAR;
@@ -1615,7 +1630,8 @@ TEST_P(RenderEngineTest, drawLayers_fillBuffer_withoutPremultiplyingAlpha) {
TEST_P(RenderEngineTest, drawLayers_fillShadow_castsWithoutCasterLayer) {
    initializeRenderEngine();

    const ubyte4 backgroundColor(255, 255, 255, 255);
    const ubyte4 backgroundColor(static_cast<uint8_t>(255), static_cast<uint8_t>(255),
                                 static_cast<uint8_t>(255), static_cast<uint8_t>(255));
    const float shadowLength = 5.0f;
    Rect casterBounds(DEFAULT_DISPLAY_WIDTH / 3.0f, DEFAULT_DISPLAY_HEIGHT / 3.0f);
    casterBounds.offsetBy(shadowLength + 1, shadowLength + 1);
@@ -1630,8 +1646,10 @@ TEST_P(RenderEngineTest, drawLayers_fillShadow_castsWithoutCasterLayer) {
TEST_P(RenderEngineTest, drawLayers_fillShadow_casterLayerMinSize) {
    initializeRenderEngine();

    const ubyte4 casterColor(255, 0, 0, 255);
    const ubyte4 backgroundColor(255, 255, 255, 255);
    const ubyte4 casterColor(static_cast<uint8_t>(255), static_cast<uint8_t>(0),
                             static_cast<uint8_t>(0), static_cast<uint8_t>(255));
    const ubyte4 backgroundColor(static_cast<uint8_t>(255), static_cast<uint8_t>(255),
                                 static_cast<uint8_t>(255), static_cast<uint8_t>(255));
    const float shadowLength = 5.0f;
    Rect casterBounds(1, 1);
    casterBounds.offsetBy(shadowLength + 1, shadowLength + 1);
@@ -1649,8 +1667,10 @@ TEST_P(RenderEngineTest, drawLayers_fillShadow_casterLayerMinSize) {
TEST_P(RenderEngineTest, drawLayers_fillShadow_casterColorLayer) {
    initializeRenderEngine();

    const ubyte4 casterColor(255, 0, 0, 255);
    const ubyte4 backgroundColor(255, 255, 255, 255);
    const ubyte4 casterColor(static_cast<uint8_t>(255), static_cast<uint8_t>(0),
                             static_cast<uint8_t>(0), static_cast<uint8_t>(255));
    const ubyte4 backgroundColor(static_cast<uint8_t>(255), static_cast<uint8_t>(255),
                                 static_cast<uint8_t>(255), static_cast<uint8_t>(255));
    const float shadowLength = 5.0f;
    Rect casterBounds(DEFAULT_DISPLAY_WIDTH / 3.0f, DEFAULT_DISPLAY_HEIGHT / 3.0f);
    casterBounds.offsetBy(shadowLength + 1, shadowLength + 1);
@@ -1669,8 +1689,10 @@ TEST_P(RenderEngineTest, drawLayers_fillShadow_casterColorLayer) {
TEST_P(RenderEngineTest, drawLayers_fillShadow_casterOpaqueBufferLayer) {
    initializeRenderEngine();

    const ubyte4 casterColor(255, 0, 0, 255);
    const ubyte4 backgroundColor(255, 255, 255, 255);
    const ubyte4 casterColor(static_cast<uint8_t>(255), static_cast<uint8_t>(0),
                             static_cast<uint8_t>(0), static_cast<uint8_t>(255));
    const ubyte4 backgroundColor(static_cast<uint8_t>(255), static_cast<uint8_t>(255),
                                 static_cast<uint8_t>(255), static_cast<uint8_t>(255));
    const float shadowLength = 5.0f;
    Rect casterBounds(DEFAULT_DISPLAY_WIDTH / 3.0f, DEFAULT_DISPLAY_HEIGHT / 3.0f);
    casterBounds.offsetBy(shadowLength + 1, shadowLength + 1);
@@ -1690,8 +1712,10 @@ TEST_P(RenderEngineTest, drawLayers_fillShadow_casterOpaqueBufferLayer) {
TEST_P(RenderEngineTest, drawLayers_fillShadow_casterWithRoundedCorner) {
    initializeRenderEngine();

    const ubyte4 casterColor(255, 0, 0, 255);
    const ubyte4 backgroundColor(255, 255, 255, 255);
    const ubyte4 casterColor(static_cast<uint8_t>(255), static_cast<uint8_t>(0),
                             static_cast<uint8_t>(0), static_cast<uint8_t>(255));
    const ubyte4 backgroundColor(static_cast<uint8_t>(255), static_cast<uint8_t>(255),
                                 static_cast<uint8_t>(255), static_cast<uint8_t>(255));
    const float shadowLength = 5.0f;
    Rect casterBounds(DEFAULT_DISPLAY_WIDTH / 3.0f, DEFAULT_DISPLAY_HEIGHT / 3.0f);
    casterBounds.offsetBy(shadowLength + 1, shadowLength + 1);
@@ -2027,6 +2051,155 @@ TEST_P(RenderEngineTest, test_isOpaque) {
        expectBufferColor(rect, 0, 255, 0, 255);
    }
}

double EOTF_PQ(double channel) {
    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;

    float tmp = std::pow(std::clamp(channel, 0.0, 1.0), 1.0 / m2);
    tmp = std::fmax(tmp - c1, 0.0) / (c2 - c3 * tmp);
    return std::pow(tmp, 1.0 / m1);
}

vec3 EOTF_PQ(vec3 color) {
    return vec3(EOTF_PQ(color.r), EOTF_PQ(color.g), EOTF_PQ(color.b));
}

double OETF_sRGB(double channel) {
    return channel <= 0.0031308 ? channel * 12.92 : (pow(channel, 1.0 / 2.4) * 1.055) - 0.055;
}

int sign(float in) {
    return in >= 0.0 ? 1 : -1;
}

vec3 OETF_sRGB(vec3 linear) {
    return vec3(sign(linear.r) * OETF_sRGB(linear.r), sign(linear.g) * OETF_sRGB(linear.g),
                sign(linear.b) * OETF_sRGB(linear.b));
}

TEST_P(RenderEngineTest, test_tonemapPQMatches) {
    if (!GetParam()->useColorManagement()) {
        return;
    }

    if (GetParam()->type() == renderengine::RenderEngine::RenderEngineType::GLES) {
        return;
    }

    initializeRenderEngine();

    constexpr int32_t kGreyLevels = 256;

    const auto rect = Rect(0, 0, kGreyLevels, 1);
    const renderengine::DisplaySettings display{
            .physicalDisplay = rect,
            .clip = rect,
            .maxLuminance = 750.0f,
            .outputDataspace = ui::Dataspace::DISPLAY_P3,
    };

    auto buf = std::make_shared<
            renderengine::ExternalTexture>(new GraphicBuffer(kGreyLevels, 1,
                                                             HAL_PIXEL_FORMAT_RGBA_8888, 1,
                                                             GRALLOC_USAGE_SW_READ_OFTEN |
                                                                     GRALLOC_USAGE_SW_WRITE_OFTEN |
                                                                     GRALLOC_USAGE_HW_RENDER |
                                                                     GRALLOC_USAGE_HW_TEXTURE,
                                                             "input"),
                                           *mRE,
                                           renderengine::ExternalTexture::Usage::READABLE |
                                                   renderengine::ExternalTexture::Usage::WRITEABLE);
    ASSERT_EQ(0, buf->getBuffer()->initCheck());

    {
        uint8_t* pixels;
        buf->getBuffer()->lock(GRALLOC_USAGE_SW_READ_OFTEN | GRALLOC_USAGE_SW_WRITE_OFTEN,
                               reinterpret_cast<void**>(&pixels));

        uint8_t color = 0;
        for (int32_t j = 0; j < buf->getBuffer()->getHeight(); j++) {
            uint8_t* dest = pixels + (buf->getBuffer()->getStride() * j * 4);
            for (int32_t i = 0; i < buf->getBuffer()->getWidth(); i++) {
                dest[0] = color;
                dest[1] = color;
                dest[2] = color;
                dest[3] = 255;
                color++;
                dest += 4;
            }
        }
        buf->getBuffer()->unlock();
    }

    mBuffer = std::make_shared<
            renderengine::ExternalTexture>(new GraphicBuffer(kGreyLevels, 1,
                                                             HAL_PIXEL_FORMAT_RGBA_8888, 1,
                                                             GRALLOC_USAGE_SW_READ_OFTEN |
                                                                     GRALLOC_USAGE_SW_WRITE_OFTEN |
                                                                     GRALLOC_USAGE_HW_RENDER |
                                                                     GRALLOC_USAGE_HW_TEXTURE,
                                                             "output"),
                                           *mRE,
                                           renderengine::ExternalTexture::Usage::READABLE |
                                                   renderengine::ExternalTexture::Usage::WRITEABLE);
    ASSERT_EQ(0, mBuffer->getBuffer()->initCheck());

    const renderengine::LayerSettings layer{
            .geometry.boundaries = rect.toFloatRect(),
            .source =
                    renderengine::PixelSource{
                            .buffer =
                                    renderengine::Buffer{
                                            .buffer = std::move(buf),
                                            .usePremultipliedAlpha = true,
                                    },
                    },
            .alpha = 1.0f,
            .sourceDataspace = static_cast<ui::Dataspace>(HAL_DATASPACE_STANDARD_BT2020 |
                                                          HAL_DATASPACE_TRANSFER_ST2084 |
                                                          HAL_DATASPACE_RANGE_FULL),
    };

    std::vector<renderengine::LayerSettings> layers{layer};
    invokeDraw(display, layers);

    ColorSpace displayP3 = ColorSpace::DisplayP3();
    ColorSpace bt2020 = ColorSpace::BT2020();

    tonemap::Metadata metadata{.displayMaxLuminance = 750.0f};

    auto generator = [=](Point location) {
        const double normColor = static_cast<double>(location.x) / (kGreyLevels - 1);
        const vec3 rgb = vec3(normColor, normColor, normColor);

        const vec3 linearRGB = EOTF_PQ(rgb);

        static constexpr float kMaxPQLuminance = 10000.f;
        const vec3 xyz = bt2020.getRGBtoXYZ() * linearRGB * kMaxPQLuminance;
        const double gain =
                tonemap::getToneMapper()
                        ->lookupTonemapGain(static_cast<aidl::android::hardware::graphics::common::
                                                                Dataspace>(
                                                    HAL_DATASPACE_STANDARD_BT2020 |
                                                    HAL_DATASPACE_TRANSFER_ST2084 |
                                                    HAL_DATASPACE_RANGE_FULL),
                                            static_cast<aidl::android::hardware::graphics::common::
                                                                Dataspace>(
                                                    ui::Dataspace::DISPLAY_P3),
                                            linearRGB * 10000.0, xyz, metadata);
        const vec3 scaledXYZ = xyz * gain / metadata.displayMaxLuminance;

        const vec3 targetRGB = OETF_sRGB(displayP3.getXYZtoRGB() * scaledXYZ) * 255;
        return ubyte4(static_cast<uint8_t>(targetRGB.r), static_cast<uint8_t>(targetRGB.g),
                      static_cast<uint8_t>(targetRGB.b), 255);
    };

    expectBufferColor(Rect(kGreyLevels, 1), generator, 2);
}
} // namespace renderengine
} // namespace android

+6 −0
Original line number Diff line number Diff line
@@ -30,7 +30,13 @@ cc_library_static {

    shared_libs: [
        "android.hardware.graphics.common-V3-ndk",
        "liblog",
    ],

    static_libs: [
        "libmath",
    ],

    srcs: [
        "tonemap.cpp",
    ],
+17 −1
Original line number Diff line number Diff line
@@ -17,6 +17,7 @@
#pragma once

#include <aidl/android/hardware/graphics/common/Dataspace.h>
#include <math/vec3.h>

#include <string>
#include <vector>
@@ -48,7 +49,9 @@ struct Metadata {
class ToneMapper {
public:
    virtual ~ToneMapper() {}
    // Constructs a tonemap shader whose shader language is SkSL
    // Constructs a tonemap shader whose shader language is SkSL, which tonemaps from an
    // input whose dataspace is described by sourceDataspace, to an output whose dataspace
    // is described by destinationDataspace
    //
    // The returned shader string *must* contain a function with the following signature:
    // float libtonemap_LookupTonemapGain(vec3 linearRGB, vec3 xyz);
@@ -94,6 +97,19 @@ public:
    // assume that there are predefined floats in_libtonemap_displayMaxLuminance and
    // in_libtonemap_inputMaxLuminance inside of the body of the tone-mapping shader.
    virtual std::vector<ShaderUniform> generateShaderSkSLUniforms(const Metadata& metadata) = 0;

    // CPU implementation of the tonemapping gain. This must match the GPU implementation returned
    // by generateTonemapGainShaderSKSL() above, with some epsilon difference to account for
    // differences in hardware precision.
    //
    // The gain is computed assuming an input described by sourceDataspace, tonemapped to an output
    // 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(
            aidl::android::hardware::graphics::common::Dataspace sourceDataspace,
            aidl::android::hardware::graphics::common::Dataspace destinationDataspace,
            vec3 linearRGB, vec3 xyz, const Metadata& metadata) = 0;
};

// Retrieves a tonemapper instance.
+1 −0
Original line number Diff line number Diff line
@@ -31,6 +31,7 @@ cc_test {
        "android.hardware.graphics.common-V3-ndk",
    ],
    static_libs: [
        "libmath",
        "libgmock",
        "libgtest",
        "libtonemap",
Loading