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 "src/core/SkTraceEvent.h"
11 #include "src/gpu/graphite/ComputePipeline.h"
12 #include "src/gpu/graphite/ContextUtils.h"
13 #include "src/gpu/graphite/GraphicsPipeline.h"
14 #include "src/gpu/graphite/Resource.h"
15
16 namespace {
17
next_compilation_id()18 uint32_t next_compilation_id() {
19 static std::atomic<uint32_t> nextId{0};
20 // Not worried about overflow since we don't expect that many GraphicsPipelines.
21 // Even if it wraps around to 0, this is solely for debug logging.
22 return nextId.fetch_add(1, std::memory_order_relaxed);
23 }
24
25 } // anonymous namespce
26
27 namespace skgpu::graphite {
28
GlobalCache()29 GlobalCache::GlobalCache()
30 : fGraphicsPipelineCache(256) // TODO: find a good value for these limits
31 , fComputePipelineCache(256) {}
32
~GlobalCache()33 GlobalCache::~GlobalCache() {
34 // These should have been cleared out earlier by deleteResources().
35 SkDEBUGCODE(SkAutoSpinlock lock{ fSpinLock });
36 SkASSERT(fGraphicsPipelineCache.count() == 0);
37 SkASSERT(fComputePipelineCache.count() == 0);
38 SkASSERT(fStaticResource.empty());
39 }
40
deleteResources()41 void GlobalCache::deleteResources() {
42 SkAutoSpinlock lock{ fSpinLock };
43
44 fGraphicsPipelineCache.reset();
45 fComputePipelineCache.reset();
46 fStaticResource.clear();
47 }
48
LogPurge(const UniqueKey & key,sk_sp<GraphicsPipeline> * p)49 void GlobalCache::LogPurge(const UniqueKey& key, sk_sp<GraphicsPipeline>* p) {
50 #if defined(SK_PIPELINE_LIFETIME_LOGGING)
51 // A "Bad" Purge is one where the Pipeline was never retrieved from the Cache (i.e., unused
52 // overgeneration).
53 static const char* kNames[2][2] = { { "BadPurgedN", "BadPurgedP" },
54 { "PurgedN", "PurgedP"} };
55
56 TRACE_EVENT_INSTANT2("skia.gpu",
57 TRACE_STR_STATIC(kNames[(*p)->wasUsed()][(*p)->fromPrecompile()]),
58 TRACE_EVENT_SCOPE_THREAD,
59 "key", key.hash(),
60 "compilationID", (*p)->getPipelineInfo().fCompilationID);
61 #endif
62 }
63
findGraphicsPipeline(const UniqueKey & key,SkEnumBitMask<PipelineCreationFlags> pipelineCreationFlags,uint32_t * compilationID)64 sk_sp<GraphicsPipeline> GlobalCache::findGraphicsPipeline(
65 const UniqueKey& key,
66 SkEnumBitMask<PipelineCreationFlags> pipelineCreationFlags,
67 uint32_t *compilationID) {
68
69 [[maybe_unused]] bool forPrecompile =
70 SkToBool(pipelineCreationFlags & PipelineCreationFlags::kForPrecompilation);
71
72 SkAutoSpinlock lock{fSpinLock};
73
74 sk_sp<GraphicsPipeline>* entry = fGraphicsPipelineCache.find(key);
75 if (entry) {
76 #if defined(GPU_TEST_UTILS)
77 ++fStats.fGraphicsCacheHits;
78 #endif
79
80 (*entry)->markUsed();
81
82 #if defined(SK_PIPELINE_LIFETIME_LOGGING)
83 static const char* kNames[2] = { "CacheHitForN", "CacheHitForP" };
84 TRACE_EVENT_INSTANT2("skia.gpu",
85 TRACE_STR_STATIC(kNames[forPrecompile]),
86 TRACE_EVENT_SCOPE_THREAD,
87 "key", key.hash(),
88 "compilationID", (*entry)->getPipelineInfo().fCompilationID);
89 #endif
90
91 return *entry;
92 } else {
93 #if defined(GPU_TEST_UTILS)
94 ++fStats.fGraphicsCacheMisses;
95 #endif
96
97 if (compilationID) {
98 // This is a cache miss so we know the next step is going to be a Pipeline
99 // creation. Create the compilationID here so we can use it in the "CacheMissFor"
100 // trace event.
101 *compilationID = next_compilation_id();
102
103 #if defined(SK_PIPELINE_LIFETIME_LOGGING)
104 static const char* kNames[2] = { "CacheMissForN", "CacheMissForP" };
105 TRACE_EVENT_INSTANT2("skia.gpu",
106 TRACE_STR_STATIC(kNames[forPrecompile]),
107 TRACE_EVENT_SCOPE_THREAD,
108 "key", key.hash(),
109 "compilationID", *compilationID);
110 #endif
111 }
112
113 return nullptr;
114 }
115 }
116
117 #if SK_HISTOGRAMS_ENABLED
118 // These values are persisted to logs. Entries should not be renumbered and
119 // numeric values should never be reused.
120 //
121 // LINT.IfChange(PipelineCreationRace)
122 enum class PipelineCreationRace {
123 // The <First>Over<Second> enum names mean the first type of compilation won a compilation race
124 // over the second type of compilation and ended up in the cache.
125 kNormalOverNormal = 0, // can happen w/ multiple Recorders on different threads
126 kNormalOverPrecompilation = 1,
127 kPrecompilationOverNormal = 2,
128 kPrecompilationOverPrecompilation = 3, // can happen with multiple threaded precompilation calls
129
130 kMaxValue = kPrecompilationOverPrecompilation,
131 };
132 // LINT.ThenChange(//tools/metrics/histograms/enums.xml:SkiaPipelineCreationRace)
133
134 [[maybe_unused]] static constexpr int kPipelineCreationRaceCount =
135 static_cast<int>(PipelineCreationRace::kMaxValue) + 1;
136 #endif // SK_HISTOGRAMS_ENABLED
137
addGraphicsPipeline(const UniqueKey & key,sk_sp<GraphicsPipeline> pipeline)138 sk_sp<GraphicsPipeline> GlobalCache::addGraphicsPipeline(const UniqueKey& key,
139 sk_sp<GraphicsPipeline> pipeline) {
140 SkAutoSpinlock lock{fSpinLock};
141
142 sk_sp<GraphicsPipeline>* entry = fGraphicsPipelineCache.find(key);
143 if (!entry) {
144 // No equivalent pipeline was stored in the cache between a previous call to
145 // findGraphicsPipeline() that returned null (triggering the pipeline creation) and this
146 // later adding to the cache.
147 entry = fGraphicsPipelineCache.insert(key, std::move(pipeline));
148
149 #if defined(GPU_TEST_UTILS)
150 ++fStats.fGraphicsCacheAdditions;
151 #endif
152
153 // Precompile Pipelines are only marked as used when they get a cache hit in
154 // findGraphicsPipeline
155 if (!(*entry)->fromPrecompile()) {
156 (*entry)->markUsed();
157 }
158
159 #if defined(SK_PIPELINE_LIFETIME_LOGGING)
160 static const char* kNames[2] = { "AddedN", "AddedP" };
161 TRACE_EVENT_INSTANT2("skia.gpu",
162 TRACE_STR_STATIC(kNames[(*entry)->fromPrecompile()]),
163 TRACE_EVENT_SCOPE_THREAD,
164 "key", key.hash(),
165 "compilationID", (*entry)->getPipelineInfo().fCompilationID);
166 #endif
167 } else {
168 #if defined(GPU_TEST_UTILS)
169 // else there was a race creating the same pipeline and this thread lost, so return
170 // the winner
171 ++fStats.fGraphicsRaces;
172 #endif
173
174 [[maybe_unused]] int race = (*entry)->fromPrecompile() * 2 + pipeline->fromPrecompile();
175
176 #if defined(SK_PIPELINE_LIFETIME_LOGGING)
177 static const char* kNames[4] = {
178 "NWonRaceOverN",
179 "NWonRaceOverP",
180 "PWonRaceOverN",
181 "PWonRaceOverP"
182 };
183 TRACE_EVENT_INSTANT2("skia.gpu",
184 TRACE_STR_STATIC(kNames[race]),
185 TRACE_EVENT_SCOPE_THREAD,
186 "key", key.hash(),
187 // The losing compilation
188 "compilationID", pipeline->getPipelineInfo().fCompilationID);
189 #endif
190
191 #if SK_HISTOGRAMS_ENABLED
192 SK_HISTOGRAM_ENUMERATION("Graphite.PipelineCreationRace",
193 race,
194 kPipelineCreationRaceCount);
195 #endif
196 }
197 return *entry;
198 }
199
200 #if defined(GPU_TEST_UTILS)
numGraphicsPipelines() const201 int GlobalCache::numGraphicsPipelines() const {
202 SkAutoSpinlock lock{fSpinLock};
203
204 return fGraphicsPipelineCache.count();
205 }
206
resetGraphicsPipelines()207 void GlobalCache::resetGraphicsPipelines() {
208 SkAutoSpinlock lock{fSpinLock};
209
210 fGraphicsPipelineCache.reset();
211 }
212
forEachGraphicsPipeline(const std::function<void (const UniqueKey &,const GraphicsPipeline *)> & fn)213 void GlobalCache::forEachGraphicsPipeline(
214 const std::function<void(const UniqueKey&, const GraphicsPipeline*)>& fn) {
215 SkAutoSpinlock lock{fSpinLock};
216
217 fGraphicsPipelineCache.foreach([&](const UniqueKey* k, const sk_sp<GraphicsPipeline>* v) {
218 fn(*k, v->get());
219 });
220 }
221
getStats() const222 GlobalCache::PipelineStats GlobalCache::getStats() const {
223 SkAutoSpinlock lock{fSpinLock};
224
225 return fStats;
226 }
227 #endif // defined(GPU_TEST_UTILS)
228
findComputePipeline(const UniqueKey & key)229 sk_sp<ComputePipeline> GlobalCache::findComputePipeline(const UniqueKey& key) {
230 SkAutoSpinlock lock{fSpinLock};
231 sk_sp<ComputePipeline>* entry = fComputePipelineCache.find(key);
232 return entry ? *entry : nullptr;
233 }
234
addComputePipeline(const UniqueKey & key,sk_sp<ComputePipeline> pipeline)235 sk_sp<ComputePipeline> GlobalCache::addComputePipeline(const UniqueKey& key,
236 sk_sp<ComputePipeline> pipeline) {
237 SkAutoSpinlock lock{fSpinLock};
238 sk_sp<ComputePipeline>* entry = fComputePipelineCache.find(key);
239 if (!entry) {
240 entry = fComputePipelineCache.insert(key, std::move(pipeline));
241 }
242 return *entry;
243 }
244
addStaticResource(sk_sp<Resource> resource)245 void GlobalCache::addStaticResource(sk_sp<Resource> resource) {
246 SkAutoSpinlock lock{fSpinLock};
247 fStaticResource.push_back(std::move(resource));
248 }
249
250 } // namespace skgpu::graphite
251