blob: a2b6416c1ed480773db2b3207abc0fae5b2a2b0e [file] [log] [blame]
Chris Dalton4da70192018-06-18 09:51:36 -06001/*
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 "GrShape.h"
11#include "SkNx.h"
12#include "ccpr/GrCCPathParser.h"
13
14// The maximum number of cache entries we allow in our own cache.
15static constexpr int kMaxCacheCount = 1 << 16;
16
17GrCCPathCache::MaskTransform::MaskTransform(const SkMatrix& m, SkIVector* shift)
18 : fMatrix2x2{m.getScaleX(), m.getSkewX(), m.getSkewY(), m.getScaleY()} {
19 SkASSERT(!m.hasPerspective());
20 Sk2f translate = Sk2f(m.getTranslateX(), m.getTranslateY());
21 Sk2f floor = translate.floor();
22 (translate - floor).store(fSubpixelTranslate);
23 shift->set((int)floor[0], (int)floor[1]);
24 SkASSERT((float)shift->fX == floor[0]);
25 SkASSERT((float)shift->fY == floor[1]);
26}
27
28inline static bool fuzzy_equals(const GrCCPathCache::MaskTransform& a,
29 const GrCCPathCache::MaskTransform& b) {
30 return (Sk4f::Load(a.fMatrix2x2) == Sk4f::Load(b.fMatrix2x2)).allTrue() &&
31 ((Sk2f::Load(a.fSubpixelTranslate) -
32 Sk2f::Load(b.fSubpixelTranslate)).abs() < 1.f/256).allTrue();
33}
34
35inline GrCCPathCache::HashNode::HashNode(GrCCPathCache* cache, const MaskTransform& m,
36 const GrShape& shape) {
37 SkASSERT(shape.hasUnstyledKey());
38
39 int keyLength = 1 + shape.unstyledKeySize();
40 void* mem = ::operator new (sizeof(GrCCPathCacheEntry) + keyLength * sizeof(uint32_t));
41 fEntry = new (mem) GrCCPathCacheEntry(cache, m);
42
43 // The shape key is a variable-length footer to the entry allocation.
44 uint32_t* keyData = (uint32_t*)((char*)mem + sizeof(GrCCPathCacheEntry));
45 keyData[0] = keyLength - 1;
46 shape.writeUnstyledKey(&keyData[1]);
47}
48
49inline bool operator==(const GrCCPathCache::HashKey& key1, const GrCCPathCache::HashKey& key2) {
50 return key1.fData[0] == key2.fData[0] &&
51 !memcmp(&key1.fData[1], &key2.fData[1], key1.fData[0] * sizeof(uint32_t));
52}
53
54inline GrCCPathCache::HashKey GrCCPathCache::HashNode::GetKey(const GrCCPathCacheEntry* entry) {
55 // The shape key is a variable-length footer to the entry allocation.
56 return HashKey{(const uint32_t*)((const char*)entry + sizeof(GrCCPathCacheEntry))};
57}
58
59inline uint32_t GrCCPathCache::HashNode::Hash(HashKey key) {
60 return GrResourceKeyHash(&key.fData[1], key.fData[0]);
61}
62
63GrCCPathCache::HashNode::~HashNode() {
64 if (!fEntry) {
65 return;
66 }
67
68 // Finalize our eviction from the path cache.
69 SkASSERT(fEntry->fCacheWeakPtr);
70 fEntry->fCacheWeakPtr->fLRU.remove(fEntry);
71 fEntry->fCacheWeakPtr = nullptr;
72
73 if (GrCCAtlas::CachedAtlasInfo* info = fEntry->fCachedAtlasInfo.get()) {
74 // Mark our own pixels invalid in the cached atlas texture now that we have been evicted.
75 info->fNumInvalidatedPathPixels += fEntry->height() * fEntry->width();
76 if (!info->fIsPurgedFromResourceCache &&
77 info->fNumInvalidatedPathPixels >= info->fNumPathPixels / 2) {
78 // Too many invalidated pixels: purge the atlas texture from the resource cache.
79 SkMessageBus<GrUniqueKeyInvalidatedMessage>::Post(
80 GrUniqueKeyInvalidatedMessage(fEntry->fAtlasKey));
81 info->fIsPurgedFromResourceCache = true;
82 }
83 }
84
85 fEntry->unref();
86}
87
88GrCCPathCache::HashNode& GrCCPathCache::HashNode::operator=(HashNode&& node) {
89 this->~HashNode();
90 return *new (this) HashNode(std::move(node));
91}
92
93sk_sp<GrCCPathCacheEntry> GrCCPathCache::find(const GrShape& shape, const MaskTransform& m,
94 CreateIfAbsent createIfAbsent) {
95 if (!shape.hasUnstyledKey()) {
96 return nullptr;
97 }
98
99 int keyLength = 1 + shape.unstyledKeySize();
100 SkAutoSTMalloc<GrShape::kMaxKeyFromDataVerbCnt * 4, uint32_t> keyData(keyLength);
101 keyData[0] = keyLength - 1;
102 shape.writeUnstyledKey(&keyData[1]);
103
104 GrCCPathCacheEntry* entry = nullptr;
105 if (HashNode* node = fHashTable.find({keyData.get()})) {
106 entry = node->entry();
107 SkASSERT(this == entry->fCacheWeakPtr);
108 if (!fuzzy_equals(m, entry->fMaskTransform)) {
109 this->evict(entry); // The path was reused with an incompatible matrix.
110 entry = nullptr;
111 }
112 }
113
114 if (!entry) {
115 if (CreateIfAbsent::kNo == createIfAbsent) {
116 return nullptr;
117 }
118 if (fHashTable.count() >= kMaxCacheCount) {
119 this->evict(fLRU.tail()); // We've exceeded our limit.
120 }
121 entry = fHashTable.set(HashNode(this, m, shape))->entry();
122 SkASSERT(fHashTable.count() <= kMaxCacheCount);
123 } else {
124 fLRU.remove(entry); // Will be re-added at head.
125 }
126
127 fLRU.addToHead(entry);
128 return sk_ref_sp(entry);
129}
130
131void GrCCPathCache::evict(const GrCCPathCacheEntry* entry) {
132 SkASSERT(entry);
133 SkASSERT(this == entry->fCacheWeakPtr);
134 SkASSERT(fLRU.isInList(entry));
135 SkASSERT(fHashTable.find(HashNode::GetKey(entry))->entry() == entry);
136
137 fHashTable.remove(HashNode::GetKey(entry)); // ~HashNode() handles the rest.
138}
139
140void GrCCPathCacheEntry::initAsStashedAtlas(const GrUniqueKey& atlasKey,
141 const SkIVector& atlasOffset, const SkRect& devBounds,
142 const SkRect& devBounds45, const SkIRect& devIBounds,
143 const SkIVector& maskShift) {
144 SkASSERT(atlasKey.isValid());
145 SkASSERT(!fCurrFlushAtlas); // Otherwise we should reuse the atlas from last time.
146
147 fAtlasKey = atlasKey;
148 fAtlasOffset = atlasOffset + maskShift;
149 SkASSERT(!fCachedAtlasInfo); // Otherwise they should have reused the cached atlas instead.
150
151 float dx = (float)maskShift.fX, dy = (float)maskShift.fY;
152 fDevBounds = devBounds.makeOffset(-dx, -dy);
153 fDevBounds45 = GrCCPathProcessor::MakeOffset45(devBounds45, -dx, -dy);
154 fDevIBounds = devIBounds.makeOffset(-maskShift.fX, -maskShift.fY);
155}
156
157void GrCCPathCacheEntry::updateToCachedAtlas(const GrUniqueKey& atlasKey,
158 const SkIVector& newAtlasOffset,
159 sk_sp<GrCCAtlas::CachedAtlasInfo> info) {
160 SkASSERT(atlasKey.isValid());
161 SkASSERT(!fCurrFlushAtlas); // Otherwise we should reuse the atlas from last time.
162
163 fAtlasKey = atlasKey;
164 fAtlasOffset = newAtlasOffset;
165
166 SkASSERT(!fCachedAtlasInfo); // Otherwise we need to invalidate our pixels in the old info.
167 fCachedAtlasInfo = std::move(info);
168 fCachedAtlasInfo->fNumPathPixels += this->height() * this->width();
169}
170
171void GrCCPathCacheEntry::onChange() {
172 // Our corresponding path was modified or deleted. Evict ourselves.
173 if (fCacheWeakPtr) {
174 fCacheWeakPtr->evict(this);
175 }
176}