aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/gpu/GrColorSpaceXform.cpp
diff options
context:
space:
mode:
authorGravatar Brian Osman <brianosman@google.com>2017-10-30 13:47:41 -0400
committerGravatar Skia Commit-Bot <skia-commit-bot@chromium.org>2017-10-31 14:55:14 +0000
commitf06ead925c631c42fae734de1e7c72237a2e91f4 (patch)
treed2ae0175607b41087df6bcc55d6fe4342eecc739 /src/gpu/GrColorSpaceXform.cpp
parent761f44853fcead7e7a105e1870cb970646814058 (diff)
Add support for transfer functions to GrColorSpaceXform
With this change, untagged sources (eg N32) are treated as sRGB data, which causes a huge number of GMs to render more correctly in GPU sRGB/F16/etc... configs. Also, because the sources are treated as having a color space, we actually do gamut conversion for wide or narrow gamut outputs. This change also applies the transfer function math to individual colors in the case of gradient stops and color shaders. (The CPU backend doesn't do this yet, but I think we've decided there's no reason not to support it). Bug: skia: Change-Id: If76e9e4a268f9f74110ff4bbe4fe189ba5d19d9f Reviewed-on: https://skia-review.googlesource.com/64100 Reviewed-by: Brian Salomon <bsalomon@google.com> Commit-Queue: Brian Osman <brianosman@google.com>
Diffstat (limited to 'src/gpu/GrColorSpaceXform.cpp')
-rw-r--r--src/gpu/GrColorSpaceXform.cpp124
1 files changed, 102 insertions, 22 deletions
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)));