| /* | |
| * 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<TestResource> Make(const SharedContext* sharedContext, | |
| ResourceCache* resourceCache, | |
| Ownership owned, | |
| Budgeted budgeted, | |
| Shareable shareable, | |
| size_t gpuMemorySize = 1) { | |
| auto resource = sk_sp<TestResource>(new TestResource(sharedContext, | |
| owned, | |
| gpuMemorySize)); | |
| if (!resource) { | |
| return nullptr; | |
| } | |
| GraphiteResourceKey key; | |
| CreateKey(&key); | |
| resourceCache->insertResource(resource.get(), key, budgeted, shareable); | |
| return resource; | |
| } | |
| const char* getResourceType() const override { return "Test Resource"; } | |
| static void CreateKey(GraphiteResourceKey* key) { | |
| // All unit tests that currently use TestResource are able to work with a single Resource, | |
| // so the key doesn't require any real state. | |
| static const ResourceType kType = GraphiteResourceKey::GenerateResourceType(); | |
| GraphiteResourceKey::Builder(key, kType, 0); | |
| } | |
| private: | |
| TestResource(const SharedContext* sharedContext, | |
| Ownership owned, | |
| size_t gpuMemorySize) | |
| : Resource(sharedContext, owned, gpuMemorySize) {} | |
| void freeGpuData() override {} | |
| }; | |
| static sk_sp<SkData> create_image_data(const SkImageInfo& info) { | |
| const size_t rowBytes = info.minRowBytes(); | |
| sk_sp<SkData> 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().proxy(); | |
| } | |
| return nullptr; | |
| } | |
| DEF_CONDITIONAL_GRAPHITE_TEST_FOR_ALL_CONTEXTS(GraphiteBudgetedResourcesTest, | |
| reporter, | |
| context, | |
| testContext, | |
| true, | |
| CtsEnforcement::kApiLevel_202404) { | |
| std::unique_ptr<Recorder> 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. | |
| sk_sp<Resource> resource = TestResource::Make( | |
| sharedContext, resourceCache, Ownership::kOwned, Budgeted::kNo, Shareable::kNo); | |
| if (!resource) { | |
| ERRORF(reporter, "Failed to make TestResource"); | |
| return; | |
| } | |
| Resource* resourcePtr = resource.get(); | |
| REPORTER_ASSERT(reporter, resource->budgeted() == Budgeted::kNo); | |
| REPORTER_ASSERT(reporter, resourceCache->getResourceCount() == 1); | |
| // Resource is not shareable and we have a ref on it. Thus it shouldn't be 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 resource (since it is not shareable). At that point the budget should be changed to | |
| // 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 resource. | |
| REPORTER_ASSERT(reporter, resourcePtr->budgeted() == Budgeted::kYes); | |
| // Test that the scratch resource can fulfill a new non-budgeted, non-shareable request | |
| GraphiteResourceKey key; | |
| TestResource::CreateKey(&key); | |
| Resource* resourcePtr2 = resourceCache->findAndRefResource(key, Budgeted::kNo, Shareable::kNo); | |
| REPORTER_ASSERT(reporter, resourcePtr == resourcePtr2); | |
| REPORTER_ASSERT(reporter, resourceCache->getResourceCount() == 1); | |
| REPORTER_ASSERT(reporter, resourceCache->numFindableResources() == 0); | |
| REPORTER_ASSERT(reporter, resourcePtr2->budgeted() == Budgeted::kNo); | |
| resourcePtr2->unref(); | |
| resourceCache->forceProcessReturnedResources(); | |
| // Test making a budgeted, shareable resource. Since we returned all refs to the prior non | |
| // shareable resource, it should be able to be switched to a shareable resource. | |
| resource = sk_sp(resourceCache->findAndRefResource(key, Budgeted::kYes, Shareable::kYes)); | |
| REPORTER_ASSERT(reporter, resource.get() == resourcePtr); | |
| REPORTER_ASSERT(reporter, resourceCache->getResourceCount() == 1); | |
| REPORTER_ASSERT(reporter, resourceCache->numFindableResources() == 1); // still findable | |
| REPORTER_ASSERT(reporter, resource->budgeted() == Budgeted::kYes); | |
| REPORTER_ASSERT(reporter, resource->shareable() == Shareable::kYes); | |
| // While the shareable resource is held, make a second shareable request which should still | |
| // find the existing resource in the cache. | |
| resourcePtr2 = resourceCache->findAndRefResource(key, Budgeted::kYes, Shareable::kYes); | |
| REPORTER_ASSERT(reporter, resourcePtr2 == resource.get()); | |
| resourcePtr2->unref(); | |
| // Now make a non-shareable request with the same key. This should fail to find a valid resource | |
| // since the one in the cache still has outstanding usage refs requiring it to be shareable. | |
| resourcePtr2 = resourceCache->findAndRefResource(key, Budgeted::kYes, Shareable::kNo); | |
| REPORTER_ASSERT(reporter, !resourcePtr2); | |
| // Return the shareable resource and then re-request the non-shareable key. Without any more | |
| // usage refs, the shareable resource can be restricted back to non-shareable usage. | |
| resource.reset(); | |
| resourceCache->forceProcessReturnedResources(); | |
| REPORTER_ASSERT(reporter, resourceCache->getResourceCount() == 1); | |
| REPORTER_ASSERT(reporter, resourceCache->numFindableResources() == 1); | |
| resourcePtr2 = resourceCache->findAndRefResource(key, Budgeted::kYes, Shareable::kNo); | |
| REPORTER_ASSERT(reporter, resourcePtr2 == resourcePtr); | |
| REPORTER_ASSERT(reporter, resourceCache->getResourceCount() == 1); | |
| REPORTER_ASSERT(reporter, resourceCache->numFindableResources() == 0); // not findable again | |
| REPORTER_ASSERT(reporter, resourcePtr2->budgeted() == Budgeted::kYes); | |
| REPORTER_ASSERT(reporter, resourcePtr2->shareable() == Shareable::kNo); | |
| 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<SkData> data(create_image_data(info)); | |
| sk_sp<SkImage> image = SkImages::RasterFromData(info, std::move(data), info.minRowBytes()); | |
| REPORTER_ASSERT(reporter, image); | |
| sk_sp<SkImage> 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. If host | |
| // image copy is supported, the buffer may or may not be used based on other parameters. | |
| const int buffersUsedForUpload = resourceCache->getResourceCount() - 2; | |
| REPORTER_ASSERT(reporter, buffersUsedForUpload <= 1); | |
| REPORTER_ASSERT(reporter, | |
| context->priv().caps()->supportsHostImageCopy() || buffersUsedForUpload == 1); | |
| REPORTER_ASSERT(reporter, resourceCache->getResourceCount() == 2 + buffersUsedForUpload); | |
| REPORTER_ASSERT(reporter, resourceCache->numFindableResources() == 1); | |
| REPORTER_ASSERT(reporter, imageResourcePtr->budgeted() == 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> 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() == 2 + buffersUsedForUpload); | |
| // Remapping async buffers before returning them to the cache can extend buffer lifetime. | |
| if (!context->priv().caps()->bufferMapsAreAsync()) { | |
| REPORTER_ASSERT(reporter, | |
| resourceCache->numFindableResources() == 2 + buffersUsedForUpload); | |
| } | |
| REPORTER_ASSERT(reporter, imageResourcePtr->budgeted() == Budgeted::kYes); | |
| // Now try an SkSurface. This is simpler since we can directly create Graphite SkSurface's. | |
| sk_sp<SkSurface> 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() == 3 + buffersUsedForUpload); | |
| // Remapping async buffers before returning them to the cache can extend buffer lifetime. | |
| if (!context->priv().caps()->bufferMapsAreAsync()) { | |
| REPORTER_ASSERT(reporter, | |
| resourceCache->numFindableResources() == 2 + buffersUsedForUpload); | |
| } | |
| REPORTER_ASSERT(reporter, surfaceResourcePtr->budgeted() == 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() == Budgeted::kYes); | |
| } | |
| namespace { | |
| sk_sp<Resource> add_new_resource(skiatest::Reporter* reporter, | |
| const SharedContext* sharedContext, | |
| ResourceCache* resourceCache, | |
| size_t gpuMemorySize, | |
| Budgeted budgeted = Budgeted::kYes) { | |
| auto resource = TestResource::Make(sharedContext, | |
| resourceCache, | |
| Ownership::kOwned, | |
| budgeted, | |
| Shareable::kNo, | |
| gpuMemorySize); | |
| if (!resource) { | |
| ERRORF(reporter, "Failed to make TestResource"); | |
| return nullptr; | |
| } | |
| 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_202404) { | |
| std::unique_ptr<Recorder> 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_202404) { | |
| std::unique_ptr<Recorder> 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_202404) { | |
| std::unique_ptr<Recorder> 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()); | |
| static constexpr std::optional<StdSteadyClock::time_point> kNoPurgingTimeLimit; | |
| // purging beforeTime should not get rid of the resource | |
| resourceCache->purgeResourcesNotUsedSince(beforeTime, kNoPurgingTimeLimit); | |
| REPORTER_ASSERT(reporter, resourceCache->getResourceCount() == 1); | |
| // purging at afterTime which is after resource became purgeable should purge it. | |
| resourceCache->purgeResourcesNotUsedSince(afterTime, kNoPurgingTimeLimit); | |
| 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, kNoPurgingTimeLimit); | |
| REPORTER_ASSERT(reporter, resourceCache->getResourceCount() == 1); | |
| REPORTER_ASSERT(reporter, resourceCache->testingInPurgeableQueue(resourcePtr2)); | |
| resourceCache->purgeResourcesNotUsedSince(afterTime, kNoPurgingTimeLimit); | |
| 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, kNoPurgingTimeLimit); | |
| 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(), kNoPurgingTimeLimit); | |
| 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()), | |
| kNoPurgingTimeLimit); | |
| 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_202404) { | |
| std::unique_ptr<Recorder> recorder = context->makeRecorder(); | |
| ResourceProvider* resourceProvider = recorder->priv().resourceProvider(); | |
| ResourceCache* resourceCache = resourceProvider->resourceCache(); | |
| const SharedContext* sharedContext = resourceProvider->sharedContext(); | |
| // set resourceCache budget to 10 for testing. | |
| static constexpr size_t kBudget = 10; | |
| resourceCache->setMaxBudget(kBudget); | |
| // 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. These are sized so that once they become budgeted, only | |
| // one will remain when purging to become under budget. | |
| auto resource1 = add_new_resource(reporter, | |
| sharedContext, | |
| resourceCache, | |
| /*gpuMemorySize=*/kBudget - 1, | |
| Budgeted::kNo); | |
| auto resource2 = add_new_resource(reporter, | |
| sharedContext, | |
| resourceCache, | |
| /*gpuMemorySize=*/kBudget - 2, | |
| Budgeted::kNo); | |
| auto resource3 = add_new_resource(reporter, | |
| sharedContext, | |
| resourceCache, | |
| /*gpuMemorySize=*/kBudget - 3, | |
| 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(); | |
| // All three resources are being processed together, and within one processing, there's no | |
| // assumed requirement that resources get put into the purgeable queue in the same order they | |
| // were in the return queue. | |
| resourceCache->forceProcessReturnedResources(); | |
| REPORTER_ASSERT(reporter, resourceCache->getResourceCount() == 4); | |
| REPORTER_ASSERT(reporter, resourceCache->currentBudgetedBytes() == 25); | |
| 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 two of them. Since each independently fits within | |
| // the budget, one (unspecified) will remain the purgeable queue. | |
| resourceCache->purgeResourcesNotUsedSince(timeBeforeReturningToCache, | |
| /*quitPurgingTime=*/std::nullopt); | |
| REPORTER_ASSERT(reporter, resourceCache->getResourceCount() == 1, | |
| "count = %d", resourceCache->getResourceCount()); | |
| if (resourceCache->currentBudgetedBytes() == kBudget - 1) { | |
| REPORTER_ASSERT(reporter, resourceCache->testingInPurgeableQueue(resource1Ptr)); | |
| } else if (resourceCache->currentBudgetedBytes() == kBudget - 2) { | |
| REPORTER_ASSERT(reporter, resourceCache->testingInPurgeableQueue(resource2Ptr)); | |
| } else { | |
| REPORTER_ASSERT(reporter, resourceCache->currentBudgetedBytes() == kBudget - 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_202404) { | |
| std::unique_ptr<Recorder> 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); | |
| } | |
| DEF_GRAPHITE_TEST_FOR_ALL_CONTEXTS(GraphiteScratchResourcesTest, reporter, context, | |
| CtsEnforcement::kNever) { | |
| std::unique_ptr<Recorder> 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. | |
| sk_sp<Resource> resource = TestResource::Make( | |
| sharedContext, resourceCache, Ownership::kOwned, Budgeted::kNo, Shareable::kNo); | |
| if (!resource) { | |
| ERRORF(reporter, "Failed to make TestResource"); | |
| return; | |
| } | |
| Resource* resourcePtr = resource.get(); | |
| REPORTER_ASSERT(reporter, resource->budgeted() == Budgeted::kNo); | |
| REPORTER_ASSERT(reporter, resourceCache->getResourceCount() == 1); | |
| // Resource is not shareable and we have a ref on it. Thus it shouldn't be findable in the cache | |
| REPORTER_ASSERT(reporter, resourceCache->numFindableResources() == 0); | |
| // Requesting a scratch shareable resouce will not return the non-shareable resource. | |
| GraphiteResourceKey key; | |
| TestResource::CreateKey(&key); | |
| ResourceCache::ScratchResourceSet unavailable; | |
| REPORTER_ASSERT(reporter, key == resource->key()); | |
| Resource* resourcePtr2 = resourceCache->findAndRefResource( | |
| key, Budgeted::kYes, Shareable::kScratch, /*label=*/{}, &unavailable); | |
| REPORTER_ASSERT(reporter, !resourcePtr2); | |
| // Return the non-shareable resource and verify that it can now be requested as scratch | |
| resource.reset(); | |
| resourceCache->forceProcessReturnedResources(); | |
| REPORTER_ASSERT(reporter, resourceCache->numFindableResources() == 1); | |
| resource = sk_sp(resourceCache->findAndRefResource( | |
| key, Budgeted::kYes, Shareable::kScratch, /*label=*/{}, &unavailable)); | |
| REPORTER_ASSERT(reporter, resource.get() == resourcePtr); | |
| REPORTER_ASSERT(reporter, resource->budgeted() == Budgeted::kYes); | |
| REPORTER_ASSERT(reporter, resource->shareable() == Shareable::kScratch); | |
| REPORTER_ASSERT(reporter, resourceCache->numFindableResources() == 1); // still findable | |
| // A request of the same key as non-shareable will not return the scratch resource | |
| resourcePtr2 = resourceCache->findAndRefResource(key, Budgeted::kYes, Shareable::kNo); | |
| REPORTER_ASSERT(reporter, !resourcePtr2); | |
| // Similarly, a request for a fully shareable resource cannot be satisfied by a scratch resource | |
| resourcePtr2 = resourceCache->findAndRefResource(key, Budgeted::kYes, Shareable::kYes); | |
| REPORTER_ASSERT(reporter, !resourcePtr2); | |
| // A request for another scratch resource can return the existing one if it hasn't been marked | |
| // unavailable in the set passed to the cache. | |
| resourcePtr2 = resourceCache->findAndRefResource( | |
| key, Budgeted::kYes, Shareable::kScratch, /*label=*/{}, &unavailable); | |
| REPORTER_ASSERT(reporter, resourcePtr2 == resourcePtr); | |
| resourcePtr2->unref(); | |
| // Mark the original resource as unvailable and now it shouldn't be seen by the request. | |
| unavailable.add(resourcePtr); | |
| resourcePtr2 = resourceCache->findAndRefResource( | |
| key, Budgeted::kYes, Shareable::kScratch, /*label=*/{}, &unavailable); | |
| REPORTER_ASSERT(reporter, !resourcePtr2); | |
| // Return the scratch resource, and then simulate a threading race where there's a request for | |
| // the scratch resource that comes in before the return queue is processed (adding a usage ref), | |
| // and then the queue is processed as part of a non-shareable request (which should then fail). | |
| unavailable.reset(); | |
| resource.reset(); | |
| resource = sk_sp(resourceCache->findAndRefResource( | |
| key, Budgeted::kYes, Shareable::kScratch, /*label=*/{}, &unavailable)); | |
| REPORTER_ASSERT(reporter, resource.get() == resourcePtr); | |
| // At this point, resourcePtr has a usage ref and should be in the return queue | |
| REPORTER_ASSERT(reporter, resourceCache->testingInReturnQueue(resourcePtr)); | |
| resourceCache->forceProcessReturnedResources(); | |
| // Its shareable type should not have changed after being processed. | |
| REPORTER_ASSERT(reporter, !resourceCache->testingInReturnQueue(resourcePtr)); | |
| REPORTER_ASSERT(reporter, resource->shareable() == Shareable::kScratch); | |
| // Now actually return the resource and confirm that it can be used for non-shareable requests | |
| // once all usage refs are dropped. | |
| resource.reset(); | |
| resourceCache->forceProcessReturnedResources(); | |
| REPORTER_ASSERT(reporter, resourceCache->numFindableResources() == 1); | |
| REPORTER_ASSERT(reporter, resourcePtr->shareable() == Shareable::kNo); | |
| // Returning the scratch resource allows it to be changed to a different shareable type | |
| resourcePtr2 = resourceCache->findAndRefResource(key, Budgeted::kYes, Shareable::kYes); | |
| REPORTER_ASSERT(reporter, resourcePtr2 == resourcePtr); | |
| REPORTER_ASSERT(reporter, resourcePtr2->shareable() == Shareable::kYes); | |
| resourcePtr2->unref(); | |
| } | |
| DEF_GRAPHITE_TEST_FOR_ALL_CONTEXTS(GraphiteTimeLimitedPurgeTest, | |
| reporter, | |
| context, | |
| CtsEnforcement::kNextRelease) { | |
| std::unique_ptr<Recorder> 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); | |
| // Add a singular purgeable resource to the cache. | |
| auto resourcePtr = add_new_purgeable_resource(reporter, | |
| sharedContext, | |
| resourceCache, | |
| /*gpuMemorySize=*/1); | |
| REPORTER_ASSERT(reporter, resourcePtr != nullptr); | |
| REPORTER_ASSERT(reporter, resourceCache->getResourceCount() == 1); | |
| // Trigger the purging of the resource with an impossibly small duration limit, confirming that | |
| // the resource remains in the cache. | |
| static constexpr auto kZeroMs = std::chrono::milliseconds(0); | |
| static constexpr auto kNoPurgingTimeLimit = std::nullopt; | |
| // Before performing cleanup, force process the cache's returned resources to ensure that | |
| // resources' last access times are updated. | |
| resourceCache->forceProcessReturnedResources(); | |
| // Make sure we actually get a new time point such that resources unused in the last 0 ms | |
| // actually leads to resource purging. | |
| (void)force_newer_timepoint(skgpu::StdSteadyClock::now()); | |
| recorder->performDeferredCleanup(kZeroMs, {kZeroMs}); | |
| REPORTER_ASSERT(reporter, resourceCache->getResourceCount() == 1); | |
| // Now purge with no time limit given to actually empty out the cache. | |
| resourceCache->forceProcessReturnedResources(); // Forcibly update resources' last used times | |
| (void)force_newer_timepoint(skgpu::StdSteadyClock::now()); // Ensures last used times < now | |
| recorder->performDeferredCleanup(kZeroMs, kNoPurgingTimeLimit); | |
| REPORTER_ASSERT(reporter, resourceCache->getResourceCount() == 0); | |
| // Now fill up the cache with kLargeResourceCount purgeable resources. | |
| static constexpr int kLargeResourceCount = 1000; | |
| for (int i = 0; i < kLargeResourceCount; i++) { | |
| resourcePtr = add_new_purgeable_resource(reporter, | |
| sharedContext, | |
| resourceCache, | |
| /*gpuMemorySize=*/1); | |
| REPORTER_ASSERT(reporter, resourcePtr != nullptr); | |
| } | |
| // Update resources' last used times and empty out the return queue before checking cache size | |
| resourceCache->forceProcessReturnedResources(); | |
| REPORTER_ASSERT(reporter, resourceCache->getResourceCount() == kLargeResourceCount); | |
| // Record how long it takes to completely purge all kLargeResourceCount resources. | |
| auto timeBeforeFullPurge = force_newer_timepoint(skgpu::StdSteadyClock::now()); | |
| recorder->performDeferredCleanup(kZeroMs, kNoPurgingTimeLimit); | |
| auto timeAfterFullPurge = force_newer_timepoint(skgpu::StdSteadyClock::now()); | |
| REPORTER_ASSERT(reporter, resourceCache->getResourceCount() == 0); | |
| auto actualFullPurgeDuration = timeAfterFullPurge - timeBeforeFullPurge; | |
| REPORTER_ASSERT(reporter, actualFullPurgeDuration.count() > 0); | |
| // Re-populate the cache. | |
| for (int i = 0; i < kLargeResourceCount; i++) { | |
| resourcePtr = add_new_purgeable_resource(reporter, | |
| sharedContext, | |
| resourceCache, | |
| /*gpuMemorySize=*/1); | |
| REPORTER_ASSERT(reporter, resourcePtr != nullptr); | |
| } | |
| // Update resources' last used times and empty out the return queue before checking cache size | |
| resourceCache->forceProcessReturnedResources(); | |
| REPORTER_ASSERT(reporter, resourceCache->getResourceCount() == kLargeResourceCount); | |
| // Finally, try purging with a time duration significantly smaller than the actual time recorded | |
| // in the previous step. This should force only a subset of the resources to be purged. | |
| auto timeBeforePartialPurge = force_newer_timepoint(skgpu::StdSteadyClock::now()); | |
| auto smallDuration = | |
| std::chrono::duration_cast<std::chrono::microseconds>(actualFullPurgeDuration / 5); | |
| recorder->performDeferredCleanup(kZeroMs, {smallDuration}); | |
| auto actualPartialPurgeDuration = | |
| force_newer_timepoint(skgpu::StdSteadyClock::now()) - timeBeforePartialPurge; | |
| // The actual duration should be >0. We expect resources to be purged until we have *exceeded* | |
| // the duration given. However, we should be able to see that not *all* purgeable resources were | |
| // purged (i.e. the stop time triggered an early exit of purging). | |
| REPORTER_ASSERT(reporter, actualPartialPurgeDuration.count() > 0 && | |
| actualPartialPurgeDuration > smallDuration); | |
| REPORTER_ASSERT(reporter, resourceCache->getResourceCount() > 0); | |
| REPORTER_ASSERT(reporter, resourceCache->getResourceCount() < kLargeResourceCount); | |
| } | |
| } // namespace skgpu::graphite |