diff options
author | 2017-03-14 12:07:12 -0400 | |
---|---|---|
committer | 2017-03-15 12:52:03 +0000 | |
commit | 63954c9944a5eed4527e0ed368f4e501faf2c625 (patch) | |
tree | e37b8575d61b68931c365e3e56c5122e95fecacb | |
parent | e8b508556cdd1b18b7461301b35e8a20d3fe35e2 (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.gni | 2 | ||||
-rw-r--r-- | src/gpu/GrProcessor.cpp | 2 | ||||
-rw-r--r-- | src/gpu/effects/GrNonlinearColorSpaceXformEffect.cpp | 244 | ||||
-rw-r--r-- | src/gpu/effects/GrNonlinearColorSpaceXformEffect.h | 72 | ||||
-rw-r--r-- | src/image/SkImage_Gpu.cpp | 33 | ||||
-rw-r--r-- | src/image/SkImage_Gpu.h | 2 |
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; |