GPU version of onMakeColorSpace
authorBrian Osman <brianosman@google.com>
Tue, 14 Mar 2017 16:07:12 +0000 (12:07 -0400)
committerSkia Commit-Bot <skia-commit-bot@chromium.org>
Wed, 15 Mar 2017 12:52:03 +0000 (12:52 +0000)
New fragment processor that implements end-to-end
color space conversion, with nonlinear blending.

BUG=skia:6242

Change-Id: Ied86170fc28537a2bc209d57530d3ded48b467a9
Reviewed-on: https://skia-review.googlesource.com/9543
Reviewed-by: Brian Salomon <bsalomon@google.com>
Reviewed-by: Matt Sarett <msarett@google.com>
Reviewed-by: Robert Phillips <robertphillips@google.com>
Commit-Queue: Brian Osman <brianosman@google.com>

gn/gpu.gni
src/gpu/GrProcessor.cpp
src/gpu/effects/GrNonlinearColorSpaceXformEffect.cpp [new file with mode: 0644]
src/gpu/effects/GrNonlinearColorSpaceXformEffect.h [new file with mode: 0644]
src/image/SkImage_Gpu.cpp
src/image/SkImage_Gpu.h

index 5ac72678b362fd49df77483955dd0dfc19da405b..6dc08a4b11407f657417c0bd8229a131d38f16a2 100644 (file)
@@ -318,6 +318,8 @@ skia_gpu_sources = [
   "$_src/gpu/effects/GrGaussianConvolutionFragmentProcessor.h",
   "$_src/gpu/effects/GrMatrixConvolutionEffect.cpp",
   "$_src/gpu/effects/GrMatrixConvolutionEffect.h",
+  "$_src/gpu/effects/GrNonlinearColorSpaceXformEffect.cpp",
+  "$_src/gpu/effects/GrNonlinearColorSpaceXformEffect.h",
   "$_src/gpu/effects/GrOvalEffect.cpp",
   "$_src/gpu/effects/GrOvalEffect.h",
   "$_src/gpu/effects/GrPorterDuffXferProcessor.cpp",
index 2365c21229196edaa1244954d1649afa6d561020..a2ed9c06c1cd2b6433c5ac4293c1ab7bf88fa733 100644 (file)
@@ -48,7 +48,7 @@ SkTArray<GrXPFactoryTestFactory*, true>* GrXPFactoryTestFactory::GetFactories()
  * we verify the count is as expected.  If a new factory is added, then these numbers must be
  * manually adjusted.
  */
-static const int kFPFactoryCount = 40;
+static const int kFPFactoryCount = 41;
 static const int kGPFactoryCount = 14;
 static const int kXPFactoryCount = 4;
 
diff --git a/src/gpu/effects/GrNonlinearColorSpaceXformEffect.cpp b/src/gpu/effects/GrNonlinearColorSpaceXformEffect.cpp
new file mode 100644 (file)
index 0000000..d4c0a39
--- /dev/null
@@ -0,0 +1,244 @@
+/*
+ * Copyright 2017 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "GrNonlinearColorSpaceXformEffect.h"
+
+#include "GrProcessor.h"
+#include "glsl/GrGLSLFragmentProcessor.h"
+#include "glsl/GrGLSLFragmentShaderBuilder.h"
+
+#include "SkColorSpace_Base.h"
+
+class GrGLNonlinearColorSpaceXformEffect : public GrGLSLFragmentProcessor {
+public:
+    void emitCode(EmitArgs& args) override {
+        const GrNonlinearColorSpaceXformEffect& csxe =
+                args.fFp.cast<GrNonlinearColorSpaceXformEffect>();
+        GrGLSLFPFragmentBuilder* fragBuilder = args.fFragBuilder;
+        GrGLSLUniformHandler* uniformHandler = args.fUniformHandler;
+
+        const char* srcCoeffsName = nullptr;
+        if (SkToBool(csxe.ops() & GrNonlinearColorSpaceXformEffect::kSrcTransfer_Op)) {
+            fSrcTransferFnUni = uniformHandler->addUniformArray(
+                    kFragment_GrShaderFlag, kFloat_GrSLType, kDefault_GrSLPrecision,
+                    "SrcTransferFn", GrNonlinearColorSpaceXformEffect::kNumTransferFnCoeffs,
+                    &srcCoeffsName);
+        }
+
+        const char* dstCoeffsName = nullptr;
+        if (SkToBool(csxe.ops() & GrNonlinearColorSpaceXformEffect::kDstTransfer_Op)) {
+            fDstTransferFnUni = uniformHandler->addUniformArray(
+                    kFragment_GrShaderFlag, kFloat_GrSLType, kDefault_GrSLPrecision,
+                    "DstTransferFn", GrNonlinearColorSpaceXformEffect::kNumTransferFnCoeffs,
+                    &dstCoeffsName);
+        }
+
+        const char* gamutXformName = nullptr;
+        if (SkToBool(csxe.ops() & GrNonlinearColorSpaceXformEffect::kGamutXform_Op)) {
+            fGamutXformUni = uniformHandler->addUniform(kFragment_GrShaderFlag, kMat44f_GrSLType,
+                                                        kDefault_GrSLPrecision, "GamutXform",
+                                                        &gamutXformName);
+        }
+
+        // Helper function to apply a transfer function to a single value
+        SkString tfFuncNameString;
+        static const GrShaderVar gTransferFnFuncArgs[] = {
+            GrShaderVar("x", kFloat_GrSLType),
+            GrShaderVar("coeffs", kFloat_GrSLType,
+                        GrNonlinearColorSpaceXformEffect::kNumTransferFnCoeffs),
+        };
+        SkString transferFnBody;
+        // Temporaries to make evaluation line readable
+        transferFnBody.printf("float A = coeffs[0];");
+        transferFnBody.append("float B = coeffs[1];");
+        transferFnBody.append("float C = coeffs[2];");
+        transferFnBody.append("float D = coeffs[3];");
+        transferFnBody.append("float E = coeffs[4];");
+        transferFnBody.append("float F = coeffs[5];");
+        transferFnBody.append("float G = coeffs[6];");
+        transferFnBody.appendf("return (x < D) ? (C * x) + F : pow(A * x + B, G) + E;");
+        fragBuilder->emitFunction(kFloat_GrSLType, "transfer_fn",
+                                  SK_ARRAY_COUNT(gTransferFnFuncArgs), gTransferFnFuncArgs,
+                                  transferFnBody.c_str(), &tfFuncNameString);
+        const char* tfFuncName = tfFuncNameString.c_str();
+
+        if (nullptr == args.fInputColor) {
+            args.fInputColor = "vec4(1)";
+        }
+        fragBuilder->codeAppendf("vec4 color = %s;", args.fInputColor);
+
+        // 1: Un-premultiply the input color (if necessary)
+        fragBuilder->codeAppendf("float nonZeroAlpha = max(color.a, 0.00001);");
+        fragBuilder->codeAppendf("color = vec4(color.rgb / nonZeroAlpha, nonZeroAlpha);");
+
+        // 2: Apply src transfer function (to get to linear RGB)
+        if (srcCoeffsName) {
+            fragBuilder->codeAppendf("color.r = %s(color.r, %s);", tfFuncName, srcCoeffsName);
+            fragBuilder->codeAppendf("color.g = %s(color.g, %s);", tfFuncName, srcCoeffsName);
+            fragBuilder->codeAppendf("color.b = %s(color.b, %s);", tfFuncName, srcCoeffsName);
+        }
+
+        // 3: Apply gamut matrix
+        if (gamutXformName) {
+            // Color is unpremultiplied at this point, so clamp to [0, 1]
+            fragBuilder->codeAppendf(
+                "color.rgb = clamp((%s * vec4(color.rgb, 1.0)).rgb, 0.0, 1.0);", gamutXformName);
+        }
+
+        // 4: Apply dst transfer fn
+        if (dstCoeffsName) {
+            fragBuilder->codeAppendf("color.r = %s(color.r, %s);", tfFuncName, dstCoeffsName);
+            fragBuilder->codeAppendf("color.g = %s(color.g, %s);", tfFuncName, dstCoeffsName);
+            fragBuilder->codeAppendf("color.b = %s(color.b, %s);", tfFuncName, dstCoeffsName);
+        }
+
+        // 5: Premultiply again
+        fragBuilder->codeAppendf("%s = vec4(color.rgb * color.a, color.a);", args.fOutputColor);
+    }
+
+    static inline void GenKey(const GrProcessor& processor, const GrShaderCaps&,
+                              GrProcessorKeyBuilder* b) {
+        const GrNonlinearColorSpaceXformEffect& csxe =
+                processor.cast<GrNonlinearColorSpaceXformEffect>();
+        b->add32(csxe.ops());
+    }
+
+protected:
+    void onSetData(const GrGLSLProgramDataManager& pdman, const GrProcessor& processor) override {
+        const GrNonlinearColorSpaceXformEffect& csxe =
+                processor.cast<GrNonlinearColorSpaceXformEffect>();
+        if (SkToBool(csxe.ops() & GrNonlinearColorSpaceXformEffect::kSrcTransfer_Op)) {
+            pdman.set1fv(fSrcTransferFnUni, GrNonlinearColorSpaceXformEffect::kNumTransferFnCoeffs,
+                         csxe.srcTransferFnCoeffs());
+        }
+        if (SkToBool(csxe.ops() & GrNonlinearColorSpaceXformEffect::kDstTransfer_Op)) {
+            pdman.set1fv(fDstTransferFnUni, GrNonlinearColorSpaceXformEffect::kNumTransferFnCoeffs,
+                         csxe.dstTransferFnCoeffs());
+        }
+        if (SkToBool(csxe.ops() & GrNonlinearColorSpaceXformEffect::kGamutXform_Op)) {
+            pdman.setSkMatrix44(fGamutXformUni, csxe.gamutXform());
+        }
+    }
+
+private:
+    GrGLSLProgramDataManager::UniformHandle fSrcTransferFnUni;
+    GrGLSLProgramDataManager::UniformHandle fDstTransferFnUni;
+    GrGLSLProgramDataManager::UniformHandle fGamutXformUni;
+
+    typedef GrGLSLFragmentProcessor INHERITED;
+};
+
+///////////////////////////////////////////////////////////////////////////////
+
+GrNonlinearColorSpaceXformEffect::GrNonlinearColorSpaceXformEffect(
+    uint32_t ops, const SkColorSpaceTransferFn& srcTransferFn,
+    const SkColorSpaceTransferFn& dstTransferFn, const SkMatrix44& gamutXform)
+        : INHERITED(kPreservesOpaqueInput_OptimizationFlag)
+        , fGamutXform(gamutXform)
+        , fOps(ops) {
+    this->initClassID<GrNonlinearColorSpaceXformEffect>();
+
+    fSrcTransferFnCoeffs[0] = srcTransferFn.fA;
+    fSrcTransferFnCoeffs[1] = srcTransferFn.fB;
+    fSrcTransferFnCoeffs[2] = srcTransferFn.fC;
+    fSrcTransferFnCoeffs[3] = srcTransferFn.fD;
+    fSrcTransferFnCoeffs[4] = srcTransferFn.fE;
+    fSrcTransferFnCoeffs[5] = srcTransferFn.fF;
+    fSrcTransferFnCoeffs[6] = srcTransferFn.fG;
+
+    fDstTransferFnCoeffs[0] = dstTransferFn.fA;
+    fDstTransferFnCoeffs[1] = dstTransferFn.fB;
+    fDstTransferFnCoeffs[2] = dstTransferFn.fC;
+    fDstTransferFnCoeffs[3] = dstTransferFn.fD;
+    fDstTransferFnCoeffs[4] = dstTransferFn.fE;
+    fDstTransferFnCoeffs[5] = dstTransferFn.fF;
+    fDstTransferFnCoeffs[6] = dstTransferFn.fG;
+}
+
+bool GrNonlinearColorSpaceXformEffect::onIsEqual(const GrFragmentProcessor& s) const {
+    const GrNonlinearColorSpaceXformEffect& other = s.cast<GrNonlinearColorSpaceXformEffect>();
+    if (other.fOps != fOps) {
+        return false;
+    }
+    if (SkToBool(fOps & kSrcTransfer_Op) &&
+        memcmp(&other.fSrcTransferFnCoeffs, &fSrcTransferFnCoeffs, sizeof(fSrcTransferFnCoeffs))) {
+        return false;
+    }
+    if (SkToBool(fOps & kDstTransfer_Op) &&
+        memcmp(&other.fDstTransferFnCoeffs, &fDstTransferFnCoeffs, sizeof(fDstTransferFnCoeffs))) {
+        return false;
+    }
+    if (SkToBool(fOps & kGamutXform_Op) && other.fGamutXform != fGamutXform) {
+        return false;
+    }
+    return true;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+GR_DEFINE_FRAGMENT_PROCESSOR_TEST(GrNonlinearColorSpaceXformEffect);
+
+#if GR_TEST_UTILS
+sk_sp<GrFragmentProcessor> GrNonlinearColorSpaceXformEffect::TestCreate(GrProcessorTestData* d) {
+    // TODO: Generate a random variety of color spaces for this effect (it can handle wacky
+    // transfer functions, etc...)
+    sk_sp<SkColorSpace> srcSpace = SkColorSpace::MakeSRGBLinear();
+    sk_sp<SkColorSpace> dstSpace = SkColorSpace::MakeSRGB();
+    return GrNonlinearColorSpaceXformEffect::Make(srcSpace.get(), dstSpace.get());
+}
+#endif
+
+///////////////////////////////////////////////////////////////////////////////
+
+void GrNonlinearColorSpaceXformEffect::onGetGLSLProcessorKey(const GrShaderCaps& caps,
+                                                             GrProcessorKeyBuilder* b) const {
+    GrGLNonlinearColorSpaceXformEffect::GenKey(*this, caps, b);
+}
+
+GrGLSLFragmentProcessor* GrNonlinearColorSpaceXformEffect::onCreateGLSLInstance() const {
+    return new GrGLNonlinearColorSpaceXformEffect();
+}
+
+sk_sp<GrFragmentProcessor> GrNonlinearColorSpaceXformEffect::Make(const SkColorSpace* src,
+                                                                  const SkColorSpace* dst) {
+    if (!src || !dst || SkColorSpace::Equals(src, dst)) {
+        // No conversion possible (or necessary)
+        return nullptr;
+    }
+
+    uint32_t ops = 0;
+
+    // We rely on GrColorSpaceXform to build the gamut xform matrix for us (to get caching)
+    auto gamutXform = GrColorSpaceXform::Make(src, dst);
+    SkMatrix44 srcToDstMtx(SkMatrix44::kUninitialized_Constructor);
+    if (gamutXform) {
+        ops |= kGamutXform_Op;
+        srcToDstMtx = gamutXform->srcToDst();
+    }
+
+    SkColorSpaceTransferFn srcTransferFn;
+    if (!src->gammaIsLinear()) {
+        if (src->isNumericalTransferFn(&srcTransferFn)) {
+            ops |= kSrcTransfer_Op;
+        } else {
+            return nullptr;
+        }
+    }
+
+    SkColorSpaceTransferFn dstTransferFn;
+    if (!dst->gammaIsLinear()) {
+        if (dst->isNumericalTransferFn(&dstTransferFn)) {
+            dstTransferFn = dstTransferFn.invert();
+            ops |= kDstTransfer_Op;
+        } else {
+            return nullptr;
+        }
+    }
+
+    return sk_sp<GrFragmentProcessor>(new GrNonlinearColorSpaceXformEffect(
+            ops, srcTransferFn, dstTransferFn, srcToDstMtx));
+}
diff --git a/src/gpu/effects/GrNonlinearColorSpaceXformEffect.h b/src/gpu/effects/GrNonlinearColorSpaceXformEffect.h
new file mode 100644 (file)
index 0000000..36f7783
--- /dev/null
@@ -0,0 +1,72 @@
+/*
+ * Copyright 2017 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef GrNonlinearColorSpaceXformEffect_DEFINED
+#define GrNonlinearColorSpaceXformEffect_DEFINED
+
+#include "GrFragmentProcessor.h"
+#include "SkColorSpace.h"
+#include "SkMatrix44.h"
+
+/**
+ * The output of this effect is the input, transformed into a different color space.
+ * This effect is used for nonlinear blending color space support - it does not assume HW sRGB
+ * capabilities, and performs both the source and destination transfer functions numerically in
+ * the shader. Any parametric transfer function is supported. Because of the nonlinear blending,
+ * premultiplication is also nonlinear - source pixels are unpremultiplied before the source
+ * transfer function, and then premultiplied after the destination transfer function.
+ */
+class GrNonlinearColorSpaceXformEffect : public GrFragmentProcessor {
+public:
+    /**
+     * The conversion effect is only well defined with a valid source and destination color space.
+     * This will return nullptr if either space is nullptr, if both spaces are equal, or if either
+     * space has a non-parametric transfer funcion (e.g. lookup table or A2B).
+     */
+    static sk_sp<GrFragmentProcessor> Make(const SkColorSpace* src, const SkColorSpace* dst);
+
+    const char* name() const override { return "NonlinearColorSpaceXform"; }
+
+    static const int kNumTransferFnCoeffs = 7;
+
+    /**
+     * Flags that specify which operations are performed for one particular conversion.
+     * Some color space pairs may not need all operations, if one or both transfer functions
+     * is linear, or if the gamuts are the same.
+     */
+    enum Ops {
+        kSrcTransfer_Op = 0x1,
+        kGamutXform_Op  = 0x2,
+        kDstTransfer_Op = 0x4,
+    };
+
+    uint32_t ops() const { return fOps; }
+    const float* srcTransferFnCoeffs() const { return fSrcTransferFnCoeffs; }
+    const float* dstTransferFnCoeffs() const { return fDstTransferFnCoeffs; }
+    const SkMatrix44& gamutXform() const { return fGamutXform; }
+
+private:
+    GrNonlinearColorSpaceXformEffect(uint32_t ops,
+                                     const SkColorSpaceTransferFn& srcTransferFn,
+                                     const SkColorSpaceTransferFn& dstTransferFn,
+                                     const SkMatrix44& gamutXform);
+
+    GrGLSLFragmentProcessor* onCreateGLSLInstance() const override;
+    void onGetGLSLProcessorKey(const GrShaderCaps&, GrProcessorKeyBuilder*) const override;
+    bool onIsEqual(const GrFragmentProcessor&) const override;
+
+    float fSrcTransferFnCoeffs[kNumTransferFnCoeffs];
+    float fDstTransferFnCoeffs[kNumTransferFnCoeffs];
+    SkMatrix44 fGamutXform;
+    uint32_t fOps;
+
+    GR_DECLARE_FRAGMENT_PROCESSOR_TEST;
+
+    typedef GrFragmentProcessor INHERITED;
+};
+
+#endif
index ffa7d9ed9fab01f0f66b0b92480d6b932a5693b0..ec8158fae2cb5a8ead60fd7f47d4dfe22b5e0374 100644 (file)
@@ -23,6 +23,7 @@
 #include "GrTexturePriv.h"
 #include "GrTextureProxy.h"
 #include "GrTextureToYUVPlanes.h"
+#include "effects/GrNonlinearColorSpaceXformEffect.h"
 #include "effects/GrYUVEffect.h"
 #include "SkCanvas.h"
 #include "SkCrossContextImageData.h"
@@ -846,3 +847,35 @@ sk_sp<SkImage> SkImage::MakeTextureFromMipMap(GrContext* ctx, const SkImageInfo&
                                    info.alphaType(), std::move(texture),
                                    sk_ref_sp(info.colorSpace()), budgeted);
 }
