blob: 0e6f4f055de530fdaa4c4c34fe6ae1a57ab29832 [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 "SkExchange.h"
#include "SkTHash.h"
#include "SkTInternalLList.h"
#include "ccpr/GrCCAtlas.h"
#include "ccpr/GrCCPathProcessor.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 {
#ifdef SK_DEBUG
~GrCCPathCache() {
// Ensure the hash table and LRU list are still coherent.
// 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];
enum class CreateIfAbsent : bool {
kNo = false,
kYes = true
// Finds an entry in the cache. Shapes are only given one entry, so any time they are accessed
// with a different MaskTransform, the old entry gets evicted.
sk_sp<GrCCPathCacheEntry> find(const GrShape&, const MaskTransform&,
CreateIfAbsent = CreateIfAbsent::kNo);
void evict(const GrCCPathCacheEntry*);
// Wrapper around a raw GrShape key that has a specialized operator==. Used by the hash table.
struct HashKey {
const uint32_t* fData;
friend bool operator==(const HashKey&, const HashKey&);
// This is a special ref ptr for GrCCPathCacheEntry, used by the hash table. It can only be
// moved, which guarantees the hash table holds exactly one reference for each entry. When a
// HashNode goes out of scope, it therefore means the entry has been evicted from the cache.
class HashNode : SkNoncopyable {
static HashKey GetKey(const HashNode& node) { return GetKey(node.fEntry); }
static HashKey GetKey(const GrCCPathCacheEntry*);
static uint32_t Hash(HashKey);
HashNode() = default;
HashNode(GrCCPathCache*, const MaskTransform&, const GrShape&);
HashNode(HashNode&& node) { fEntry = skstd::exchange(node.fEntry, nullptr); }
~HashNode(); // Called when fEntry (if not null) has been evicted from the cache.
HashNode& operator=(HashNode&&);
GrCCPathCacheEntry* entry() const { return fEntry; }
GrCCPathCacheEntry* fEntry = nullptr;
// The GrShape's unstyled key is stored as a variable-length footer to the 'fEntry'
// allocation. GetKey provides access to it.
SkTHashTable<HashNode, HashKey> fHashTable;
SkTInternalLList<GrCCPathCacheEntry> fLRU;
* This class stores all the data necessary to draw a specific path from its corresponding cached
* atlas.
class GrCCPathCacheEntry : public SkPathRef::GenIDChangeListener {
// Does this entry reference a permanent, 8-bit atlas that resides in the resource cache?
// (i.e. not a temporarily-stashed, fp16 coverage count atlas.)
bool hasCachedAtlas() const { return SkToBool(fCachedAtlasInfo); }
const SkIRect& devIBounds() const { return fDevIBounds; }
int width() const { return fDevIBounds.width(); }
int height() const { return fDevIBounds.height(); }
// 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 initAsStashedAtlas(const GrUniqueKey& atlasKey, const SkIVector& atlasOffset,
const SkRect& devBounds, const SkRect& devBounds45,
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 CachedAtlasInfo data.
void updateToCachedAtlas(const GrUniqueKey& atlasKey, const SkIVector& newAtlasOffset,
const GrUniqueKey& atlasKey() const { return fAtlasKey; }
void resetAtlasKeyAndInfo() {
// This is a utility for the caller to detect when a path gets drawn more than once during the
// same flush, with compatible matrices. Before adding a path to an atlas, the caller may check
// here to see if they have already placed the path previously during the same flush. The caller
// is required to reset all currFlushAtlas references back to null before any subsequent flush.
void setCurrFlushAtlas(const GrCCAtlas* currFlushAtlas) {
// This should not get called more than once in a single flush. Once fCurrFlushAtlas is
// non-null, it can only be set back to null (once the flush is over).
SkASSERT(!fCurrFlushAtlas || !currFlushAtlas);
fCurrFlushAtlas = currFlushAtlas;
const GrCCAtlas* currFlushAtlas() const { return fCurrFlushAtlas; }
using MaskTransform = GrCCPathCache::MaskTransform;
GrCCPathCacheEntry(GrCCPathCache* cache, const MaskTransform& m)
: fCacheWeakPtr(cache), fMaskTransform(m) {}
// Called when our corresponding path is modified or deleted.
void onChange() override;
GrCCPathCache* fCacheWeakPtr; // Gets manually reset to null by the path cache upon eviction.
const MaskTransform fMaskTransform;
GrUniqueKey fAtlasKey;
SkIVector fAtlasOffset;
// If null, then we are referencing a "stashed" atlas (see initAsStashedAtlas()).
sk_sp<GrCCAtlas::CachedAtlasInfo> fCachedAtlasInfo;
SkRect fDevBounds;
SkRect fDevBounds45;
SkIRect fDevIBounds;
// This field is for when a path gets drawn more than once during the same flush.
const GrCCAtlas* fCurrFlushAtlas = nullptr;
friend class GrCCPathCache;
friend void GrCCPathProcessor::Instance::set(const GrCCPathCacheEntry&, const SkIVector&,
uint32_t, DoEvenOddFill); // To access data.
inline void GrCCPathProcessor::Instance::set(const GrCCPathCacheEntry& entry,
const SkIVector& shift, GrColor color,
DoEvenOddFill doEvenOddFill) {
float dx = (float)shift.fX, dy = (float)shift.fY;
this->set(entry.fDevBounds.makeOffset(dx, dy), MakeOffset45(entry.fDevBounds45, dx, dy),
entry.fAtlasOffset - shift, color, doEvenOddFill);