blob: a49d7585be6bf7f61c3a687f9065bcdcfce6e029 [file] [log] [blame]
* 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 {
GrCCPathCache(uint32_t contextUniqueID);
class Key : public SkPathRef::GenIDChangeListener {
static sk_sp<Key> 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(),, fDataSizeInBytes);
// Called when our corresponding path is modified or deleted. Not threadsafe.
void onChange() override;
// TODO(b/30449950): use sized delete once P0722R3 is available
static void operator delete(void* p);
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];
// 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];
// Represents a ref on a GrCCPathCacheEntry that should only be used during the current flush.
class OnFlushEntryRef : SkNoncopyable {
static OnFlushEntryRef OnFlushRef(GrCCPathCacheEntry*);
OnFlushEntryRef() = default;
OnFlushEntryRef(OnFlushEntryRef&& ref) : fEntry(skstd::exchange(ref.fEntry, nullptr)) {}
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); }
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*);
// 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 {
static const Key& GetKey(const HashNode&);
inline static uint32_t Hash(const Key& key) {
return GrResourceKeyHash(, key.dataSizeInBytes());
HashNode() = default;
HashNode(GrCCPathCache*, sk_sp<Key>, const MaskTransform&, const GrShape&);
HashNode(HashNode&& node)
: fPathCache(node.fPathCache), fEntry(std::move(node.fEntry)) {
void operator=(HashNode&& node);
GrCCPathCacheEntry* entry() const { return fEntry.get(); }
GrCCPathCache* fPathCache = nullptr;
sk_sp<GrCCPathCacheEntry> 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<HashNode, const Key&> fHashTable;
SkTInternalLList<GrCCPathCacheEntry> fLRU;
SkMessageBus<sk_sp<Key>>::Inbox fInvalidatedKeysInbox;
sk_sp<Key> 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<GrTextureProxy>> fInvalidatedProxies;
SkSTArray<4, GrUniqueKey> fInvalidatedProxyUniqueKeys;
friend class GrCCCachedAtlas; // To append to fInvalidatedProxies, fInvalidatedProxyUniqueKeys.
const SkTHashTable<HashNode, const Key&>& testingOnly_getHashTable() const;
const SkTInternalLList<GrCCPathCacheEntry>& 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<GrCCPathCacheEntry> {
~GrCCPathCacheEntry() {
SkASSERT(this->hasBeenEvicted()); // Should have called GrCCPathCache::evict().
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 {
// 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);
using MaskTransform = GrCCPathCache::MaskTransform;
GrCCPathCacheEntry(sk_sp<GrCCPathCache::Key> 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<GrCCPathCache::Key> fCacheKey;
GrStdSteadyClock::time_point fTimestamp;
int fHitCount = 0;
SkIRect fHitRect = SkIRect::MakeEmpty();
sk_sp<GrCCCachedAtlas> 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.
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<GrCCCachedAtlas> {
using ReleaseAtlasResult = GrCCPathCacheEntry::ReleaseAtlasResult;
GrCCCachedAtlas(GrCCAtlas::CoverageType type, const GrUniqueKey& textureKey,
sk_sp<GrTextureProxy> onFlushProxy)
: fCoverageType(type)
, fTextureKey(textureKey)
, fOnFlushProxy(std::move(onFlushProxy)) {}
~GrCCCachedAtlas() {
GrCCAtlas::CoverageType coverageType() const { return fCoverageType; }
const GrUniqueKey& textureKey() const { return fTextureKey; }
GrTextureProxy* getOnFlushProxy() const { return fOnFlushProxy.get(); }
void setOnFlushProxy(sk_sp<GrTextureProxy> proxy) {
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);
fOnFlushRefCnt += count;
void decrOnFlushRefCnt(int count = 1) const;
const GrCCAtlas::CoverageType fCoverageType;
const GrUniqueKey fTextureKey;
int fNumPathPixels = 0;
int fNumInvalidatedPathPixels = 0;
bool fIsInvalidatedFromResourceCache = false;
mutable sk_sp<GrTextureProxy> fOnFlushProxy;
mutable int fOnFlushRefCnt = 0;
int testingOnly_peekOnFlushRefCnt() const;
inline GrCCPathCache::HashNode::HashNode(GrCCPathCache* pathCache, sk_sp<Key> key,
const MaskTransform& m, const GrShape& shape)
: fPathCache(pathCache)
, fEntry(new GrCCPathCacheEntry(key, m)) {
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);