+
+sk_sp<SkImage> SkImage_Gpu::onMakeColorSpace(sk_sp<SkColorSpace> colorSpace) const {
+    auto xform = GrNonlinearColorSpaceXformEffect::Make(fColorSpace.get(), colorSpace.get());
+    if (!xform) {
+        return sk_ref_sp(const_cast<SkImage_Gpu*>(this));
+    }
+
+    sk_sp<GrRenderTargetContext> renderTargetContext(fContext->makeRenderTargetContext(
+        SkBackingFit::kExact, this->width(), this->height(), kRGBA_8888_GrPixelConfig, nullptr));
+    if (!renderTargetContext) {
+        return nullptr;
+    }
+
+    GrPaint paint;
+    paint.setPorterDuffXPFactory(SkBlendMode::kSrc);
+    paint.addColorTextureProcessor(fContext, fProxy, nullptr, SkMatrix::I());
+    paint.addColorFragmentProcessor(std::move(xform));
+
+    const SkRect rect = SkRect::MakeIWH(this->width(), this->height());
+
+    renderTargetContext->drawRect(GrNoClip(), std::move(paint), GrAA::kNo, SkMatrix::I(), rect);
+
+    if (!renderTargetContext->accessRenderTarget()) {
+        return nullptr;
+    }
+
+    // MDB: this call is okay bc we know 'renderTargetContext' was exact
+    return sk_make_sp<SkImage_Gpu>(fContext, kNeedNewImageUniqueID,
+                                   fAlphaType, renderTargetContext->asTextureProxyRef(),
+                                   std::move(colorSpace), fBudgeted);
+
+}
index c70c18ac78cf72bf30a19d1e387f9b882bc70966..b3a165d079b022d3afba65a9ff2c56f34fc47c9c 100644 (file)
@@ -65,6 +65,8 @@ public:
     GrContext* context() { return fContext; }
     sk_sp<SkColorSpace> refColorSpace() { return fColorSpace; }
 
+    sk_sp<SkImage> onMakeColorSpace(sk_sp<SkColorSpace>) const override;
+
 private:
     GrContext*             fContext;
     sk_sp<GrTextureProxy>  fProxy;