1 /*
2 * Copyright 2023 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 "tests/Test.h"
9
10 #include "include/core/SkBitmap.h"
11 #include "include/gpu/graphite/Context.h"
12 #include "include/gpu/graphite/Recorder.h"
13 #include "src/gpu/GpuTypesPriv.h"
14 #include "src/gpu/graphite/Caps.h"
15 #include "src/gpu/graphite/ContextPriv.h"
16 #include "src/gpu/graphite/ProxyCache.h"
17 #include "src/gpu/graphite/RecorderPriv.h"
18 #include "src/gpu/graphite/Texture.h"
19 #include "src/gpu/graphite/TextureProxy.h"
20 #include "tools/DecodeUtils.h"
21 #include "tools/Resources.h"
22 #include "tools/graphite/GraphiteTestContext.h"
23
24 #include <thread>
25
26 namespace skgpu::graphite {
27
28 // This test exercises the basic MessageBus behavior of the ProxyCache by manually inserting an
29 // SkBitmap into the proxy cache and then changing its contents. This simple test should create
30 // an IDChangeListener that will remove the entry in the cache when the bitmap is changed and
31 // the resulting message processed.
DEF_GRAPHITE_TEST_FOR_ALL_CONTEXTS(ProxyCacheTest1,r,context,CtsEnforcement::kApiLevel_V)32 DEF_GRAPHITE_TEST_FOR_ALL_CONTEXTS(ProxyCacheTest1, r, context, CtsEnforcement::kApiLevel_V) {
33 std::unique_ptr<Recorder> recorder = context->makeRecorder();
34 ProxyCache* proxyCache = recorder->priv().proxyCache();
35
36 SkBitmap bitmap;
37 bool success = ToolUtils::GetResourceAsBitmap("images/mandrill_128.png", &bitmap);
38 REPORTER_ASSERT(r, success);
39 if (!success) {
40 return;
41 }
42
43 REPORTER_ASSERT(r, proxyCache->numCached() == 0);
44
45 sk_sp<TextureProxy> proxy = proxyCache->findOrCreateCachedProxy(recorder.get(), bitmap,
46 "ProxyCacheTestTexture");
47
48 REPORTER_ASSERT(r, proxyCache->numCached() == 1);
49
50 bitmap.eraseColor(SK_ColorBLACK);
51
52 proxyCache->forceProcessInvalidKeyMsgs();
53
54 REPORTER_ASSERT(r, proxyCache->numCached() == 0);
55 }
56
57 // This test checks that, if the same bitmap is added to two separate ProxyCaches, when it is
58 // changed, both of the ProxyCaches will receive the message.
DEF_GRAPHITE_TEST_FOR_ALL_CONTEXTS(ProxyCacheTest2,r,context,CtsEnforcement::kApiLevel_V)59 DEF_GRAPHITE_TEST_FOR_ALL_CONTEXTS(ProxyCacheTest2, r, context, CtsEnforcement::kApiLevel_V) {
60 std::unique_ptr<Recorder> recorder1 = context->makeRecorder();
61 ProxyCache* proxyCache1 = recorder1->priv().proxyCache();
62 std::unique_ptr<Recorder> recorder2 = context->makeRecorder();
63 ProxyCache* proxyCache2 = recorder2->priv().proxyCache();
64
65 SkBitmap bitmap;
66 bool success = ToolUtils::GetResourceAsBitmap("images/mandrill_128.png", &bitmap);
67 REPORTER_ASSERT(r, success);
68 if (!success) {
69 return;
70 }
71
72 REPORTER_ASSERT(r, proxyCache1->numCached() == 0);
73 REPORTER_ASSERT(r, proxyCache2->numCached() == 0);
74
75 sk_sp<TextureProxy> proxy1 = proxyCache1->findOrCreateCachedProxy(recorder1.get(), bitmap,
76 "ProxyCacheTestTexture");
77 sk_sp<TextureProxy> proxy2 = proxyCache2->findOrCreateCachedProxy(recorder2.get(), bitmap,
78 "ProxyCacheTestTexture");
79
80 REPORTER_ASSERT(r, proxyCache1->numCached() == 1);
81 REPORTER_ASSERT(r, proxyCache2->numCached() == 1);
82
83 bitmap.eraseColor(SK_ColorBLACK);
84
85 proxyCache1->forceProcessInvalidKeyMsgs();
86 proxyCache2->forceProcessInvalidKeyMsgs();
87
88 REPORTER_ASSERT(r, proxyCache1->numCached() == 0);
89 REPORTER_ASSERT(r, proxyCache2->numCached() == 0);
90 }
91
92 namespace {
93
94 struct ProxyCacheSetup {
validskgpu::graphite::__anoncd1e84aa0111::ProxyCacheSetup95 bool valid() const {
96 return !fBitmap1.empty() && !fBitmap2.empty() && fProxy1 && fProxy2;
97 }
98
99 SkBitmap fBitmap1;
100 sk_sp<TextureProxy> fProxy1;
101 SkBitmap fBitmap2;
102 sk_sp<TextureProxy> fProxy2;
103
104 skgpu::StdSteadyClock::time_point fTimeBetweenProxyCreation;
105 skgpu::StdSteadyClock::time_point fTimeAfterAllProxyCreation;
106 };
107
setup_test(Context * context,skiatest::graphite::GraphiteTestContext * testContext,Recorder * recorder,skiatest::Reporter * r)108 ProxyCacheSetup setup_test(Context* context,
109 skiatest::graphite::GraphiteTestContext* testContext,
110 Recorder* recorder,
111 skiatest::Reporter* r) {
112 ProxyCache* proxyCache = recorder->priv().proxyCache();
113
114 ProxyCacheSetup setup;
115
116 bool success1 = ToolUtils::GetResourceAsBitmap("images/mandrill_32.png", &setup.fBitmap1);
117 bool success2 = ToolUtils::GetResourceAsBitmap("images/mandrill_64.png", &setup.fBitmap2);
118 if (!success1 || !success2) {
119 return {};
120 }
121
122 REPORTER_ASSERT(r, proxyCache->numCached() == 0);
123
124 setup.fProxy1 = proxyCache->findOrCreateCachedProxy(recorder, setup.fBitmap1,
125 "ProxyCacheTestTexture");
126 REPORTER_ASSERT(r, proxyCache->numCached() == 1);
127
128 {
129 // Ensure proxy1's Texture is created (and timestamped) at this time
130 auto recording = recorder->snap();
131 context->insertRecording({ recording.get() });
132 context->submit(SyncToCpu::kYes);
133 }
134
135 std::this_thread::sleep_for(std::chrono::milliseconds(2));
136 setup.fTimeBetweenProxyCreation = skgpu::StdSteadyClock::now();
137 std::this_thread::sleep_for(std::chrono::milliseconds(2));
138
139 setup.fProxy2 = proxyCache->findOrCreateCachedProxy(recorder, setup.fBitmap2,
140 "ProxyCacheTestTexture");
141 REPORTER_ASSERT(r, proxyCache->numCached() == 2);
142
143 {
144 // Ensure proxy2's Texture is created (and timestamped) at this time
145 auto recording = recorder->snap();
146 context->insertRecording({ recording.get() });
147 testContext->syncedSubmit(context);
148 }
149
150 std::this_thread::sleep_for(std::chrono::milliseconds(2));
151 setup.fTimeAfterAllProxyCreation = skgpu::StdSteadyClock::now();
152 std::this_thread::sleep_for(std::chrono::milliseconds(2));
153
154 return setup;
155 }
156
157 } // anonymous namespace
158
159 // This test exercises the ProxyCache's freeUniquelyHeld method.
DEF_CONDITIONAL_GRAPHITE_TEST_FOR_ALL_CONTEXTS(ProxyCacheTest4,r,context,testContext,true,CtsEnforcement::kApiLevel_V)160 DEF_CONDITIONAL_GRAPHITE_TEST_FOR_ALL_CONTEXTS(ProxyCacheTest4,
161 r,
162 context,
163 testContext,
164 true,
165 CtsEnforcement::kApiLevel_V) {
166 std::unique_ptr<Recorder> recorder = context->makeRecorder();
167 ProxyCache* proxyCache = recorder->priv().proxyCache();
168
169 ProxyCacheSetup setup = setup_test(context, testContext, recorder.get(), r);
170 REPORTER_ASSERT(r, setup.valid());
171 if (!setup.valid()) {
172 return;
173 }
174
175 proxyCache->forceFreeUniquelyHeld();
176 REPORTER_ASSERT(r, proxyCache->numCached() == 2);
177
178 setup.fProxy1.reset();
179 proxyCache->forceFreeUniquelyHeld();
180 REPORTER_ASSERT(r, proxyCache->numCached() == 1);
181
182 setup.fProxy2.reset();
183 proxyCache->forceFreeUniquelyHeld();
184 REPORTER_ASSERT(r, proxyCache->numCached() == 0);
185 }
186
187 // This test exercises the ProxyCache's purgeProxiesNotUsedSince method.
DEF_CONDITIONAL_GRAPHITE_TEST_FOR_ALL_CONTEXTS(ProxyCacheTest5,r,context,testContext,true,CtsEnforcement::kApiLevel_V)188 DEF_CONDITIONAL_GRAPHITE_TEST_FOR_ALL_CONTEXTS(ProxyCacheTest5,
189 r,
190 context,
191 testContext,
192 true,
193 CtsEnforcement::kApiLevel_V) {
194 std::unique_ptr<Recorder> recorder = context->makeRecorder();
195 ProxyCache* proxyCache = recorder->priv().proxyCache();
196
197 ProxyCacheSetup setup = setup_test(context, testContext, recorder.get(), r);
198 REPORTER_ASSERT(r, setup.valid());
199 if (!setup.valid()) {
200 return;
201 }
202
203 proxyCache->forcePurgeProxiesNotUsedSince(setup.fTimeBetweenProxyCreation);
204 REPORTER_ASSERT(r, proxyCache->numCached() == 1);
205
206 sk_sp<TextureProxy> test = proxyCache->find(setup.fBitmap1);
207 REPORTER_ASSERT(r, !test); // proxy1 should've been purged
208
209 proxyCache->forcePurgeProxiesNotUsedSince(setup.fTimeAfterAllProxyCreation);
210 REPORTER_ASSERT(r, proxyCache->numCached() == 0);
211 }
212
213 // This test simply verifies that the ProxyCache is correctly updating the Resource's
214 // last access time stamp.
DEF_CONDITIONAL_GRAPHITE_TEST_FOR_ALL_CONTEXTS(ProxyCacheTest6,r,context,testContext,true,CtsEnforcement::kApiLevel_V)215 DEF_CONDITIONAL_GRAPHITE_TEST_FOR_ALL_CONTEXTS(ProxyCacheTest6,
216 r,
217 context,
218 testContext,
219 true,
220 CtsEnforcement::kApiLevel_V) {
221 std::unique_ptr<Recorder> recorder = context->makeRecorder();
222 ProxyCache* proxyCache = recorder->priv().proxyCache();
223
224 ProxyCacheSetup setup = setup_test(context, testContext, recorder.get(), r);
225 REPORTER_ASSERT(r, setup.valid());
226 if (!setup.valid()) {
227 return;
228 }
229
230 // update proxy1's timestamp
231 sk_sp<TextureProxy> test = proxyCache->findOrCreateCachedProxy(recorder.get(), setup.fBitmap1,
232 "ProxyCacheTestTexture");
233 REPORTER_ASSERT(r, test == setup.fProxy1);
234
235 std::this_thread::sleep_for(std::chrono::milliseconds(2));
236 auto timeAfterProxy1Update = skgpu::StdSteadyClock::now();
237
238 proxyCache->forcePurgeProxiesNotUsedSince(setup.fTimeBetweenProxyCreation);
239 REPORTER_ASSERT(r, proxyCache->numCached() == 2);
240
241 proxyCache->forcePurgeProxiesNotUsedSince(setup.fTimeAfterAllProxyCreation);
242 REPORTER_ASSERT(r, proxyCache->numCached() == 1);
243
244 test = proxyCache->find(setup.fBitmap2);
245 REPORTER_ASSERT(r, !test); // proxy2 should've been purged
246
247 proxyCache->forcePurgeProxiesNotUsedSince(timeAfterProxy1Update);
248 REPORTER_ASSERT(r, proxyCache->numCached() == 0);
249 }
250
251 // Verify that the ProxyCache's purgeProxiesNotUsedSince method can clear out multiple proxies.
DEF_CONDITIONAL_GRAPHITE_TEST_FOR_ALL_CONTEXTS(ProxyCacheTest7,r,context,testContext,true,CtsEnforcement::kApiLevel_V)252 DEF_CONDITIONAL_GRAPHITE_TEST_FOR_ALL_CONTEXTS(ProxyCacheTest7,
253 r,
254 context,
255 testContext,
256 true,
257 CtsEnforcement::kApiLevel_V) {
258 std::unique_ptr<Recorder> recorder = context->makeRecorder();
259 ProxyCache* proxyCache = recorder->priv().proxyCache();
260
261 ProxyCacheSetup setup = setup_test(context, testContext, recorder.get(), r);
262 REPORTER_ASSERT(r, setup.valid());
263 if (!setup.valid()) {
264 return;
265 }
266
267 proxyCache->forcePurgeProxiesNotUsedSince(setup.fTimeAfterAllProxyCreation);
268 REPORTER_ASSERT(r, proxyCache->numCached() == 0);
269 }
270
271 // Verify that the ProxyCache's freeUniquelyHeld behavior is working in the ResourceCache.
DEF_CONDITIONAL_GRAPHITE_TEST_FOR_ALL_CONTEXTS(ProxyCacheTest8,r,context,testContext,true,CtsEnforcement::kApiLevel_V)272 DEF_CONDITIONAL_GRAPHITE_TEST_FOR_ALL_CONTEXTS(ProxyCacheTest8,
273 r,
274 context,
275 testContext,
276 true,
277 CtsEnforcement::kApiLevel_V) {
278 std::unique_ptr<Recorder> recorder = context->makeRecorder();
279 ResourceCache* resourceCache = recorder->priv().resourceCache();
280 ProxyCache* proxyCache = recorder->priv().proxyCache();
281
282 resourceCache->setMaxBudget(0);
283
284 ProxyCacheSetup setup = setup_test(context, testContext, recorder.get(), r);
285 REPORTER_ASSERT(r, setup.valid());
286 if (!setup.valid()) {
287 return;
288 }
289
290 resourceCache->forcePurgeAsNeeded();
291
292 REPORTER_ASSERT(r, proxyCache->numCached() == 2);
293
294 setup.fProxy1.reset();
295 proxyCache->forceProcessInvalidKeyMsgs();
296
297 // unreffing fProxy1 and forcing message processing shouldn't purge proxy1 from the cache
298 sk_sp<TextureProxy> test = proxyCache->find(setup.fBitmap1);
299 REPORTER_ASSERT(r, test);
300 test.reset();
301
302 resourceCache->forcePurgeAsNeeded();
303
304 REPORTER_ASSERT(r, proxyCache->numCached() == 1);
305 test = proxyCache->find(setup.fBitmap1);
306 REPORTER_ASSERT(r, !test); // proxy1 should've been purged
307
308 setup.fProxy2.reset();
309 proxyCache->forceProcessInvalidKeyMsgs();
310
311 // unreffing fProxy2 and forcing message processing shouldn't purge proxy2 from the cache
312 test = proxyCache->find(setup.fBitmap2);
313 REPORTER_ASSERT(r, test);
314 test.reset();
315
316 resourceCache->forcePurgeAsNeeded();
317
318 REPORTER_ASSERT(r, proxyCache->numCached() == 0);
319 }
320
321 // Verify that the ProxyCache's purgeProxiesNotUsedSince behavior is working when triggered from
322 // ResourceCache.
DEF_CONDITIONAL_GRAPHITE_TEST_FOR_ALL_CONTEXTS(ProxyCacheTest9,r,context,testContext,true,CtsEnforcement::kApiLevel_V)323 DEF_CONDITIONAL_GRAPHITE_TEST_FOR_ALL_CONTEXTS(ProxyCacheTest9,
324 r,
325 context,
326 testContext,
327 true,
328 CtsEnforcement::kApiLevel_V) {
329 std::unique_ptr<Recorder> recorder = context->makeRecorder();
330 ResourceCache* resourceCache = recorder->priv().resourceCache();
331 ProxyCache* proxyCache = recorder->priv().proxyCache();
332
333 ProxyCacheSetup setup = setup_test(context, testContext, recorder.get(), r);
334 REPORTER_ASSERT(r, setup.valid());
335 if (!setup.valid()) {
336 return;
337 }
338
339 REPORTER_ASSERT(r, setup.fProxy1->isInstantiated());
340 REPORTER_ASSERT(r, setup.fProxy2->isInstantiated());
341
342 if (!setup.fProxy1->texture() || !setup.fProxy2->texture()) {
343 return;
344 }
345
346 // Clear out resources used to setup bitmap proxies so we can track things easier.
347 resourceCache->setMaxBudget(0);
348 resourceCache->setMaxBudget(256 * (1 << 20));
349
350 REPORTER_ASSERT(r, proxyCache->numCached() == 2);
351 int baselineResourceCount = resourceCache->getResourceCount();
352 // When buffer maps are async it can take extra time for buffers to be returned to the cache.
353 if (context->priv().caps()->bufferMapsAreAsync()) {
354 // We expect at least 2 textures (and possibly buffers).
355 REPORTER_ASSERT(r, baselineResourceCount >= 2);
356 } else {
357 REPORTER_ASSERT(r, baselineResourceCount == 2);
358 }
359 // Force a command buffer ref on the second proxy in the cache so it can't be purged immediately
360 setup.fProxy2->texture()->refCommandBuffer();
361
362 Resource* proxy1ResourcePtr = setup.fProxy1->texture();
363 Resource* proxy2ResourcePtr = setup.fProxy2->texture();
364
365 setup.fProxy1.reset();
366 setup.fProxy2.reset();
367 REPORTER_ASSERT(r, proxyCache->numCached() == 2);
368
369 std::this_thread::sleep_for(std::chrono::milliseconds(2));
370 auto timeAfterProxyCreation = skgpu::StdSteadyClock::now();
371
372 // This should trigger both proxies to be purged from the ProxyCache. Neither proxy should be
373 // released from the ResourceCache since their timestamp gets reset when returned to the
374 // ResourceCache. However, the first proxy should be purgeable and the second not since it still
375 // has a command buffer ref.
376 resourceCache->purgeResourcesNotUsedSince(timeAfterProxyCreation);
377
378 REPORTER_ASSERT(r, proxyCache->numCached() == 0);
379 REPORTER_ASSERT(r, resourceCache->getResourceCount() == baselineResourceCount);
380 REPORTER_ASSERT(r, resourceCache->topOfPurgeableQueue() == proxy1ResourcePtr);
381
382 // Removing the command buffer ref and returning proxy2Resource to the cache should cause it to
383 // become purgeable.
384 proxy2ResourcePtr->unrefCommandBuffer();
385 resourceCache->forceProcessReturnedResources();
386
387 REPORTER_ASSERT(r, proxyCache->numCached() == 0);
388 REPORTER_ASSERT(r, resourceCache->getResourceCount() == baselineResourceCount);
389 REPORTER_ASSERT(r, resourceCache->topOfPurgeableQueue() == proxy1ResourcePtr);
390 REPORTER_ASSERT(r, resourceCache->testingInPurgeableQueue(proxy2ResourcePtr));
391 }
392
find_or_create_by_key(Recorder * recorder,int id,bool * regenerated)393 static sk_sp<TextureProxy> find_or_create_by_key(Recorder* recorder, int id, bool* regenerated) {
394 *regenerated = false;
395
396 skgpu::UniqueKey key;
397 {
398 static const skgpu::UniqueKey::Domain kTestDomain = UniqueKey::GenerateDomain();
399 UniqueKey::Builder builder(&key, kTestDomain, 1, "TestExplicitKey");
400 builder[0] = id;
401 }
402
403 struct Context {
404 int id;
405 bool* regenerated;
406 } params { id, regenerated };
407
408 return recorder->priv().proxyCache()->findOrCreateCachedProxy(
409 recorder, key, ¶ms,
410 [](const void* context) {
411 const Context* params = static_cast<const Context*>(context);
412 *params->regenerated = true;
413
414 SkBitmap bm;
415 if (params->id == 1) {
416 if (!ToolUtils::GetResourceAsBitmap("images/mandrill_32.png", &bm)) {
417 return SkBitmap();
418 }
419 } else if (!ToolUtils::GetResourceAsBitmap("images/mandrill_64.png", &bm)) {
420 return SkBitmap();
421 }
422 return bm;
423 });
424 }
425
426 // Verify that the ProxyCache's explicit keying only generates the bitmaps as needed.
DEF_CONDITIONAL_GRAPHITE_TEST_FOR_ALL_CONTEXTS(ProxyCacheTest10,r,context,testContext,true,CtsEnforcement::kApiLevel_V)427 DEF_CONDITIONAL_GRAPHITE_TEST_FOR_ALL_CONTEXTS(ProxyCacheTest10,
428 r,
429 context,
430 testContext,
431 true,
432 CtsEnforcement::kApiLevel_V) {
433 std::unique_ptr<Recorder> recorder = context->makeRecorder();
434 ProxyCache* proxyCache = recorder->priv().proxyCache();
435
436 REPORTER_ASSERT(r, proxyCache->numCached() == 0);
437
438 bool regenerated;
439 sk_sp<TextureProxy> proxy1 = find_or_create_by_key(recorder.get(), 1, ®enerated);
440 REPORTER_ASSERT(r, proxy1 && proxy1->dimensions().width() == 32);
441 REPORTER_ASSERT(r, regenerated);
442
443 sk_sp<TextureProxy> proxy2 = find_or_create_by_key(recorder.get(), 2, ®enerated);
444 REPORTER_ASSERT(r, proxy2 && proxy2->dimensions().width() == 64);
445 REPORTER_ASSERT(r, regenerated);
446
447 REPORTER_ASSERT(r, proxyCache->numCached() == 2);
448
449 // These cached proxies shouldn't be deleted because we hold local refs still
450 proxyCache->forceFreeUniquelyHeld();
451 REPORTER_ASSERT(r, proxyCache->numCached() == 2);
452
453 // Cache hit should not invoke the bitmap generation function.
454 sk_sp<TextureProxy> proxy1b = find_or_create_by_key(recorder.get(), 1, ®enerated);
455 REPORTER_ASSERT(r, proxy1.get() == proxy1b.get());
456 REPORTER_ASSERT(r, !regenerated);
457
458 proxy1.reset();
459 proxy1b.reset();
460 proxy2.reset();
461 (void) recorder->snap(); // Dump pending commands to release internal refs to the cached proxies
462
463 // Now the cache should clean the cache entries up
464 proxyCache->forceFreeUniquelyHeld();
465 REPORTER_ASSERT(r, proxyCache->numCached() == 0);
466
467 // And regeneration functions as expected
468 proxy1 = find_or_create_by_key(recorder.get(), 1, ®enerated);
469 REPORTER_ASSERT(r, proxy1 && proxy1->dimensions().width() == 32);
470 REPORTER_ASSERT(r, regenerated);
471 }
472
473 } // namespace skgpu::graphite
474