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

Commit ba8f28d7 authored by Michael Ludwig's avatar Michael Ludwig
Browse files

Update MouriMap to use SkShader::makeWithWorkingColorSpace()

This removes all uses of per-pixel to/fromLinearSrgb and explicitly has
the temporary offscreen images created with a linear sRGB color space
(so that the input is mapped automatically to this space when sampling).
Then the final tonemapping step is wrapped with
makeWithWorkingColorSpace(linearSRGB) to also automatically map the
input to linear, and to have a no-op conversion for the offscreen
buffers. Its output is then automatically mapped to whatever dst color
space the returned shader is rendered into.

Bug: b/426601394
Flag: EXEMPT bug fix
Change-Id: I62810ce8f8ded9c44de5182d79c123d112d9d229
parent 20c8fef6
Loading
Loading
Loading
Loading
+37 −15
Original line number Diff line number Diff line
@@ -384,6 +384,9 @@ PaintOptions ImageHWOnlySRGBSrcover() {
    return paintOptions;
}

// TODO(b/426601394): Update this to take an SkColorInfo for the input image.
// The other MouriMap* precompile paint options should use a linear SkColorInfo
// derived from this same input image.
skgpu::graphite::PaintOptions MouriMapCrosstalkAndChunk16x16(RuntimeEffectManager& effectManager) {
    SkColorInfo ci { kRGBA_8888_SkColorType, kPremul_SkAlphaType, nullptr };
    sk_sp<PrecompileShader> img = PrecompileShaders::Image(ImageShaderFlags::kExcludeCubic,
@@ -403,7 +406,7 @@ skgpu::graphite::PaintOptions MouriMapCrosstalkAndChunk16x16(RuntimeEffectManage
}

skgpu::graphite::PaintOptions MouriMapChunk8x8Effect(RuntimeEffectManager& effectManager) {
    SkColorInfo ci { kRGBA_8888_SkColorType, kPremul_SkAlphaType, nullptr };
    SkColorInfo ci { kRGBA_F16_SkColorType, kPremul_SkAlphaType, SkColorSpace::MakeSRGBLinear() };
    sk_sp<PrecompileShader> img = PrecompileShaders::Image(ImageShaderFlags::kExcludeCubic,
                                                           { &ci, 1 },
                                                           {});
@@ -420,7 +423,7 @@ skgpu::graphite::PaintOptions MouriMapChunk8x8Effect(RuntimeEffectManager& effec
}

skgpu::graphite::PaintOptions MouriMapBlur(RuntimeEffectManager& effectManager) {
    SkColorInfo ci { kRGBA_8888_SkColorType, kPremul_SkAlphaType, nullptr };
    SkColorInfo ci { kRGBA_F16_SkColorType, kPremul_SkAlphaType, SkColorSpace::MakeSRGBLinear() };
    sk_sp<PrecompileShader> img = PrecompileShaders::Image(ImageShaderFlags::kExcludeCubic,
                                                           { &ci, 1 },
                                                           {});
@@ -438,20 +441,27 @@ skgpu::graphite::PaintOptions MouriMapBlur(RuntimeEffectManager& effectManager)

skgpu::graphite::PaintOptions MouriMapToneMap(RuntimeEffectManager& effectManager) {
    SkColorInfo ci { kRGBA_8888_SkColorType, kPremul_SkAlphaType, nullptr };
    sk_sp<PrecompileShader> img1 = PrecompileShaders::Image(ImageShaderFlags::kExcludeCubic,
    sk_sp<PrecompileShader> input = PrecompileShaders::Image(ImageShaderFlags::kExcludeCubic,
                                                            { &ci, 1 },
                                                            {});
    sk_sp<PrecompileShader> img2 = PrecompileShaders::Image(ImageShaderFlags::kExcludeCubic,
                                                            { &ci, 1 },

    SkColorInfo luxCI { kRGBA_F16_SkColorType,
                        kPremul_SkAlphaType,
                        SkColorSpace::MakeSRGBLinear() };
    sk_sp<PrecompileShader> lux = PrecompileShaders::Image(ImageShaderFlags::kExcludeCubic,
                                                            { &luxCI, 1 },
                                                            {});

    sk_sp<PrecompileShader> toneMap = PrecompileRuntimeEffects::
            MakePrecompileShader(effectManager.getKnownRuntimeEffect(
                                         RuntimeEffectManager::KnownId::kMouriMap_TonemapEffect),
                                 {{std::move(img1)}, {std::move(img2)}});
                                 {{std::move(input)}, {std::move(lux)}});

    sk_sp<PrecompileShader> inLinear =
            toneMap->makeWithWorkingColorSpace(luxCI.refColorSpace());

    PaintOptions paintOptions;
    paintOptions.setShaders({ std::move(toneMap) });
    paintOptions.setShaders({ std::move(inLinear) });
    paintOptions.setBlendModes({ SkBlendMode::kSrc });
    return paintOptions;
}
@@ -571,7 +581,8 @@ skgpu::graphite::PaintOptions MouriMapCrosstalkAndChunk16x16YCbCr247(RuntimeEffe
            247,
            VK_SAMPLER_YCBCR_MODEL_CONVERSION_YCBCR_2020,
            VK_SAMPLER_YCBCR_RANGE_ITU_NARROW,
            VK_CHROMA_LOCATION_COSITED_EVEN);
            VK_CHROMA_LOCATION_COSITED_EVEN,
            /*pqCS=*/true);

    sk_sp<PrecompileShader> crosstalk = PrecompileRuntimeEffects::MakePrecompileShader(
            effectManager.getKnownRuntimeEffect(RuntimeEffectManager::KnownId::kMouriMap_CrossTalkAndChunk16x16Effect),
@@ -723,6 +734,14 @@ const skgpu::graphite::RenderPassProperties kRGBA16F_1_D_SRGB {
    /* fRequiresMSAA= */ false
};

// The same as kRGBA16F_1_D but w/ a linear SRGB colorSpace
const skgpu::graphite::RenderPassProperties kRGBA16F_1_D_Linear {
    skgpu::graphite::DepthStencilFlags::kDepth,
    kRGBA_F16_SkColorType,
    SkColorSpace::MakeSRGBLinear(),
    /* fRequiresMSAA= */ false
};

const RenderPassProperties kRGBA_1D_4DS[2] = { kRGBA_1_D, kRGBA_4_DS };
const RenderPassProperties kRGBA_1D_4DS_SRGB[2] = { kRGBA_1_D_SRGB, kRGBA_4_DS_SRGB };

@@ -856,10 +875,13 @@ void GraphitePipelineManager::PrecompilePipelines(
/* 21 */ { ImagePremulHWOnlySrc(),             DrawTypeFlags::kPerEdgeAAQuad,   kRGBA_1_D },
/* 22 */ { ImagePremulHWOnlySrc(),             DrawTypeFlags::kNonAAFillRect,   kRGBA_4_DS },

/* 23 */ { MouriMapBlur(effectManager),                     DrawTypeFlags::kNonAAFillRect,   kRGBA16F_1_D },
// TODO(b/426601394): Group these paint option settings into a function that accepts an input
// image color space so that the intermediate linear color spaces adapt correctly.
/* 23 */ { MouriMapBlur(effectManager),                     DrawTypeFlags::kNonAAFillRect,   kRGBA16F_1_D_Linear },
/* 24 */ { MouriMapToneMap(effectManager),                  DrawTypeFlags::kNonAAFillRect,   kRGBA_1_D_SRGB },
/* 25 */ { MouriMapCrosstalkAndChunk16x16(effectManager),   DrawTypeFlags::kNonAAFillRect,   kRGBA16F_1_D_SRGB },
/* 26 */ { MouriMapChunk8x8Effect(effectManager),           DrawTypeFlags::kNonAAFillRect,   kRGBA16F_1_D },
/* 25 */ { MouriMapCrosstalkAndChunk16x16(effectManager),   DrawTypeFlags::kNonAAFillRect,   kRGBA16F_1_D_Linear },
/* 26 */ { MouriMapChunk8x8Effect(effectManager),           DrawTypeFlags::kNonAAFillRect,   kRGBA16F_1_D_Linear },

/* 27 */ { KawaseBlurLowSrcSrcOver(effectManager),          DrawTypeFlags::kNonAAFillRect,   kRGBA_1_D },
/* 28 */ { KawaseBlurHighSrc(effectManager),                DrawTypeFlags::kNonAAFillRect,   kRGBA_1_D },
/* 29 */ { BlurFilterMix(effectManager),                    kRRectAndNonAARect,              kRGBA_1_D },
+45 −10
Original line number Diff line number Diff line
@@ -25,6 +25,15 @@ namespace android {
namespace renderengine {
namespace skia {
namespace {

// NOTE: These shaders are intended to operate on linear color values, but to
// avoid mapping to/from linear Srgb using the SkSL intrinsics, color space
// management is coordinated with the SkColorSpace of the output surfaces and
// with `SkShader::withWorkingColorSpace`.

// This shader must output to a surface with a linear color space, and relies on
// Skia's automatic colorspace conversion to map from the input `bitmap`'s
// color space to said linear color space.
const SkString kCrosstalkAndChunk16x16String(R"(
    uniform shader bitmap;
    uniform float inputMultiplier;
@@ -32,7 +41,7 @@ const SkString kCrosstalkAndChunk16x16String(R"(
        float maximum = 0.0;
        for (int y = 0; y < 16; y++) {
            for (int x = 0; x < 16; x++) {
                float3 linear = toLinearSrgb(bitmap.eval((xy - 0.5) * 16 + 0.5 + vec2(x, y)).rgb) * inputMultiplier;
                float3 linear = bitmap.eval((xy - 0.5) * 16 + 0.5 + vec2(x, y)).rgb * inputMultiplier;
                float maxRGB = max(linear.r, max(linear.g, linear.b));
                maximum = max(maximum, log2(max(maxRGB, 1.0)));
            }
@@ -40,6 +49,8 @@ const SkString kCrosstalkAndChunk16x16String(R"(
        return float4(float3(maximum), 1.0);
    }
)");
// This shader assumes `bitmap` is already in a linear color space and does not
// perform any color space conversion.
const SkString kChunk8x8String(R"(
    uniform shader bitmap;
    vec4 main(vec2 xy) {
@@ -52,6 +63,8 @@ const SkString kChunk8x8String(R"(
        return float4(float3(maximum), 1.0);
    }
)");
// This shader assumes `bitmap` is already in a linear color space and does not
// perform any color space conversion.
const SkString kBlurString(R"(
    uniform shader bitmap;
    vec4 main(vec2 xy) {
@@ -70,6 +83,11 @@ const SkString kBlurString(R"(
        return float4(float3(exp2(result)), 1.0);
    }
)");
// This shader assumes `image` is in the original color space and `lux` is in a
// linear color space. The shader must be wrapped with `SkShader::makeWithWorkingColorSpace`
// to automatically convert `image` into the linear color space (`lux`'s conversion
// is a no-op). `makeWithWorkingColorSpace` will also automatically convert back to
// the output color space, avoiding the need to call to/fromLinearSrgb per pixel.
const SkString kTonemapString(R"(
    uniform shader image;
    uniform shader lux;
@@ -79,17 +97,17 @@ const SkString kTonemapString(R"(
    vec4 main(vec2 xy) {
        float localMax = lux.eval(xy * scaleFactor).r;
        float4 rgba = image.eval(xy);
        float3 linear = toLinearSrgb(rgba.rgb) * inputMultiplier;
        float3 linear = rgba.rgb * inputMultiplier;

        if (localMax <= targetHdrSdrRatio) {
            return float4(fromLinearSrgb(linear), rgba.a);
            return float4(linear, rgba.a);
        }

        float maxRGB = max(linear.r, max(linear.g, linear.b));
        localMax = max(localMax, maxRGB);
        float gain = (1 + maxRGB * (targetHdrSdrRatio / (localMax * localMax)))
                / (1 + maxRGB / targetHdrSdrRatio);
        return float4(fromLinearSrgb(linear * gain), rgba.a);
        return float4(linear * gain, rgba.a);
    }
)");

@@ -146,18 +164,25 @@ sk_sp<SkImage> MouriMap::downchunk(SkiaGpuContext* context, sk_sp<SkShader> inpu
    // not need to be so precise. So, it's possible that we could use A8 or R8 instead. If we want
    // to be really conservative we can try to use R16 or even RGBA1010102 to fake an R10 surface,
    // which would cut write bandwidth significantly.
    // TODO(b/426601394): Linear sRGB is used currently to preserve behavior of the original
    // implementation calling to/fromLinearSrgb. In follow up work, it should be adjusted to be
    // image->imageInfo().colorSpace()->makeLinearGamma().
    static constexpr auto kFirstDownscaleAmount = 16;
    sk_sp<SkSurface> firstDownsampledSurface = context->createRenderTarget(
            image->imageInfo()
                    .makeWH(std::max(1, image->width() / kFirstDownscaleAmount),
                            std::max(1, image->height() / kFirstDownscaleAmount))
                    .makeColorSpace(SkColorSpace::MakeSRGBLinear())
                    .makeColorType(kRGBA_F16_SkColorType));
    LOG_ALWAYS_FATAL_IF(!firstDownsampledSurface, "%s: Failed to create surface!", __func__);
    auto firstDownsampledImage =
            makeImage(firstDownsampledSurface.get(), crosstalkAndChunk16x16Builder);

    // Since `secondDownsampledSurface` has the same color space as `firstDownsampledImage`, this
    // is equivalent to using makeRawShader(), but preserves the color space on the objects.
    SkRuntimeShaderBuilder chunk8x8Builder(mChunk8x8);
    chunk8x8Builder.child("bitmap") =
            firstDownsampledImage->makeRawShader(SkTileMode::kClamp, SkTileMode::kClamp,
            firstDownsampledImage->makeShader(SkTileMode::kClamp, SkTileMode::kClamp,
                                              SkSamplingOptions());
    static constexpr auto kSecondDownscaleAmount = 8;
    sk_sp<SkSurface> secondDownsampledSurface = context->createRenderTarget(
@@ -168,9 +193,11 @@ sk_sp<SkImage> MouriMap::downchunk(SkiaGpuContext* context, sk_sp<SkShader> inpu
    return makeImage(secondDownsampledSurface.get(), chunk8x8Builder);
}
sk_sp<SkImage> MouriMap::blur(SkiaGpuContext* context, SkImage* input) const {
    // Since the `blurSurface` has the same color space as `input`, this will perform no color
    // space conversion (and it is assumed that `input` is already in a linear color space).
    SkRuntimeShaderBuilder blurBuilder(mBlur);
    blurBuilder.child("bitmap") =
            input->makeRawShader(SkTileMode::kClamp, SkTileMode::kClamp, SkSamplingOptions());
            input->makeShader(SkTileMode::kClamp, SkTileMode::kClamp, SkSamplingOptions());
    sk_sp<SkSurface> blurSurface = context->createRenderTarget(input->imageInfo());
    LOG_ALWAYS_FATAL_IF(!blurSurface, "%s: Failed to create surface!", __func__);
    return makeImage(blurSurface.get(), blurBuilder);
@@ -178,15 +205,23 @@ sk_sp<SkImage> MouriMap::blur(SkiaGpuContext* context, SkImage* input) const {
sk_sp<SkShader> MouriMap::tonemap(sk_sp<SkShader> input, SkImage* localLux, float inputMultiplier,
                                  float targetHdrSdrRatio) const {
    static constexpr float kScaleFactor = 1.0f / 128.0f;
    // The tonemap shader will work in `localLux`'s color space, so that child shader will not
    // actually perform any color space conversion. Skia will automatically convert `input`
    // into this same linear color space.
    SkRuntimeShaderBuilder tonemapBuilder(mTonemap);
    tonemapBuilder.child("image") = input;
    tonemapBuilder.child("lux") =
            localLux->makeRawShader(SkTileMode::kClamp, SkTileMode::kClamp,
            localLux->makeShader(SkTileMode::kClamp, SkTileMode::kClamp,
                                 SkSamplingOptions(SkFilterMode::kLinear, SkMipmapMode::kNone));
    tonemapBuilder.uniform("scaleFactor") = kScaleFactor;
    tonemapBuilder.uniform("inputMultiplier") = inputMultiplier;
    tonemapBuilder.uniform("targetHdrSdrRatio") = targetHdrSdrRatio;
    return tonemapBuilder.makeShader();

    sk_sp<SkShader> tonemapShader = tonemapBuilder.makeShader();
    // Now wrap the shader in a `makeWithWorkingColorSpace` call with lux's color space so `input`
    // is automatically converted at the start, and the output is automatically converted from
    // lux's space to the final destination color space.
    return tonemapShader->makeWithWorkingColorSpace(localLux->imageInfo().refColorSpace());
}
} // namespace skia
} // namespace renderengine