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