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

Commit 602a1794 authored by John Reck's avatar John Reck Committed by Automerger Merge Worker
Browse files

Merge "Support gainmaps in BitmapShader" into udc-dev am: f4ad413f

parents ee258cac f4ad413f
Loading
Loading
Loading
Loading
+248 −5
Original line number Diff line number Diff line
@@ -23,21 +23,55 @@
#include "utils/Trace.h"

#ifdef __ANDROID__
#include "include/core/SkColorSpace.h"
#include "include/core/SkImage.h"
#include "include/core/SkShader.h"
#include "include/effects/SkRuntimeEffect.h"
#include "include/private/SkGainmapInfo.h"
#include "renderthread/CanvasContext.h"
#include "src/core/SkColorFilterPriv.h"
#include "src/core/SkImageInfoPriv.h"
#include "src/core/SkRuntimeEffectPriv.h"
#endif

namespace android::uirenderer {

using namespace renderthread;

static float getTargetHdrSdrRatio(const SkColorSpace* destColorspace) {
    // We should always have a known destination colorspace. If we don't we must be in some
    // legacy mode where we're lost and also definitely not going to HDR
    if (destColorspace == nullptr) {
        return 1.f;
    }

    constexpr float GenericSdrWhiteNits = 203.f;
    constexpr float maxPQLux = 10000.f;
    constexpr float maxHLGLux = 1000.f;
    skcms_TransferFunction destTF;
    destColorspace->transferFn(&destTF);
    if (skcms_TransferFunction_isPQish(&destTF)) {
        return maxPQLux / GenericSdrWhiteNits;
    } else if (skcms_TransferFunction_isHLGish(&destTF)) {
        return maxHLGLux / GenericSdrWhiteNits;
    } else {
#ifdef __ANDROID__
        CanvasContext* context = CanvasContext::getActiveContext();
        return context ? context->targetSdrHdrRatio() : 1.f;
#else
        return 1.f;
#endif
    }
}

void DrawGainmapBitmap(SkCanvas* c, const sk_sp<const SkImage>& image, const SkRect& src,
                       const SkRect& dst, const SkSamplingOptions& sampling, const SkPaint* paint,
                       SkCanvas::SrcRectConstraint constraint,
                       const sk_sp<const SkImage>& gainmapImage, const SkGainmapInfo& gainmapInfo) {
    ATRACE_CALL();
#ifdef __ANDROID__
    CanvasContext* context = CanvasContext::getActiveContext();
    float targetSdrHdrRatio = context ? context->targetSdrHdrRatio() : 1.f;
    auto destColorspace = c->imageInfo().refColorSpace();
    float targetSdrHdrRatio = getTargetHdrSdrRatio(destColorspace.get());
    if (targetSdrHdrRatio > 1.f && gainmapImage) {
        SkPaint gainmapPaint = *paint;
        float sX = gainmapImage->width() / (float)image->width();
@@ -48,9 +82,9 @@ void DrawGainmapBitmap(SkCanvas* c, const sk_sp<const SkImage>& image, const SkR
        gainmapSrc.fRight *= sX;
        gainmapSrc.fTop *= sY;
        gainmapSrc.fBottom *= sY;
        auto shader = SkGainmapShader::Make(image, src, sampling, gainmapImage, gainmapSrc,
                                            sampling, gainmapInfo, dst, targetSdrHdrRatio,
                                            c->imageInfo().refColorSpace());
        auto shader =
                SkGainmapShader::Make(image, src, sampling, gainmapImage, gainmapSrc, sampling,
                                      gainmapInfo, dst, targetSdrHdrRatio, destColorspace);
        gainmapPaint.setShader(shader);
        c->drawRect(dst, gainmapPaint);
    } else
@@ -58,4 +92,213 @@ void DrawGainmapBitmap(SkCanvas* c, const sk_sp<const SkImage>& image, const SkR
        c->drawImageRect(image.get(), src, dst, sampling, paint, constraint);
}

#ifdef __ANDROID__

static constexpr char gGainmapSKSL[] = R"SKSL(
    uniform shader base;
    uniform shader gainmap;
    uniform colorFilter workingSpaceToLinearSrgb;
    uniform half4 logRatioMin;
    uniform half4 logRatioMax;
    uniform half4 gainmapGamma;
    uniform half4 epsilonSdr;
    uniform half4 epsilonHdr;
    uniform half W;
    uniform int gainmapIsAlpha;
    uniform int gainmapIsRed;
    uniform int singleChannel;
    uniform int noGamma;

    half4 toDest(half4 working) {
        half4 ls = workingSpaceToLinearSrgb.eval(working);
        vec3 dest = fromLinearSrgb(ls.rgb);
        return half4(dest.r, dest.g, dest.b, ls.a);
    }

    half4 main(float2 coord) {
        half4 S = base.eval(coord);
        half4 G = gainmap.eval(coord);
        if (gainmapIsAlpha == 1) {
            G = half4(G.a, G.a, G.a, 1.0);
        }
        if (gainmapIsRed == 1) {
            G = half4(G.r, G.r, G.r, 1.0);
        }
        if (singleChannel == 1) {
            half L;
            if (noGamma == 1) {
                L = mix(logRatioMin.r, logRatioMax.r, G.r);
            } else {
                L = mix(logRatioMin.r, logRatioMax.r, pow(G.r, gainmapGamma.r));
            }
            half3 H = (S.rgb + epsilonSdr.rgb) * exp(L * W) - epsilonHdr.rgb;
            return toDest(half4(H.r, H.g, H.b, S.a));
        } else {
            half3 L;
            if (noGamma == 1) {
                L = mix(logRatioMin.rgb, logRatioMax.rgb, G.rgb);
            } else {
                L = mix(logRatioMin.rgb, logRatioMax.rgb, pow(G.rgb, gainmapGamma.rgb));
            }
            half3 H = (S.rgb + epsilonSdr.rgb) * exp(L * W) - epsilonHdr.rgb;
            return toDest(half4(H.r, H.g, H.b, S.a));
        }
    }
)SKSL";

static sk_sp<SkRuntimeEffect> gainmap_apply_effect() {
    static const SkRuntimeEffect* effect = []() -> SkRuntimeEffect* {
        auto buildResult = SkRuntimeEffect::MakeForShader(SkString(gGainmapSKSL), {});
        if (buildResult.effect) {
            return buildResult.effect.release();
        } else {
            LOG_ALWAYS_FATAL("Failed to build gainmap shader: %s", buildResult.errorText.c_str());
        }
    }();
    SkASSERT(effect);
    return sk_ref_sp(effect);
}

static bool all_channels_equal(const SkColor4f& c) {
    return c.fR == c.fG && c.fR == c.fB;
}

class DeferredGainmapShader {
private:
    sk_sp<SkRuntimeEffect> mShader{gainmap_apply_effect()};
    SkRuntimeShaderBuilder mBuilder{mShader};
    SkGainmapInfo mGainmapInfo;
    std::mutex mUniformGuard;

    void setupChildren(const sk_sp<const SkImage>& baseImage,
                       const sk_sp<const SkImage>& gainmapImage, SkTileMode tileModeX,
                       SkTileMode tileModeY, const SkSamplingOptions& samplingOptions) {
        sk_sp<SkColorSpace> baseColorSpace =
                baseImage->colorSpace() ? baseImage->refColorSpace() : SkColorSpace::MakeSRGB();

        // Determine the color space in which the gainmap math is to be applied.
        sk_sp<SkColorSpace> gainmapMathColorSpace = baseColorSpace->makeLinearGamma();

        // Create a color filter to transform from the base image's color space to the color space
        // in which the gainmap is to be applied.
        auto colorXformSdrToGainmap =
                SkColorFilterPriv::MakeColorSpaceXform(baseColorSpace, gainmapMathColorSpace);

        // The base image shader will convert into the color space in which the gainmap is applied.
        auto baseImageShader = baseImage->makeRawShader(tileModeX, tileModeY, samplingOptions)
                                       ->makeWithColorFilter(colorXformSdrToGainmap);

        // The gainmap image shader will ignore any color space that the gainmap has.
        const SkMatrix gainmapRectToDstRect =
                SkMatrix::RectToRect(SkRect::MakeWH(gainmapImage->width(), gainmapImage->height()),
                                     SkRect::MakeWH(baseImage->width(), baseImage->height()));
        auto gainmapImageShader = gainmapImage->makeRawShader(tileModeX, tileModeY, samplingOptions,
                                                              &gainmapRectToDstRect);

        // Create a color filter to transform from the color space in which the gainmap is applied
        // to the intermediate destination color space.
        auto colorXformGainmapToDst = SkColorFilterPriv::MakeColorSpaceXform(
                gainmapMathColorSpace, SkColorSpace::MakeSRGBLinear());

        mBuilder.child("base") = std::move(baseImageShader);
        mBuilder.child("gainmap") = std::move(gainmapImageShader);
        mBuilder.child("workingSpaceToLinearSrgb") = std::move(colorXformGainmapToDst);
    }

    void setupGenericUniforms(const sk_sp<const SkImage>& gainmapImage,
                              const SkGainmapInfo& gainmapInfo) {
        const SkColor4f logRatioMin({sk_float_log(gainmapInfo.fGainmapRatioMin.fR),
                                     sk_float_log(gainmapInfo.fGainmapRatioMin.fG),
                                     sk_float_log(gainmapInfo.fGainmapRatioMin.fB), 1.f});
        const SkColor4f logRatioMax({sk_float_log(gainmapInfo.fGainmapRatioMax.fR),
                                     sk_float_log(gainmapInfo.fGainmapRatioMax.fG),
                                     sk_float_log(gainmapInfo.fGainmapRatioMax.fB), 1.f});
        const int noGamma = gainmapInfo.fGainmapGamma.fR == 1.f &&
                            gainmapInfo.fGainmapGamma.fG == 1.f &&
                            gainmapInfo.fGainmapGamma.fB == 1.f;
        const uint32_t colorTypeFlags = SkColorTypeChannelFlags(gainmapImage->colorType());
        const int gainmapIsAlpha = colorTypeFlags == kAlpha_SkColorChannelFlag;
        const int gainmapIsRed = colorTypeFlags == kRed_SkColorChannelFlag;
        const int singleChannel = all_channels_equal(gainmapInfo.fGainmapGamma) &&
                                  all_channels_equal(gainmapInfo.fGainmapRatioMin) &&
                                  all_channels_equal(gainmapInfo.fGainmapRatioMax) &&
                                  (colorTypeFlags == kGray_SkColorChannelFlag ||
                                   colorTypeFlags == kAlpha_SkColorChannelFlag ||
                                   colorTypeFlags == kRed_SkColorChannelFlag);
        mBuilder.uniform("logRatioMin") = logRatioMin;
        mBuilder.uniform("logRatioMax") = logRatioMax;
        mBuilder.uniform("gainmapGamma") = gainmapInfo.fGainmapGamma;
        mBuilder.uniform("epsilonSdr") = gainmapInfo.fEpsilonSdr;
        mBuilder.uniform("epsilonHdr") = gainmapInfo.fEpsilonHdr;
        mBuilder.uniform("noGamma") = noGamma;
        mBuilder.uniform("singleChannel") = singleChannel;
        mBuilder.uniform("gainmapIsAlpha") = gainmapIsAlpha;
        mBuilder.uniform("gainmapIsRed") = gainmapIsRed;
    }

    sk_sp<const SkData> build(float targetHdrSdrRatio) {
        sk_sp<const SkData> uniforms;
        {
            // If we are called concurrently from multiple threads, we need to guard the call
            // to writableUniforms() which mutates mUniform. This is otherwise safe because
            // writeableUniforms() will make a copy if it's not unique before mutating
            // This can happen if a BitmapShader is used on multiple canvas', such as a
            // software + hardware canvas, which is otherwise valid as SkShader is "immutable"
            std::lock_guard _lock(mUniformGuard);
            const float Wunclamped = (sk_float_log(targetHdrSdrRatio) -
                                      sk_float_log(mGainmapInfo.fDisplayRatioSdr)) /
                                     (sk_float_log(mGainmapInfo.fDisplayRatioHdr) -
                                      sk_float_log(mGainmapInfo.fDisplayRatioSdr));
            const float W = std::max(std::min(Wunclamped, 1.f), 0.f);
            mBuilder.uniform("W") = W;
            uniforms = mBuilder.uniforms();
        }
        return uniforms;
    }

public:
    explicit DeferredGainmapShader(const sk_sp<const SkImage>& image,
                                   const sk_sp<const SkImage>& gainmapImage,
                                   const SkGainmapInfo& gainmapInfo, SkTileMode tileModeX,
                                   SkTileMode tileModeY, const SkSamplingOptions& sampling) {
        mGainmapInfo = gainmapInfo;
        setupChildren(image, gainmapImage, tileModeX, tileModeY, sampling);
        setupGenericUniforms(gainmapImage, gainmapInfo);
    }

    static sk_sp<SkShader> Make(const sk_sp<const SkImage>& image,
                                const sk_sp<const SkImage>& gainmapImage,
                                const SkGainmapInfo& gainmapInfo, SkTileMode tileModeX,
                                SkTileMode tileModeY, const SkSamplingOptions& sampling) {
        auto deferredHandler = std::make_shared<DeferredGainmapShader>(
                image, gainmapImage, gainmapInfo, tileModeX, tileModeY, sampling);
        auto callback =
                [deferredHandler](const SkRuntimeEffectPriv::UniformsCallbackContext& renderContext)
                -> sk_sp<const SkData> {
            return deferredHandler->build(getTargetHdrSdrRatio(renderContext.fDstColorSpace));
        };
        return SkRuntimeEffectPriv::MakeDeferredShader(deferredHandler->mShader.get(), callback,
                                                       deferredHandler->mBuilder.children());
    }
};

sk_sp<SkShader> MakeGainmapShader(const sk_sp<const SkImage>& image,
                                  const sk_sp<const SkImage>& gainmapImage,
                                  const SkGainmapInfo& gainmapInfo, SkTileMode tileModeX,
                                  SkTileMode tileModeY, const SkSamplingOptions& sampling) {
    return DeferredGainmapShader::Make(image, gainmapImage, gainmapInfo, tileModeX, tileModeY,
                                       sampling);
}

#else  // __ANDROID__

sk_sp<SkShader> MakeGainmapShader(const sk_sp<const SkImage>& image,
                                  const sk_sp<const SkImage>& gainmapImage,
                                  const SkGainmapInfo& gainmapInfo, SkTileMode tileModeX,
                                  SkTileMode tileModeY, const SkSamplingOptions& sampling) {
        return nullptr;
}

#endif  // __ANDROID__

}  // namespace android::uirenderer
 No newline at end of file
+5 −0
Original line number Diff line number Diff line
@@ -30,4 +30,9 @@ void DrawGainmapBitmap(SkCanvas* c, const sk_sp<const SkImage>& image, const SkR
                       SkCanvas::SrcRectConstraint constraint,
                       const sk_sp<const SkImage>& gainmapImage, const SkGainmapInfo& gainmapInfo);

sk_sp<SkShader> MakeGainmapShader(const sk_sp<const SkImage>& image,
                                  const sk_sp<const SkImage>& gainmapImage,
                                  const SkGainmapInfo& gainmapInfo, SkTileMode tileModeX,
                                  SkTileMode tileModeY, const SkSamplingOptions& sampling);

}  // namespace android::uirenderer
+18 −3
Original line number Diff line number Diff line
#undef LOG_TAG
#define LOG_TAG "ShaderJNI"

