1 /*
2 * Copyright 2015 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 #include "src/text/gpu/StrikeCache.h"
8
9 #include "include/private/base/SkAssert.h"
10 #include "include/private/base/SkDebug.h"
11 #include "include/private/chromium/SkChromeRemoteGlyphCache.h"
12 #include "src/base/SkArenaAlloc.h"
13 #include "src/core/SkGlyph.h"
14 #include "src/core/SkReadBuffer.h"
15 #include "src/core/SkStrikeCache.h"
16 #include "src/core/SkStrikeSpec.h"
17 #include "src/text/StrikeForGPU.h"
18 #include "src/text/gpu/Glyph.h"
19
20 #include <algorithm>
21 #include <optional>
22 #include <utility>
23
24 class SkStrike;
25
26 namespace sktext::gpu {
27
~StrikeCache()28 StrikeCache::~StrikeCache() {
29 this->freeAll();
30 }
31
freeAll()32 void StrikeCache::freeAll() {
33 this->internalPurge(fTotalMemoryUsed);
34 }
35
findOrCreateStrike(const SkStrikeSpec & strikeSpec)36 sk_sp<TextStrike> StrikeCache::findOrCreateStrike(const SkStrikeSpec& strikeSpec) {
37 if (sk_sp<TextStrike>* cached = fCache.find(strikeSpec.descriptor())) {
38 return *cached;
39 }
40 sk_sp<TextStrike> strike = this->generateStrike(strikeSpec);
41 this->internalPurge();
42
43 return strike;
44 }
45
internalFindStrikeOrNull(const SkDescriptor & desc)46 sk_sp<TextStrike> StrikeCache::internalFindStrikeOrNull(const SkDescriptor& desc) {
47 // Check head because it is likely the strike we are looking for.
48 if (fHead != nullptr && fHead->getDescriptor() == desc) { return sk_ref_sp(fHead); }
49
50 // Do the heavy search looking for the strike.
51 sk_sp<TextStrike>* strikeHandle = fCache.find(desc);
52 if (strikeHandle == nullptr) { return nullptr; }
53 TextStrike* strikePtr = strikeHandle->get();
54 SkASSERT(strikePtr != nullptr);
55 if (fHead != strikePtr) {
56 // Make most recently used
57 strikePtr->fPrev->fNext = strikePtr->fNext;
58 if (strikePtr->fNext != nullptr) {
59 strikePtr->fNext->fPrev = strikePtr->fPrev;
60 } else {
61 fTail = strikePtr->fPrev;
62 }
63 fHead->fPrev = strikePtr;
64 strikePtr->fNext = fHead;
65 strikePtr->fPrev = nullptr;
66 fHead = strikePtr;
67 }
68 return sk_ref_sp(strikePtr);
69 }
70
generateStrike(const SkStrikeSpec & strikeSpec)71 sk_sp<TextStrike> StrikeCache::generateStrike(const SkStrikeSpec& strikeSpec) {
72 sk_sp<TextStrike> strike = sk_make_sp<TextStrike>(this, strikeSpec);
73 this->internalAttachToHead(strike);
74 return strike;
75 }
76
internalPurge(size_t minBytesNeeded)77 size_t StrikeCache::internalPurge(size_t minBytesNeeded) {
78 size_t bytesNeeded = 0;
79 if (fTotalMemoryUsed > fCacheSizeLimit) {
80 bytesNeeded = fTotalMemoryUsed - fCacheSizeLimit;
81 }
82 bytesNeeded = std::max(bytesNeeded, minBytesNeeded);
83 if (bytesNeeded) {
84 // no small purges!
85 bytesNeeded = std::max(bytesNeeded, fTotalMemoryUsed >> 2);
86 }
87
88 int countNeeded = 0;
89 if (fCacheCount > fCacheCountLimit) {
90 countNeeded = fCacheCount - fCacheCountLimit;
91 // no small purges!
92 countNeeded = std::max(countNeeded, fCacheCount >> 2);
93 }
94
95 // early exit
96 if (!countNeeded && !bytesNeeded) {
97 return 0;
98 }
99
100 size_t bytesFreed = 0;
101 int countFreed = 0;
102
103 // Start at the tail and proceed backwards deleting; the list is in LRU
104 // order, with unimportant entries at the tail.
105 TextStrike* strike = fTail;
106 while (strike != nullptr && (bytesFreed < bytesNeeded || countFreed < countNeeded)) {
107 TextStrike* prev = strike->fPrev;
108
109 bytesFreed += strike->fMemoryUsed;
110 countFreed += 1;
111 this->internalRemoveStrike(strike);
112
113 strike = prev;
114 }
115
116 this->validate();
117
118 #ifdef SPEW_PURGE_STATUS
119 if (countFreed) {
120 SkDebugf("purging %dK from font cache [%d entries]\n",
121 (int)(bytesFreed >> 10), countFreed);
122 }
123 #endif
124
125 return bytesFreed;
126 }
127
internalAttachToHead(sk_sp<TextStrike> strike)128 void StrikeCache::internalAttachToHead(sk_sp<TextStrike> strike) {
129 SkASSERT(fCache.find(strike->getDescriptor()) == nullptr);
130 TextStrike* strikePtr = strike.get();
131 fCache.set(std::move(strike));
132 SkASSERT(nullptr == strikePtr->fPrev && nullptr == strikePtr->fNext);
133
134 fCacheCount += 1;
135 fTotalMemoryUsed += strikePtr->fMemoryUsed;
136
137 if (fHead != nullptr) {
138 fHead->fPrev = strikePtr;
139 strikePtr->fNext = fHead;
140 }
141
142 if (fTail == nullptr) {
143 fTail = strikePtr;
144 }
145
146 fHead = strikePtr; // Transfer ownership of strike to the cache list.
147 }
148
internalRemoveStrike(TextStrike * strike)149 void StrikeCache::internalRemoveStrike(TextStrike* strike) {
150 SkASSERT(fCacheCount > 0);
151 fCacheCount -= 1;
152 fTotalMemoryUsed -= strike->fMemoryUsed;
153
154 if (strike->fPrev) {
155 strike->fPrev->fNext = strike->fNext;
156 } else {
157 fHead = strike->fNext;
158 }
159 if (strike->fNext) {
160 strike->fNext->fPrev = strike->fPrev;
161 } else {
162 fTail = strike->fPrev;
163 }
164
165 strike->fPrev = strike->fNext = nullptr;
166 strike->fRemoved = true;
167 fCache.remove(strike->getDescriptor());
168 }
169
validate() const170 void StrikeCache::validate() const {
171 #ifdef SK_DEBUG
172 size_t computedBytes = 0;
173 int computedCount = 0;
174
175 const TextStrike* strike = fHead;
176 while (strike != nullptr) {
177 computedBytes += strike->fMemoryUsed;
178 computedCount += 1;
179 SkASSERT(fCache.findOrNull(strike->getDescriptor()) != nullptr);
180 strike = strike->fNext;
181 }
182
183 if (fCacheCount != computedCount) {
184 SkDebugf("fCacheCount: %d, computedCount: %d", fCacheCount, computedCount);
185 SK_ABORT("fCacheCount != computedCount");
186 }
187 if (fTotalMemoryUsed != computedBytes) {
188 SkDebugf("fTotalMemoryUsed: %zu, computedBytes: %zu", fTotalMemoryUsed, computedBytes);
189 SK_ABORT("fTotalMemoryUsed == computedBytes");
190 }
191 #endif
192 }
193
GetKey(const sk_sp<TextStrike> & strike)194 const SkDescriptor& StrikeCache::HashTraits::GetKey(const sk_sp<TextStrike>& strike) {
195 return strike->fStrikeSpec.descriptor();
196 }
197
Hash(const SkDescriptor & descriptor)198 uint32_t StrikeCache::HashTraits::Hash(const SkDescriptor& descriptor) {
199 return descriptor.getChecksum();
200 }
201
TextStrike(StrikeCache * strikeCache,const SkStrikeSpec & strikeSpec)202 TextStrike::TextStrike(StrikeCache* strikeCache, const SkStrikeSpec& strikeSpec)
203 : fStrikeCache(strikeCache)
204 , fStrikeSpec{strikeSpec} {}
205
getGlyph(SkPackedGlyphID packedGlyphID)206 Glyph* TextStrike::getGlyph(SkPackedGlyphID packedGlyphID) {
207 Glyph* glyph = fCache.findOrNull(packedGlyphID);
208 if (glyph == nullptr) {
209 glyph = fAlloc.make<Glyph>(packedGlyphID);
210 fCache.set(glyph);
211 fMemoryUsed += sizeof(Glyph);
212 if (!fRemoved) {
213 fStrikeCache->fTotalMemoryUsed += sizeof(Glyph);
214 }
215 }
216 return glyph;
217 }
218
GetKey(const Glyph * glyph)219 const SkPackedGlyphID& TextStrike::HashTraits::GetKey(const Glyph* glyph) {
220 return glyph->fPackedID;
221 }
222
Hash(SkPackedGlyphID key)223 uint32_t TextStrike::HashTraits::Hash(SkPackedGlyphID key) {
224 return key.hash();
225 }
226
227 } // namespace sktext::gpu
228
229 namespace sktext {
MakeFromBuffer(SkReadBuffer & buffer,const SkStrikeClient * client,SkStrikeCache * strikeCache)230 std::optional<SkStrikePromise> SkStrikePromise::MakeFromBuffer(
231 SkReadBuffer& buffer, const SkStrikeClient* client, SkStrikeCache* strikeCache) {
232 std::optional<SkAutoDescriptor> descriptor = SkAutoDescriptor::MakeFromBuffer(buffer);
233 if (!buffer.validate(descriptor.has_value())) {
234 return std::nullopt;
235 }
236
237 // If there is a client, then this from a different process. Translate the SkTypefaceID from
238 // the strike server (Renderer) process to strike client (GPU) process.
239 if (client != nullptr) {
240 if (!client->translateTypefaceID(&descriptor.value())) {
241 return std::nullopt;
242 }
243 }
244
245 sk_sp<SkStrike> strike = strikeCache->findStrike(*descriptor->getDesc());
246 SkASSERT(strike != nullptr);
247 if (!buffer.validate(strike != nullptr)) {
248 return std::nullopt;
249 }
250
251 return SkStrikePromise{std::move(strike)};
252 }
253 } // namespace sktext
254