Flutter Engine
The Flutter Engine
ProxyCacheTest.cpp
Go to the documentation of this file.
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
20#include "tools/DecodeUtils.h"
21#include "tools/Resources.h"
23
24#include <thread>
25
26namespace 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.
33 std::unique_ptr<Recorder> recorder = context->makeRecorder();
34 ProxyCache* proxyCache = recorder->priv().proxyCache();
35
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.
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
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
92namespace {
93
94struct ProxyCacheSetup {
95 bool valid() const {
96 return !fBitmap1.empty() && !fBitmap2.empty() && fProxy1 && fProxy2;
97 }
98
103
104 skgpu::StdSteadyClock::time_point fTimeBetweenProxyCreation;
105 skgpu::StdSteadyClock::time_point fTimeAfterAllProxyCreation;
106};
107
108ProxyCacheSetup setup_test(Context* context,
110 Recorder* recorder,
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.
161 r,
162 context,
163 testContext,
164 true,
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.
189 r,
190 context,
191 testContext,
192 true,
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.
223 r,
224 context,
225 testContext,
226 true,
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.
269 r,
270 context,
271 testContext,
272 true,
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.
294 r,
295 context,
296 testContext,
297 true,
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);
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);
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.
345 r,
346 context,
347 testContext,
348 true,
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
412static sk_sp<TextureProxy> find_or_create_by_key(Recorder* recorder, int id, bool* regenerated) {
413 *regenerated = false;
414
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.
447 r,
448 context,
449 testContext,
450 true,
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
sk_sp< TextureProxy > fProxy2
SkBitmap fBitmap1
SkBitmap fBitmap2
skgpu::StdSteadyClock::time_point fTimeAfterAllProxyCreation
skgpu::StdSteadyClock::time_point fTimeBetweenProxyCreation
sk_sp< TextureProxy > fProxy1
constexpr SkColor SK_ColorBLACK
Definition: SkColor.h:103
#define REPORTER_ASSERT(r, cond,...)
Definition: Test.h:286
bool empty() const
Definition: SkBitmap.h:210
T * get() const
Definition: SkRefCnt.h:303
void reset(T *ptr=nullptr)
Definition: SkRefCnt.h:310
static Domain GenerateDomain()
Definition: ResourceKey.cpp:27
sk_sp< TextureProxy > findOrCreateCachedProxy(Recorder *, const SkBitmap &, std::string_view label)
Definition: ProxyCache.cpp:74
std::unique_ptr< Recording > snap()
Definition: Recorder.cpp:159
void purgeResourcesNotUsedSince(StdSteadyClock::time_point purgeTime)
void unrefCommandBuffer() const
Definition: Resource.h:79
void syncedSubmit(skgpu::graphite::Context *)
const EmbeddedViewParams * params
bool GetResourceAsBitmap(const char *resource, SkBitmap *dst)
Definition: DecodeUtils.h:21
Definition: bitmap.py:1
Definition: setup.py:1
static sk_sp< TextureProxy > find_or_create_by_key(Recorder *recorder, int id, bool *regenerated)
DEF_CONDITIONAL_GRAPHITE_TEST_FOR_ALL_CONTEXTS(GraphiteBudgetedResourcesTest, reporter, context, testContext, true, CtsEnforcement::kNextRelease)
DEF_GRAPHITE_TEST_FOR_ALL_CONTEXTS(DeviceTestVertexTransparency, reporter, context, CtsEnforcement::kNextRelease)
Definition: DeviceTest.cpp:21
const uintptr_t id
static void setup(SkCanvas *canvas, SkPaint *paint, const SkBitmap &bm, SkFilterMode fm, SkTileMode tmx, SkTileMode tmy)
Definition: tilemodes.cpp:52