• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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::kNextRelease)32 DEF_GRAPHITE_TEST_FOR_ALL_CONTEXTS(ProxyCacheTest1, r, context, CtsEnforcement::kNextRelease) {
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::kNextRelease)59 DEF_GRAPHITE_TEST_FOR_ALL_CONTEXTS(ProxyCacheTest2, r, context, CtsEnforcement::kNextRelease) {
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::__anonbbe0ee480111::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::kNextRelease)160 DEF_CONDITIONAL_GRAPHITE_TEST_FOR_ALL_CONTEXTS(ProxyCacheTest4,
161                                                r,
162                                                context,
163                                                testContext,
164                                                true,
165                                                CtsEnforcement::kNextRelease) {
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::kNextRelease)188 DEF_CONDITIONAL_GRAPHITE_TEST_FOR_ALL_CONTEXTS(ProxyCacheTest5,
189                                                r,
190                                                context,
191                                                testContext,
192                                                true,
193                                                CtsEnforcement::kNextRelease) {
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     REPORTER_ASSERT(r, !setup.fProxy1->texture()->testingShouldDeleteASAP());
204     REPORTER_ASSERT(r, !setup.fProxy2->texture()->testingShouldDeleteASAP());
205 
206     proxyCache->forcePurgeProxiesNotUsedSince(setup.fTimeBetweenProxyCreation);
207     REPORTER_ASSERT(r, proxyCache->numCached() == 1);
208     REPORTER_ASSERT(r, setup.fProxy1->texture()->testingShouldDeleteASAP());
209     REPORTER_ASSERT(r, !setup.fProxy2->texture()->testingShouldDeleteASAP());
210 
211     sk_sp<TextureProxy> test = proxyCache->find(setup.fBitmap1);
212     REPORTER_ASSERT(r, !test);   // proxy1 should've been purged
213 
214     proxyCache->forcePurgeProxiesNotUsedSince(setup.fTimeAfterAllProxyCreation);
215     REPORTER_ASSERT(r, proxyCache->numCached() == 0);
216     REPORTER_ASSERT(r, setup.fProxy1->texture()->testingShouldDeleteASAP());
217     REPORTER_ASSERT(r, setup.fProxy2->texture()->testingShouldDeleteASAP());
218 }
219 
220 // This test simply verifies that the ProxyCache is correctly updating the Resource's
221 // last access time stamp.
DEF_CONDITIONAL_GRAPHITE_TEST_FOR_ALL_CONTEXTS(ProxyCacheTest6,r,context,testContext,true,CtsEnforcement::kNextRelease)222 DEF_CONDITIONAL_GRAPHITE_TEST_FOR_ALL_CONTEXTS(ProxyCacheTest6,
223                                                r,
224                                                context,
225                                                testContext,
226                                                true,
227                                                CtsEnforcement::kNextRelease) {
228     std::unique_ptr<Recorder> recorder = context->makeRecorder();
229     ProxyCache* proxyCache = recorder->priv().proxyCache();
230 
231     ProxyCacheSetup setup = setup_test(context, testContext, recorder.get(), r);
232     REPORTER_ASSERT(r, setup.valid());
233     if (!setup.valid()) {
234         return;
235     }
236 
237     REPORTER_ASSERT(r, !setup.fProxy1->texture()->testingShouldDeleteASAP());
238     REPORTER_ASSERT(r, !setup.fProxy2->texture()->testingShouldDeleteASAP());
239 
240     // update proxy1's timestamp
241     sk_sp<TextureProxy> test = proxyCache->findOrCreateCachedProxy(recorder.get(), setup.fBitmap1,
242                                                                    "ProxyCacheTestTexture");
243     REPORTER_ASSERT(r, test == setup.fProxy1);
244 
245     std::this_thread::sleep_for(std::chrono::milliseconds(2));
246     auto timeAfterProxy1Update = skgpu::StdSteadyClock::now();
247 
248     proxyCache->forcePurgeProxiesNotUsedSince(setup.fTimeBetweenProxyCreation);
249     REPORTER_ASSERT(r, proxyCache->numCached() == 2);
250     REPORTER_ASSERT(r, !setup.fProxy1->texture()->testingShouldDeleteASAP());
251     REPORTER_ASSERT(r, !setup.fProxy2->texture()->testingShouldDeleteASAP());
252 
253     proxyCache->forcePurgeProxiesNotUsedSince(setup.fTimeAfterAllProxyCreation);
254     REPORTER_ASSERT(r, proxyCache->numCached() == 1);
255     REPORTER_ASSERT(r, !setup.fProxy1->texture()->testingShouldDeleteASAP());
256     REPORTER_ASSERT(r, setup.fProxy2->texture()->testingShouldDeleteASAP());
257 
258     test = proxyCache->find(setup.fBitmap2);
259     REPORTER_ASSERT(r, !test);   // proxy2 should've been purged
260 
261     proxyCache->forcePurgeProxiesNotUsedSince(timeAfterProxy1Update);
262     REPORTER_ASSERT(r, proxyCache->numCached() == 0);
263     REPORTER_ASSERT(r, setup.fProxy1->texture()->testingShouldDeleteASAP());
264     REPORTER_ASSERT(r, setup.fProxy2->texture()->testingShouldDeleteASAP());
265 }
266 
267 // 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::kNextRelease)268 DEF_CONDITIONAL_GRAPHITE_TEST_FOR_ALL_CONTEXTS(ProxyCacheTest7,
269                                                r,
270                                                context,
271                                                testContext,
272                                                true,
273                                                CtsEnforcement::kNextRelease) {
274     std::unique_ptr<Recorder> recorder = context->makeRecorder();
275     ProxyCache* proxyCache = recorder->priv().proxyCache();
276 
277     ProxyCacheSetup setup = setup_test(context, testContext, recorder.get(), r);
278     REPORTER_ASSERT(r, setup.valid());
279     if (!setup.valid()) {
280         return;
281     }
282 
283     REPORTER_ASSERT(r, !setup.fProxy1->texture()->testingShouldDeleteASAP());
284     REPORTER_ASSERT(r, !setup.fProxy2->texture()->testingShouldDeleteASAP());
285 
286     proxyCache->forcePurgeProxiesNotUsedSince(setup.fTimeAfterAllProxyCreation);
287     REPORTER_ASSERT(r, proxyCache->numCached() == 0);
288     REPORTER_ASSERT(r, setup.fProxy1->texture()->testingShouldDeleteASAP());
289     REPORTER_ASSERT(r, setup.fProxy2->texture()->testingShouldDeleteASAP());
290 }
291 
292 // 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::kNextRelease)293 DEF_CONDITIONAL_GRAPHITE_TEST_FOR_ALL_CONTEXTS(ProxyCacheTest8,
294                                                r,
295                                                context,
296                                                testContext,
297                                                true,
298                                                CtsEnforcement::kNextRelease) {
299     std::unique_ptr<Recorder> recorder = context->makeRecorder();
300     ResourceCache* resourceCache = recorder->priv().resourceCache();
301     ProxyCache* proxyCache = recorder->priv().proxyCache();
302 
303     resourceCache->setMaxBudget(0);
304 
305     ProxyCacheSetup setup = setup_test(context, testContext, recorder.get(), r);
306     REPORTER_ASSERT(r, setup.valid());
307     if (!setup.valid()) {
308         return;
309     }
310 
311     resourceCache->forcePurgeAsNeeded();
312 
313     REPORTER_ASSERT(r, proxyCache->numCached() == 2);
314 
315     setup.fProxy1.reset();
316     proxyCache->forceProcessInvalidKeyMsgs();
317 
318     // unreffing fProxy1 and forcing message processing shouldn't purge proxy1 from the cache
319     sk_sp<TextureProxy> test = proxyCache->find(setup.fBitmap1);
320     REPORTER_ASSERT(r, test);
321     test.reset();
322 
323     resourceCache->forcePurgeAsNeeded();
324 
325     REPORTER_ASSERT(r, proxyCache->numCached() == 1);
326     test = proxyCache->find(setup.fBitmap1);
327     REPORTER_ASSERT(r, !test);   // proxy1 should've been purged
328 
329     setup.fProxy2.reset();
330     proxyCache->forceProcessInvalidKeyMsgs();
331 
332     // unreffing fProxy2 and forcing message processing shouldn't purge proxy2 from the cache
333     test = proxyCache->find(setup.fBitmap2);
334     REPORTER_ASSERT(r, test);
335     test.reset();
336 
337     resourceCache->forcePurgeAsNeeded();
338 
339     REPORTER_ASSERT(r, proxyCache->numCached() == 0);
340 }
341 
342 // Verify that the ProxyCache's purgeProxiesNotUsedSince behavior is working when triggered from
343 // ResourceCache.
DEF_CONDITIONAL_GRAPHITE_TEST_FOR_ALL_CONTEXTS(ProxyCacheTest9,r,context,testContext,true,CtsEnforcement::kNextRelease)344 DEF_CONDITIONAL_GRAPHITE_TEST_FOR_ALL_CONTEXTS(ProxyCacheTest9,
345                                                r,
346                                                context,
347                                                testContext,
348                                                true,
349                                                CtsEnforcement::kNextRelease) {
350     std::unique_ptr<Recorder> recorder = context->makeRecorder();
351     ResourceCache* resourceCache = recorder->priv().resourceCache();
352     ProxyCache* proxyCache = recorder->priv().proxyCache();
353 
354     ProxyCacheSetup setup = setup_test(context, testContext, recorder.get(), r);
355     REPORTER_ASSERT(r, setup.valid());
356     if (!setup.valid()) {
357         return;
358     }
359 
360     REPORTER_ASSERT(r, setup.fProxy1->isInstantiated());
361     REPORTER_ASSERT(r, setup.fProxy2->isInstantiated());
362 
363     if (!setup.fProxy1->texture() || !setup.fProxy2->texture()) {
364         return;
365     }
366 
367     // Clear out resources used to setup bitmap proxies so we can track things easier.
368     resourceCache->setMaxBudget(0);
369     resourceCache->setMaxBudget(256 * (1 << 20));
370 
371     REPORTER_ASSERT(r, proxyCache->numCached() == 2);
372     int baselineResourceCount = resourceCache->getResourceCount();
373     // When buffer maps are async it can take extra time for buffers to be returned to the cache.
374     if (context->priv().caps()->bufferMapsAreAsync()) {
375         // We expect at least 2 textures (and possibly buffers).
376         REPORTER_ASSERT(r, baselineResourceCount >= 2);
377     } else {
378         REPORTER_ASSERT(r, baselineResourceCount == 2);
379     }
380     // Force a command buffer ref on the second proxy in the cache so it can't be purged immediately
381     setup.fProxy2->texture()->refCommandBuffer();
382 
383     Resource* proxy2ResourcePtr = setup.fProxy2->texture();
384 
385     setup.fProxy1.reset();
386     setup.fProxy2.reset();
387     REPORTER_ASSERT(r, proxyCache->numCached() == 2);
388 
389     auto timeAfterProxyCreation = skgpu::StdSteadyClock::now();
390 
391     // This should trigger both proxies to be purged from the ProxyCache. The first proxy should
392     // immediately be purged from the ResourceCache as well since it has not other refs. The second
393     // proxy will not be purged from the ResourceCache since it still has a command buffer ref.
394     // However, that resource should have its deleteASAP flag set.
395     resourceCache->purgeResourcesNotUsedSince(timeAfterProxyCreation);
396 
397     REPORTER_ASSERT(r, proxyCache->numCached() == 0);
398     REPORTER_ASSERT(r, resourceCache->getResourceCount() == baselineResourceCount - 1);
399     REPORTER_ASSERT(r, resourceCache->topOfPurgeableQueue() == nullptr);
400     REPORTER_ASSERT(r, proxy2ResourcePtr->testingShouldDeleteASAP());
401 
402     // Removing the command buffer ref and returning proxy2Resource to the cache should cause it to
403     // immediately get deleted without going in the purgeable queue.
404     proxy2ResourcePtr->unrefCommandBuffer();
405     resourceCache->forceProcessReturnedResources();
406 
407     REPORTER_ASSERT(r, proxyCache->numCached() == 0);
408     REPORTER_ASSERT(r, resourceCache->getResourceCount() == baselineResourceCount - 2);
409     REPORTER_ASSERT(r, resourceCache->topOfPurgeableQueue() == nullptr);
410 }
411 
find_or_create_by_key(Recorder * recorder,int id,bool * regenerated)412 static sk_sp<TextureProxy> find_or_create_by_key(Recorder* recorder, int id, bool* regenerated) {
413     *regenerated = false;
414 
415     skgpu::UniqueKey key;
416     {
417         static const skgpu::UniqueKey::Domain kTestDomain = UniqueKey::GenerateDomain();
418         UniqueKey::Builder builder(&key, kTestDomain, 1, "TestExplicitKey");
419         builder[0] = id;
420     }
421 
422     struct Context {
423         int id;
424         bool* regenerated;
425     } params { id, regenerated };
426 
427     return recorder->priv().proxyCache()->findOrCreateCachedProxy(
428             recorder, key, &params,
429             [](const void* context) {
430                 const Context* params = static_cast<const Context*>(context);
431                 *params->regenerated = true;
432 
433                 SkBitmap bm;
434                 if (params->id == 1) {
435                     if (!ToolUtils::GetResourceAsBitmap("images/mandrill_32.png", &bm)) {
436                         return SkBitmap();
437                     }
438                 } else if (!ToolUtils::GetResourceAsBitmap("images/mandrill_64.png", &bm)) {
439                     return SkBitmap();
440                 }
441                 return bm;
442             });
443 }
444 
445 // 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::kNextRelease)446 DEF_CONDITIONAL_GRAPHITE_TEST_FOR_ALL_CONTEXTS(ProxyCacheTest10,
447                                                r,
448                                                context,
449                                                testContext,
450                                                true,
451                                                CtsEnforcement::kNextRelease) {
452     std::unique_ptr<Recorder> recorder = context->makeRecorder();
453     ProxyCache* proxyCache = recorder->priv().proxyCache();
454 
455     REPORTER_ASSERT(r, proxyCache->numCached() == 0);
456 
457     bool regenerated;
458     sk_sp<TextureProxy> proxy1 = find_or_create_by_key(recorder.get(), 1, &regenerated);
459     REPORTER_ASSERT(r, proxy1 && proxy1->dimensions().width() == 32);
460     REPORTER_ASSERT(r, regenerated);
461 
462     sk_sp<TextureProxy> proxy2 = find_or_create_by_key(recorder.get(), 2, &regenerated);
463     REPORTER_ASSERT(r, proxy2 && proxy2->dimensions().width() == 64);
464     REPORTER_ASSERT(r, regenerated);
465 
466     REPORTER_ASSERT(r, proxyCache->numCached() == 2);
467 
468     // These cached proxies shouldn't be deleted because we hold local refs still
469     proxyCache->forceFreeUniquelyHeld();
470     REPORTER_ASSERT(r, proxyCache->numCached() == 2);
471 
472     // Cache hit should not invoke the bitmap generation function.
473     sk_sp<TextureProxy> proxy1b = find_or_create_by_key(recorder.get(), 1, &regenerated);
474     REPORTER_ASSERT(r, proxy1.get() == proxy1b.get());
475     REPORTER_ASSERT(r, !regenerated);
476 
477     proxy1.reset();
478     proxy1b.reset();
479     proxy2.reset();
480     (void) recorder->snap(); // Dump pending commands to release internal refs to the cached proxies
481 
482     // Now the cache should clean the cache entries up
483     proxyCache->forceFreeUniquelyHeld();
484     REPORTER_ASSERT(r, proxyCache->numCached() == 0);
485 
486     // And regeneration functions as expected
487     proxy1 = find_or_create_by_key(recorder.get(), 1, &regenerated);
488     REPORTER_ASSERT(r, proxy1 && proxy1->dimensions().width() == 32);
489     REPORTER_ASSERT(r, regenerated);
490 }
491 
492 }  // namespace skgpu::graphite
493