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