blob: 29a3d7f7a8267c9a6189c3f9c94c2662e2c0f2ac [file] [log] [blame]
brianosman54f30c12016-07-18 10:53:52 -07001/*
2 * Copyright 2016 Google Inc.
3 *
4 * Use of this source code is governed by a BSD-style license that can be
5 * found in the LICENSE file.
6 */
7
8#include "GrColorSpaceXform.h"
9#include "SkColorSpace.h"
Brian Osmanf06ead92017-10-30 13:47:41 -040010#include "SkColorSpacePriv.h"
brianosman9f978822016-07-27 05:25:26 -070011#include "SkMatrix44.h"
Brian Osman7c2114f2016-10-20 15:34:06 -040012#include "SkSpinlock.h"
Brian Osmanc4f93ca2017-10-17 17:15:52 -040013#include "glsl/GrGLSLColorSpaceXformHelper.h"
14#include "glsl/GrGLSLFragmentProcessor.h"
15#include "glsl/GrGLSLFragmentShaderBuilder.h"
Brian Osman7c2114f2016-10-20 15:34:06 -040016
17class GrColorSpaceXformCache {
18public:
19 using NewValueFn = std::function<sk_sp<GrColorSpaceXform>(void)>;
20
21 GrColorSpaceXformCache() : fSequence(0) {}
22
23 sk_sp<GrColorSpaceXform> findOrAdd(uint64_t key, NewValueFn newValue) {
24 int oldest = 0;
25 for (int i = 0; i < kEntryCount; ++i) {
26 if (fEntries[i].fKey == key) {
27 fEntries[i].fLastUse = fSequence++;
28 return fEntries[i].fXform;
29 }
30 if (fEntries[i].fLastUse < fEntries[oldest].fLastUse) {
31 oldest = i;
32 }
33 }
34 fEntries[oldest].fKey = key;
35 fEntries[oldest].fXform = newValue();
36 fEntries[oldest].fLastUse = fSequence++;
37 return fEntries[oldest].fXform;
38 }
39
40private:
41 enum { kEntryCount = 32 };
42
43 struct Entry {
44 // The default Entry is "valid". Any 64-bit key that is the same 32-bit value repeated
45 // implies no xform is necessary, so nullptr should be returned. This particular case should
46 // never happen, but by initializing all entries with this data, we can avoid special cases
47 // for the array not yet being full.
48 Entry() : fKey(0), fXform(nullptr), fLastUse(0) {}
49
50 uint64_t fKey;
51 sk_sp<GrColorSpaceXform> fXform;
52 uint64_t fLastUse;
53 };
54
55 Entry fEntries[kEntryCount];
56 uint64_t fSequence;
57};
brianosman54f30c12016-07-18 10:53:52 -070058
Brian Osmanf06ead92017-10-30 13:47:41 -040059GrColorSpaceXform::GrColorSpaceXform(const SkColorSpaceTransferFn& srcTransferFn,
60 const SkMatrix44& gamutXform, uint32_t flags)
61 : fSrcTransferFn(srcTransferFn), fGamutXform(gamutXform), fFlags(flags) {}
brianosman9f978822016-07-27 05:25:26 -070062
Brian Osman7c2114f2016-10-20 15:34:06 -040063static SkSpinlock gColorSpaceXformCacheSpinlock;
64
Brian Osmanf06ead92017-10-30 13:47:41 -040065sk_sp<GrColorSpaceXform> GrColorSpaceXform::Make(const SkColorSpace* src,
66 GrPixelConfig srcConfig,
67 const SkColorSpace* dst) {
68 if (!dst) {
69 // No transformation is performed in legacy mode
brianosman54f30c12016-07-18 10:53:52 -070070 return nullptr;
71 }
72
Brian Osmanf06ead92017-10-30 13:47:41 -040073 // Treat null sources as sRGB
74 if (!src) {
75 if (GrPixelConfigIsFloatingPoint(srcConfig)) {
76 src = SkColorSpace::MakeSRGBLinear().get();
77 } else {
78 src = SkColorSpace::MakeSRGB().get();
79 }
80 }
81
82 uint32_t flags = 0;
83 SkColorSpaceTransferFn srcTransferFn;
84
85 // kUnknown_GrPixelConfig is a sentinel that means we don't care about transfer functions,
86 // just the gamut xform.
87 if (kUnknown_GrPixelConfig != srcConfig) {
88 // Determine if src transfer function is needed, based on src config and color space
89 if (GrPixelConfigIsSRGB(srcConfig)) {
90 // Source texture is sRGB, will be converted to linear when we sample
91 if (src->gammaCloseToSRGB()) {
92 // Hardware linearize does the right thing
93 } else if (src->gammaIsLinear()) {
94 // Oops, need to undo the (extra) linearize
95 flags |= kApplyInverseSRGB_Flag;
96 } else if (src->isNumericalTransferFn(&srcTransferFn)) {
97 // Need to undo the (extra) linearize, then apply the correct transfer function
98 flags |= (kApplyInverseSRGB_Flag | kApplyTransferFn_Flag);
99 } else {
100 // We don't (yet) support more complex transfer functions
101 return nullptr;
102 }
103 } else {
104 // Source texture is some non-sRGB format, we consider it linearly encoded
105 if (src->gammaIsLinear()) {
106 // Linear sampling does the right thing
107 } else if (src->isNumericalTransferFn(&srcTransferFn)) {
108 // Need to manually apply some transfer function (including sRGB)
109 flags |= kApplyTransferFn_Flag;
110 } else {
111 // We don't (yet) support more complex transfer functions
112 return nullptr;
113 }
114 }
115 }
116 if (src == dst && (0 == flags)) {
117 // Quick equality check - no conversion (or transfer function) needed in this case
brianosman54f30c12016-07-18 10:53:52 -0700118 return nullptr;
119 }
Brian Osman7c2114f2016-10-20 15:34:06 -0400120
Brian Osman36703d92017-12-12 14:09:31 -0500121 const SkMatrix44* toXYZD50 = src->toXYZD50();
122 const SkMatrix44* fromXYZD50 = dst->fromXYZD50();
raftias94888332016-10-18 10:02:51 -0700123 if (!toXYZD50 || !fromXYZD50) {
Brian Osmanf06ead92017-10-30 13:47:41 -0400124 // Unsupported colour spaces -- cannot specify gamut as a matrix
raftias94888332016-10-18 10:02:51 -0700125 return nullptr;
126 }
brianosman54f30c12016-07-18 10:53:52 -0700127
Brian Osmanf06ead92017-10-30 13:47:41 -0400128 // Determine if a gamut xform is needed
Brian Osman36703d92017-12-12 14:09:31 -0500129 uint32_t srcHash = src->toXYZD50Hash();
130 uint32_t dstHash = dst->toXYZD50Hash();
Brian Osmanf06ead92017-10-30 13:47:41 -0400131 if (srcHash != dstHash) {
132 flags |= kApplyGamutXform_Flag;
133 } else {
Brian Osman36703d92017-12-12 14:09:31 -0500134 SkASSERT(*toXYZD50 == *dst->toXYZD50() && "Hash collision");
Brian Osmanf06ead92017-10-30 13:47:41 -0400135 }
136
137 if (0 == flags) {
138 // Identical gamut and no transfer function - no conversion needed in this case
brianosman54f30c12016-07-18 10:53:52 -0700139 return nullptr;
140 }
141
Brian Osmanf06ead92017-10-30 13:47:41 -0400142 auto makeXform = [srcTransferFn, fromXYZD50, toXYZD50, flags]() {
Brian Osman7c2114f2016-10-20 15:34:06 -0400143 SkMatrix44 srcToDst(SkMatrix44::kUninitialized_Constructor);
Brian Osmanf06ead92017-10-30 13:47:41 -0400144 if (SkToBool(flags & kApplyGamutXform_Flag)) {
145 srcToDst.setConcat(*fromXYZD50, *toXYZD50);
146 } else {
147 srcToDst.setIdentity();
148 }
149 return sk_make_sp<GrColorSpaceXform>(srcTransferFn, srcToDst, flags);
Brian Osman7c2114f2016-10-20 15:34:06 -0400150 };
Brian Osmanbbf251b2016-10-19 14:56:07 -0400151
Brian Osmanf06ead92017-10-30 13:47:41 -0400152 // For now, we only cache pure gamut xforms (no transfer functions)
153 // TODO: Fold a hash of the transfer function into the cache key
154 if ((kApplyGamutXform_Flag == flags) && gColorSpaceXformCacheSpinlock.tryAcquire()) {
Brian Osman7c2114f2016-10-20 15:34:06 -0400155 static GrColorSpaceXformCache* gCache;
156 if (nullptr == gCache) {
157 gCache = new GrColorSpaceXformCache();
158 }
159
160 uint64_t key = static_cast<uint64_t>(srcHash) << 32 | static_cast<uint64_t>(dstHash);
Brian Osmanf06ead92017-10-30 13:47:41 -0400161 sk_sp<GrColorSpaceXform> xform = gCache->findOrAdd(key, makeXform);
Brian Osman7c2114f2016-10-20 15:34:06 -0400162 gColorSpaceXformCacheSpinlock.release();
163 return xform;
164 } else {
Brian Osmanf06ead92017-10-30 13:47:41 -0400165 // If our xform has non-gamut components, or we can't get the spin lock, just build it
166 return makeXform();
Brian Osman7c2114f2016-10-20 15:34:06 -0400167 }
brianosman54f30c12016-07-18 10:53:52 -0700168}
brianosman5a7ae7e2016-09-12 12:07:25 -0700169
brianosmanb9c51372016-09-15 11:09:45 -0700170bool GrColorSpaceXform::Equals(const GrColorSpaceXform* a, const GrColorSpaceXform* b) {
171 if (a == b) {
172 return true;
173 }
174
Brian Osmanf06ead92017-10-30 13:47:41 -0400175 if (!a || !b || a->fFlags != b->fFlags) {
brianosmanb9c51372016-09-15 11:09:45 -0700176 return false;
177 }
178
Brian Osmanf06ead92017-10-30 13:47:41 -0400179 if (SkToBool(a->fFlags & kApplyTransferFn_Flag) &&
180 0 != memcmp(&a->fSrcTransferFn, &b->fSrcTransferFn, sizeof(SkColorSpaceTransferFn))) {
181 return false;
182 }
183
184 if (SkToBool(a->fFlags && kApplyGamutXform_Flag) && a->fGamutXform != b->fGamutXform) {
185 return false;
186 }
187
188 return true;
brianosmanb9c51372016-09-15 11:09:45 -0700189}
190
Brian Osmanfe3e8582017-10-20 11:27:49 -0400191GrColor4f GrColorSpaceXform::unclampedXform(const GrColor4f& srcColor) {
Brian Osmanf06ead92017-10-30 13:47:41 -0400192 // This transform step should only happen with textures (not CPU xform of individual values)
193 SkASSERT(!SkToBool(fFlags & kApplyInverseSRGB_Flag));
194
195 GrColor4f result = srcColor;
196 if (fFlags & kApplyTransferFn_Flag) {
197 // Only transform RGB (not alpha)
198 for (int i = 0; i < 3; ++i) {
199 result.fRGBA[i] = fSrcTransferFn(result.fRGBA[i]);
200 }
201 }
202 if (fFlags & kApplyGamutXform_Flag) {
203 fGamutXform.mapScalars(result.fRGBA, result.fRGBA);
204 }
Brian Osmanfe3e8582017-10-20 11:27:49 -0400205 return result;
206}
207
208GrColor4f GrColorSpaceXform::clampedXform(const GrColor4f& srcColor) {
209 GrColor4f result = this->unclampedXform(srcColor);
Brian Osman01648962016-12-01 17:04:43 -0500210 for (int i = 0; i < 4; ++i) {
Brian Osmanfe3e8582017-10-20 11:27:49 -0400211 // We always operate on unpremul colors, so clamp to [0,1].
Brian Osman01648962016-12-01 17:04:43 -0500212 result.fRGBA[i] = SkTPin(result.fRGBA[i], 0.0f, 1.0f);
213 }
brianosman5a7ae7e2016-09-12 12:07:25 -0700214 return result;
215}
Brian Osmanc4f93ca2017-10-17 17:15:52 -0400216
217//////////////////////////////////////////////////////////////////////////////
218
219class GrGLColorSpaceXformEffect : public GrGLSLFragmentProcessor {
220public:
221 void emitCode(EmitArgs& args) override {
222 const GrColorSpaceXformEffect& csxe = args.fFp.cast<GrColorSpaceXformEffect>();
223 GrGLSLFPFragmentBuilder* fragBuilder = args.fFragBuilder;
224 GrGLSLUniformHandler* uniformHandler = args.fUniformHandler;
225
226 fColorSpaceHelper.emitCode(uniformHandler, csxe.colorXform());
227
228 SkString childColor("src_color");
229 this->emitChild(0, &childColor, args);
230
231 SkString xformedColor;
232 fragBuilder->appendColorGamutXform(&xformedColor, childColor.c_str(), &fColorSpaceHelper);
233 fragBuilder->codeAppendf("%s = %s * %s;", args.fOutputColor, xformedColor.c_str(),
234 args.fInputColor);
235 }
236
237private:
238 void onSetData(const GrGLSLProgramDataManager& pdman,
239 const GrFragmentProcessor& processor) override {
240 const GrColorSpaceXformEffect& csxe = processor.cast<GrColorSpaceXformEffect>();
241 if (fColorSpaceHelper.isValid()) {
242 fColorSpaceHelper.setData(pdman, csxe.colorXform());
243 }
244 }
245
246 GrGLSLColorSpaceXformHelper fColorSpaceHelper;
247
248 typedef GrGLSLFragmentProcessor INHERITED;
249};
250
251//////////////////////////////////////////////////////////////////////////////
252
253GrColorSpaceXformEffect::GrColorSpaceXformEffect(std::unique_ptr<GrFragmentProcessor> child,
254 sk_sp<GrColorSpaceXform> colorXform)
255 : INHERITED(kGrColorSpaceXformEffect_ClassID, OptFlags(child.get()))
256 , fColorXform(std::move(colorXform)) {
257 this->registerChildProcessor(std::move(child));
258}
259
260std::unique_ptr<GrFragmentProcessor> GrColorSpaceXformEffect::clone() const {
261 return std::unique_ptr<GrFragmentProcessor>(
262 new GrColorSpaceXformEffect(this->childProcessor(0).clone(), fColorXform));
263}
264
265bool GrColorSpaceXformEffect::onIsEqual(const GrFragmentProcessor& s) const {
266 const GrColorSpaceXformEffect& other = s.cast<GrColorSpaceXformEffect>();
267 return GrColorSpaceXform::Equals(fColorXform.get(), other.fColorXform.get());
268}
269
270void GrColorSpaceXformEffect::onGetGLSLProcessorKey(const GrShaderCaps& caps,
271 GrProcessorKeyBuilder* b) const {
272 b->add32(GrColorSpaceXform::XformKey(fColorXform.get()));
273}
274
275GrGLSLFragmentProcessor* GrColorSpaceXformEffect::onCreateGLSLInstance() const {
276 return new GrGLColorSpaceXformEffect();
277}
278
279GrFragmentProcessor::OptimizationFlags GrColorSpaceXformEffect::OptFlags(
280 const GrFragmentProcessor* child) {
281 // TODO: Implement constant output for constant input
282 OptimizationFlags flags = kNone_OptimizationFlags;
283 if (child->compatibleWithCoverageAsAlpha()) {
284 flags |= kCompatibleWithCoverageAsAlpha_OptimizationFlag;
285 }
286 if (child->preservesOpaqueInput()) {
287 flags |= kPreservesOpaqueInput_OptimizationFlag;
288 }
289 return flags;
290}
291
292std::unique_ptr<GrFragmentProcessor> GrColorSpaceXformEffect::Make(
293 std::unique_ptr<GrFragmentProcessor> child,
294 const SkColorSpace* src,
Brian Osmanf06ead92017-10-30 13:47:41 -0400295 GrPixelConfig srcConfig,
Brian Osmanc4f93ca2017-10-17 17:15:52 -0400296 const SkColorSpace* dst) {
297 if (!child) {
298 return nullptr;
299 }
300
Brian Osmanf06ead92017-10-30 13:47:41 -0400301 auto colorXform = GrColorSpaceXform::Make(src, srcConfig, dst);
Brian Osmanc4f93ca2017-10-17 17:15:52 -0400302 if (colorXform) {
303 return std::unique_ptr<GrFragmentProcessor>(
304 new GrColorSpaceXformEffect(std::move(child), std::move(colorXform)));
305 } else {
306 return child;
307 }
308}