#include <vector>

#include "Gainmap.h"
#include "GraphicsJNI.h"
#include "SkBitmap.h"
#include "SkBlendMode.h"
@@ -17,10 +20,9 @@
#include "SkShader.h"
#include "SkString.h"
#include "SkTileMode.h"
#include "effects/GainmapRenderer.h"
#include "include/effects/SkRuntimeEffect.h"

#include <vector>

using namespace android::uirenderer;

/**
@@ -74,7 +76,20 @@ static jlong createBitmapShaderHelper(JNIEnv* env, jobject o, jlong matrixPtr, j
    if (bitmapHandle) {
        // Only pass a valid SkBitmap object to the constructor if the Bitmap exists. Otherwise,
        // we'll pass an empty SkBitmap to avoid crashing/excepting for compatibility.
        image = android::bitmap::toBitmap(bitmapHandle).makeImage();
        auto& bitmap = android::bitmap::toBitmap(bitmapHandle);
        image = bitmap.makeImage();

        if (!isDirectSampled && bitmap.hasGainmap()) {
            sk_sp<SkShader> gainmapShader = MakeGainmapShader(
                    image, bitmap.gainmap()->bitmap->makeImage(), bitmap.gainmap()->info,
                    (SkTileMode)tileModeX, (SkTileMode)tileModeY, sampling);
            if (gainmapShader) {
                if (matrix) {
                    gainmapShader = gainmapShader->makeWithLocalMatrix(*matrix);
                }
                return reinterpret_cast<jlong>(gainmapShader.release());
            }
        }
    }

    if (!image.get()) {