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" 10 #include "SkColorSpacePriv.h" 11 #include "SkMatrix44.h" 12 #include "SkSpinlock.h" 13 #include "glsl/GrGLSLColorSpaceXformHelper.h" 14 #include "glsl/GrGLSLFragmentProcessor.h" 15 #include "glsl/GrGLSLFragmentShaderBuilder.h" 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 }; 58 59 GrColorSpaceXform::GrColorSpaceXform(const SkColorSpaceTransferFn& srcTransferFn, 60 const SkMatrix44& gamutXform, uint32_t flags) 61 : fSrcTransferFn(srcTransferFn), fGamutXform(gamutXform), fFlags(flags) {} 62 63 static SkSpinlock gColorSpaceXformCacheSpinlock; 64 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 70 return nullptr; 71 } 72 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 118 return nullptr; 119 } 120 121 const SkMatrix44* toXYZD50 = src->toXYZD50(); 122 const SkMatrix44* fromXYZD50 = dst->fromXYZD50(); 123 if (!toXYZD50 || !fromXYZD50) { 124 // Unsupported colour spaces -- cannot specify gamut as a matrix 125 return nullptr; 126 } 127 128 // Determine if a gamut xform is needed 129 uint32_t srcHash = src->toXYZD50Hash(); 130 uint32_t dstHash = dst->toXYZD50Hash(); 131 if (srcHash != dstHash) { 132 flags |= kApplyGamutXform_Flag; 133 } else { 134 SkASSERT(*toXYZD50 == *dst->toXYZD50() && "Hash collision"); 135 } 136 137 if (0 == flags) { 138 // Identical gamut and no transfer function - no conversion needed in this case 139 return nullptr; 140 } 141 142 auto makeXform = [srcTransferFn, fromXYZD50, toXYZD50, flags]() { 143 SkMatrix44 srcToDst(SkMatrix44::kUninitialized_Constructor); 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); 150 }; 151 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()) { 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); 161 sk_sp<GrColorSpaceXform> xform = gCache->findOrAdd(key, makeXform); 162 gColorSpaceXformCacheSpinlock.release(); 163 return xform; 164 } else { 165 // If our xform has non-gamut components, or we can't get the spin lock, just build it 166 return makeXform(); 167 } 168 } 169 170 bool GrColorSpaceXform::Equals(const GrColorSpaceXform* a, const GrColorSpaceXform* b) { 171 if (a == b) { 172 return true; 173 } 174 175 if (!a || !b || a->fFlags != b->fFlags) { 176 return false; 177 } 178 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; 189 } 190 191 GrColor4f GrColorSpaceXform::unclampedXform(const GrColor4f& srcColor) { 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 } 205 return result; 206 } 207 208 GrColor4f GrColorSpaceXform::clampedXform(const GrColor4f& srcColor) { 209 GrColor4f result = this->unclampedXform(srcColor); 210 for (int i = 0; i < 4; ++i) { 211 // We always operate on unpremul colors, so clamp to [0,1]. 212 result.fRGBA[i] = SkTPin(result.fRGBA[i], 0.0f, 1.0f); 213 } 214 return result; 215 } 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, 295 GrPixelConfig srcConfig, 296 const SkColorSpace* dst) { 297 if (!child) { 298 return nullptr; 299 } 300 301 auto colorXform = GrColorSpaceXform::Make(src, srcConfig, dst); 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 } 309