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, ¶ms,
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, ®enerated);
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, ®enerated);
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, ®enerated);
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, ®enerated);
488 REPORTER_ASSERT(r, proxy1 && proxy1->dimensions().width() == 32);
489 REPORTER_ASSERT(r, regenerated);
490 }
491
492 } // namespace skgpu::graphite
493