• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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