diff options
22 files changed, 314 insertions, 87 deletions
diff --git a/include/core/SkColorSpace.h b/include/core/SkColorSpace.h index 5b2d86b175..962253243d 100644 --- a/include/core/SkColorSpace.h +++ b/include/core/SkColorSpace.h @@ -54,6 +54,20 @@ struct SK_API SkColorSpaceTransferFn { * this one. */ SkColorSpaceTransferFn invert() const; + + /** + * Transform a single float by this transfer function. + * For negative inputs, returns sign(x) * f(abs(x)). + */ + float operator()(float x) { + SkScalar s = SkScalarSignAsScalar(x); + x = sk_float_abs(x); + if (x >= fD) { + return s * (powf(fA * x + fB, fG) + fE); + } else { + return s * (fC * x + fF); + } + } }; class SK_API SkColorSpace : public SkRefCnt { diff --git a/src/core/SkImageFilter.cpp b/src/core/SkImageFilter.cpp index cdea2d1ccc..bf34ae3180 100644 --- a/src/core/SkImageFilter.cpp +++ b/src/core/SkImageFilter.cpp @@ -368,8 +368,11 @@ sk_sp<SkSpecialImage> SkImageFilter::ImageToColorSpace(SkSpecialImage* src, // object. If that produces something, then both are tagged, and the source is in a different // gamut than the dest. There is some overhead to making the xform, but those are cached, and // if we get one back, that means we're about to use it during the conversion anyway. - sk_sp<GrColorSpaceXform> colorSpaceXform = GrColorSpaceXform::Make(src->getColorSpace(), - outProps.colorSpace()); + // + // TODO: Fix this check, to handle wider support of transfer functions, config mismatch, etc. + // For now, continue to just check if gamut is different, which may not be sufficient. + auto colorSpaceXform = GrColorSpaceXform::MakeGamutXform(src->getColorSpace(), + outProps.colorSpace()); if (!colorSpaceXform) { // No xform needed, just return the original image diff --git a/src/effects/SkAlphaThresholdFilter.cpp b/src/effects/SkAlphaThresholdFilter.cpp index fa77c85bf9..e7dfa5f252 100644 --- a/src/effects/SkAlphaThresholdFilter.cpp +++ b/src/effects/SkAlphaThresholdFilter.cpp @@ -172,9 +172,10 @@ sk_sp<SkSpecialImage> SkAlphaThresholdFilterImpl::onFilterImage(SkSpecialImage* } const OutputProperties& outProps = ctx.outputProperties(); + GrPixelConfig inputConfig = inputProxy->config(); auto textureFP = GrSimpleTextureEffect::Make(std::move(inputProxy), SkMatrix::I()); textureFP = GrColorSpaceXformEffect::Make(std::move(textureFP), input->getColorSpace(), - outProps.colorSpace()); + inputConfig, outProps.colorSpace()); if (!textureFP) { return nullptr; } diff --git a/src/effects/SkArithmeticImageFilter.cpp b/src/effects/SkArithmeticImageFilter.cpp index 0ef40a23d9..a4d6ffe7dd 100644 --- a/src/effects/SkArithmeticImageFilter.cpp +++ b/src/effects/SkArithmeticImageFilter.cpp @@ -306,12 +306,13 @@ sk_sp<SkSpecialImage> ArithmeticImageFilterImpl::filterImageGPU( if (backgroundProxy) { SkMatrix backgroundMatrix = SkMatrix::MakeTrans(-SkIntToScalar(backgroundOffset.fX), -SkIntToScalar(backgroundOffset.fY)); + GrPixelConfig bgConfig = backgroundProxy->config(); bgFP = GrTextureDomainEffect::Make( std::move(backgroundProxy), backgroundMatrix, GrTextureDomain::MakeTexelDomain(background->subset()), GrTextureDomain::kDecal_Mode, GrSamplerState::Filter::kNearest); bgFP = GrColorSpaceXformEffect::Make(std::move(bgFP), background->getColorSpace(), - outputProperties.colorSpace()); + bgConfig, outputProperties.colorSpace()); } else { bgFP = GrConstColorProcessor::Make(GrColor4f::TransparentBlack(), GrConstColorProcessor::kIgnore_InputMode); @@ -320,12 +321,13 @@ sk_sp<SkSpecialImage> ArithmeticImageFilterImpl::filterImageGPU( if (foregroundProxy) { SkMatrix foregroundMatrix = SkMatrix::MakeTrans(-SkIntToScalar(foregroundOffset.fX), -SkIntToScalar(foregroundOffset.fY)); + GrPixelConfig fgConfig = foregroundProxy->config(); auto foregroundFP = GrTextureDomainEffect::Make( std::move(foregroundProxy), foregroundMatrix, GrTextureDomain::MakeTexelDomain(foreground->subset()), GrTextureDomain::kDecal_Mode, GrSamplerState::Filter::kNearest); foregroundFP = GrColorSpaceXformEffect::Make(std::move(foregroundFP), - foreground->getColorSpace(), + foreground->getColorSpace(), fgConfig, outputProperties.colorSpace()); paint.addColorFragmentProcessor(std::move(foregroundFP)); diff --git a/src/effects/SkDisplacementMapEffect.cpp b/src/effects/SkDisplacementMapEffect.cpp index 1cdd0ce231..09aaf7cc4b 100644 --- a/src/effects/SkDisplacementMapEffect.cpp +++ b/src/effects/SkDisplacementMapEffect.cpp @@ -286,6 +286,7 @@ sk_sp<SkSpecialImage> SkDisplacementMapEffect::onFilterImage(SkSpecialImage* sou SkIntToScalar(colorOffset.fY - displOffset.fY)); SkColorSpace* colorSpace = ctx.outputProperties().colorSpace(); + GrPixelConfig colorConfig = colorProxy->config(); std::unique_ptr<GrFragmentProcessor> fp = GrDisplacementMapEffect::Make(fXChannelSelector, fYChannelSelector, @@ -294,7 +295,8 @@ sk_sp<SkSpecialImage> SkDisplacementMapEffect::onFilterImage(SkSpecialImage* sou offsetMatrix, std::move(colorProxy), SkISize::Make(color->width(), color->height())); - fp = GrColorSpaceXformEffect::Make(std::move(fp), color->getColorSpace(), colorSpace); + fp = GrColorSpaceXformEffect::Make(std::move(fp), color->getColorSpace(), colorConfig, + colorSpace); GrPaint paint; paint.addColorFragmentProcessor(std::move(fp)); diff --git a/src/effects/SkMagnifierImageFilter.cpp b/src/effects/SkMagnifierImageFilter.cpp index bb0e7ed4fb..dd5714ba75 100644 --- a/src/effects/SkMagnifierImageFilter.cpp +++ b/src/effects/SkMagnifierImageFilter.cpp @@ -355,6 +355,7 @@ sk_sp<SkSpecialImage> SkMagnifierImageFilter::onFilterImage(SkSpecialImage* sour offset->fY = bounds.top(); bounds.offset(-inputOffset); + GrPixelConfig inputConfig = inputProxy->config(); auto fp = GrMagnifierEffect::Make(std::move(inputProxy), bounds, fSrcRect, @@ -363,7 +364,7 @@ sk_sp<SkSpecialImage> SkMagnifierImageFilter::onFilterImage(SkSpecialImage* sour bounds.width() * invInset, bounds.height() * invInset); fp = GrColorSpaceXformEffect::Make(std::move(fp), input->getColorSpace(), - ctx.outputProperties().colorSpace()); + inputConfig, ctx.outputProperties().colorSpace()); if (!fp) { return nullptr; } diff --git a/src/effects/SkXfermodeImageFilter.cpp b/src/effects/SkXfermodeImageFilter.cpp index 1fb4cdef7b..111dc61a56 100644 --- a/src/effects/SkXfermodeImageFilter.cpp +++ b/src/effects/SkXfermodeImageFilter.cpp @@ -295,12 +295,13 @@ sk_sp<SkSpecialImage> SkXfermodeImageFilter_Base::filterImageGPU( if (backgroundProxy) { SkMatrix bgMatrix = SkMatrix::MakeTrans(-SkIntToScalar(backgroundOffset.fX), -SkIntToScalar(backgroundOffset.fY)); + GrPixelConfig bgConfig = backgroundProxy->config(); bgFP = GrTextureDomainEffect::Make(std::move(backgroundProxy), bgMatrix, GrTextureDomain::MakeTexelDomain(background->subset()), GrTextureDomain::kDecal_Mode, GrSamplerState::Filter::kNearest); bgFP = GrColorSpaceXformEffect::Make(std::move(bgFP), background->getColorSpace(), - outputProperties.colorSpace()); + bgConfig, outputProperties.colorSpace()); } else { bgFP = GrConstColorProcessor::Make(GrColor4f::TransparentBlack(), GrConstColorProcessor::kIgnore_InputMode); @@ -309,12 +310,13 @@ sk_sp<SkSpecialImage> SkXfermodeImageFilter_Base::filterImageGPU( if (foregroundProxy) { SkMatrix fgMatrix = SkMatrix::MakeTrans(-SkIntToScalar(foregroundOffset.fX), -SkIntToScalar(foregroundOffset.fY)); + GrPixelConfig fgConfig = foregroundProxy->config(); auto foregroundFP = GrTextureDomainEffect::Make( std::move(foregroundProxy), fgMatrix, GrTextureDomain::MakeTexelDomain(foreground->subset()), GrTextureDomain::kDecal_Mode, GrSamplerState::Filter::kNearest); foregroundFP = GrColorSpaceXformEffect::Make(std::move(foregroundFP), - foreground->getColorSpace(), + foreground->getColorSpace(), fgConfig, outputProperties.colorSpace()); paint.addColorFragmentProcessor(std::move(foregroundFP)); diff --git a/src/gpu/GrColorSpaceInfo.cpp b/src/gpu/GrColorSpaceInfo.cpp index cd0dc1634d..1a49e8622d 100644 --- a/src/gpu/GrColorSpaceInfo.cpp +++ b/src/gpu/GrColorSpaceInfo.cpp @@ -17,7 +17,8 @@ GrColorSpaceXform* GrColorSpaceInfo::colorSpaceXformFromSRGB() const { if (!fInitializedColorSpaceXformFromSRGB) { // sRGB sources are very common (SkColor, etc...), so we cache that gamut transformation auto srgbColorSpace = SkColorSpace::MakeSRGB(); - fColorXformFromSRGB = GrColorSpaceXform::Make(srgbColorSpace.get(), fColorSpace.get()); + fColorXformFromSRGB = GrColorSpaceXform::MakeGamutXform(srgbColorSpace.get(), + fColorSpace.get()); fInitializedColorSpaceXformFromSRGB = true; } // You can't be color-space aware in legacy mode diff --git a/src/gpu/GrColorSpaceXform.cpp b/src/gpu/GrColorSpaceXform.cpp index e43ccdc14d..f0dc279be3 100644 --- a/src/gpu/GrColorSpaceXform.cpp +++ b/src/gpu/GrColorSpaceXform.cpp @@ -7,6 +7,7 @@ #include "GrColorSpaceXform.h" #include "SkColorSpace.h" +#include "SkColorSpacePriv.h" #include "SkColorSpace_Base.h" #include "SkMatrix44.h" #include "SkSpinlock.h" @@ -56,56 +57,114 @@ private: uint64_t fSequence; }; -GrColorSpaceXform::GrColorSpaceXform(const SkMatrix44& srcToDst) - : fSrcToDst(srcToDst) {} +GrColorSpaceXform::GrColorSpaceXform(const SkColorSpaceTransferFn& srcTransferFn, + const SkMatrix44& gamutXform, uint32_t flags) + : fSrcTransferFn(srcTransferFn), fGamutXform(gamutXform), fFlags(flags) {} static SkSpinlock gColorSpaceXformCacheSpinlock; -sk_sp<GrColorSpaceXform> GrColorSpaceXform::Make(const SkColorSpace* src, const SkColorSpace* dst) { - if (!src || !dst) { - // Invalid +sk_sp<GrColorSpaceXform> GrColorSpaceXform::Make(const SkColorSpace* src, + GrPixelConfig srcConfig, + const SkColorSpace* dst) { + if (!dst) { + // No transformation is performed in legacy mode return nullptr; } - if (src == dst) { - // Quick equality check - no conversion needed in this case + // Treat null sources as sRGB + if (!src) { + if (GrPixelConfigIsFloatingPoint(srcConfig)) { + src = SkColorSpace::MakeSRGBLinear().get(); + } else { + src = SkColorSpace::MakeSRGB().get(); + } + } + + uint32_t flags = 0; + SkColorSpaceTransferFn srcTransferFn; + + // kUnknown_GrPixelConfig is a sentinel that means we don't care about transfer functions, + // just the gamut xform. + if (kUnknown_GrPixelConfig != srcConfig) { + // Determine if src transfer function is needed, based on src config and color space + if (GrPixelConfigIsSRGB(srcConfig)) { + // Source texture is sRGB, will be converted to linear when we sample + if (src->gammaCloseToSRGB()) { + // Hardware linearize does the right thing + } else if (src->gammaIsLinear()) { + // Oops, need to undo the (extra) linearize + flags |= kApplyInverseSRGB_Flag; + } else if (src->isNumericalTransferFn(&srcTransferFn)) { + // Need to undo the (extra) linearize, then apply the correct transfer function + flags |= (kApplyInverseSRGB_Flag | kApplyTransferFn_Flag); + } else { + // We don't (yet) support more complex transfer functions + return nullptr; + } + } else { + // Source texture is some non-sRGB format, we consider it linearly encoded + if (src->gammaIsLinear()) { + // Linear sampling does the right thing + } else if (src->isNumericalTransferFn(&srcTransferFn)) { + // Need to manually apply some transfer function (including sRGB) + flags |= kApplyTransferFn_Flag; + } else { + // We don't (yet) support more complex transfer functions + return nullptr; + } + } + } + if (src == dst && (0 == flags)) { + // Quick equality check - no conversion (or transfer function) needed in this case return nullptr; } const SkMatrix44* toXYZD50 = as_CSB(src)->toXYZD50(); const SkMatrix44* fromXYZD50 = as_CSB(dst)->fromXYZD50(); if (!toXYZD50 || !fromXYZD50) { - // unsupported colour spaces -- cannot specify gamut as a matrix + // Unsupported colour spaces -- cannot specify gamut as a matrix return nullptr; } + // Determine if a gamut xform is needed uint32_t srcHash = as_CSB(src)->toXYZD50Hash(); uint32_t dstHash = as_CSB(dst)->toXYZD50Hash(); - if (srcHash == dstHash) { - // Identical gamut - no conversion needed in this case + if (srcHash != dstHash) { + flags |= kApplyGamutXform_Flag; + } else { SkASSERT(*toXYZD50 == *as_CSB(dst)->toXYZD50() && "Hash collision"); + } + + if (0 == flags) { + // Identical gamut and no transfer function - no conversion needed in this case return nullptr; } - auto deferredResult = [fromXYZD50, toXYZD50]() { + auto makeXform = [srcTransferFn, fromXYZD50, toXYZD50, flags]() { SkMatrix44 srcToDst(SkMatrix44::kUninitialized_Constructor); - srcToDst.setConcat(*fromXYZD50, *toXYZD50); - return sk_make_sp<GrColorSpaceXform>(srcToDst); + if (SkToBool(flags & kApplyGamutXform_Flag)) { + srcToDst.setConcat(*fromXYZD50, *toXYZD50); + } else { + srcToDst.setIdentity(); + } + return sk_make_sp<GrColorSpaceXform>(srcTransferFn, srcToDst, flags); }; - if (gColorSpaceXformCacheSpinlock.tryAcquire()) { + // For now, we only cache pure gamut xforms (no transfer functions) + // TODO: Fold a hash of the transfer function into the cache key + if ((kApplyGamutXform_Flag == flags) && gColorSpaceXformCacheSpinlock.tryAcquire()) { static GrColorSpaceXformCache* gCache; if (nullptr == gCache) { gCache = new GrColorSpaceXformCache(); } uint64_t key = static_cast<uint64_t>(srcHash) << 32 | static_cast<uint64_t>(dstHash); - sk_sp<GrColorSpaceXform> xform = gCache->findOrAdd(key, deferredResult); + sk_sp<GrColorSpaceXform> xform = gCache->findOrAdd(key, makeXform); gColorSpaceXformCacheSpinlock.release(); return xform; } else { - // Rather than wait for the spin lock, just bypass the cache - return deferredResult(); + // If our xform has non-gamut components, or we can't get the spin lock, just build it + return makeXform(); } } @@ -114,16 +173,36 @@ bool GrColorSpaceXform::Equals(const GrColorSpaceXform* a, const GrColorSpaceXfo return true; } - if (!a || !b) { + if (!a || !b || a->fFlags != b->fFlags) { + return false; + } + + if (SkToBool(a->fFlags & kApplyTransferFn_Flag) && + 0 != memcmp(&a->fSrcTransferFn, &b->fSrcTransferFn, sizeof(SkColorSpaceTransferFn))) { + return false; + } + + if (SkToBool(a->fFlags && kApplyGamutXform_Flag) && a->fGamutXform != b->fGamutXform) { return false; } - return a->fSrcToDst == b->fSrcToDst; + return true; } GrColor4f GrColorSpaceXform::unclampedXform(const GrColor4f& srcColor) { - GrColor4f result; - fSrcToDst.mapScalars(srcColor.fRGBA, result.fRGBA); + // This transform step should only happen with textures (not CPU xform of individual values) + SkASSERT(!SkToBool(fFlags & kApplyInverseSRGB_Flag)); + + GrColor4f result = srcColor; + if (fFlags & kApplyTransferFn_Flag) { + // Only transform RGB (not alpha) + for (int i = 0; i < 3; ++i) { + result.fRGBA[i] = fSrcTransferFn(result.fRGBA[i]); + } + } + if (fFlags & kApplyGamutXform_Flag) { + fGamutXform.mapScalars(result.fRGBA, result.fRGBA); + } return result; } @@ -214,12 +293,13 @@ GrFragmentProcessor::OptimizationFlags GrColorSpaceXformEffect::OptFlags( std::unique_ptr<GrFragmentProcessor> GrColorSpaceXformEffect::Make( std::unique_ptr<GrFragmentProcessor> child, const SkColorSpace* src, + GrPixelConfig srcConfig, const SkColorSpace* dst) { if (!child) { return nullptr; } - auto colorXform = GrColorSpaceXform::Make(src, dst); + auto colorXform = GrColorSpaceXform::Make(src, srcConfig, dst); if (colorXform) { return std::unique_ptr<GrFragmentProcessor>( new GrColorSpaceXformEffect(std::move(child), std::move(colorXform))); diff --git a/src/gpu/GrColorSpaceXform.h b/src/gpu/GrColorSpaceXform.h index 3f03e5652d..f63b1660f3 100644 --- a/src/gpu/GrColorSpaceXform.h +++ b/src/gpu/GrColorSpaceXform.h @@ -10,29 +10,42 @@ #include "GrColor.h" #include "GrFragmentProcessor.h" +#include "SkColorSpace.h" #include "SkMatrix44.h" #include "SkRefCnt.h" -class SkColorSpace; - /** - * Represents a color gamut transformation (as a 4x4 color matrix) + * Represents a color space transformation */ class GrColorSpaceXform : public SkRefCnt { public: - GrColorSpaceXform(const SkMatrix44& srcToDst); + GrColorSpaceXform(const SkColorSpaceTransferFn&, const SkMatrix44&, uint32_t); + + static sk_sp<GrColorSpaceXform> Make(const SkColorSpace* src, + GrPixelConfig srcConfig, + const SkColorSpace* dst); + static sk_sp<GrColorSpaceXform> MakeGamutXform(const SkColorSpace* src, + const SkColorSpace* dst) { + auto result = Make(src, kUnknown_GrPixelConfig, dst); + SkASSERT(!result || 0 == (result->fFlags & ~kApplyGamutXform_Flag)); + return result; + } - static sk_sp<GrColorSpaceXform> Make(const SkColorSpace* src, const SkColorSpace* dst); + const SkColorSpaceTransferFn& transferFn() const { return fSrcTransferFn; } + const float* transferFnCoeffs() const { + static_assert(0 == offsetof(SkColorSpaceTransferFn, fG), "TransferFn layout"); + return &fSrcTransferFn.fG; + } - const SkMatrix44& srcToDst() const { return fSrcToDst; } + const SkMatrix44& gamutXform() const { return fGamutXform; } /** * GrGLSLFragmentProcessor::GenKey() must call this and include the returned value in its * computed key. */ static uint32_t XformKey(const GrColorSpaceXform* xform) { - // Code generation changes if there is an xform, but it otherwise constant - return SkToBool(xform) ? 1 : 0; + // Code generation depends on which steps we apply (as encoded by fFlags) + return SkToBool(xform) ? xform->fFlags : 0; } static bool Equals(const GrColorSpaceXform* a, const GrColorSpaceXform* b); @@ -41,7 +54,21 @@ public: GrColor4f clampedXform(const GrColor4f& srcColor); private: - SkMatrix44 fSrcToDst; + friend class GrGLSLColorSpaceXformHelper; + + enum Flags { + kApplyTransferFn_Flag = 0x1, + kApplyGamutXform_Flag = 0x2, + + // Almost never used. This handles the case where the src data is sRGB pixel config, + // but the color space has a different transfer function. In that case, we first undo + // the HW sRGB -> Linear conversion, before applying any other steps. + kApplyInverseSRGB_Flag = 0x4, + }; + + SkColorSpaceTransferFn fSrcTransferFn; + SkMatrix44 fGamutXform; + uint32_t fFlags; }; class GrColorSpaceXformEffect : public GrFragmentProcessor { @@ -52,6 +79,7 @@ public: */ static std::unique_ptr<GrFragmentProcessor> Make(std::unique_ptr<GrFragmentProcessor> child, const SkColorSpace* src, + GrPixelConfig srcConfig, const SkColorSpace* dst); const char* name() const override { return "ColorSpaceXform"; } diff --git a/src/gpu/GrTestUtils.cpp b/src/gpu/GrTestUtils.cpp index da3cb97dfe..bd190a42ee 100644 --- a/src/gpu/GrTestUtils.cpp +++ b/src/gpu/GrTestUtils.cpp @@ -325,10 +325,10 @@ sk_sp<GrColorSpaceXform> TestColorXform(SkRandom* random) { sk_sp<SkColorSpace> adobe = SkColorSpace_Base::MakeNamed(SkColorSpace_Base::kAdobeRGB_Named); // No gamut change gXforms[0] = nullptr; - // To larger gamut - gXforms[1] = GrColorSpaceXform::Make(srgb.get(), adobe.get()); - // To smaller gamut - gXforms[2] = GrColorSpaceXform::Make(adobe.get(), srgb.get()); + // To larger gamut (with automatic transfer function) + gXforms[1] = GrColorSpaceXform::Make(srgb.get(), kSRGBA_8888_GrPixelConfig, adobe.get()); + // To smaller gamut (with manual transfer function) + gXforms[2] = GrColorSpaceXform::Make(adobe.get(), kRGBA_8888_GrPixelConfig, srgb.get()); } return gXforms[random->nextULessThan(static_cast<uint32_t>(SK_ARRAY_COUNT(gXforms)))]; } diff --git a/src/gpu/GrTextureAdjuster.cpp b/src/gpu/GrTextureAdjuster.cpp index 1a9b228390..2ae95a929e 100644 --- a/src/gpu/GrTextureAdjuster.cpp +++ b/src/gpu/GrTextureAdjuster.cpp @@ -156,7 +156,8 @@ std::unique_ptr<GrFragmentProcessor> GrTextureAdjuster::createFragmentProcessor( } SkASSERT(kNoDomain_DomainMode == domainMode || (domain.fLeft <= domain.fRight && domain.fTop <= domain.fBottom)); + GrPixelConfig config = proxy->config(); auto fp = CreateFragmentProcessorForDomainAndFilter(std::move(proxy), textureMatrix, domainMode, domain, filterOrNullForBicubic); - return GrColorSpaceXformEffect::Make(std::move(fp), fColorSpace, dstColorSpace); + return GrColorSpaceXformEffect::Make(std::move(fp), fColorSpace, config, dstColorSpace); } diff --git a/src/gpu/GrTextureMaker.cpp b/src/gpu/GrTextureMaker.cpp index 822d2141cb..a60a745696 100644 --- a/src/gpu/GrTextureMaker.cpp +++ b/src/gpu/GrTextureMaker.cpp @@ -118,9 +118,10 @@ std::unique_ptr<GrFragmentProcessor> GrTextureMaker::createFragmentProcessor( proxy.get(), nullptr, fmForDetermineDomain, &domain); SkASSERT(kTightCopy_DomainMode != domainMode); + GrPixelConfig config = proxy->config(); auto fp = CreateFragmentProcessorForDomainAndFilter(std::move(proxy), adjustedMatrix, domainMode, domain, filterOrNullForBicubic); - return GrColorSpaceXformEffect::Make(std::move(fp), texColorSpace.get(), dstColorSpace); + return GrColorSpaceXformEffect::Make(std::move(fp), texColorSpace.get(), config, dstColorSpace); } sk_sp<GrTextureProxy> GrTextureMaker::generateTextureProxyForParams(const CopyParams& copyParams, diff --git a/src/gpu/SkGpuDevice.cpp b/src/gpu/SkGpuDevice.cpp index df81d0494b..760e6064bb 100644 --- a/src/gpu/SkGpuDevice.cpp +++ b/src/gpu/SkGpuDevice.cpp @@ -992,6 +992,7 @@ void SkGpuDevice::drawBitmapTile(const SkBitmap& bitmap, // Construct a GrPaint by setting the bitmap texture as the first effect and then configuring // the rest from the SkPaint. std::unique_ptr<GrFragmentProcessor> fp; + GrPixelConfig config = proxy->config(); if (needsTextureDomain && (SkCanvas::kStrict_SrcRectConstraint == constraint)) { // Use a constrained texture domain to avoid color bleeding @@ -1022,7 +1023,7 @@ void SkGpuDevice::drawBitmapTile(const SkBitmap& bitmap, fp = GrSimpleTextureEffect::Make(std::move(proxy), texMatrix, samplerState); } - fp = GrColorSpaceXformEffect::Make(std::move(fp), bitmap.colorSpace(), + fp = GrColorSpaceXformEffect::Make(std::move(fp), bitmap.colorSpace(), config, fRenderTargetContext->colorSpaceInfo().colorSpace()); GrPaint grPaint; if (!SkPaintToGrPaintWithTexture(this->context(), fRenderTargetContext->colorSpaceInfo(), paint, @@ -1088,7 +1089,7 @@ void SkGpuDevice::drawSpecial(SkSpecialImage* special1, int left, int top, const tmpUnfiltered.setImageFilter(nullptr); auto fp = GrSimpleTextureEffect::Make(std::move(proxy), SkMatrix::I()); - fp = GrColorSpaceXformEffect::Make(std::move(fp), result->getColorSpace(), + fp = GrColorSpaceXformEffect::Make(std::move(fp), result->getColorSpace(), config, fRenderTargetContext->colorSpaceInfo().colorSpace()); if (GrPixelConfigIsAlphaOnly(config)) { fp = GrFragmentProcessor::MakeInputPremulAndMulByOutput(std::move(fp)); diff --git a/src/gpu/SkGpuDevice_drawTexture.cpp b/src/gpu/SkGpuDevice_drawTexture.cpp index 8d0ed6eabd..9838f46ee2 100644 --- a/src/gpu/SkGpuDevice_drawTexture.cpp +++ b/src/gpu/SkGpuDevice_drawTexture.cpp @@ -114,7 +114,8 @@ static void draw_texture_affine(const SkPaint& paint, const SkMatrix& ctm, const SkAssertResult(srcRect.intersect(SkRect::MakeIWH(proxy->width(), proxy->height()))); srcToDst.mapRect(&dstRect, srcRect); } - auto csxf = GrColorSpaceXform::Make(colorSpace, rtc->colorSpaceInfo().colorSpace()); + auto csxf = GrColorSpaceXform::Make(colorSpace, proxy->config(), + rtc->colorSpaceInfo().colorSpace()); GrSamplerState::Filter filter; switch (paint.getFilterQuality()) { case kNone_SkFilterQuality: diff --git a/src/gpu/effects/GrNonlinearColorSpaceXformEffect.cpp b/src/gpu/effects/GrNonlinearColorSpaceXformEffect.cpp index 7bdb04ec59..cc48356e0d 100644 --- a/src/gpu/effects/GrNonlinearColorSpaceXformEffect.cpp +++ b/src/gpu/effects/GrNonlinearColorSpaceXformEffect.cpp @@ -229,11 +229,11 @@ std::unique_ptr<GrFragmentProcessor> GrNonlinearColorSpaceXformEffect::Make( 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); + auto gamutXform = GrColorSpaceXform::MakeGamutXform(src, dst); SkMatrix44 srcToDstMtx(SkMatrix44::kUninitialized_Constructor); if (gamutXform) { ops |= kGamutXform_Op; - srcToDstMtx = gamutXform->srcToDst(); + srcToDstMtx = gamutXform->gamutXform(); } SkColorSpaceTransferFn srcTransferFn; diff --git a/src/gpu/glsl/GrGLSLColorSpaceXformHelper.h b/src/gpu/glsl/GrGLSLColorSpaceXformHelper.h index f9ac30b6aa..527de91c50 100644 --- a/src/gpu/glsl/GrGLSLColorSpaceXformHelper.h +++ b/src/gpu/glsl/GrGLSLColorSpaceXformHelper.h @@ -18,28 +18,56 @@ */ class GrGLSLColorSpaceXformHelper : public SkNoncopyable { public: - GrGLSLColorSpaceXformHelper() : fValid(false) {} + GrGLSLColorSpaceXformHelper() : fFlags(0) {} void emitCode(GrGLSLUniformHandler* uniformHandler, const GrColorSpaceXform* colorSpaceXform, uint32_t visibility = kFragment_GrShaderFlag) { SkASSERT(uniformHandler); if (colorSpaceXform) { - fGamutXformVar = uniformHandler->addUniform(visibility, kHalf4x4_GrSLType, - "ColorXform"); - fValid = true; + fFlags = colorSpaceXform->fFlags; + if (this->applyGamutXform()) { + fGamutXformVar = uniformHandler->addUniform(visibility, + kHalf4x4_GrSLType, + "ColorXform"); + } + if (this->applyTransferFn()) { + fTransferFnVar = uniformHandler->addUniformArray(visibility, + kHalf_GrSLType, + "TransferFn", + kNumTransferFnCoeffs); + } } } void setData(const GrGLSLProgramDataManager& pdman, const GrColorSpaceXform* colorSpaceXform) { - pdman.setSkMatrix44(fGamutXformVar, colorSpaceXform->srcToDst()); + if (this->applyGamutXform()) { + pdman.setSkMatrix44(fGamutXformVar, colorSpaceXform->gamutXform()); + } + if (this->applyTransferFn()) { + pdman.set1fv(fTransferFnVar, kNumTransferFnCoeffs, colorSpaceXform->transferFnCoeffs()); + } } - bool isValid() const { return fValid; } - GrGLSLProgramDataManager::UniformHandle const gamutXformUniform() { return fGamutXformVar; } + bool isValid() const { return (0 != fFlags); } + bool applyInverseSRGB() const { + return SkToBool(fFlags & GrColorSpaceXform::kApplyInverseSRGB_Flag); + } + bool applyTransferFn() const { + return SkToBool(fFlags & GrColorSpaceXform::kApplyTransferFn_Flag); + } + bool applyGamutXform() const { + return SkToBool(fFlags & GrColorSpaceXform::kApplyGamutXform_Flag); + } + + GrGLSLProgramDataManager::UniformHandle gamutXformUniform() const { return fGamutXformVar; } + GrGLSLProgramDataManager::UniformHandle transferFnUniform() const { return fTransferFnVar; } private: + static const int kNumTransferFnCoeffs = 7; + GrGLSLProgramDataManager::UniformHandle fGamutXformVar; - bool fValid; + GrGLSLProgramDataManager::UniformHandle fTransferFnVar; + uint32_t fFlags; }; #endif diff --git a/src/gpu/glsl/GrGLSLShaderBuilder.cpp b/src/gpu/glsl/GrGLSLShaderBuilder.cpp index f87194e3d3..750df63a79 100644 --- a/src/gpu/glsl/GrGLSLShaderBuilder.cpp +++ b/src/gpu/glsl/GrGLSLShaderBuilder.cpp @@ -118,29 +118,78 @@ void GrGLSLShaderBuilder::appendTextureLookupAndModulate( void GrGLSLShaderBuilder::appendColorGamutXform(SkString* out, const char* srcColor, GrGLSLColorSpaceXformHelper* colorXformHelper) { - // Our color is (r, g, b, a), but we want to multiply (r, g, b, 1) by our matrix, then - // re-insert the original alpha. The supplied srcColor is likely to be of the form - // "texture(...)", and we don't want to evaluate that twice, so wrap everything in a function. - static const GrShaderVar gColorGamutXformArgs[] = { - GrShaderVar("color", kHalf4_GrSLType), - GrShaderVar("xform", kHalf4x4_GrSLType), - }; - SkString functionBody; - // Gamut xform, clamp to destination gamut. We only support/have premultiplied textures, so we - // always just clamp to alpha. - functionBody.append("\tcolor.rgb = clamp((xform * half4(color.rgb, 1.0)).rgb, 0.0, color.a);\n"); - functionBody.append("\treturn color;"); - SkString colorGamutXformFuncName; - this->emitFunction(kHalf4_GrSLType, - "colorGamutXform", - SK_ARRAY_COUNT(gColorGamutXformArgs), - gColorGamutXformArgs, - functionBody.c_str(), - &colorGamutXformFuncName); - GrGLSLUniformHandler* uniformHandler = fProgramBuilder->uniformHandler(); - out->appendf("%s(%s, %s)", colorGamutXformFuncName.c_str(), srcColor, - uniformHandler->getUniformCStr(colorXformHelper->gamutXformUniform())); + + // We define up to three helper functions, to keep things clearer. One does inverse sRGB, + // one does an arbitrary transfer function, and the last does gamut xform. Any combination of + // these may be present, although some configurations are much more likely. + + SkString inverseSrgbFuncName; + if (colorXformHelper->applyInverseSRGB()) { + static const GrShaderVar gInverseSRGBArgs[] = { GrShaderVar("x", kHalf_GrSLType) }; + SkString body; + body.append("return (x <= 0.0031308) ? (x * 12.92) : (1.055 * pow(x, 0.4166667) - 0.055);"); + this->emitFunction(kHalf_GrSLType, "inverse_srgb", SK_ARRAY_COUNT(gInverseSRGBArgs), + gInverseSRGBArgs, body.c_str(), &inverseSrgbFuncName); + + } + + SkString transferFnFuncName; + if (colorXformHelper->applyTransferFn()) { + static const GrShaderVar gTransferFnArgs[] = { GrShaderVar("x", kHalf_GrSLType) }; + const char* coeffs = uniformHandler->getUniformCStr(colorXformHelper->transferFnUniform()); + SkString body; + // Temporaries to make evaluation line readable + body.appendf("half G = %s[0];", coeffs); + body.appendf("half A = %s[1];", coeffs); + body.appendf("half B = %s[2];", coeffs); + body.appendf("half C = %s[3];", coeffs); + body.appendf("half D = %s[4];", coeffs); + body.appendf("half E = %s[5];", coeffs); + body.appendf("half F = %s[6];", coeffs); + body.append("half s = sign(x);"); + body.append("x = abs(x);"); + body.appendf("return s * ((x < D) ? (C * x) + F : pow(A * x + B, G) + E);"); + this->emitFunction(kHalf_GrSLType, "transfer_fn", SK_ARRAY_COUNT(gTransferFnArgs), + gTransferFnArgs, body.c_str(), &transferFnFuncName); + } + + SkString gamutXformFuncName; + if (colorXformHelper->applyGamutXform()) { + // Our color is (r, g, b, a), but we want to multiply (r, g, b, 1) by our matrix, then + // re-insert the original alpha. + static const GrShaderVar gGamutXformArgs[] = { GrShaderVar("color", kHalf4_GrSLType) }; + const char* xform = uniformHandler->getUniformCStr(colorXformHelper->gamutXformUniform()); + SkString body; + body.appendf("color.rgb = clamp((%s * half4(color.rgb, 1.0)).rgb, 0.0, color.a);", xform); + body.append("return color;"); + this->emitFunction(kHalf4_GrSLType, "gamut_xform", SK_ARRAY_COUNT(gGamutXformArgs), + gGamutXformArgs, body.c_str(), &gamutXformFuncName); + } + + // Now define a wrapper function that applies all the intermediate steps + { + static const GrShaderVar gColorXformArgs[] = { GrShaderVar("color", kHalf4_GrSLType) }; + SkString body; + if (colorXformHelper->applyInverseSRGB()) { + body.appendf("color.r = %s(color.r);", inverseSrgbFuncName.c_str()); + body.appendf("color.g = %s(color.g);", inverseSrgbFuncName.c_str()); + body.appendf("color.b = %s(color.b);", inverseSrgbFuncName.c_str()); + } + if (colorXformHelper->applyTransferFn()) { + body.appendf("color.r = %s(color.r);", transferFnFuncName.c_str()); + body.appendf("color.g = %s(color.g);", transferFnFuncName.c_str()); + body.appendf("color.b = %s(color.b);", transferFnFuncName.c_str()); + } + if (colorXformHelper->applyGamutXform()) { + body.appendf("color = %s(color);", gamutXformFuncName.c_str()); + } + body.append("return color;"); + SkString colorXformFuncName; + this->emitFunction(kHalf4_GrSLType, "color_xform", SK_ARRAY_COUNT(gColorXformArgs), + gColorXformArgs, body.c_str(), &colorXformFuncName); + out->appendf("%s(%s)", colorXformFuncName.c_str(), srcColor); + } } void GrGLSLShaderBuilder::appendColorGamutXform(const char* srcColor, diff --git a/src/shaders/SkColorShader.cpp b/src/shaders/SkColorShader.cpp index 4bc7d71194..861898737b 100644 --- a/src/shaders/SkColorShader.cpp +++ b/src/shaders/SkColorShader.cpp @@ -211,8 +211,10 @@ SkShader::GradientType SkColor4Shader::asAGradient(GradientInfo* info) const { std::unique_ptr<GrFragmentProcessor> SkColor4Shader::asFragmentProcessor( const AsFPArgs& args) const { - sk_sp<GrColorSpaceXform> colorSpaceXform = - GrColorSpaceXform::Make(fColorSpace.get(), args.fDstColorSpaceInfo->colorSpace()); + // Construct an xform assuming float inputs. The color space can have a transfer function on + // it, which will be applied below. + auto colorSpaceXform = GrColorSpaceXform::Make(fColorSpace.get(), kRGBA_float_GrPixelConfig, + args.fDstColorSpaceInfo->colorSpace()); GrColor4f color = GrColor4f::FromSkColor4f(fColor4); if (colorSpaceXform) { color = colorSpaceXform->clampedXform(color); diff --git a/src/shaders/SkImageShader.cpp b/src/shaders/SkImageShader.cpp index 753024dbad..46b20b1de3 100644 --- a/src/shaders/SkImageShader.cpp +++ b/src/shaders/SkImageShader.cpp @@ -218,7 +218,8 @@ std::unique_ptr<GrFragmentProcessor> SkImageShader::asFragmentProcessor( return nullptr; } - bool isAlphaOnly = GrPixelConfigIsAlphaOnly(proxy->config()); + GrPixelConfig config = proxy->config(); + bool isAlphaOnly = GrPixelConfigIsAlphaOnly(config); lmInverse.postScale(scaleAdjust[0], scaleAdjust[1]); @@ -228,7 +229,7 @@ std::unique_ptr<GrFragmentProcessor> SkImageShader::asFragmentProcessor( } else { inner = GrSimpleTextureEffect::Make(std::move(proxy), lmInverse, samplerState); } - inner = GrColorSpaceXformEffect::Make(std::move(inner), texColorSpace.get(), + inner = GrColorSpaceXformEffect::Make(std::move(inner), texColorSpace.get(), config, args.fDstColorSpaceInfo->colorSpace()); if (isAlphaOnly) { return inner; diff --git a/src/shaders/gradients/SkGradientShader.cpp b/src/shaders/gradients/SkGradientShader.cpp index 7d31e50f84..279491b177 100644 --- a/src/shaders/gradients/SkGradientShader.cpp +++ b/src/shaders/gradients/SkGradientShader.cpp @@ -184,6 +184,7 @@ SkGradientShaderBase::SkGradientShaderBase(const Descriptor& desc, const SkMatri fColorSpace = SkColorSpace::MakeSRGBLinear(); } else { // The color space refers to the float colors, so it must be linear gamma + // TODO: GPU code no longer requires this (see GrGradientEffect). Remove this restriction? SkASSERT(desc.fColorSpace->gammaIsLinear()); fColorSpace = desc.fColorSpace; } @@ -1260,8 +1261,11 @@ GrGradientEffect::GrGradientEffect(ClassID classID, const CreateArgs& args, bool fPremulType = kAfterInterp_PremulType; } - // Convert input colors to GrColor4f, possibly premul, and apply color space xform + // Convert input colors to GrColor4f, possibly premul, and apply color space xform. + // The xform is constructed assuming floats as input, but the color space can have a + // transfer function on it, which will be applied below. auto colorSpaceXform = GrColorSpaceXform::Make(shader.fColorSpace.get(), + kRGBA_float_GrPixelConfig, args.fDstColorSpace); SkASSERT(shader.fOrigColors && shader.fOrigColors4f); fColors4f.setCount(shader.fColorCount); diff --git a/src/shaders/gradients/SkGradientShaderPriv.h b/src/shaders/gradients/SkGradientShaderPriv.h index 8fabc5c4fd..0d8e7bef9a 100644 --- a/src/shaders/gradients/SkGradientShaderPriv.h +++ b/src/shaders/gradients/SkGradientShaderPriv.h @@ -277,8 +277,13 @@ protected: // xform is needed. With texture-based gradients, we leave the data in the source color // space (to avoid clamping if we can't use F16)... Add an extra FP to do the xform. if (kTexture_ColorType == gradientFP->getColorType()) { + // Our texture is always either F16 or sRGB, so the data is "linear" in the shader. + // Create our xform assuming float inputs, which will suppress any extra sRGB work. + // We do support having a transfer function on the color space of the stops, so + // this FP may include that transformation. fp = GrColorSpaceXformEffect::Make(std::move(gradientFP), args.fShader->fColorSpace.get(), + kRGBA_float_GrPixelConfig, args.fDstColorSpace); } else { fp = std::move(gradientFP); |