Home | History | Annotate | Download | only in ccpr
      1 /*
      2  * Copyright 2018 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 "GrCCPathCache.h"
      9 
     10 #include "GrOnFlushResourceProvider.h"
     11 #include "GrProxyProvider.h"
     12 #include "SkNx.h"
     13 
     14 static constexpr int kMaxKeyDataCountU32 = 256;  // 1kB of uint32_t's.
     15 
     16 DECLARE_SKMESSAGEBUS_MESSAGE(sk_sp<GrCCPathCache::Key>);
     17 
     18 static inline uint32_t next_path_cache_id() {
     19     static std::atomic<uint32_t> gNextID(1);
     20     for (;;) {
     21         uint32_t id = gNextID.fetch_add(+1, std::memory_order_acquire);
     22         if (SK_InvalidUniqueID != id) {
     23             return id;
     24         }
     25     }
     26 }
     27 
     28 static inline bool SkShouldPostMessageToBus(
     29         const sk_sp<GrCCPathCache::Key>& key, uint32_t msgBusUniqueID) {
     30     return key->pathCacheUniqueID() == msgBusUniqueID;
     31 }
     32 
     33 // The maximum number of cache entries we allow in our own cache.
     34 static constexpr int kMaxCacheCount = 1 << 16;
     35 
     36 
     37 GrCCPathCache::MaskTransform::MaskTransform(const SkMatrix& m, SkIVector* shift)
     38         : fMatrix2x2{m.getScaleX(), m.getSkewX(), m.getSkewY(), m.getScaleY()} {
     39     SkASSERT(!m.hasPerspective());
     40     Sk2f translate = Sk2f(m.getTranslateX(), m.getTranslateY());
     41     Sk2f transFloor;
     42 #ifdef SK_BUILD_FOR_ANDROID_FRAMEWORK
     43     // On Android framework we pre-round view matrix translates to integers for better caching.
     44     transFloor = translate;
     45 #else
     46     transFloor = translate.floor();
     47     (translate - transFloor).store(fSubpixelTranslate);
     48 #endif
     49     shift->set((int)transFloor[0], (int)transFloor[1]);
     50     SkASSERT((float)shift->fX == transFloor[0]);  // Make sure transFloor had integer values.
     51     SkASSERT((float)shift->fY == transFloor[1]);
     52 }
     53 
     54 inline static bool fuzzy_equals(const GrCCPathCache::MaskTransform& a,
     55                                 const GrCCPathCache::MaskTransform& b) {
     56     if ((Sk4f::Load(a.fMatrix2x2) != Sk4f::Load(b.fMatrix2x2)).anyTrue()) {
     57         return false;
     58     }
     59 #ifndef SK_BUILD_FOR_ANDROID_FRAMEWORK
     60     if (((Sk2f::Load(a.fSubpixelTranslate) -
     61           Sk2f::Load(b.fSubpixelTranslate)).abs() > 1.f/256).anyTrue()) {
     62         return false;
     63     }
     64 #endif
     65     return true;
     66 }
     67 
     68 sk_sp<GrCCPathCache::Key> GrCCPathCache::Key::Make(uint32_t pathCacheUniqueID,
     69                                                    int dataCountU32, const void* data) {
     70     void* memory = ::operator new (sizeof(Key) + dataCountU32 * sizeof(uint32_t));
     71     sk_sp<GrCCPathCache::Key> key(new (memory) Key(pathCacheUniqueID, dataCountU32));
     72     if (data) {
     73         memcpy(key->data(), data, key->dataSizeInBytes());
     74     }
     75     return key;
     76 }
     77 
     78 const uint32_t* GrCCPathCache::Key::data() const {
     79     // The shape key is a variable-length footer to the entry allocation.
     80     return reinterpret_cast<const uint32_t*>(reinterpret_cast<const char*>(this) + sizeof(Key));
     81 }
     82 
     83 uint32_t* GrCCPathCache::Key::data() {
     84     // The shape key is a variable-length footer to the entry allocation.
     85     return reinterpret_cast<uint32_t*>(reinterpret_cast<char*>(this) + sizeof(Key));
     86 }
     87 
     88 void GrCCPathCache::Key::onChange() {
     89     // Our key's corresponding path was invalidated. Post a thread-safe eviction message.
     90     SkMessageBus<sk_sp<Key>>::Post(sk_ref_sp(this));
     91 }
     92 
     93 GrCCPathCache::GrCCPathCache(uint32_t contextUniqueID)
     94         : fContextUniqueID(contextUniqueID)
     95         , fInvalidatedKeysInbox(next_path_cache_id())
     96         , fScratchKey(Key::Make(fInvalidatedKeysInbox.uniqueID(), kMaxKeyDataCountU32)) {
     97 }
     98 
     99 GrCCPathCache::~GrCCPathCache() {
    100     while (!fLRU.isEmpty()) {
    101         this->evict(*fLRU.tail()->fCacheKey, fLRU.tail());
    102     }
    103     SkASSERT(0 == fHashTable.count());  // Ensure the hash table and LRU list were coherent.
    104 
    105     // Now take all the atlas textures we just invalidated and purge them from the GrResourceCache.
    106     // We just purge via message bus since we don't have any access to the resource cache right now.
    107     for (sk_sp<GrTextureProxy>& proxy : fInvalidatedProxies) {
    108         SkMessageBus<GrUniqueKeyInvalidatedMessage>::Post(
    109                 GrUniqueKeyInvalidatedMessage(proxy->getUniqueKey(), fContextUniqueID));
    110     }
    111     for (const GrUniqueKey& key : fInvalidatedProxyUniqueKeys) {
    112         SkMessageBus<GrUniqueKeyInvalidatedMessage>::Post(
    113                 GrUniqueKeyInvalidatedMessage(key, fContextUniqueID));
    114     }
    115 }
    116 
    117 namespace {
    118 
    119 // Produces a key that accounts both for a shape's path geometry, as well as any stroke/style.
    120 class WriteKeyHelper {
    121 public:
    122     static constexpr int kStrokeWidthIdx = 0;
    123     static constexpr int kStrokeMiterIdx = 1;
    124     static constexpr int kStrokeCapJoinIdx = 2;
    125     static constexpr int kShapeUnstyledKeyIdx = 3;
    126 
    127     WriteKeyHelper(const GrShape& shape) : fShapeUnstyledKeyCount(shape.unstyledKeySize()) {}
    128 
    129     // Returns the total number of uint32_t's to allocate for the key.
    130     int allocCountU32() const { return kShapeUnstyledKeyIdx + fShapeUnstyledKeyCount; }
    131 
    132     // Writes the key data to out[].
    133     void write(const GrShape& shape, uint32_t* out) {
    134         // Stroke key.
    135         // We don't use GrStyle::WriteKey() because it does not account for hairlines.
    136         // http://skbug.com/8273
    137         SkASSERT(!shape.style().hasPathEffect());
    138         const SkStrokeRec& stroke = shape.style().strokeRec();
    139         if (stroke.isFillStyle()) {
    140             // Use a value for width that won't collide with a valid fp32 value >= 0.
    141             out[kStrokeWidthIdx] = ~0;
    142             out[kStrokeMiterIdx] = out[kStrokeCapJoinIdx] = 0;
    143         } else {
    144             float width = stroke.getWidth(), miterLimit = stroke.getMiter();
    145             memcpy(&out[kStrokeWidthIdx], &width, sizeof(float));
    146             memcpy(&out[kStrokeMiterIdx], &miterLimit, sizeof(float));
    147             out[kStrokeCapJoinIdx] = (stroke.getCap() << 16) | stroke.getJoin();
    148             GR_STATIC_ASSERT(sizeof(out[kStrokeWidthIdx]) == sizeof(float));
    149         }
    150 
    151         // Shape unstyled key.
    152         shape.writeUnstyledKey(&out[kShapeUnstyledKeyIdx]);
    153     }
    154 
    155 private:
    156     int fShapeUnstyledKeyCount;
    157 };
    158 
    159 }
    160 
    161 GrCCPathCache::OnFlushEntryRef GrCCPathCache::find(
    162         GrOnFlushResourceProvider* onFlushRP, const GrShape& shape,
    163         const SkIRect& clippedDrawBounds, const SkMatrix& viewMatrix, SkIVector* maskShift) {
    164     if (!shape.hasUnstyledKey()) {
    165         return OnFlushEntryRef();
    166     }
    167 
    168     WriteKeyHelper writeKeyHelper(shape);
    169     if (writeKeyHelper.allocCountU32() > kMaxKeyDataCountU32) {
    170         return OnFlushEntryRef();
    171     }
    172 
    173     SkASSERT(fScratchKey->unique());
    174     fScratchKey->resetDataCountU32(writeKeyHelper.allocCountU32());
    175     writeKeyHelper.write(shape, fScratchKey->data());
    176 
    177     MaskTransform m(viewMatrix, maskShift);
    178     GrCCPathCacheEntry* entry = nullptr;
    179     if (HashNode* node = fHashTable.find(*fScratchKey)) {
    180         entry = node->entry();
    181         SkASSERT(fLRU.isInList(entry));
    182 
    183         if (!fuzzy_equals(m, entry->fMaskTransform)) {
    184             // The path was reused with an incompatible matrix.
    185             if (entry->unique()) {
    186                 // This entry is unique: recycle it instead of deleting and malloc-ing a new one.
    187                 SkASSERT(0 == entry->fOnFlushRefCnt);  // Because we are unique.
    188                 entry->fMaskTransform = m;
    189                 entry->fHitCount = 0;
    190                 entry->fHitRect = SkIRect::MakeEmpty();
    191                 entry->releaseCachedAtlas(this);
    192             } else {
    193                 this->evict(*fScratchKey);
    194                 entry = nullptr;
    195             }
    196         }
    197     }
    198 
    199     if (!entry) {
    200         if (fHashTable.count() >= kMaxCacheCount) {
    201             SkDEBUGCODE(HashNode* node = fHashTable.find(*fLRU.tail()->fCacheKey));
    202             SkASSERT(node && node->entry() == fLRU.tail());
    203             this->evict(*fLRU.tail()->fCacheKey);  // We've exceeded our limit.
    204         }
    205 
    206         // Create a new entry in the cache.
    207         sk_sp<Key> permanentKey = Key::Make(fInvalidatedKeysInbox.uniqueID(),
    208                                             writeKeyHelper.allocCountU32(), fScratchKey->data());
    209         SkASSERT(*permanentKey == *fScratchKey);
    210         SkASSERT(!fHashTable.find(*permanentKey));
    211         entry = fHashTable.set(HashNode(this, std::move(permanentKey), m, shape))->entry();
    212 
    213         SkASSERT(fHashTable.count() <= kMaxCacheCount);
    214     } else {
    215         fLRU.remove(entry);  // Will be re-added at head.
    216     }
    217 
    218     SkDEBUGCODE(HashNode* node = fHashTable.find(*fScratchKey));
    219     SkASSERT(node && node->entry() == entry);
    220     fLRU.addToHead(entry);
    221 
    222     if (0 == entry->fOnFlushRefCnt) {
    223         // Only update the time stamp and hit count if we haven't seen this entry yet during the
    224         // current flush.
    225         entry->fTimestamp = this->quickPerFlushTimestamp();
    226         ++entry->fHitCount;
    227 
    228         if (entry->fCachedAtlas) {
    229             SkASSERT(SkToBool(entry->fCachedAtlas->peekOnFlushRefCnt())
    230                              == SkToBool(entry->fCachedAtlas->getOnFlushProxy()));
    231             if (!entry->fCachedAtlas->getOnFlushProxy()) {
    232                 entry->fCachedAtlas->setOnFlushProxy(
    233                     onFlushRP->findOrCreateProxyByUniqueKey(entry->fCachedAtlas->textureKey(),
    234                                                             GrCCAtlas::kTextureOrigin));
    235             }
    236             if (!entry->fCachedAtlas->getOnFlushProxy()) {
    237                 // Our atlas's backing texture got purged from the GrResourceCache. Release the
    238                 // cached atlas.
    239                 entry->releaseCachedAtlas(this);
    240             }
    241         }
    242     }
    243     entry->fHitRect.join(clippedDrawBounds.makeOffset(-maskShift->x(), -maskShift->y()));
    244     SkASSERT(!entry->fCachedAtlas || entry->fCachedAtlas->getOnFlushProxy());
    245     return OnFlushEntryRef::OnFlushRef(entry);
    246 }
    247 
    248 void GrCCPathCache::evict(const GrCCPathCache::Key& key, GrCCPathCacheEntry* entry) {
    249     if (!entry) {
    250         HashNode* node = fHashTable.find(key);
    251         SkASSERT(node);
    252         entry = node->entry();
    253     }
    254     SkASSERT(*entry->fCacheKey == key);
    255     SkASSERT(!entry->hasBeenEvicted());
    256     entry->fCacheKey->markShouldUnregisterFromPath();  // Unregister the path listener.
    257     entry->releaseCachedAtlas(this);
    258     fLRU.remove(entry);
    259     fHashTable.remove(key);
    260 }
    261 
    262 void GrCCPathCache::doPreFlushProcessing() {
    263     this->evictInvalidatedCacheKeys();
    264 
    265     // Mark the per-flush timestamp as needing to be updated with a newer clock reading.
    266     fPerFlushTimestamp = GrStdSteadyClock::time_point::min();
    267 }
    268 
    269 void GrCCPathCache::purgeEntriesOlderThan(GrProxyProvider* proxyProvider,
    270                                           const GrStdSteadyClock::time_point& purgeTime) {
    271     this->evictInvalidatedCacheKeys();
    272 
    273 #ifdef SK_DEBUG
    274     auto lastTimestamp = (fLRU.isEmpty())
    275             ? GrStdSteadyClock::time_point::max()
    276             : fLRU.tail()->fTimestamp;
    277 #endif
    278 
    279     // Evict every entry from our local path cache whose timestamp is older than purgeTime.
    280     while (!fLRU.isEmpty() && fLRU.tail()->fTimestamp < purgeTime) {
    281 #ifdef SK_DEBUG
    282         // Verify that fLRU is sorted by timestamp.
    283         auto timestamp = fLRU.tail()->fTimestamp;
    284         SkASSERT(timestamp >= lastTimestamp);
    285         lastTimestamp = timestamp;
    286 #endif
    287         this->evict(*fLRU.tail()->fCacheKey);
    288     }
    289 
    290     // Now take all the atlas textures we just invalidated and purge them from the GrResourceCache.
    291     this->purgeInvalidatedAtlasTextures(proxyProvider);
    292 }
    293 
    294 void GrCCPathCache::purgeInvalidatedAtlasTextures(GrOnFlushResourceProvider* onFlushRP) {
    295     for (sk_sp<GrTextureProxy>& proxy : fInvalidatedProxies) {
    296         onFlushRP->removeUniqueKeyFromProxy(proxy.get());
    297     }
    298     fInvalidatedProxies.reset();
    299 
    300     for (const GrUniqueKey& key : fInvalidatedProxyUniqueKeys) {
    301         onFlushRP->processInvalidUniqueKey(key);
    302     }
    303     fInvalidatedProxyUniqueKeys.reset();
    304 }
    305 
    306 void GrCCPathCache::purgeInvalidatedAtlasTextures(GrProxyProvider* proxyProvider) {
    307     for (sk_sp<GrTextureProxy>& proxy : fInvalidatedProxies) {
    308         proxyProvider->removeUniqueKeyFromProxy(proxy.get());
    309     }
    310     fInvalidatedProxies.reset();
    311 
    312     for (const GrUniqueKey& key : fInvalidatedProxyUniqueKeys) {
    313         proxyProvider->processInvalidUniqueKey(key, nullptr,
    314                                                GrProxyProvider::InvalidateGPUResource::kYes);
    315     }
    316     fInvalidatedProxyUniqueKeys.reset();
    317 }
    318 
    319 void GrCCPathCache::evictInvalidatedCacheKeys() {
    320     SkTArray<sk_sp<Key>> invalidatedKeys;
    321     fInvalidatedKeysInbox.poll(&invalidatedKeys);
    322     for (const sk_sp<Key>& key : invalidatedKeys) {
    323         bool isInCache = !key->shouldUnregisterFromPath();  // Gets set upon exiting the cache.
    324         if (isInCache) {
    325             this->evict(*key);
    326         }
    327     }
    328 }
    329 
    330 GrCCPathCache::OnFlushEntryRef
    331 GrCCPathCache::OnFlushEntryRef::OnFlushRef(GrCCPathCacheEntry* entry) {
    332     entry->ref();
    333     ++entry->fOnFlushRefCnt;
    334     if (entry->fCachedAtlas) {
    335         entry->fCachedAtlas->incrOnFlushRefCnt();
    336     }
    337     return OnFlushEntryRef(entry);
    338 }
    339 
    340 GrCCPathCache::OnFlushEntryRef::~OnFlushEntryRef() {
    341     if (!fEntry) {
    342         return;
    343     }
    344     --fEntry->fOnFlushRefCnt;
    345     SkASSERT(fEntry->fOnFlushRefCnt >= 0);
    346     if (fEntry->fCachedAtlas) {
    347         fEntry->fCachedAtlas->decrOnFlushRefCnt();
    348     }
    349     fEntry->unref();
    350 }
    351 
    352 
    353 void GrCCPathCacheEntry::setCoverageCountAtlas(
    354         GrOnFlushResourceProvider* onFlushRP, GrCCAtlas* atlas, const SkIVector& atlasOffset,
    355         const SkRect& devBounds, const SkRect& devBounds45, const SkIRect& devIBounds,
    356         const SkIVector& maskShift) {
    357     SkASSERT(fOnFlushRefCnt > 0);
    358     SkASSERT(!fCachedAtlas);  // Otherwise we would need to call releaseCachedAtlas().
    359 
    360     if (this->hasBeenEvicted()) {
    361         // This entry will never be found in the path cache again. Don't bother trying to save an
    362         // atlas texture for it in the GrResourceCache.
    363         return;
    364     }
    365 
    366     fCachedAtlas = atlas->refOrMakeCachedAtlas(onFlushRP);
    367     fCachedAtlas->incrOnFlushRefCnt(fOnFlushRefCnt);
    368     fCachedAtlas->addPathPixels(devIBounds.height() * devIBounds.width());
    369 
    370     fAtlasOffset = atlasOffset + maskShift;
    371 
    372     float dx = (float)maskShift.fX, dy = (float)maskShift.fY;
    373     fDevBounds = devBounds.makeOffset(-dx, -dy);
    374     fDevBounds45 = GrCCPathProcessor::MakeOffset45(devBounds45, -dx, -dy);
    375     fDevIBounds = devIBounds.makeOffset(-maskShift.fX, -maskShift.fY);
    376 }
    377 
    378 GrCCPathCacheEntry::ReleaseAtlasResult GrCCPathCacheEntry::upgradeToLiteralCoverageAtlas(
    379         GrCCPathCache* pathCache, GrOnFlushResourceProvider* onFlushRP, GrCCAtlas* atlas,
    380         const SkIVector& newAtlasOffset) {
    381     SkASSERT(!this->hasBeenEvicted());
    382     SkASSERT(fOnFlushRefCnt > 0);
    383     SkASSERT(fCachedAtlas);
    384     SkASSERT(GrCCAtlas::CoverageType::kFP16_CoverageCount == fCachedAtlas->coverageType());
    385 
    386     ReleaseAtlasResult releaseAtlasResult = this->releaseCachedAtlas(pathCache);
    387 
    388     fCachedAtlas = atlas->refOrMakeCachedAtlas(onFlushRP);
    389     fCachedAtlas->incrOnFlushRefCnt(fOnFlushRefCnt);
    390     fCachedAtlas->addPathPixels(this->height() * this->width());
    391 
    392     fAtlasOffset = newAtlasOffset;
    393     return releaseAtlasResult;
    394 }
    395 
    396 GrCCPathCacheEntry::ReleaseAtlasResult GrCCPathCacheEntry::releaseCachedAtlas(
    397         GrCCPathCache* pathCache) {
    398     ReleaseAtlasResult result = ReleaseAtlasResult::kNone;
    399     if (fCachedAtlas) {
    400         result = fCachedAtlas->invalidatePathPixels(pathCache, this->height() * this->width());
    401         if (fOnFlushRefCnt) {
    402             SkASSERT(fOnFlushRefCnt > 0);
    403             fCachedAtlas->decrOnFlushRefCnt(fOnFlushRefCnt);
    404         }
    405         fCachedAtlas = nullptr;
    406     }
    407     return result;
    408 }
    409 
    410 GrCCPathCacheEntry::ReleaseAtlasResult GrCCCachedAtlas::invalidatePathPixels(
    411         GrCCPathCache* pathCache, int numPixels) {
    412     // Mark the pixels invalid in the cached atlas texture.
    413     fNumInvalidatedPathPixels += numPixels;
    414     SkASSERT(fNumInvalidatedPathPixels <= fNumPathPixels);
    415     if (!fIsInvalidatedFromResourceCache && fNumInvalidatedPathPixels >= fNumPathPixels / 2) {
    416         // Too many invalidated pixels: purge the atlas texture from the resource cache.
    417         if (fOnFlushProxy) {
    418             // Don't clear (or std::move) fOnFlushProxy. Other path cache entries might still have a
    419             // reference on this atlas and expect to use our proxy during the current flush.
    420             // fOnFlushProxy will be cleared once fOnFlushRefCnt decrements to zero.
    421             pathCache->fInvalidatedProxies.push_back(fOnFlushProxy);
    422         } else {
    423             pathCache->fInvalidatedProxyUniqueKeys.push_back(fTextureKey);
    424         }
    425         fIsInvalidatedFromResourceCache = true;
    426         return ReleaseAtlasResult::kDidInvalidateFromCache;
    427     }
    428     return ReleaseAtlasResult::kNone;
    429 }
    430 
    431 void GrCCCachedAtlas::decrOnFlushRefCnt(int count) const {
    432     SkASSERT(count > 0);
    433     fOnFlushRefCnt -= count;
    434     SkASSERT(fOnFlushRefCnt >= 0);
    435     if (0 == fOnFlushRefCnt) {
    436         // Don't hold the actual proxy past the end of the current flush.
    437         SkASSERT(fOnFlushProxy);
    438         fOnFlushProxy = nullptr;
    439     }
    440 }
    441