brianosman | 54f30c1 | 2016-07-18 10:53:52 -0700 | [diff] [blame] | 1 | /* |
| 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 Osman | f06ead9 | 2017-10-30 13:47:41 -0400 | [diff] [blame] | 10 | #include "SkColorSpacePriv.h" |
brianosman | 9f97882 | 2016-07-27 05:25:26 -0700 | [diff] [blame] | 11 | #include "SkMatrix44.h" |
Brian Osman | 7c2114f | 2016-10-20 15:34:06 -0400 | [diff] [blame] | 12 | #include "SkSpinlock.h" |
Brian Osman | c4f93ca | 2017-10-17 17:15:52 -0400 | [diff] [blame] | 13 | #include "glsl/GrGLSLColorSpaceXformHelper.h" |
| 14 | #include "glsl/GrGLSLFragmentProcessor.h" |
| 15 | #include "glsl/GrGLSLFragmentShaderBuilder.h" |
Brian Osman | 7c2114f | 2016-10-20 15:34:06 -0400 | [diff] [blame] | 16 | |
| 17 | class GrColorSpaceXformCache { |
| 18 | public: |
| 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 | |
| 40 | private: |
| 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 | }; |
brianosman | 54f30c1 | 2016-07-18 10:53:52 -0700 | [diff] [blame] | 58 | |
Brian Osman | f06ead9 | 2017-10-30 13:47:41 -0400 | [diff] [blame] | 59 | GrColorSpaceXform::GrColorSpaceXform(const SkColorSpaceTransferFn& srcTransferFn, |
| 60 | const SkMatrix44& gamutXform, uint32_t flags) |
| 61 | : fSrcTransferFn(srcTransferFn), fGamutXform(gamutXform), fFlags(flags) {} |
brianosman | 9f97882 | 2016-07-27 05:25:26 -0700 | [diff] [blame] | 62 | |
Brian Osman | 7c2114f | 2016-10-20 15:34:06 -0400 | [diff] [blame] | 63 | static SkSpinlock gColorSpaceXformCacheSpinlock; |
| 64 | |
Brian Osman | f06ead9 | 2017-10-30 13:47:41 -0400 | [diff] [blame] | 65 | sk_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 |
brianosman | 54f30c1 | 2016-07-18 10:53:52 -0700 | [diff] [blame] | 70 | return nullptr; |
| 71 | } |
| 72 | |
Brian Osman | f06ead9 | 2017-10-30 13:47:41 -0400 | [diff] [blame] | 73 | // 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 |
brianosman | 54f30c1 | 2016-07-18 10:53:52 -0700 | [diff] [blame] | 118 | return nullptr; |
| 119 | } |
Brian Osman | 7c2114f | 2016-10-20 15:34:06 -0400 | [diff] [blame] | 120 | |
Brian Osman | 36703d9 | 2017-12-12 14:09:31 -0500 | [diff] [blame] | 121 | const SkMatrix44* toXYZD50 = src->toXYZD50(); |
| 122 | const SkMatrix44* fromXYZD50 = dst->fromXYZD50(); |
raftias | 9488833 | 2016-10-18 10:02:51 -0700 | [diff] [blame] | 123 | if (!toXYZD50 || !fromXYZD50) { |
Brian Osman | f06ead9 | 2017-10-30 13:47:41 -0400 | [diff] [blame] | 124 | // Unsupported colour spaces -- cannot specify gamut as a matrix |
raftias | 9488833 | 2016-10-18 10:02:51 -0700 | [diff] [blame] | 125 | return nullptr; |
| 126 | } |
brianosman | 54f30c1 | 2016-07-18 10:53:52 -0700 | [diff] [blame] | 127 | |
Brian Osman | f06ead9 | 2017-10-30 13:47:41 -0400 | [diff] [blame] | 128 | // Determine if a gamut xform is needed |
Brian Osman | 36703d9 | 2017-12-12 14:09:31 -0500 | [diff] [blame] | 129 | uint32_t srcHash = src->toXYZD50Hash(); |
| 130 | uint32_t dstHash = dst->toXYZD50Hash(); |
Brian Osman | f06ead9 | 2017-10-30 13:47:41 -0400 | [diff] [blame] | 131 | if (srcHash != dstHash) { |
| 132 | flags |= kApplyGamutXform_Flag; |
| 133 | } else { |
Brian Osman | 36703d9 | 2017-12-12 14:09:31 -0500 | [diff] [blame] | 134 | SkASSERT(*toXYZD50 == *dst->toXYZD50() && "Hash collision"); |
Brian Osman | f06ead9 | 2017-10-30 13:47:41 -0400 | [diff] [blame] | 135 | } |
| 136 | |
| 137 | if (0 == flags) { |
| 138 | // Identical gamut and no transfer function - no conversion needed in this case |
brianosman | 54f30c1 | 2016-07-18 10:53:52 -0700 | [diff] [blame] | 139 | return nullptr; |
| 140 | } |
| 141 | |
Brian Osman | f06ead9 | 2017-10-30 13:47:41 -0400 | [diff] [blame] | 142 | auto makeXform = [srcTransferFn, fromXYZD50, toXYZD50, flags]() { |
Brian Osman | 7c2114f | 2016-10-20 15:34:06 -0400 | [diff] [blame] | 143 | SkMatrix44 srcToDst(SkMatrix44::kUninitialized_Constructor); |
Brian Osman | f06ead9 | 2017-10-30 13:47:41 -0400 | [diff] [blame] | 144 | 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 Osman | 7c2114f | 2016-10-20 15:34:06 -0400 | [diff] [blame] | 150 | }; |
Brian Osman | bbf251b | 2016-10-19 14:56:07 -0400 | [diff] [blame] | 151 | |
Brian Osman | f06ead9 | 2017-10-30 13:47:41 -0400 | [diff] [blame] | 152 | // 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 Osman | 7c2114f | 2016-10-20 15:34:06 -0400 | [diff] [blame] | 155 | 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 Osman | f06ead9 | 2017-10-30 13:47:41 -0400 | [diff] [blame] | 161 | sk_sp<GrColorSpaceXform> xform = gCache->findOrAdd(key, makeXform); |
Brian Osman | 7c2114f | 2016-10-20 15:34:06 -0400 | [diff] [blame] | 162 | gColorSpaceXformCacheSpinlock.release(); |
| 163 | return xform; |
| 164 | } else { |
Brian Osman | f06ead9 | 2017-10-30 13:47:41 -0400 | [diff] [blame] | 165 | // If our xform has non-gamut components, or we can't get the spin lock, just build it |
| 166 | return makeXform(); |
Brian Osman | 7c2114f | 2016-10-20 15:34:06 -0400 | [diff] [blame] | 167 | } |
brianosman | 54f30c1 | 2016-07-18 10:53:52 -0700 | [diff] [blame] | 168 | } |
brianosman | 5a7ae7e | 2016-09-12 12:07:25 -0700 | [diff] [blame] | 169 | |
brianosman | b9c5137 | 2016-09-15 11:09:45 -0700 | [diff] [blame] | 170 | bool GrColorSpaceXform::Equals(const GrColorSpaceXform* a, const GrColorSpaceXform* b) { |
| 171 | if (a == b) { |
| 172 | return true; |
| 173 | } |
| 174 | |
Brian Osman | f06ead9 | 2017-10-30 13:47:41 -0400 | [diff] [blame] | 175 | if (!a || !b || a->fFlags != b->fFlags) { |
brianosman | b9c5137 | 2016-09-15 11:09:45 -0700 | [diff] [blame] | 176 | return false; |
| 177 | } |
| 178 | |
Brian Osman | f06ead9 | 2017-10-30 13:47:41 -0400 | [diff] [blame] | 179 | 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; |
brianosman | b9c5137 | 2016-09-15 11:09:45 -0700 | [diff] [blame] | 189 | } |
| 190 | |
Brian Osman | fe3e858 | 2017-10-20 11:27:49 -0400 | [diff] [blame] | 191 | GrColor4f GrColorSpaceXform::unclampedXform(const GrColor4f& srcColor) { |
Brian Osman | f06ead9 | 2017-10-30 13:47:41 -0400 | [diff] [blame] | 192 | // 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 Osman | fe3e858 | 2017-10-20 11:27:49 -0400 | [diff] [blame] | 205 | return result; |
| 206 | } |
| 207 | |
| 208 | GrColor4f GrColorSpaceXform::clampedXform(const GrColor4f& srcColor) { |
| 209 | GrColor4f result = this->unclampedXform(srcColor); |
Brian Osman | 0164896 | 2016-12-01 17:04:43 -0500 | [diff] [blame] | 210 | for (int i = 0; i < 4; ++i) { |
Brian Osman | fe3e858 | 2017-10-20 11:27:49 -0400 | [diff] [blame] | 211 | // We always operate on unpremul colors, so clamp to [0,1]. |
Brian Osman | 0164896 | 2016-12-01 17:04:43 -0500 | [diff] [blame] | 212 | result.fRGBA[i] = SkTPin(result.fRGBA[i], 0.0f, 1.0f); |
| 213 | } |
brianosman | 5a7ae7e | 2016-09-12 12:07:25 -0700 | [diff] [blame] | 214 | return result; |
| 215 | } |
Brian Osman | c4f93ca | 2017-10-17 17:15:52 -0400 | [diff] [blame] | 216 | |
| 217 | ////////////////////////////////////////////////////////////////////////////// |
| 218 | |
| 219 | class GrGLColorSpaceXformEffect : public GrGLSLFragmentProcessor { |
| 220 | public: |
| 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 | |
| 237 | private: |
| 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 | |
| 253 | GrColorSpaceXformEffect::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 | |
| 260 | std::unique_ptr<GrFragmentProcessor> GrColorSpaceXformEffect::clone() const { |
| 261 | return std::unique_ptr<GrFragmentProcessor>( |
| 262 | new GrColorSpaceXformEffect(this->childProcessor(0).clone(), fColorXform)); |
| 263 | } |
| 264 | |
| 265 | bool GrColorSpaceXformEffect::onIsEqual(const GrFragmentProcessor& s) const { |
| 266 | const GrColorSpaceXformEffect& other = s.cast<GrColorSpaceXformEffect>(); |
| 267 | return GrColorSpaceXform::Equals(fColorXform.get(), other.fColorXform.get()); |
| 268 | } |
| 269 | |
| 270 | void GrColorSpaceXformEffect::onGetGLSLProcessorKey(const GrShaderCaps& caps, |
| 271 | GrProcessorKeyBuilder* b) const { |
| 272 | b->add32(GrColorSpaceXform::XformKey(fColorXform.get())); |
| 273 | } |
| 274 | |
| 275 | GrGLSLFragmentProcessor* GrColorSpaceXformEffect::onCreateGLSLInstance() const { |
| 276 | return new GrGLColorSpaceXformEffect(); |
| 277 | } |
| 278 | |
| 279 | GrFragmentProcessor::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 | |
| 292 | std::unique_ptr<GrFragmentProcessor> GrColorSpaceXformEffect::Make( |
| 293 | std::unique_ptr<GrFragmentProcessor> child, |
| 294 | const SkColorSpace* src, |
Brian Osman | f06ead9 | 2017-10-30 13:47:41 -0400 | [diff] [blame] | 295 | GrPixelConfig srcConfig, |
Brian Osman | c4f93ca | 2017-10-17 17:15:52 -0400 | [diff] [blame] | 296 | const SkColorSpace* dst) { |
| 297 | if (!child) { |
| 298 | return nullptr; |
| 299 | } |
| 300 | |
Brian Osman | f06ead9 | 2017-10-30 13:47:41 -0400 | [diff] [blame] | 301 | auto colorXform = GrColorSpaceXform::Make(src, srcConfig, dst); |
Brian Osman | c4f93ca | 2017-10-17 17:15:52 -0400 | [diff] [blame] | 302 | if (colorXform) { |
| 303 | return std::unique_ptr<GrFragmentProcessor>( |
| 304 | new GrColorSpaceXformEffect(std::move(child), std::move(colorXform))); |
| 305 | } else { |
| 306 | return child; |
| 307 | } |
| 308 | } |