Flutter Engine
The Flutter Engine
Loading...
Searching...
No Matches
GraphiteResourceCacheTest.cpp
Go to the documentation of this file.
1/*
2 * Copyright 2022 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
32#include "tools/Resources.h"
34
35namespace skgpu::graphite {
36
37class TestResource : public Resource {
38public:
40 Ownership owned,
42 Shareable shareable,
43 size_t gpuMemorySize = 1) {
45 owned,
48 if (!resource) {
49 return nullptr;
50 }
51
53 CreateKey(&key, shareable);
54
55 resource->setKey(key);
56 return resource;
57 }
58
59 const char* getResourceType() const override { return "Test Resource"; }
60
61 static void CreateKey(GraphiteResourceKey* key, Shareable shareable) {
62 // Internally we assert that we don't make the same key twice where the only difference is
63 // shareable vs non-shareable. That allows us to now have Shareable be part of the Key's
64 // key. So here we make two different resource types so the keys will be different.
66 static const ResourceType kShareableType = GraphiteResourceKey::GenerateResourceType();
67 ResourceType type = shareable == Shareable::kNo ? kType : kShareableType;
69 }
70
71private:
73 Ownership owned,
75 size_t gpuMemorySize)
77 owned,
80 /*label=*/"TestResource") {}
81
82 void freeGpuData() override {}
83};
84
86 const size_t rowBytes = info.minRowBytes();
87 sk_sp<SkData> data(SkData::MakeUninitialized(rowBytes * info.height()));
88 {
89 SkBitmap bm;
90 bm.installPixels(info, data->writable_data(), rowBytes);
91 SkCanvas canvas(bm);
92 canvas.clear(SK_ColorRED);
93 }
94 return data;
95}
96
98 if (auto gpuDevice = SkCanvasPriv::TopDevice(canvas)->asGraphiteDevice()) {
99 return gpuDevice->target();
100 }
101 return nullptr;
102}
103
105 reporter,
106 context,
107 testContext,
108 true,
110 std::unique_ptr<Recorder> recorder = context->makeRecorder();
111 ResourceProvider* resourceProvider = recorder->priv().resourceProvider();
112 ResourceCache* resourceCache = resourceProvider->resourceCache();
113 const SharedContext* sharedContext = resourceProvider->sharedContext();
114
115 REPORTER_ASSERT(reporter, resourceCache->getResourceCount() == 0);
116 REPORTER_ASSERT(reporter, resourceCache->numFindableResources() == 0);
117
118 // Test making a non budgeted, non shareable resource.
119 auto resource = TestResource::Make(
121 if (!resource) {
122 ERRORF(reporter, "Failed to make TestResource");
123 return;
124 }
125 Resource* resourcePtr = resource.get();
126
127 REPORTER_ASSERT(reporter, resource->budgeted() == skgpu::Budgeted::kNo);
128 resourceCache->insertResource(resourcePtr);
129 REPORTER_ASSERT(reporter, resourceCache->getResourceCount() == 1);
130 // Resource is not shareable and we have a ref on it. Thus it shouldn't ben findable in the
131 // cache.
132 REPORTER_ASSERT(reporter, resourceCache->numFindableResources() == 0);
133
134 // When we reset our TestResource it should go back into the cache since it can be used as a
135 // scratch texture (since it is not shareable). At that point the budget should be changed to
136 // skgpu::Budgeted::kYes.
137 resource.reset();
138 resourceCache->forceProcessReturnedResources();
139 REPORTER_ASSERT(reporter, resourceCache->getResourceCount() == 1);
140 REPORTER_ASSERT(reporter, resourceCache->numFindableResources() == 1);
141 // Even though we reset our ref on the resource we still have the ptr to it and should be the
142 // resource in the cache. So in general this is dangerous it should be safe for this test to
143 // directly access the texture.
145
148 Resource* resourcePtr2 = resourceCache->findAndRefResource(key, skgpu::Budgeted::kNo);
149 REPORTER_ASSERT(reporter, resourcePtr == resourcePtr2);
150 REPORTER_ASSERT(reporter, resourceCache->getResourceCount() == 1);
151 REPORTER_ASSERT(reporter, resourceCache->numFindableResources() == 0);
153 resourcePtr2->unref();
154 resourceCache->forceProcessReturnedResources();
155
156 // Test making a budgeted, shareable resource.
157 resource = TestResource::Make(
159 if (!resource) {
160 ERRORF(reporter, "Failed to make TestResource");
161 return;
162 }
163 resourcePtr = resource.get();
164 REPORTER_ASSERT(reporter, resource->budgeted() == skgpu::Budgeted::kYes);
165 resourceCache->insertResource(resourcePtr);
166 REPORTER_ASSERT(reporter, resourceCache->getResourceCount() == 2);
167 REPORTER_ASSERT(reporter, resourceCache->numFindableResources() == 2);
168
169 resource.reset();
170 resourceCache->forceProcessReturnedResources();
171 REPORTER_ASSERT(reporter, resourceCache->getResourceCount() == 2);
172 REPORTER_ASSERT(reporter, resourceCache->numFindableResources() == 2);
174
176 resourcePtr2 = resourceCache->findAndRefResource(key, skgpu::Budgeted::kYes);
177 REPORTER_ASSERT(reporter, resourcePtr == resourcePtr2);
178 REPORTER_ASSERT(reporter, resourceCache->getResourceCount() == 2);
179 REPORTER_ASSERT(reporter, resourceCache->numFindableResources() == 2);
181 resourcePtr2->unref();
182
183 ///////////////////////////////////////////////////////////////////////////////////////////////
184 // Test that SkImage's and SkSurface's underlying Resource's follow the expected budgeted
185 // system.
187
188 // First test SkImages. Since we can't directly create a Graphite SkImage we first have to make
189 // a raster SkImage than convert that to a Graphite SkImage via makeTextureImage.
191 sk_sp<SkImage> image = SkImages::RasterFromData(info, std::move(data), info.minRowBytes());
193
194 sk_sp<SkImage> imageGpu = SkImages::TextureFromImage(recorder.get(), image, {});
195 REPORTER_ASSERT(reporter, imageGpu);
196
197 TextureProxy* imageProxy = nullptr;
198 {
199 // We don't want the view holding a ref to the Proxy or else we can't send things back to
200 // the cache.
201 auto view = skgpu::graphite::AsView(imageGpu.get());
203 imageProxy = view.proxy();
204 }
205 // Make sure the proxy is instantiated
206 if (!imageProxy->instantiate(resourceProvider)) {
207 ERRORF(reporter, "Failed to instantiate Proxy");
208 return;
209 }
210 const Resource* imageResourcePtr = imageProxy->texture();
211 REPORTER_ASSERT(reporter, imageResourcePtr);
212 // There is an extra resource for the buffer that is uploading the data to the texture
213 REPORTER_ASSERT(reporter, resourceCache->getResourceCount() == 4);
214 REPORTER_ASSERT(reporter, resourceCache->numFindableResources() == 2);
215 REPORTER_ASSERT(reporter, imageResourcePtr->budgeted() == skgpu::Budgeted::kNo);
216
217 // Submit all upload work so we can drop refs to the image and get it returned to the cache.
218 std::unique_ptr<Recording> recording = recorder->snap();
219 if (!recording) {
220 ERRORF(reporter, "Failed to make recording");
221 return;
222 }
223 InsertRecordingInfo insertInfo;
224 insertInfo.fRecording = recording.get();
225 context->insertRecording(insertInfo);
226 testContext->syncedSubmit(context);
227 recording.reset();
228 imageGpu.reset();
229 resourceCache->forceProcessReturnedResources();
230
231 REPORTER_ASSERT(reporter, resourceCache->getResourceCount() == 4);
232 // Remapping async buffers before returning them to the cache can extend buffer lifetime.
233 if (!context->priv().caps()->bufferMapsAreAsync()) {
234 REPORTER_ASSERT(reporter, resourceCache->numFindableResources() == 4);
235 }
237
238 // Now try an SkSurface. This is simpler since we can directly create Graphite SkSurface's.
240 if (!surface) {
241 ERRORF(reporter, "Failed to make surface");
242 return;
243 }
244
245 TextureProxy* surfaceProxy = top_device_graphite_target_proxy(surface->getCanvas());
246 if (!surfaceProxy) {
247 ERRORF(reporter, "Failed to get surface proxy");
248 return;
249 }
250
251 // Make sure the proxy is instantiated
252 if (!surfaceProxy->instantiate(resourceProvider)) {
253 ERRORF(reporter, "Failed to instantiate surface proxy");
254 return;
255 }
256 const Resource* surfaceResourcePtr = surfaceProxy->texture();
257
258 REPORTER_ASSERT(reporter, resourceCache->getResourceCount() == 5);
259 // Remapping async buffers before returning them to the cache can extend buffer lifetime.
260 if (!context->priv().caps()->bufferMapsAreAsync()) {
261 REPORTER_ASSERT(reporter, resourceCache->numFindableResources() == 4);
262 }
263 REPORTER_ASSERT(reporter, surfaceResourcePtr->budgeted() == skgpu::Budgeted::kNo);
264
265 // The creation of the surface may have added an initial clear to it. Thus if we just reset the
266 // surface it will flush the clean on the device and we don't be dropping all our refs to the
267 // surface. So we force all the work to happen first.
268 recording = recorder->snap();
269 insertInfo.fRecording = recording.get();
270 context->insertRecording(insertInfo);
271 testContext->syncedSubmit(context);
272 recording.reset();
273
274 surface.reset();
275 resourceCache->forceProcessReturnedResources();
276 REPORTER_ASSERT(reporter, surfaceResourcePtr->budgeted() == skgpu::Budgeted::kYes);
277}
278
279namespace {
281 const SharedContext* sharedContext,
282 ResourceCache* resourceCache,
283 size_t gpuMemorySize,
285 auto resource = TestResource::Make(sharedContext,
287 budgeted,
289 gpuMemorySize);
290 if (!resource) {
291 ERRORF(reporter, "Failed to make TestResource");
292 return nullptr;
293 }
294 resourceCache->insertResource(resource.get());
295 return resource;
296}
297
298Resource* add_new_purgeable_resource(skiatest::Reporter* reporter,
299 const SharedContext* sharedContext,
300 ResourceCache* resourceCache,
301 size_t gpuMemorySize) {
302 auto resource = add_new_resource(reporter, sharedContext, resourceCache, gpuMemorySize);
303 if (!resource) {
304 return nullptr;
305 }
306
307 Resource* ptr = resource.get();
308 resource.reset();
309 resourceCache->forceProcessReturnedResources();
310 return ptr;
311}
312} // namespace
313
314DEF_GRAPHITE_TEST_FOR_ALL_CONTEXTS(GraphitePurgeAsNeededResourcesTest, reporter, context,
316 std::unique_ptr<Recorder> recorder = context->makeRecorder();
317 ResourceProvider* resourceProvider = recorder->priv().resourceProvider();
318 ResourceCache* resourceCache = resourceProvider->resourceCache();
319 const SharedContext* sharedContext = resourceProvider->sharedContext();
320
321 resourceCache->setMaxBudget(10);
322
323 auto resourceSize10 = add_new_resource(reporter,
324 sharedContext,
325 resourceCache,
326 /*gpuMemorySize=*/10);
327
328 REPORTER_ASSERT(reporter, resourceCache->getResourceCount() == 1);
329 REPORTER_ASSERT(reporter, resourceCache->topOfPurgeableQueue() == nullptr);
330 REPORTER_ASSERT(reporter, resourceCache->currentBudgetedBytes() == 10);
331
332 auto resourceSize1 = add_new_resource(reporter,
333 sharedContext,
334 resourceCache,
335 /*gpuMemorySize=*/1);
336
337 // We should now be over budget, but nothing should be purged since neither resource is
338 // purgeable.
339 REPORTER_ASSERT(reporter, resourceCache->getResourceCount() == 2);
340 REPORTER_ASSERT(reporter, resourceCache->topOfPurgeableQueue() == nullptr);
341 REPORTER_ASSERT(reporter, resourceCache->currentBudgetedBytes() == 11);
342
343 // Dropping the ref to the size 1 resource should cause it to get purged when we add a new
344 // resource to the cache.
345 resourceSize1.reset();
346
347 auto resourceSize2 = add_new_resource(reporter,
348 sharedContext,
349 resourceCache,
350 /*gpuMemorySize=*/2);
351
352 // The purging should have happened when we return the resource above so we also shouldn't
353 // see anything in the purgeable queue.
354 REPORTER_ASSERT(reporter, resourceCache->getResourceCount() == 2);
355 REPORTER_ASSERT(reporter, resourceCache->topOfPurgeableQueue() == nullptr);
356 REPORTER_ASSERT(reporter, resourceCache->currentBudgetedBytes() == 12);
357
358 // Reset the cache back to no resources by setting budget to 0.
359 resourceSize10.reset();
360 resourceSize2.reset();
361 resourceCache->forceProcessReturnedResources();
362 resourceCache->setMaxBudget(0);
363
364 REPORTER_ASSERT(reporter, resourceCache->getResourceCount() == 0);
365 REPORTER_ASSERT(reporter, resourceCache->topOfPurgeableQueue() == nullptr);
366 REPORTER_ASSERT(reporter, resourceCache->currentBudgetedBytes() == 0);
367
368 // Add a bunch of purgeable resources that keeps us under budget. Nothing should ever get purged.
369 resourceCache->setMaxBudget(10);
370 auto resourceSize1Ptr = add_new_purgeable_resource(reporter,
371 sharedContext,
372 resourceCache,
373 /*gpuMemorySize=*/1);
374 /*auto resourceSize2Ptr=*/ add_new_purgeable_resource(reporter,
375 sharedContext,
376 resourceCache,
377 /*gpuMemorySize=*/2);
378 auto resourceSize3Ptr = add_new_purgeable_resource(reporter,
379 sharedContext,
380 resourceCache,
381 /*gpuMemorySize=*/3);
382 auto resourceSize4Ptr = add_new_purgeable_resource(reporter,
383 sharedContext,
384 resourceCache,
385 /*gpuMemorySize=*/4);
386
387 REPORTER_ASSERT(reporter, resourceCache->getResourceCount() == 4);
388 REPORTER_ASSERT(reporter, resourceCache->topOfPurgeableQueue() == resourceSize1Ptr);
389 REPORTER_ASSERT(reporter, resourceCache->currentBudgetedBytes() == 10);
390
391 // Now add some resources that should cause things to get purged.
392 // Add a size 2 resource should purge the original size 1 and size 2
393 add_new_purgeable_resource(reporter,
394 sharedContext,
395 resourceCache,
396 /*gpuMemorySize=*/2);
397
398 REPORTER_ASSERT(reporter, resourceCache->getResourceCount() == 3);
399 REPORTER_ASSERT(reporter, resourceCache->topOfPurgeableQueue() == resourceSize3Ptr);
400 REPORTER_ASSERT(reporter, resourceCache->currentBudgetedBytes() == 9);
401
402 // Adding a non-purgeable resource should also trigger resources to be purged from purgeable
403 // queue.
404 resourceSize10 = add_new_resource(reporter,
405 sharedContext,
406 resourceCache,
407 /*gpuMemorySize=*/10);
408
409 REPORTER_ASSERT(reporter, resourceCache->getResourceCount() == 1);
410 REPORTER_ASSERT(reporter, resourceCache->topOfPurgeableQueue() == nullptr);
411 REPORTER_ASSERT(reporter, resourceCache->currentBudgetedBytes() == 10);
412
413 // Adding a resources that is purgeable back to the cache shouldn't trigger the previous
414 // non-purgeable resource or itself to be purged yet (since processing our return mailbox
415 // doesn't trigger the purgeAsNeeded call)
416 resourceSize4Ptr = add_new_purgeable_resource(reporter,
417 sharedContext,
418 resourceCache,
419 /*gpuMemorySize=*/4);
420
421 REPORTER_ASSERT(reporter, resourceCache->getResourceCount() == 2);
422 REPORTER_ASSERT(reporter, resourceCache->topOfPurgeableQueue() == resourceSize4Ptr);
423 REPORTER_ASSERT(reporter, resourceCache->currentBudgetedBytes() == 14);
424
425 // Resetting the budget to 0 should trigger purging the size 4 purgeable resource but should
426 // leave the non purgeable size 10 alone.
427 resourceCache->setMaxBudget(0);
428 REPORTER_ASSERT(reporter, resourceCache->getResourceCount() == 1);
429 REPORTER_ASSERT(reporter, resourceCache->topOfPurgeableQueue() == nullptr);
430 REPORTER_ASSERT(reporter, resourceCache->currentBudgetedBytes() == 10);
431
432 resourceSize10.reset();
433 resourceCache->forceProcessReturnedResources();
434 resourceCache->forcePurgeAsNeeded();
435
436 REPORTER_ASSERT(reporter, resourceCache->getResourceCount() == 0);
437 REPORTER_ASSERT(reporter, resourceCache->topOfPurgeableQueue() == nullptr);
438 REPORTER_ASSERT(reporter, resourceCache->currentBudgetedBytes() == 0);
439}
440
441DEF_GRAPHITE_TEST_FOR_ALL_CONTEXTS(GraphiteZeroSizedResourcesTest, reporter, context,
443 std::unique_ptr<Recorder> recorder = context->makeRecorder();
444 ResourceProvider* resourceProvider = recorder->priv().resourceProvider();
445 ResourceCache* resourceCache = resourceProvider->resourceCache();
446 const SharedContext* sharedContext = resourceProvider->sharedContext();
447
448 // First make a normal resource that has a non zero size
449 Resource* resourcePtr = add_new_purgeable_resource(reporter,
450 sharedContext,
451 resourceCache,
452 /*gpuMemorySize=*/1);
453 if (!resourcePtr) {
454 return;
455 }
456
457 REPORTER_ASSERT(reporter, resourceCache->getResourceCount() == 1);
458 REPORTER_ASSERT(reporter, resourceCache->numFindableResources() == 1);
459 REPORTER_ASSERT(reporter, resourceCache->topOfPurgeableQueue() == resourcePtr);
460
461 // First confirm if we set the max budget to zero, this sized resource is removed.
462 resourceCache->setMaxBudget(0);
463 REPORTER_ASSERT(reporter, resourceCache->getResourceCount() == 0);
464 REPORTER_ASSERT(reporter, resourceCache->numFindableResources() == 0);
465 REPORTER_ASSERT(reporter, resourceCache->topOfPurgeableQueue() == nullptr);
466
467 // Set the budget back to something higher
468 resourceCache->setMaxBudget(100);
469
470 // Now create a zero sized resource and add it to the cache.
471 resourcePtr = add_new_purgeable_resource(reporter,
472 sharedContext,
473 resourceCache,
474 /*gpuMemorySize=*/0);
475 if (!resourcePtr) {
476 return;
477 }
478
479 REPORTER_ASSERT(reporter, resourceCache->getResourceCount() == 1);
480 REPORTER_ASSERT(reporter, resourceCache->numFindableResources() == 1);
481 REPORTER_ASSERT(reporter, resourceCache->topOfPurgeableQueue() == resourcePtr);
482
483 // Setting the budget down to 0 should not cause the zero sized resource to be purged
484 resourceCache->setMaxBudget(0);
485 REPORTER_ASSERT(reporter, resourceCache->getResourceCount() == 1);
486 REPORTER_ASSERT(reporter, resourceCache->numFindableResources() == 1);
487 REPORTER_ASSERT(reporter, resourceCache->topOfPurgeableQueue() == resourcePtr);
488
489 // Now add a sized resource to cache. Set budget higher again so that it fits
490 resourceCache->setMaxBudget(100);
491
492 Resource* sizedResourcePtr = add_new_purgeable_resource(reporter,
493 sharedContext,
494 resourceCache,
495 /*gpuMemorySize=*/1);
496 if (!resourcePtr) {
497 return;
498 }
499
500 REPORTER_ASSERT(reporter, resourceCache->getResourceCount() == 2);
501 REPORTER_ASSERT(reporter, resourceCache->numFindableResources() == 2);
502 // Even though the zero sized resource was added to the cache first, the top of the purgeable
503 // stack should be the sized resource.
504 REPORTER_ASSERT(reporter, resourceCache->topOfPurgeableQueue() == sizedResourcePtr);
505
506 // Add another zero sized resource
507 resourcePtr = add_new_purgeable_resource(reporter,
508 sharedContext,
509 resourceCache,
510 /*gpuMemorySize=*/0);
511 if (!resourcePtr) {
512 return;
513 }
514
515 REPORTER_ASSERT(reporter, resourceCache->getResourceCount() == 3);
516 REPORTER_ASSERT(reporter, resourceCache->numFindableResources() == 3);
517 // Again the sized resource should still be the top of the purgeable queue
518 REPORTER_ASSERT(reporter, resourceCache->topOfPurgeableQueue() == sizedResourcePtr);
519
520 // If we set the cache budget to 0, it should clear out the sized resource but leave the two
521 // zero-sized resources.
522 resourceCache->setMaxBudget(0);
523 REPORTER_ASSERT(reporter, resourceCache->getResourceCount() == 2);
524 REPORTER_ASSERT(reporter, resourceCache->numFindableResources() == 2);
525 REPORTER_ASSERT(reporter, resourceCache->topOfPurgeableQueue()->gpuMemorySize() == 0);
526}
527
528DEF_GRAPHITE_TEST_FOR_ALL_CONTEXTS(GraphitePurgeNotUsedSinceResourcesTest, reporter, context,
530 std::unique_ptr<Recorder> recorder = context->makeRecorder();
531 ResourceProvider* resourceProvider = recorder->priv().resourceProvider();
532 ResourceCache* resourceCache = resourceProvider->resourceCache();
533 const SharedContext* sharedContext = resourceProvider->sharedContext();
534
535 // Basic test where we purge 1 resource
536 auto beforeTime = skgpu::StdSteadyClock::now();
537
538 auto resourcePtr = add_new_purgeable_resource(reporter,
539 sharedContext,
540 resourceCache,
541 /*gpuMemorySize=*/1);
542 if (!resourcePtr) {
543 return;
544 }
545
546 REPORTER_ASSERT(reporter, resourceCache->getResourceCount() == 1);
547
548 auto afterTime = skgpu::StdSteadyClock::now();
549
550 // purging beforeTime should not get rid of the resource
551 resourceCache->purgeResourcesNotUsedSince(beforeTime);
552
553 REPORTER_ASSERT(reporter, resourceCache->getResourceCount() == 1);
554
555 // purging at afterTime which is after resource became purgeable should purge it.
556 resourceCache->purgeResourcesNotUsedSince(afterTime);
557
558 REPORTER_ASSERT(reporter, resourceCache->getResourceCount() == 0);
559
560 // Test making 2 purgeable resources, but asking to purge on a time between the two.
561 Resource* resourcePtr1 = add_new_purgeable_resource(reporter,
562 sharedContext,
563 resourceCache,
564 /*gpuMemorySize=*/1);
565
566 auto betweenTime = skgpu::StdSteadyClock::now();
567
568 Resource* resourcePtr2 = add_new_purgeable_resource(reporter,
569 sharedContext,
570 resourceCache,
571 /*gpuMemorySize=*/1);
572
573 afterTime = skgpu::StdSteadyClock::now();
574
575 REPORTER_ASSERT(reporter, resourceCache->getResourceCount() == 2);
576 REPORTER_ASSERT(reporter, resourceCache->testingInPurgeableQueue(resourcePtr1));
577 REPORTER_ASSERT(reporter, resourceCache->testingInPurgeableQueue(resourcePtr2));
578
579 resourceCache->purgeResourcesNotUsedSince(betweenTime);
580
581 REPORTER_ASSERT(reporter, resourceCache->getResourceCount() == 1);
582 REPORTER_ASSERT(reporter, resourceCache->testingInPurgeableQueue(resourcePtr2));
583
584 resourceCache->purgeResourcesNotUsedSince(afterTime);
585 REPORTER_ASSERT(reporter, resourceCache->getResourceCount() == 0);
586
587 // purgeResourcesNotUsedSince should have no impact on non-purgeable resources
588 auto resource = add_new_resource(reporter,
589 sharedContext,
590 resourceCache,
591 /*gpuMemorySize=*/1);
592 if (!resource) {
593 return;
594 }
595 resourcePtr = resource.get();
596
597 REPORTER_ASSERT(reporter, resourceCache->getResourceCount() == 1);
598
599 afterTime = skgpu::StdSteadyClock::now();
600 resourceCache->purgeResourcesNotUsedSince(afterTime);
601 REPORTER_ASSERT(reporter, resourceCache->getResourceCount() == 1);
602 REPORTER_ASSERT(reporter, !resourceCache->testingInPurgeableQueue(resourcePtr));
603
604 resource.reset();
605 // purgeResourcesNotUsedSince should check the mailbox for the returned resource. Though the
606 // time is set before that happens so nothing should purge.
607 resourceCache->purgeResourcesNotUsedSince(skgpu::StdSteadyClock::now());
608 REPORTER_ASSERT(reporter, resourceCache->getResourceCount() == 1);
609 REPORTER_ASSERT(reporter, resourceCache->testingInPurgeableQueue(resourcePtr));
610
611 // Now it should be purged since it is already purgeable
612 resourceCache->purgeResourcesNotUsedSince(skgpu::StdSteadyClock::now());
613 REPORTER_ASSERT(reporter, resourceCache->getResourceCount() == 0);
614}
615
616// This test is used to check the case where we call purgeNotUsedSince, which triggers us to return
617// resources from mailbox. Even though the returned resources aren't purged by the last used, we
618// still end up purging things to get under budget.
619DEF_GRAPHITE_TEST_FOR_ALL_CONTEXTS(GraphitePurgeNotUsedOverBudgetTest, reporter, context,
621 std::unique_ptr<Recorder> recorder = context->makeRecorder();
622 ResourceProvider* resourceProvider = recorder->priv().resourceProvider();
623 ResourceCache* resourceCache = resourceProvider->resourceCache();
624 const SharedContext* sharedContext = resourceProvider->sharedContext();
625
626 // set resourceCache budget to 10 for testing.
627 resourceCache->setMaxBudget(10);
628
629 // First make a purgeable resources
630 auto resourcePtr = add_new_purgeable_resource(reporter,
631 sharedContext,
632 resourceCache,
633 /*gpuMemorySize=*/1);
634 if (!resourcePtr) {
635 return;
636 }
637
638 // Now create a bunch of non purgeable (yet) resources that are not budgeted (i.e. in real world
639 // they would be wrapped in an SkSurface or SkImage), but will cause us to go over our budget
640 // limit when they do return to cache.
641
642 auto resource1 = add_new_resource(reporter,
643 sharedContext,
644 resourceCache,
645 /*gpuMemorySize=*/15,
647
648 auto resource2 = add_new_resource(reporter,
649 sharedContext,
650 resourceCache,
651 /*gpuMemorySize=*/16,
653
654 auto resource3 = add_new_resource(reporter,
655 sharedContext,
656 resourceCache,
657 /*gpuMemorySize=*/3,
659
660 auto resource1Ptr = resource1.get();
661 auto resource2Ptr = resource2.get();
662 auto resource3Ptr = resource3.get();
663
664 REPORTER_ASSERT(reporter, resourceCache->getResourceCount() == 4);
665 REPORTER_ASSERT(reporter, resourceCache->currentBudgetedBytes() == 1);
666
667 auto timeBeforeReturningToCache = skgpu::StdSteadyClock::now();
668
669 // Now reset all the non budgeted resources so they return to the cache and become budgeted.
670 // Returning to the cache will not immedidately trigger a purgeAsNeededCall.
671 resource1.reset();
672 resource2.reset();
673 resource3.reset();
674
675 resourceCache->forceProcessReturnedResources();
676
677 REPORTER_ASSERT(reporter, resourceCache->getResourceCount() == 4);
678 REPORTER_ASSERT(reporter, resourceCache->currentBudgetedBytes() == 35);
679 REPORTER_ASSERT(reporter, resourceCache->testingInPurgeableQueue(resourcePtr));
680 REPORTER_ASSERT(reporter, resourceCache->testingInPurgeableQueue(resource1Ptr));
681 REPORTER_ASSERT(reporter, resourceCache->testingInPurgeableQueue(resource2Ptr));
682 REPORTER_ASSERT(reporter, resourceCache->testingInPurgeableQueue(resource3Ptr));
683
684 // Now we call purgeNotUsedSince with timeBeforeReturnToCache. The original resource should get
685 // purged because it is older than this time. The three originally non budgeted resources are
686 // newer than this time so they won't be purged by the time on this call. However, since we are
687 // overbudget it should trigger us to purge the first two of these resources to get us back
688 // under.
689 resourceCache->purgeResourcesNotUsedSince(timeBeforeReturningToCache);
690 REPORTER_ASSERT(reporter, resourceCache->getResourceCount() == 1);
691 REPORTER_ASSERT(reporter, resourceCache->currentBudgetedBytes() == 3);
692 REPORTER_ASSERT(reporter, resourceCache->testingInPurgeableQueue(resource3Ptr));
693}
694
695// Test call purgeResources on the ResourceCache and make sure all unlocked resources are getting
696// purged regardless of when they were last used.
697DEF_GRAPHITE_TEST_FOR_ALL_CONTEXTS(GraphitePurgeResourcesTest, reporter, context,
699 std::unique_ptr<Recorder> recorder = context->makeRecorder();
700 ResourceProvider* resourceProvider = recorder->priv().resourceProvider();
701 ResourceCache* resourceCache = resourceProvider->resourceCache();
702 const SharedContext* sharedContext = resourceProvider->sharedContext();
703
704 // set resourceCache budget to 10 for testing.
705 resourceCache->setMaxBudget(10);
706
707 // Basic test where we purge 1 resource
708 auto resourcePtr = add_new_purgeable_resource(reporter,
709 sharedContext,
710 resourceCache,
711 /*gpuMemorySize=*/1);
712 if (!resourcePtr) {
713 return;
714 }
715
716 REPORTER_ASSERT(reporter, resourceCache->getResourceCount() == 1);
717
718 // purging should purge the one unlocked resource.
719 resourceCache->purgeResources();
720 REPORTER_ASSERT(reporter, resourceCache->getResourceCount() == 0);
721
722 // Test making 2 purgeable resources
723 Resource* resourcePtr1 = add_new_purgeable_resource(reporter,
724 sharedContext,
725 resourceCache,
726 /*gpuMemorySize=*/1);
727
728 Resource* resourcePtr2 = add_new_purgeable_resource(reporter,
729 sharedContext,
730 resourceCache,
731 /*gpuMemorySize=*/1);
732 if (!resourcePtr1 || !resourcePtr2) {
733 return;
734 }
735
736 REPORTER_ASSERT(reporter, resourceCache->getResourceCount() == 2);
737 REPORTER_ASSERT(reporter, resourceCache->testingInPurgeableQueue(resourcePtr1));
738 REPORTER_ASSERT(reporter, resourceCache->testingInPurgeableQueue(resourcePtr2));
739
740 resourceCache->purgeResources();
741 REPORTER_ASSERT(reporter, resourceCache->getResourceCount() == 0);
742
743 // purgeResources should have no impact on non-purgeable resources
744 auto resource = add_new_resource(reporter,
745 sharedContext,
746 resourceCache,
747 /*gpuMemorySize=*/1);
748 if (!resource) {
749 return;
750 }
751 resourcePtr = resource.get();
752
753 REPORTER_ASSERT(reporter, resourceCache->getResourceCount() == 1);
754
755 resourceCache->purgeResources();
756 REPORTER_ASSERT(reporter, resourceCache->getResourceCount() == 1);
757 REPORTER_ASSERT(reporter, !resourceCache->testingInPurgeableQueue(resourcePtr));
758
759 resource.reset();
760 resourceCache->purgeResources();
761 REPORTER_ASSERT(reporter, resourceCache->getResourceCount() == 0);
762}
763
764} // namespace skgpu::graphite
static void info(const char *fmt,...) SK_PRINTF_LIKE(1
Definition DM.cpp:213
reporter
@ kPremul_SkAlphaType
pixel components are premultiplied by alpha
Definition SkAlphaType.h:29
@ kRGBA_8888_SkColorType
pixel with 8 bits for red, green, blue, alpha; in 32-bit word
Definition SkColorType.h:24
constexpr SkColor SK_ColorRED
Definition SkColor.h:126
#define DEF_GRAPHITE_TEST_FOR_ALL_CONTEXTS(name, reporter, graphite_ctx, ctsEnforcement)
Definition Test.h:373
#define DEF_CONDITIONAL_GRAPHITE_TEST_FOR_ALL_CONTEXTS(name, reporter, graphite_ctx, test_ctx, cond, ctsEnforcement)
Definition Test.h:352
#define REPORTER_ASSERT(r, cond,...)
Definition Test.h:286
#define ERRORF(r,...)
Definition Test.h:293
bool installPixels(const SkImageInfo &info, void *pixels, size_t rowBytes, void(*releaseProc)(void *addr, void *context), void *context)
Definition SkBitmap.cpp:323
static SkDevice * TopDevice(const SkCanvas *canvas)
void clear(SkColor color)
Definition SkCanvas.h:1199
static sk_sp< SkData > MakeUninitialized(size_t length)
Definition SkData.cpp:116
T * get() const
Definition SkRefCnt.h:303
void reset(T *ptr=nullptr)
Definition SkRefCnt.h:310
void purgeResourcesNotUsedSince(StdSteadyClock::time_point purgeTime)
Resource * findAndRefResource(const GraphiteResourceKey &key, skgpu::Budgeted)
skgpu::Budgeted budgeted() const
Definition Resource.h:100
const GraphiteResourceKey & key() const
Definition Resource.h:153
size_t gpuMemorySize() const
Definition Resource.h:104
const SharedContext * sharedContext() const
Definition Resource.h:187
static void CreateKey(GraphiteResourceKey *key, Shareable shareable)
const char * getResourceType() const override
static sk_sp< TestResource > Make(const SharedContext *sharedContext, Ownership owned, skgpu::Budgeted budgeted, Shareable shareable, size_t gpuMemorySize=1)
bool instantiate(ResourceProvider *)
const Texture * texture() const
VkSurfaceKHR surface
Definition main.cc:49
sk_sp< SkImage > image
Definition examples.cpp:29
SK_API sk_sp< SkImage > RasterFromData(const SkImageInfo &info, sk_sp< SkData > pixels, size_t rowBytes)
SK_API sk_sp< SkImage > TextureFromImage(GrDirectContext *, const SkImage *, skgpu::Mipmapped=skgpu::Mipmapped::kNo, skgpu::Budgeted=skgpu::Budgeted::kYes)
SK_API sk_sp< SkSurface > RenderTarget(GrRecordingContext *context, skgpu::Budgeted budgeted, const SkImageInfo &imageInfo, int sampleCount, GrSurfaceOrigin surfaceOrigin, const SkSurfaceProps *surfaceProps, bool shouldCreateWithMips=false, bool isProtected=false)
uint32_t ResourceType
static sk_sp< SkData > create_image_data(const SkImageInfo &info)
static skgpu::graphite::TextureProxy * top_device_graphite_target_proxy(SkCanvas *canvas)
TextureProxyView AsView(const SkImage *image)
Budgeted
Definition GpuTypes.h:35
static SkImageInfo Make(int width, int height, SkColorType ct, SkAlphaType at)