aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorGravatar Brian Osman <brianosman@google.com>2017-03-14 12:07:12 -0400
committerGravatar Skia Commit-Bot <skia-commit-bot@chromium.org>2017-03-15 12:52:03 +0000
commit63954c9944a5eed4527e0ed368f4e501faf2c625 (patch)
treee37b8575d61b68931c365e3e56c5122e95fecacb
parente8b508556cdd1b18b7461301b35e8a20d3fe35e2 (diff)
GPU version of onMakeColorSpace
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>
-rw-r--r--gn/gpu.gni2
-rw-r--r--src/gpu/GrProcessor.cpp2
-rw-r--r--src/gpu/effects/GrNonlinearColorSpaceXformEffect.cpp244
-rw-r--r--src/gpu/effects/GrNonlinearColorSpaceXformEffect.h72
-rw-r--r--src/image/SkImage_Gpu.cpp33
-rw-r--r--src/image/SkImage_Gpu.h2
6 files changed, 354 insertions, 1 deletions
diff --git a/gn/gpu.gni b/gn/gpu.gni
index 5ac72678b3..6dc08a4b11 100644
--- a/gn/gpu.gni
+++ b/gn/gpu.gni
@@ -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",
diff --git a/src/gpu/GrProcessor.cpp b/src/gpu/GrProcessor.cpp
index 2365c21229..a2ed9c06c1 100644
--- a/src/gpu/GrProcessor.cpp
+++ b/src/gpu/GrProcessor.cpp
@@ -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
index 0000000000..d4c0a392d6
--- /dev/null
+++ b/src/gpu/effects/GrNonlinearColorSpaceXformEffect.cpp
@@ -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
index 0000000000..36f778398e
--- /dev/null
+++ b/src/gpu/effects/GrNonlinearColorSpaceXformEffect.h
@@ -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
diff --git a/src/image/SkImage_Gpu.cpp b/src/image/SkImage_Gpu.cpp
index ffa7d9ed9f..ec8158fae2 100644
--- a/src/image/SkImage_Gpu.cpp
+++ b/src/image/SkImage_Gpu.cpp
@@ -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);
+
+}
diff --git a/src/image/SkImage_Gpu.h b/src/image/SkImage_Gpu.h
index c70c18ac78..b3a165d079 100644
--- a/src/image/SkImage_Gpu.h
+++ b/src/image/SkImage_Gpu.h
@@ -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;