Home | History | Annotate | Download | only in gpu
      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