/* * Copyright 2018 Google Inc. * * Use of this source code is governed by a BSD-style license that can be * found in the LICENSE file. */ #ifndef GrCCPathCache_DEFINED #define GrCCPathCache_DEFINED #include "include/private/SkTHash.h" #include "src/core/SkExchange.h" #include "src/core/SkTInternalLList.h" #include "src/gpu/ccpr/GrCCAtlas.h" #include "src/gpu/ccpr/GrCCPathProcessor.h" #include "src/gpu/geometry/GrShape.h" class GrCCPathCacheEntry; class GrShape; /** * This class implements an LRU cache that maps from GrShape to GrCCPathCacheEntry objects. Shapes * are only given one entry in the cache, so any time they are accessed with a different matrix, the * old entry gets evicted. */ class GrCCPathCache { public: GrCCPathCache(uint32_t contextUniqueID); ~GrCCPathCache(); class Key : public SkPathRef::GenIDChangeListener { public: static sk_sp Make(uint32_t pathCacheUniqueID, int dataCountU32, const void* data = nullptr); uint32_t pathCacheUniqueID() const { return fPathCacheUniqueID; } int dataSizeInBytes() const { return fDataSizeInBytes; } const uint32_t* data() const; void resetDataCountU32(int dataCountU32) { SkASSERT(dataCountU32 <= fDataReserveCountU32); fDataSizeInBytes = dataCountU32 * sizeof(uint32_t); } uint32_t* data(); bool operator==(const Key& that) const { return fDataSizeInBytes == that.fDataSizeInBytes && !memcmp(this->data(), that.data(), fDataSizeInBytes); } // Called when our corresponding path is modified or deleted. Not threadsafe. void onChange() override; private: Key(uint32_t pathCacheUniqueID, int dataCountU32) : fPathCacheUniqueID(pathCacheUniqueID) , fDataSizeInBytes(dataCountU32 * sizeof(uint32_t)) SkDEBUGCODE(, fDataReserveCountU32(dataCountU32)) { SkASSERT(SK_InvalidUniqueID != fPathCacheUniqueID); } const uint32_t fPathCacheUniqueID; int fDataSizeInBytes; SkDEBUGCODE(const int fDataReserveCountU32); // The GrShape's unstyled key is stored as a variable-length footer to this class. GetKey // provides access to it. }; // Stores the components of a transformation that affect a path mask (i.e. everything but // integer translation). During construction, any integer portions of the matrix's translate are // shaved off and returned to the caller. The caller is responsible for those integer shifts. struct MaskTransform { MaskTransform(const SkMatrix& m, SkIVector* shift); float fMatrix2x2[4]; #ifndef SK_BUILD_FOR_ANDROID_FRAMEWORK // Except on AOSP, cache hits must have matching subpixel portions of their view matrix. // On AOSP we follow after HWUI and ignore the subpixel translate. float fSubpixelTranslate[2]; #endif }; // Represents a ref on a GrCCPathCacheEntry that should only be used during the current flush. class OnFlushEntryRef : SkNoncopyable { public: static OnFlushEntryRef OnFlushRef(GrCCPathCacheEntry*); OnFlushEntryRef() = default; OnFlushEntryRef(OnFlushEntryRef&& ref) : fEntry(skstd::exchange(ref.fEntry, nullptr)) {} ~OnFlushEntryRef(); GrCCPathCacheEntry* get() const { return fEntry; } GrCCPathCacheEntry* operator->() const { return fEntry; } GrCCPathCacheEntry& operator*() const { return *fEntry; } explicit operator bool() const { return fEntry; } void operator=(OnFlushEntryRef&& ref) { fEntry = skstd::exchange(ref.fEntry, nullptr); } private: OnFlushEntryRef(GrCCPathCacheEntry* entry) : fEntry(entry) {} GrCCPathCacheEntry* fEntry = nullptr; }; // Finds an entry in the cache that matches the given shape and transformation matrix. // 'maskShift' is filled with an integer post-translate that the caller must apply when drawing // the entry's mask to the device. // // NOTE: Shapes are only given one entry, so any time they are accessed with a new // transformation, the old entry gets evicted. OnFlushEntryRef find(GrOnFlushResourceProvider*, const GrShape&, const SkIRect& clippedDrawBounds, const SkMatrix& viewMatrix, SkIVector* maskShift); void doPreFlushProcessing(); void purgeEntriesOlderThan(GrProxyProvider*, const GrStdSteadyClock::time_point& purgeTime); // As we evict entries from our local path cache, we accumulate a list of invalidated atlas // textures. This call purges the invalidated atlas textures from the mainline GrResourceCache. // This call is available with two different "provider" objects, to accomodate whatever might // be available at the callsite. void purgeInvalidatedAtlasTextures(GrOnFlushResourceProvider*); void purgeInvalidatedAtlasTextures(GrProxyProvider*); private: // This is a special ref ptr for GrCCPathCacheEntry, used by the hash table. It provides static // methods for SkTHash, and can only be moved. This guarantees the hash table holds exactly one // reference for each entry. Also, when a HashNode goes out of scope, that means it is exiting // the hash table. We take that opportunity to remove it from the LRU list and do some cleanup. class HashNode : SkNoncopyable { public: static const Key& GetKey(const HashNode&); inline static uint32_t Hash(const Key& key) { return GrResourceKeyHash(key.data(), key.dataSizeInBytes()); } HashNode() = default; HashNode(GrCCPathCache*, sk_sp, const MaskTransform&, const GrShape&); HashNode(HashNode&& node) : fPathCache(node.fPathCache), fEntry(std::move(node.fEntry)) { SkASSERT(!node.fEntry); } ~HashNode(); void operator=(HashNode&& node); GrCCPathCacheEntry* entry() const { return fEntry.get(); } private: GrCCPathCache* fPathCache = nullptr; sk_sp fEntry; }; GrStdSteadyClock::time_point quickPerFlushTimestamp() { // time_point::min() means it's time to update fPerFlushTimestamp with a newer clock read. if (GrStdSteadyClock::time_point::min() == fPerFlushTimestamp) { fPerFlushTimestamp = GrStdSteadyClock::now(); } return fPerFlushTimestamp; } void evict(const GrCCPathCache::Key&, GrCCPathCacheEntry* = nullptr); // Evicts all the cache entries whose keys have been queued up in fInvalidatedKeysInbox via // SkPath listeners. void evictInvalidatedCacheKeys(); const uint32_t fContextUniqueID; SkTHashTable fHashTable; SkTInternalLList fLRU; SkMessageBus>::Inbox fInvalidatedKeysInbox; sk_sp fScratchKey; // Reused for creating a temporary key in the find() method. // We only read the clock once per flush, and cache it in this variable. This prevents us from // excessive clock reads for cache timestamps that might degrade performance. GrStdSteadyClock::time_point fPerFlushTimestamp = GrStdSteadyClock::time_point::min(); // As we evict entries from our local path cache, we accumulate lists of invalidated atlas // textures in these two members. We hold these until we purge them from the GrResourceCache // (e.g. via purgeInvalidatedAtlasTextures().) SkSTArray<4, sk_sp> fInvalidatedProxies; SkSTArray<4, GrUniqueKey> fInvalidatedProxyUniqueKeys; friend class GrCCCachedAtlas; // To append to fInvalidatedProxies, fInvalidatedProxyUniqueKeys. public: const SkTHashTable& testingOnly_getHashTable() const; const SkTInternalLList& testingOnly_getLRU() const; }; /** * This class stores all the data necessary to draw a specific path + matrix combination from their * corresponding cached atlas. */ class GrCCPathCacheEntry : public GrNonAtomicRef { public: SK_DECLARE_INTERNAL_LLIST_INTERFACE(GrCCPathCacheEntry); ~GrCCPathCacheEntry() { SkASSERT(this->hasBeenEvicted()); // Should have called GrCCPathCache::evict(). SkASSERT(!fCachedAtlas); SkASSERT(0 == fOnFlushRefCnt); } const GrCCPathCache::Key& cacheKey() const { SkASSERT(fCacheKey); return *fCacheKey; } // The number of flushes during which this specific entry (path + matrix combination) has been // pulled from the path cache. If a path is pulled from the cache more than once in a single // flush, the hit count is only incremented once. // // If the entry did not previously exist, its hit count will be 1. int hitCount() const { return fHitCount; } // The accumulative region of the path that has been drawn during the lifetime of this cache // entry (as defined by the 'clippedDrawBounds' parameter for GrCCPathCache::find). const SkIRect& hitRect() const { return fHitRect; } const GrCCCachedAtlas* cachedAtlas() const { return fCachedAtlas.get(); } const SkIRect& devIBounds() const { return fDevIBounds; } int width() const { return fDevIBounds.width(); } int height() const { return fDevIBounds.height(); } enum class ReleaseAtlasResult : bool { kNone, kDidInvalidateFromCache }; // Called once our path has been rendered into the mainline CCPR (fp16, coverage count) atlas. // The caller will stash this atlas texture away after drawing, and during the next flush, // recover it and attempt to copy any paths that got reused into permanent 8-bit atlases. void setCoverageCountAtlas( GrOnFlushResourceProvider*, GrCCAtlas*, const SkIVector& atlasOffset, const GrOctoBounds& octoBounds, const SkIRect& devIBounds, const SkIVector& maskShift); // Called once our path mask has been copied into a permanent, 8-bit atlas. This method points // the entry at the new atlas and updates the GrCCCCachedAtlas data. ReleaseAtlasResult upgradeToLiteralCoverageAtlas(GrCCPathCache*, GrOnFlushResourceProvider*, GrCCAtlas*, const SkIVector& newAtlasOffset); private: using MaskTransform = GrCCPathCache::MaskTransform; GrCCPathCacheEntry(sk_sp cacheKey, const MaskTransform& maskTransform) : fCacheKey(std::move(cacheKey)), fMaskTransform(maskTransform) { } bool hasBeenEvicted() const { return fCacheKey->shouldUnregisterFromPath(); } // Resets this entry back to not having an atlas, and purges its previous atlas texture from the // resource cache if needed. ReleaseAtlasResult releaseCachedAtlas(GrCCPathCache*); sk_sp fCacheKey; GrStdSteadyClock::time_point fTimestamp; int fHitCount = 0; SkIRect fHitRect = SkIRect::MakeEmpty(); sk_sp fCachedAtlas; SkIVector fAtlasOffset; MaskTransform fMaskTransform; GrOctoBounds fOctoBounds; SkIRect fDevIBounds; int fOnFlushRefCnt = 0; friend class GrCCPathCache; friend void GrCCPathProcessor::Instance::set(const GrCCPathCacheEntry&, const SkIVector&, uint64_t color, GrFillRule); // To access data. public: int testingOnly_peekOnFlushRefCnt() const; }; /** * Encapsulates the data for an atlas whose texture is stored in the mainline GrResourceCache. Many * instances of GrCCPathCacheEntry will reference the same GrCCCachedAtlas. * * We use this object to track the percentage of the original atlas pixels that could still ever * potentially be reused (i.e., those which still represent an extant path). When the percentage * of useful pixels drops below 50%, we purge the entire texture from the resource cache. * * This object also holds a ref on the atlas's actual texture proxy during flush. When * fOnFlushRefCnt decrements back down to zero, we release fOnFlushProxy and reset it back to null. */ class GrCCCachedAtlas : public GrNonAtomicRef { public: using ReleaseAtlasResult = GrCCPathCacheEntry::ReleaseAtlasResult; GrCCCachedAtlas(GrCCAtlas::CoverageType type, const GrUniqueKey& textureKey, sk_sp onFlushProxy) : fCoverageType(type) , fTextureKey(textureKey) , fOnFlushProxy(std::move(onFlushProxy)) {} ~GrCCCachedAtlas() { SkASSERT(!fOnFlushProxy); SkASSERT(!fOnFlushRefCnt); } GrCCAtlas::CoverageType coverageType() const { return fCoverageType; } const GrUniqueKey& textureKey() const { return fTextureKey; } GrTextureProxy* getOnFlushProxy() const { return fOnFlushProxy.get(); } void setOnFlushProxy(sk_sp proxy) { SkASSERT(!fOnFlushProxy); fOnFlushProxy = std::move(proxy); } void addPathPixels(int numPixels) { fNumPathPixels += numPixels; } ReleaseAtlasResult invalidatePathPixels(GrCCPathCache*, int numPixels); int peekOnFlushRefCnt() const { return fOnFlushRefCnt; } void incrOnFlushRefCnt(int count = 1) const { SkASSERT(count > 0); SkASSERT(fOnFlushProxy); fOnFlushRefCnt += count; } void decrOnFlushRefCnt(int count = 1) const; private: const GrCCAtlas::CoverageType fCoverageType; const GrUniqueKey fTextureKey; int fNumPathPixels = 0; int fNumInvalidatedPathPixels = 0; bool fIsInvalidatedFromResourceCache = false; mutable sk_sp fOnFlushProxy; mutable int fOnFlushRefCnt = 0; public: int testingOnly_peekOnFlushRefCnt() const; }; inline GrCCPathCache::HashNode::HashNode(GrCCPathCache* pathCache, sk_sp key, const MaskTransform& m, const GrShape& shape) : fPathCache(pathCache) , fEntry(new GrCCPathCacheEntry(key, m)) { SkASSERT(shape.hasUnstyledKey()); shape.addGenIDChangeListener(std::move(key)); } inline const GrCCPathCache::Key& GrCCPathCache::HashNode::GetKey( const GrCCPathCache::HashNode& node) { return *node.entry()->fCacheKey; } inline GrCCPathCache::HashNode::~HashNode() { SkASSERT(!fEntry || fEntry->hasBeenEvicted()); // Should have called GrCCPathCache::evict(). } inline void GrCCPathCache::HashNode::operator=(HashNode&& node) { SkASSERT(!fEntry || fEntry->hasBeenEvicted()); // Should have called GrCCPathCache::evict(). fEntry = skstd::exchange(node.fEntry, nullptr); } inline void GrCCPathProcessor::Instance::set( const GrCCPathCacheEntry& entry, const SkIVector& shift, uint64_t color, GrFillRule fillRule) { float dx = (float)shift.fX, dy = (float)shift.fY; this->set(entry.fOctoBounds.makeOffset(dx, dy), entry.fAtlasOffset - shift, color, fillRule); } #endif