xref: /aosp_15_r20/external/skia/tests/graphite/ProxyCacheTest.cpp (revision c8dee2aa9b3f27cf6c858bd81872bdeb2c07ed17)
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, &params,
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, &regenerated);
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, &regenerated);
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, &regenerated);
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, &regenerated);
469     REPORTER_ASSERT(r, proxy1 && proxy1->dimensions().width() == 32);
470     REPORTER_ASSERT(r, regenerated);
471 }
472 
473 }  // namespace skgpu::graphite
474