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