/* * Copyright 2022 Google LLC * * Use of this source code is governed by a BSD-style license that can be * found in the LICENSE file. */ #include "tests/Test.h" #include "include/core/SkBitmap.h" #include "include/core/SkCanvas.h" #include "include/core/SkImage.h" #include "include/core/SkSurface.h" #include "include/gpu/graphite/Context.h" #include "include/gpu/graphite/Image.h" #include "include/gpu/graphite/Recorder.h" #include "include/gpu/graphite/Recording.h" #include "include/gpu/graphite/Surface.h" #include "src/core/SkCanvasPriv.h" #include "src/gpu/graphite/Caps.h" #include "src/gpu/graphite/ContextPriv.h" #include "src/gpu/graphite/Device.h" #include "src/gpu/graphite/RecorderPriv.h" #include "src/gpu/graphite/Resource.h" #include "src/gpu/graphite/ResourceCache.h" #include "src/gpu/graphite/ResourceProvider.h" #include "src/gpu/graphite/SharedContext.h" #include "src/gpu/graphite/Texture.h" #include "src/gpu/graphite/TextureProxyView.h" #include "src/gpu/graphite/TextureUtils.h" #include "src/image/SkImage_Base.h" #include "tools/Resources.h" #include "tools/graphite/GraphiteTestContext.h" namespace skgpu::graphite { class TestResource : public Resource { public: static sk_sp Make(const SharedContext* sharedContext, Ownership owned, skgpu::Budgeted budgeted, Shareable shareable, size_t gpuMemorySize = 1) { auto resource = sk_sp(new TestResource(sharedContext, owned, budgeted, gpuMemorySize)); if (!resource) { return nullptr; } GraphiteResourceKey key; CreateKey(&key, shareable); resource->setKey(key); return resource; } const char* getResourceType() const override { return "Test Resource"; } static void CreateKey(GraphiteResourceKey* key, Shareable shareable) { // Internally we assert that we don't make the same key twice where the only difference is // shareable vs non-shareable. That allows us to now have Shareable be part of the Key's // key. So here we make two different resource types so the keys will be different. static const ResourceType kType = GraphiteResourceKey::GenerateResourceType(); static const ResourceType kShareableType = GraphiteResourceKey::GenerateResourceType(); ResourceType type = shareable == Shareable::kNo ? kType : kShareableType; GraphiteResourceKey::Builder(key, type, 0, shareable); } private: TestResource(const SharedContext* sharedContext, Ownership owned, skgpu::Budgeted budgeted, size_t gpuMemorySize) : Resource(sharedContext, owned, budgeted, gpuMemorySize) {} void freeGpuData() override {} }; static sk_sp create_image_data(const SkImageInfo& info) { const size_t rowBytes = info.minRowBytes(); sk_sp data(SkData::MakeUninitialized(rowBytes * info.height())); { SkBitmap bm; bm.installPixels(info, data->writable_data(), rowBytes); SkCanvas canvas(bm); canvas.clear(SK_ColorRED); } return data; } static skgpu::graphite::TextureProxy* top_device_graphite_target_proxy(SkCanvas* canvas) { if (auto gpuDevice = SkCanvasPriv::TopDevice(canvas)->asGraphiteDevice()) { return gpuDevice->target(); } return nullptr; } DEF_CONDITIONAL_GRAPHITE_TEST_FOR_ALL_CONTEXTS(GraphiteBudgetedResourcesTest, reporter, context, testContext, true, CtsEnforcement::kApiLevel_V) { std::unique_ptr recorder = context->makeRecorder(); ResourceProvider* resourceProvider = recorder->priv().resourceProvider(); ResourceCache* resourceCache = resourceProvider->resourceCache(); const SharedContext* sharedContext = resourceProvider->sharedContext(); REPORTER_ASSERT(reporter, resourceCache->getResourceCount() == 0); REPORTER_ASSERT(reporter, resourceCache->numFindableResources() == 0); // Test making a non budgeted, non shareable resource. auto resource = TestResource::Make( sharedContext, Ownership::kOwned, skgpu::Budgeted::kNo, Shareable::kNo); if (!resource) { ERRORF(reporter, "Failed to make TestResource"); return; } Resource* resourcePtr = resource.get(); REPORTER_ASSERT(reporter, resource->budgeted() == skgpu::Budgeted::kNo); resourceCache->insertResource(resourcePtr); REPORTER_ASSERT(reporter, resourceCache->getResourceCount() == 1); // Resource is not shareable and we have a ref on it. Thus it shouldn't ben findable in the // cache. REPORTER_ASSERT(reporter, resourceCache->numFindableResources() == 0); // When we reset our TestResource it should go back into the cache since it can be used as a // scratch texture (since it is not shareable). At that point the budget should be changed to // skgpu::Budgeted::kYes. resource.reset(); resourceCache->forceProcessReturnedResources(); REPORTER_ASSERT(reporter, resourceCache->getResourceCount() == 1); REPORTER_ASSERT(reporter, resourceCache->numFindableResources() == 1); // Even though we reset our ref on the resource we still have the ptr to it and should be the // resource in the cache. So in general this is dangerous it should be safe for this test to // directly access the texture. REPORTER_ASSERT(reporter, resourcePtr->budgeted() == skgpu::Budgeted::kYes); GraphiteResourceKey key; TestResource::CreateKey(&key, Shareable::kNo); Resource* resourcePtr2 = resourceCache->findAndRefResource(key, skgpu::Budgeted::kNo); REPORTER_ASSERT(reporter, resourcePtr == resourcePtr2); REPORTER_ASSERT(reporter, resourceCache->getResourceCount() == 1); REPORTER_ASSERT(reporter, resourceCache->numFindableResources() == 0); REPORTER_ASSERT(reporter, resourcePtr2->budgeted() == skgpu::Budgeted::kNo); resourcePtr2->unref(); resourceCache->forceProcessReturnedResources(); // Test making a budgeted, shareable resource. resource = TestResource::Make( sharedContext, Ownership::kOwned, skgpu::Budgeted::kYes, Shareable::kYes); if (!resource) { ERRORF(reporter, "Failed to make TestResource"); return; } resourcePtr = resource.get(); REPORTER_ASSERT(reporter, resource->budgeted() == skgpu::Budgeted::kYes); resourceCache->insertResource(resourcePtr); REPORTER_ASSERT(reporter, resourceCache->getResourceCount() == 2); REPORTER_ASSERT(reporter, resourceCache->numFindableResources() == 2); resource.reset(); resourceCache->forceProcessReturnedResources(); REPORTER_ASSERT(reporter, resourceCache->getResourceCount() == 2); REPORTER_ASSERT(reporter, resourceCache->numFindableResources() == 2); REPORTER_ASSERT(reporter, resourcePtr->budgeted() == skgpu::Budgeted::kYes); TestResource::CreateKey(&key, Shareable::kYes); resourcePtr2 = resourceCache->findAndRefResource(key, skgpu::Budgeted::kYes); REPORTER_ASSERT(reporter, resourcePtr == resourcePtr2); REPORTER_ASSERT(reporter, resourceCache->getResourceCount() == 2); REPORTER_ASSERT(reporter, resourceCache->numFindableResources() == 2); REPORTER_ASSERT(reporter, resourcePtr2->budgeted() == skgpu::Budgeted::kYes); resourcePtr2->unref(); /////////////////////////////////////////////////////////////////////////////////////////////// // Test that SkImage's and SkSurface's underlying Resource's follow the expected budgeted // system. auto info = SkImageInfo::Make(10, 10, kRGBA_8888_SkColorType, kPremul_SkAlphaType); // First test SkImages. Since we can't directly create a Graphite SkImage we first have to make // a raster SkImage than convert that to a Graphite SkImage via makeTextureImage. sk_sp data(create_image_data(info)); sk_sp image = SkImages::RasterFromData(info, std::move(data), info.minRowBytes()); REPORTER_ASSERT(reporter, image); sk_sp imageGpu = SkImages::TextureFromImage(recorder.get(), image, {}); REPORTER_ASSERT(reporter, imageGpu); TextureProxy* imageProxy = nullptr; { // We don't want the view holding a ref to the Proxy or else we can't send things back to // the cache. auto view = skgpu::graphite::AsView(imageGpu.get()); REPORTER_ASSERT(reporter, view); imageProxy = view.proxy(); } // Make sure the proxy is instantiated if (!imageProxy->instantiate(resourceProvider)) { ERRORF(reporter, "Failed to instantiate Proxy"); return; } const Resource* imageResourcePtr = imageProxy->texture(); REPORTER_ASSERT(reporter, imageResourcePtr); // There is an extra resource for the buffer that is uploading the data to the texture REPORTER_ASSERT(reporter, resourceCache->getResourceCount() == 4); REPORTER_ASSERT(reporter, resourceCache->numFindableResources() == 2); REPORTER_ASSERT(reporter, imageResourcePtr->budgeted() == skgpu::Budgeted::kNo); // Submit all upload work so we can drop refs to the image and get it returned to the cache. std::unique_ptr recording = recorder->snap(); if (!recording) { ERRORF(reporter, "Failed to make recording"); return; } InsertRecordingInfo insertInfo; insertInfo.fRecording = recording.get(); context->insertRecording(insertInfo); testContext->syncedSubmit(context); recording.reset(); imageGpu.reset(); resourceCache->forceProcessReturnedResources(); REPORTER_ASSERT(reporter, resourceCache->getResourceCount() == 4); // Remapping async buffers before returning them to the cache can extend buffer lifetime. if (!context->priv().caps()->bufferMapsAreAsync()) { REPORTER_ASSERT(reporter, resourceCache->numFindableResources() == 4); } REPORTER_ASSERT(reporter, imageResourcePtr->budgeted() == skgpu::Budgeted::kYes); // Now try an SkSurface. This is simpler since we can directly create Graphite SkSurface's. sk_sp surface = SkSurfaces::RenderTarget(recorder.get(), info); if (!surface) { ERRORF(reporter, "Failed to make surface"); return; } TextureProxy* surfaceProxy = top_device_graphite_target_proxy(surface->getCanvas()); if (!surfaceProxy) { ERRORF(reporter, "Failed to get surface proxy"); return; } // Make sure the proxy is instantiated if (!surfaceProxy->instantiate(resourceProvider)) { ERRORF(reporter, "Failed to instantiate surface proxy"); return; } const Resource* surfaceResourcePtr = surfaceProxy->texture(); REPORTER_ASSERT(reporter, resourceCache->getResourceCount() == 5); // Remapping async buffers before returning them to the cache can extend buffer lifetime. if (!context->priv().caps()->bufferMapsAreAsync()) { REPORTER_ASSERT(reporter, resourceCache->numFindableResources() == 4); } REPORTER_ASSERT(reporter, surfaceResourcePtr->budgeted() == skgpu::Budgeted::kNo); // The creation of the surface may have added an initial clear to it. Thus if we just reset the // surface it will flush the clean on the device and we don't be dropping all our refs to the // surface. So we force all the work to happen first. recording = recorder->snap(); insertInfo.fRecording = recording.get(); context->insertRecording(insertInfo); testContext->syncedSubmit(context); recording.reset(); surface.reset(); resourceCache->forceProcessReturnedResources(); REPORTER_ASSERT(reporter, surfaceResourcePtr->budgeted() == skgpu::Budgeted::kYes); } namespace { sk_sp add_new_resource(skiatest::Reporter* reporter, const SharedContext* sharedContext, ResourceCache* resourceCache, size_t gpuMemorySize, skgpu::Budgeted budgeted = skgpu::Budgeted::kYes) { auto resource = TestResource::Make(sharedContext, Ownership::kOwned, budgeted, Shareable::kNo, gpuMemorySize); if (!resource) { ERRORF(reporter, "Failed to make TestResource"); return nullptr; } resourceCache->insertResource(resource.get()); return resource; } Resource* add_new_purgeable_resource(skiatest::Reporter* reporter, const SharedContext* sharedContext, ResourceCache* resourceCache, size_t gpuMemorySize) { auto resource = add_new_resource(reporter, sharedContext, resourceCache, gpuMemorySize); if (!resource) { return nullptr; } Resource* ptr = resource.get(); resource.reset(); resourceCache->forceProcessReturnedResources(); return ptr; } } // namespace DEF_GRAPHITE_TEST_FOR_ALL_CONTEXTS(GraphitePurgeAsNeededResourcesTest, reporter, context, CtsEnforcement::kApiLevel_V) { std::unique_ptr recorder = context->makeRecorder(); ResourceProvider* resourceProvider = recorder->priv().resourceProvider(); ResourceCache* resourceCache = resourceProvider->resourceCache(); const SharedContext* sharedContext = resourceProvider->sharedContext(); resourceCache->setMaxBudget(10); auto resourceSize10 = add_new_resource(reporter, sharedContext, resourceCache, /*gpuMemorySize=*/10); REPORTER_ASSERT(reporter, resourceCache->getResourceCount() == 1); REPORTER_ASSERT(reporter, resourceCache->topOfPurgeableQueue() == nullptr); REPORTER_ASSERT(reporter, resourceCache->currentBudgetedBytes() == 10); auto resourceSize1 = add_new_resource(reporter, sharedContext, resourceCache, /*gpuMemorySize=*/1); // We should now be over budget, but nothing should be purged since neither resource is // purgeable. REPORTER_ASSERT(reporter, resourceCache->getResourceCount() == 2); REPORTER_ASSERT(reporter, resourceCache->topOfPurgeableQueue() == nullptr); REPORTER_ASSERT(reporter, resourceCache->currentBudgetedBytes() == 11); // Dropping the ref to the size 1 resource should cause it to get purged when we add a new // resource to the cache. resourceSize1.reset(); auto resourceSize2 = add_new_resource(reporter, sharedContext, resourceCache, /*gpuMemorySize=*/2); // The purging should have happened when we return the resource above so we also shouldn't // see anything in the purgeable queue. REPORTER_ASSERT(reporter, resourceCache->getResourceCount() == 2); REPORTER_ASSERT(reporter, resourceCache->topOfPurgeableQueue() == nullptr); REPORTER_ASSERT(reporter, resourceCache->currentBudgetedBytes() == 12); // Reset the cache back to no resources by setting budget to 0. resourceSize10.reset(); resourceSize2.reset(); resourceCache->forceProcessReturnedResources(); resourceCache->setMaxBudget(0); REPORTER_ASSERT(reporter, resourceCache->getResourceCount() == 0); REPORTER_ASSERT(reporter, resourceCache->topOfPurgeableQueue() == nullptr); REPORTER_ASSERT(reporter, resourceCache->currentBudgetedBytes() == 0); // Add a bunch of purgeable resources that keeps us under budget. Nothing should ever get purged. resourceCache->setMaxBudget(10); auto resourceSize1Ptr = add_new_purgeable_resource(reporter, sharedContext, resourceCache, /*gpuMemorySize=*/1); /*auto resourceSize2Ptr=*/ add_new_purgeable_resource(reporter, sharedContext, resourceCache, /*gpuMemorySize=*/2); auto resourceSize3Ptr = add_new_purgeable_resource(reporter, sharedContext, resourceCache, /*gpuMemorySize=*/3); auto resourceSize4Ptr = add_new_purgeable_resource(reporter, sharedContext, resourceCache, /*gpuMemorySize=*/4); REPORTER_ASSERT(reporter, resourceCache->getResourceCount() == 4); REPORTER_ASSERT(reporter, resourceCache->topOfPurgeableQueue() == resourceSize1Ptr); REPORTER_ASSERT(reporter, resourceCache->currentBudgetedBytes() == 10); // Now add some resources that should cause things to get purged. // Add a size 2 resource should purge the original size 1 and size 2 add_new_purgeable_resource(reporter, sharedContext, resourceCache, /*gpuMemorySize=*/2); REPORTER_ASSERT(reporter, resourceCache->getResourceCount() == 3); REPORTER_ASSERT(reporter, resourceCache->topOfPurgeableQueue() == resourceSize3Ptr); REPORTER_ASSERT(reporter, resourceCache->currentBudgetedBytes() == 9); // Adding a non-purgeable resource should also trigger resources to be purged from purgeable // queue. resourceSize10 = add_new_resource(reporter, sharedContext, resourceCache, /*gpuMemorySize=*/10); REPORTER_ASSERT(reporter, resourceCache->getResourceCount() == 1); REPORTER_ASSERT(reporter, resourceCache->topOfPurgeableQueue() == nullptr); REPORTER_ASSERT(reporter, resourceCache->currentBudgetedBytes() == 10); // Adding a resources that is purgeable back to the cache shouldn't trigger the previous // non-purgeable resource or itself to be purged yet (since processing our return mailbox // doesn't trigger the purgeAsNeeded call) resourceSize4Ptr = add_new_purgeable_resource(reporter, sharedContext, resourceCache, /*gpuMemorySize=*/4); REPORTER_ASSERT(reporter, resourceCache->getResourceCount() == 2); REPORTER_ASSERT(reporter, resourceCache->topOfPurgeableQueue() == resourceSize4Ptr); REPORTER_ASSERT(reporter, resourceCache->currentBudgetedBytes() == 14); // Resetting the budget to 0 should trigger purging the size 4 purgeable resource but should // leave the non purgeable size 10 alone. resourceCache->setMaxBudget(0); REPORTER_ASSERT(reporter, resourceCache->getResourceCount() == 1); REPORTER_ASSERT(reporter, resourceCache->topOfPurgeableQueue() == nullptr); REPORTER_ASSERT(reporter, resourceCache->currentBudgetedBytes() == 10); resourceSize10.reset(); resourceCache->forceProcessReturnedResources(); resourceCache->forcePurgeAsNeeded(); REPORTER_ASSERT(reporter, resourceCache->getResourceCount() == 0); REPORTER_ASSERT(reporter, resourceCache->topOfPurgeableQueue() == nullptr); REPORTER_ASSERT(reporter, resourceCache->currentBudgetedBytes() == 0); } DEF_GRAPHITE_TEST_FOR_ALL_CONTEXTS(GraphiteZeroSizedResourcesTest, reporter, context, CtsEnforcement::kApiLevel_V) { std::unique_ptr recorder = context->makeRecorder(); ResourceProvider* resourceProvider = recorder->priv().resourceProvider(); ResourceCache* resourceCache = resourceProvider->resourceCache(); const SharedContext* sharedContext = resourceProvider->sharedContext(); // First make a normal resource that has a non zero size Resource* resourcePtr = add_new_purgeable_resource(reporter, sharedContext, resourceCache, /*gpuMemorySize=*/1); if (!resourcePtr) { return; } REPORTER_ASSERT(reporter, resourceCache->getResourceCount() == 1); REPORTER_ASSERT(reporter, resourceCache->numFindableResources() == 1); REPORTER_ASSERT(reporter, resourceCache->topOfPurgeableQueue() == resourcePtr); // First confirm if we set the max budget to zero, this sized resource is removed. resourceCache->setMaxBudget(0); REPORTER_ASSERT(reporter, resourceCache->getResourceCount() == 0); REPORTER_ASSERT(reporter, resourceCache->numFindableResources() == 0); REPORTER_ASSERT(reporter, resourceCache->topOfPurgeableQueue() == nullptr); // Set the budget back to something higher resourceCache->setMaxBudget(100); // Now create a zero sized resource and add it to the cache. resourcePtr = add_new_purgeable_resource(reporter, sharedContext, resourceCache, /*gpuMemorySize=*/0); if (!resourcePtr) { return; } REPORTER_ASSERT(reporter, resourceCache->getResourceCount() == 1); REPORTER_ASSERT(reporter, resourceCache->numFindableResources() == 1); REPORTER_ASSERT(reporter, resourceCache->topOfPurgeableQueue() == resourcePtr); // Setting the budget down to 0 should not cause the zero sized resource to be purged resourceCache->setMaxBudget(0); REPORTER_ASSERT(reporter, resourceCache->getResourceCount() == 1); REPORTER_ASSERT(reporter, resourceCache->numFindableResources() == 1); REPORTER_ASSERT(reporter, resourceCache->topOfPurgeableQueue() == resourcePtr); // Now add a sized resource to cache. Set budget higher again so that it fits resourceCache->setMaxBudget(100); Resource* sizedResourcePtr = add_new_purgeable_resource(reporter, sharedContext, resourceCache, /*gpuMemorySize=*/1); if (!resourcePtr) { return; } REPORTER_ASSERT(reporter, resourceCache->getResourceCount() == 2); REPORTER_ASSERT(reporter, resourceCache->numFindableResources() == 2); // Even though the zero sized resource was added to the cache first, the top of the purgeable // stack should be the sized resource. REPORTER_ASSERT(reporter, resourceCache->topOfPurgeableQueue() == sizedResourcePtr); // Add another zero sized resource resourcePtr = add_new_purgeable_resource(reporter, sharedContext, resourceCache, /*gpuMemorySize=*/0); if (!resourcePtr) { return; } REPORTER_ASSERT(reporter, resourceCache->getResourceCount() == 3); REPORTER_ASSERT(reporter, resourceCache->numFindableResources() == 3); // Again the sized resource should still be the top of the purgeable queue REPORTER_ASSERT(reporter, resourceCache->topOfPurgeableQueue() == sizedResourcePtr); // If we set the cache budget to 0, it should clear out the sized resource but leave the two // zero-sized resources. resourceCache->setMaxBudget(0); REPORTER_ASSERT(reporter, resourceCache->getResourceCount() == 2); REPORTER_ASSERT(reporter, resourceCache->numFindableResources() == 2); REPORTER_ASSERT(reporter, resourceCache->topOfPurgeableQueue()->gpuMemorySize() == 0); // However, purging all resources should clear the zero-sized resources. resourceCache->purgeResources(); REPORTER_ASSERT(reporter, resourceCache->getResourceCount() == 0); REPORTER_ASSERT(reporter, resourceCache->numFindableResources() == 0); } // Depending on the granularity of the clock for a given device, in the // GraphitePurgeNotUsedSinceResourcesTest we may end up with times that are all equal which messes // up the expected behavior of the purge calls. So this helper forces us to return a new time that // is different from a previous one. skgpu::StdSteadyClock::time_point force_newer_timepoint( const skgpu::StdSteadyClock::time_point& prevTime) { auto time = skgpu::StdSteadyClock::now(); while (time <= prevTime) { time = skgpu::StdSteadyClock::now(); } return time; } DEF_GRAPHITE_TEST_FOR_ALL_CONTEXTS(GraphitePurgeNotUsedSinceResourcesTest, reporter, context, CtsEnforcement::kApiLevel_V) { std::unique_ptr recorder = context->makeRecorder(); ResourceProvider* resourceProvider = recorder->priv().resourceProvider(); ResourceCache* resourceCache = resourceProvider->resourceCache(); const SharedContext* sharedContext = resourceProvider->sharedContext(); // Basic test where we purge 1 resource auto beforeTime = skgpu::StdSteadyClock::now(); auto resourcePtr = add_new_purgeable_resource(reporter, sharedContext, resourceCache, /*gpuMemorySize=*/1); if (!resourcePtr) { return; } REPORTER_ASSERT(reporter, resourceCache->getResourceCount() == 1); auto afterTime = force_newer_timepoint(skgpu::StdSteadyClock::now()); // purging beforeTime should not get rid of the resource resourceCache->purgeResourcesNotUsedSince(beforeTime); REPORTER_ASSERT(reporter, resourceCache->getResourceCount() == 1); // purging at afterTime which is after resource became purgeable should purge it. resourceCache->purgeResourcesNotUsedSince(afterTime); REPORTER_ASSERT(reporter, resourceCache->getResourceCount() == 0); // Test making 2 purgeable resources, but asking to purge on a time between the two. Resource* resourcePtr1 = add_new_purgeable_resource(reporter, sharedContext, resourceCache, /*gpuMemorySize=*/1); auto betweenTime = force_newer_timepoint(skgpu::StdSteadyClock::now()); Resource* resourcePtr2 = add_new_purgeable_resource(reporter, sharedContext, resourceCache, /*gpuMemorySize=*/1); afterTime = force_newer_timepoint(skgpu::StdSteadyClock::now()); REPORTER_ASSERT(reporter, resourceCache->getResourceCount() == 2); REPORTER_ASSERT(reporter, resourceCache->testingInPurgeableQueue(resourcePtr1)); REPORTER_ASSERT(reporter, resourceCache->testingInPurgeableQueue(resourcePtr2)); resourceCache->purgeResourcesNotUsedSince(betweenTime); REPORTER_ASSERT(reporter, resourceCache->getResourceCount() == 1); REPORTER_ASSERT(reporter, resourceCache->testingInPurgeableQueue(resourcePtr2)); resourceCache->purgeResourcesNotUsedSince(afterTime); REPORTER_ASSERT(reporter, resourceCache->getResourceCount() == 0); // purgeResourcesNotUsedSince should have no impact on non-purgeable resources auto resource = add_new_resource(reporter, sharedContext, resourceCache, /*gpuMemorySize=*/1); if (!resource) { return; } resourcePtr = resource.get(); REPORTER_ASSERT(reporter, resourceCache->getResourceCount() == 1); afterTime = force_newer_timepoint(skgpu::StdSteadyClock::now()); resourceCache->purgeResourcesNotUsedSince(afterTime); REPORTER_ASSERT(reporter, resourceCache->getResourceCount() == 1); REPORTER_ASSERT(reporter, !resourceCache->testingInPurgeableQueue(resourcePtr)); resource.reset(); // purgeResourcesNotUsedSince should check the mailbox for the returned resource. Though the // time is set before that happens so nothing should purge. resourceCache->purgeResourcesNotUsedSince(skgpu::StdSteadyClock::now()); REPORTER_ASSERT(reporter, resourceCache->getResourceCount() == 1); REPORTER_ASSERT(reporter, resourceCache->testingInPurgeableQueue(resourcePtr)); // Now it should be purged since it is already purgeable resourceCache->purgeResourcesNotUsedSince(force_newer_timepoint(skgpu::StdSteadyClock::now())); REPORTER_ASSERT(reporter, resourceCache->getResourceCount() == 0); } // This test is used to check the case where we call purgeNotUsedSince, which triggers us to return // resources from mailbox. Even though the returned resources aren't purged by the last used, we // still end up purging things to get under budget. DEF_GRAPHITE_TEST_FOR_ALL_CONTEXTS(GraphitePurgeNotUsedOverBudgetTest, reporter, context, CtsEnforcement::kApiLevel_V) { std::unique_ptr recorder = context->makeRecorder(); ResourceProvider* resourceProvider = recorder->priv().resourceProvider(); ResourceCache* resourceCache = resourceProvider->resourceCache(); const SharedContext* sharedContext = resourceProvider->sharedContext(); // set resourceCache budget to 10 for testing. resourceCache->setMaxBudget(10); // First make a purgeable resources auto resourcePtr = add_new_purgeable_resource(reporter, sharedContext, resourceCache, /*gpuMemorySize=*/1); if (!resourcePtr) { return; } // Now create a bunch of non purgeable (yet) resources that are not budgeted (i.e. in real world // they would be wrapped in an SkSurface or SkImage), but will cause us to go over our budget // limit when they do return to cache. auto resource1 = add_new_resource(reporter, sharedContext, resourceCache, /*gpuMemorySize=*/15, skgpu::Budgeted::kNo); auto resource2 = add_new_resource(reporter, sharedContext, resourceCache, /*gpuMemorySize=*/16, skgpu::Budgeted::kNo); auto resource3 = add_new_resource(reporter, sharedContext, resourceCache, /*gpuMemorySize=*/3, skgpu::Budgeted::kNo); auto resource1Ptr = resource1.get(); auto resource2Ptr = resource2.get(); auto resource3Ptr = resource3.get(); REPORTER_ASSERT(reporter, resourceCache->getResourceCount() == 4); REPORTER_ASSERT(reporter, resourceCache->currentBudgetedBytes() == 1); auto timeBeforeReturningToCache = skgpu::StdSteadyClock::now(); // Now reset all the non budgeted resources so they return to the cache and become budgeted. // Returning to the cache will not immedidately trigger a purgeAsNeededCall. resource1.reset(); resource2.reset(); resource3.reset(); resourceCache->forceProcessReturnedResources(); REPORTER_ASSERT(reporter, resourceCache->getResourceCount() == 4); REPORTER_ASSERT(reporter, resourceCache->currentBudgetedBytes() == 35); REPORTER_ASSERT(reporter, resourceCache->testingInPurgeableQueue(resourcePtr)); REPORTER_ASSERT(reporter, resourceCache->testingInPurgeableQueue(resource1Ptr)); REPORTER_ASSERT(reporter, resourceCache->testingInPurgeableQueue(resource2Ptr)); REPORTER_ASSERT(reporter, resourceCache->testingInPurgeableQueue(resource3Ptr)); // Now we call purgeNotUsedSince with timeBeforeReturnToCache. The original resource should get // purged because it is older than this time. The three originally non budgeted resources are // newer than this time so they won't be purged by the time on this call. However, since we are // overbudget it should trigger us to purge the first two of these resources to get us back // under. resourceCache->purgeResourcesNotUsedSince(timeBeforeReturningToCache); REPORTER_ASSERT(reporter, resourceCache->getResourceCount() == 1); REPORTER_ASSERT(reporter, resourceCache->currentBudgetedBytes() == 3); REPORTER_ASSERT(reporter, resourceCache->testingInPurgeableQueue(resource3Ptr)); } // Test call purgeResources on the ResourceCache and make sure all unlocked resources are getting // purged regardless of when they were last used. DEF_GRAPHITE_TEST_FOR_ALL_CONTEXTS(GraphitePurgeResourcesTest, reporter, context, CtsEnforcement::kApiLevel_V) { std::unique_ptr recorder = context->makeRecorder(); ResourceProvider* resourceProvider = recorder->priv().resourceProvider(); ResourceCache* resourceCache = resourceProvider->resourceCache(); const SharedContext* sharedContext = resourceProvider->sharedContext(); // set resourceCache budget to 10 for testing. resourceCache->setMaxBudget(10); // Basic test where we purge 1 resource auto resourcePtr = add_new_purgeable_resource(reporter, sharedContext, resourceCache, /*gpuMemorySize=*/1); if (!resourcePtr) { return; } REPORTER_ASSERT(reporter, resourceCache->getResourceCount() == 1); // purging should purge the one unlocked resource. resourceCache->purgeResources(); REPORTER_ASSERT(reporter, resourceCache->getResourceCount() == 0); // Test making 2 purgeable resources Resource* resourcePtr1 = add_new_purgeable_resource(reporter, sharedContext, resourceCache, /*gpuMemorySize=*/1); Resource* resourcePtr2 = add_new_purgeable_resource(reporter, sharedContext, resourceCache, /*gpuMemorySize=*/1); if (!resourcePtr1 || !resourcePtr2) { return; } REPORTER_ASSERT(reporter, resourceCache->getResourceCount() == 2); REPORTER_ASSERT(reporter, resourceCache->testingInPurgeableQueue(resourcePtr1)); REPORTER_ASSERT(reporter, resourceCache->testingInPurgeableQueue(resourcePtr2)); resourceCache->purgeResources(); REPORTER_ASSERT(reporter, resourceCache->getResourceCount() == 0); // purgeResources should have no impact on non-purgeable resources auto resource = add_new_resource(reporter, sharedContext, resourceCache, /*gpuMemorySize=*/1); if (!resource) { return; } resourcePtr = resource.get(); REPORTER_ASSERT(reporter, resourceCache->getResourceCount() == 1); resourceCache->purgeResources(); REPORTER_ASSERT(reporter, resourceCache->getResourceCount() == 1); REPORTER_ASSERT(reporter, !resourceCache->testingInPurgeableQueue(resourcePtr)); resource.reset(); resourceCache->purgeResources(); REPORTER_ASSERT(reporter, resourceCache->getResourceCount() == 0); } } // namespace skgpu::graphite