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