1 /*
2 * Copyright 2022 Google LLC
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 "src/gpu/graphite/GlobalCache.h"
9
10 #include "include/private/base/SkTArray.h"
11 #include "src/core/SkTraceEvent.h"
12 #include "src/gpu/graphite/ComputePipeline.h"
13 #include "src/gpu/graphite/ContextUtils.h"
14 #include "src/gpu/graphite/GraphicsPipeline.h"
15 #include "src/gpu/graphite/GraphicsPipelineDesc.h"
16 #include "src/gpu/graphite/RenderPassDesc.h"
17 #include "src/gpu/graphite/Resource.h"
18 #include "src/gpu/graphite/SharedContext.h"
19
20 #if defined(SK_ENABLE_PRECOMPILE)
21 #include "src/gpu/graphite/precompile/SerializationUtils.h"
22 #endif
23
24 namespace {
25
next_compilation_id()26 uint32_t next_compilation_id() {
27 static std::atomic<uint32_t> nextId{0};
28 // Not worried about overflow since we don't expect that many GraphicsPipelines.
29 // Even if it wraps around to 0, this is solely for debug logging.
30 return nextId.fetch_add(1, std::memory_order_relaxed);
31 }
32
33 #if defined(GPU_TEST_UTILS)
34 // TODO(b/391403921): get rid of this special case once we've got color space transform shader
35 // specialization more under control
36 constexpr int kGlobalGraphicsPipelineCacheSizeLimit = 1 << 13;
37 constexpr int kGlobalComputePipelineCacheSizeLimit = 256;
38
39 #else
40 // TODO: find a good value for these limits
41 constexpr int kGlobalGraphicsPipelineCacheSizeLimit = 256;
42 constexpr int kGlobalComputePipelineCacheSizeLimit = 256;
43 #endif
44
45 } // anonymous namespce
46
47 namespace skgpu::graphite {
48
GlobalCache()49 GlobalCache::GlobalCache()
50 : fGraphicsPipelineCache(kGlobalGraphicsPipelineCacheSizeLimit)
51 , fComputePipelineCache(kGlobalComputePipelineCacheSizeLimit) {}
52
~GlobalCache()53 GlobalCache::~GlobalCache() {
54 // These should have been cleared out earlier by deleteResources().
55 SkDEBUGCODE(SkAutoSpinlock lock{ fSpinLock });
56 SkASSERT(fGraphicsPipelineCache.count() == 0);
57 SkASSERT(fComputePipelineCache.count() == 0);
58 SkASSERT(fStaticResource.empty());
59 }
60
setPipelineCallback(PipelineCallback callback,PipelineCallbackContext context)61 void GlobalCache::setPipelineCallback(PipelineCallback callback, PipelineCallbackContext context) {
62 SkAutoSpinlock lock{fSpinLock};
63
64 fPipelineCallback = callback;
65 fPipelineCallbackContext = context;
66 }
67
invokePipelineCallback(SharedContext * sharedContext,const GraphicsPipelineDesc & pipelineDesc,const RenderPassDesc & renderPassDesc)68 void GlobalCache::invokePipelineCallback(SharedContext* sharedContext,
69 const GraphicsPipelineDesc& pipelineDesc,
70 const RenderPassDesc& renderPassDesc) {
71 #if defined(SK_ENABLE_PRECOMPILE)
72 PipelineCallback tmpCB = nullptr;
73 PipelineCallbackContext tmpContext = nullptr;
74
75 {
76 // We want to get a consistent callback/context pair but not invoke the callback
77 // w/in our lock.
78 SkAutoSpinlock lock{fSpinLock};
79
80 tmpCB = fPipelineCallback;
81 tmpContext = fPipelineCallbackContext;
82 }
83
84 if (tmpCB) {
85 sk_sp<SkData> data = PipelineDescToData(sharedContext->caps(),
86 sharedContext->shaderCodeDictionary(),
87 pipelineDesc,
88 renderPassDesc);
89
90 // Enable this to thoroughly test Pipeline serialization
91 #if 0
92 {
93 // Check that the PipelineDesc round trips through serialization
94 GraphicsPipelineDesc readBackPipelineDesc;
95 RenderPassDesc readBackRenderPassDesc;
96
97 SkAssertResult(DataToPipelineDesc(sharedContext->caps(),
98 sharedContext->shaderCodeDictionary(),
99 data.get(),
100 &readBackPipelineDesc,
101 &readBackRenderPassDesc));
102
103 DumpPipelineDesc("invokeCallback - original", sharedContext->shaderCodeDictionary(),
104 pipelineDesc, renderPassDesc);
105
106 DumpPipelineDesc("invokeCallback - readback", sharedContext->shaderCodeDictionary(),
107 readBackPipelineDesc, readBackRenderPassDesc);
108
109 SkASSERT(ComparePipelineDescs(pipelineDesc, renderPassDesc,
110 readBackPipelineDesc, readBackRenderPassDesc));
111 }
112 #endif
113
114 if (data) {
115 tmpCB(tmpContext, std::move(data));
116 }
117 }
118 #endif
119 }
120
deleteResources()121 void GlobalCache::deleteResources() {
122 SkAutoSpinlock lock{ fSpinLock };
123
124 fGraphicsPipelineCache.reset();
125 fComputePipelineCache.reset();
126 fStaticResource.clear();
127 }
128
LogPurge(const UniqueKey & key,sk_sp<GraphicsPipeline> * p)129 void GlobalCache::LogPurge(const UniqueKey& key, sk_sp<GraphicsPipeline>* p) {
130 #if defined(SK_PIPELINE_LIFETIME_LOGGING)
131 // A "Bad" Purge is one where the Pipeline was never retrieved from the Cache (i.e., unused
132 // overgeneration).
133 static const char* kNames[2][2] = { { "BadPurgedN", "BadPurgedP" },
134 { "PurgedN", "PurgedP"} };
135
136 TRACE_EVENT_INSTANT2("skia.gpu",
137 TRACE_STR_STATIC(kNames[(*p)->wasUsed()][(*p)->fromPrecompile()]),
138 TRACE_EVENT_SCOPE_THREAD,
139 "key", key.hash(),
140 "compilationID", (*p)->getPipelineInfo().fCompilationID);
141 #endif
142 }
143
findGraphicsPipeline(const UniqueKey & key,SkEnumBitMask<PipelineCreationFlags> pipelineCreationFlags,uint32_t * compilationID)144 sk_sp<GraphicsPipeline> GlobalCache::findGraphicsPipeline(
145 const UniqueKey& key,
146 SkEnumBitMask<PipelineCreationFlags> pipelineCreationFlags,
147 uint32_t *compilationID) {
148
149 [[maybe_unused]] bool forPrecompile =
150 SkToBool(pipelineCreationFlags & PipelineCreationFlags::kForPrecompilation);
151
152 SkAutoSpinlock lock{fSpinLock};
153
154 sk_sp<GraphicsPipeline>* entry = fGraphicsPipelineCache.find(key);
155 if (entry) {
156 #if defined(GPU_TEST_UTILS)
157 ++fStats.fGraphicsCacheHits;
158 #endif
159
160 (*entry)->updateAccessTime();
161 (*entry)->markUsed();
162
163 #if defined(SK_PIPELINE_LIFETIME_LOGGING)
164 static const char* kNames[2] = { "CacheHitForN", "CacheHitForP" };
165 TRACE_EVENT_INSTANT2("skia.gpu",
166 TRACE_STR_STATIC(kNames[forPrecompile]),
167 TRACE_EVENT_SCOPE_THREAD,
168 "key", key.hash(),
169 "compilationID", (*entry)->getPipelineInfo().fCompilationID);
170 #endif
171
172 return *entry;
173 } else {
174 #if defined(GPU_TEST_UTILS)
175 ++fStats.fGraphicsCacheMisses;
176 #endif
177
178 if (compilationID) {
179 // This is a cache miss so we know the next step is going to be a Pipeline
180 // creation. Create the compilationID here so we can use it in the "CacheMissFor"
181 // trace event.
182 *compilationID = next_compilation_id();
183
184 #if defined(SK_PIPELINE_LIFETIME_LOGGING)
185 static const char* kNames[2] = { "CacheMissForN", "CacheMissForP" };
186 TRACE_EVENT_INSTANT2("skia.gpu",
187 TRACE_STR_STATIC(kNames[forPrecompile]),
188 TRACE_EVENT_SCOPE_THREAD,
189 "key", key.hash(),
190 "compilationID", *compilationID);
191 #endif
192 }
193
194 return nullptr;
195 }
196 }
197
198 #if SK_HISTOGRAMS_ENABLED
199 // These values are persisted to logs. Entries should not be renumbered and
200 // numeric values should never be reused.
201 //
202 // LINT.IfChange(PipelineCreationRace)
203 enum class PipelineCreationRace {
204 // The <First>Over<Second> enum names mean the first type of compilation won a compilation race
205 // over the second type of compilation and ended up in the cache.
206 kNormalOverNormal = 0, // can happen w/ multiple Recorders on different threads
207 kNormalOverPrecompilation = 1,
208 kPrecompilationOverNormal = 2,
209 kPrecompilationOverPrecompilation = 3, // can happen with multiple threaded precompilation calls
210
211 kMaxValue = kPrecompilationOverPrecompilation,
212 };
213 // LINT.ThenChange(//tools/metrics/histograms/enums.xml:SkiaPipelineCreationRace)
214
215 [[maybe_unused]] static constexpr int kPipelineCreationRaceCount =
216 static_cast<int>(PipelineCreationRace::kMaxValue) + 1;
217 #endif // SK_HISTOGRAMS_ENABLED
218
addGraphicsPipeline(const UniqueKey & key,sk_sp<GraphicsPipeline> pipeline)219 sk_sp<GraphicsPipeline> GlobalCache::addGraphicsPipeline(const UniqueKey& key,
220 sk_sp<GraphicsPipeline> pipeline) {
221 SkAutoSpinlock lock{fSpinLock};
222
223 sk_sp<GraphicsPipeline>* entry = fGraphicsPipelineCache.find(key);
224 if (!entry) {
225 // No equivalent pipeline was stored in the cache between a previous call to
226 // findGraphicsPipeline() that returned null (triggering the pipeline creation) and this
227 // later adding to the cache.
228 entry = fGraphicsPipelineCache.insert(key, std::move(pipeline));
229
230 #if defined(GPU_TEST_UTILS)
231 ++fStats.fGraphicsCacheAdditions;
232 #endif
233
234 // Precompile Pipelines are only marked as used when they get a cache hit in
235 // findGraphicsPipeline
236 if (!(*entry)->fromPrecompile()) {
237 (*entry)->updateAccessTime();
238 (*entry)->markUsed();
239 }
240
241 #if defined(SK_PIPELINE_LIFETIME_LOGGING)
242 static const char* kNames[2] = { "AddedN", "AddedP" };
243 TRACE_EVENT_INSTANT2("skia.gpu",
244 TRACE_STR_STATIC(kNames[(*entry)->fromPrecompile()]),
245 TRACE_EVENT_SCOPE_THREAD,
246 "key", key.hash(),
247 "compilationID", (*entry)->getPipelineInfo().fCompilationID);
248 #endif
249 } else {
250 #if defined(GPU_TEST_UTILS)
251 // else there was a race creating the same pipeline and this thread lost, so return
252 // the winner
253 ++fStats.fGraphicsRaces;
254 #endif
255
256 [[maybe_unused]] int race = (*entry)->fromPrecompile() * 2 + pipeline->fromPrecompile();
257
258 #if defined(SK_PIPELINE_LIFETIME_LOGGING)
259 static const char* kNames[4] = {
260 "NWonRaceOverN",
261 "NWonRaceOverP",
262 "PWonRaceOverN",
263 "PWonRaceOverP"
264 };
265 TRACE_EVENT_INSTANT2("skia.gpu",
266 TRACE_STR_STATIC(kNames[race]),
267 TRACE_EVENT_SCOPE_THREAD,
268 "key", key.hash(),
269 // The losing compilation
270 "compilationID", pipeline->getPipelineInfo().fCompilationID);
271 #endif
272
273 #if SK_HISTOGRAMS_ENABLED
274 SK_HISTOGRAM_ENUMERATION("Graphite.PipelineCreationRace",
275 race,
276 kPipelineCreationRaceCount);
277 #endif
278 }
279 return *entry;
280 }
281
282
purgePipelinesNotUsedSince(StdSteadyClock::time_point purgeTime)283 void GlobalCache::purgePipelinesNotUsedSince(StdSteadyClock::time_point purgeTime) {
284 SkAutoSpinlock lock{fSpinLock};
285
286 skia_private::TArray<skgpu::UniqueKey> toRemove;
287
288 // This is probably fine for now but is looping from most-recently-used to least-recently-used.
289 // It seems like a reverse loop with an early out could be more efficient.
290 fGraphicsPipelineCache.foreach([&toRemove, purgeTime](const UniqueKey* key,
291 const sk_sp<GraphicsPipeline>* pipeline) {
292 if ((*pipeline)->lastAccessTime() < purgeTime) {
293 toRemove.push_back(*key);
294 }
295 });
296
297 for (const skgpu::UniqueKey& k : toRemove) {
298 #if defined(GPU_TEST_UTILS)
299 ++fStats.fGraphicsPurges;
300 #endif
301 fGraphicsPipelineCache.remove(k);
302 }
303
304 // TODO: add purging of Compute Pipelines (b/389073204)
305 }
306
307 #if defined(GPU_TEST_UTILS)
numGraphicsPipelines() const308 int GlobalCache::numGraphicsPipelines() const {
309 SkAutoSpinlock lock{fSpinLock};
310
311 return fGraphicsPipelineCache.count();
312 }
313
resetGraphicsPipelines()314 void GlobalCache::resetGraphicsPipelines() {
315 SkAutoSpinlock lock{fSpinLock};
316
317 fGraphicsPipelineCache.reset();
318 }
319
forEachGraphicsPipeline(const std::function<void (const UniqueKey &,const GraphicsPipeline *)> & fn)320 void GlobalCache::forEachGraphicsPipeline(
321 const std::function<void(const UniqueKey&, const GraphicsPipeline*)>& fn) {
322 SkAutoSpinlock lock{fSpinLock};
323
324 fGraphicsPipelineCache.foreach([&](const UniqueKey* k, const sk_sp<GraphicsPipeline>* v) {
325 fn(*k, v->get());
326 });
327 }
328
getStats() const329 GlobalCache::PipelineStats GlobalCache::getStats() const {
330 SkAutoSpinlock lock{fSpinLock};
331
332 return fStats;
333 }
334 #endif // defined(GPU_TEST_UTILS)
335
findComputePipeline(const UniqueKey & key)336 sk_sp<ComputePipeline> GlobalCache::findComputePipeline(const UniqueKey& key) {
337 SkAutoSpinlock lock{fSpinLock};
338 sk_sp<ComputePipeline>* entry = fComputePipelineCache.find(key);
339 return entry ? *entry : nullptr;
340 }
341
addComputePipeline(const UniqueKey & key,sk_sp<ComputePipeline> pipeline)342 sk_sp<ComputePipeline> GlobalCache::addComputePipeline(const UniqueKey& key,
343 sk_sp<ComputePipeline> pipeline) {
344 SkAutoSpinlock lock{fSpinLock};
345 sk_sp<ComputePipeline>* entry = fComputePipelineCache.find(key);
346 if (!entry) {
347 entry = fComputePipelineCache.insert(key, std::move(pipeline));
348 }
349 return *entry;
350 }
351
addStaticResource(sk_sp<Resource> resource)352 void GlobalCache::addStaticResource(sk_sp<Resource> resource) {
353 SkAutoSpinlock lock{fSpinLock};
354 fStaticResource.push_back(std::move(resource));
355 }
356
357 } // namespace skgpu::graphite
358