Increase testing of GrThreadSafeUniquelyKeyedProxyViewCache wrt GrResourceCache purging
Bug: 1108408
Change-Id: I2d6f89ce71b8d94be22b1ff38b9e205df94ca765
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/318205
Commit-Queue: Robert Phillips <robertphillips@google.com>
Reviewed-by: Adlai Holler <adlai@google.com>
diff --git a/src/gpu/GrResourceCache.cpp b/src/gpu/GrResourceCache.cpp
index fcdd8d9..7c5a640 100644
--- a/src/gpu/GrResourceCache.cpp
+++ b/src/gpu/GrResourceCache.cpp
@@ -530,7 +530,7 @@
}
if (stillOverbudget) {
- fThreadSafeViewCache->dropAllUniqueRefs(); // Is there a less nuclear option?
+ fThreadSafeViewCache->dropAllUniqueRefs(this);
while (stillOverbudget && fPurgeableQueue.count()) {
GrGpuResource* resource = fPurgeableQueue.peek();
@@ -546,7 +546,7 @@
void GrResourceCache::purgeUnlockedResources(bool scratchResourcesOnly) {
// In the scratch-only mode we could just drop the ones that are uniquely held and would
// be converted to scratch textures
- fThreadSafeViewCache->dropAllUniqueRefs();
+ fThreadSafeViewCache->dropAllUniqueRefs(nullptr);
if (!scratchResourcesOnly) {
// We could disable maintaining the heap property here, but it would add a lot of
diff --git a/src/gpu/GrThreadSafeUniquelyKeyedProxyViewCache.cpp b/src/gpu/GrThreadSafeUniquelyKeyedProxyViewCache.cpp
index 4a40198..1732677 100644
--- a/src/gpu/GrThreadSafeUniquelyKeyedProxyViewCache.cpp
+++ b/src/gpu/GrThreadSafeUniquelyKeyedProxyViewCache.cpp
@@ -7,6 +7,8 @@
#include "src/gpu/GrThreadSafeUniquelyKeyedProxyViewCache.h"
+#include "src/gpu/GrResourceCache.h"
+
GrThreadSafeUniquelyKeyedProxyViewCache::GrThreadSafeUniquelyKeyedProxyViewCache()
: fFreeEntryList(nullptr) {
}
@@ -40,21 +42,26 @@
// TODO: should we empty out the fFreeEntryList and reset fEntryAllocator?
}
-void GrThreadSafeUniquelyKeyedProxyViewCache::dropAllUniqueRefs() {
+void GrThreadSafeUniquelyKeyedProxyViewCache::dropAllUniqueRefs(GrResourceCache* resourceCache) {
SkAutoSpinlock lock{fSpinLock};
- Entry* cur = fUniquelyKeyedProxyViewList.head();
- Entry* next = cur ? cur->fNext : nullptr;
+ // Iterate from LRU to MRU
+ Entry* cur = fUniquelyKeyedProxyViewList.tail();
+ Entry* prev = cur ? cur->fPrev : nullptr;
while (cur) {
+ if (resourceCache && !resourceCache->overBudget()) {
+ return;
+ }
+
if (cur->fView.proxy()->unique()) {
fUniquelyKeyedProxyViewMap.remove(cur->fKey);
fUniquelyKeyedProxyViewList.remove(cur);
this->recycleEntry(cur);
}
- cur = next;
- next = cur ? cur->fNext : nullptr;
+ cur = prev;
+ prev = cur ? cur->fPrev : nullptr;
}
}
diff --git a/src/gpu/GrThreadSafeUniquelyKeyedProxyViewCache.h b/src/gpu/GrThreadSafeUniquelyKeyedProxyViewCache.h
index a82b13a..f692505 100644
--- a/src/gpu/GrThreadSafeUniquelyKeyedProxyViewCache.h
+++ b/src/gpu/GrThreadSafeUniquelyKeyedProxyViewCache.h
@@ -49,7 +49,7 @@
#endif
void dropAllRefs() SK_EXCLUDES(fSpinLock);
- void dropAllUniqueRefs() SK_EXCLUDES(fSpinLock);
+ void dropAllUniqueRefs(GrResourceCache* resourceCache) SK_EXCLUDES(fSpinLock);
GrSurfaceProxyView find(const GrUniqueKey&) SK_EXCLUDES(fSpinLock);
diff --git a/tests/GrThreadSafeViewCacheTest.cpp b/tests/GrThreadSafeViewCacheTest.cpp
index 97b0695..2420323 100644
--- a/tests/GrThreadSafeViewCacheTest.cpp
+++ b/tests/GrThreadSafeViewCacheTest.cpp
@@ -82,13 +82,11 @@
fRecorder1 = std::make_unique<SkDeferredDisplayListRecorder>(characterization);
fRecorder2 = std::make_unique<SkDeferredDisplayListRecorder>(characterization);
-
- SkBitmap tmp = create_up_arrow_bitmap(kImageWH);
- SkAssertResult(CreateBackendTexture(fDContext, &fBETex, tmp));
}
~TestHelper() {
- DeleteBackendTexture(fDContext, fBETex);
+ fDContext->flush();
+ fDContext->submit(true);
}
Stats* stats() { return &fStats; }
@@ -134,7 +132,7 @@
GrRecordingContext* rContext = canvas->recordingContext();
auto view = AccessCachedView(rContext, this->threadSafeViewCache(),
- fBETex, wh, failLookup, &fStats);
+ wh, failLookup, &fStats);
SkASSERT(view);
auto rtc = canvas->internal_private_accessTopLayerRenderTargetContext();
@@ -159,6 +157,8 @@
// method also validates that the unique key doesn't appear in any of the other caches.
bool checkView(SkCanvas* canvas, int wh, int hits, int misses, int numRefs) {
if (fStats.fCacheHits != hits || fStats.fCacheMisses != misses) {
+ SkDebugf("Hits E: %d A: %d --- Misses E: %d A: %d\n",
+ hits, fStats.fCacheHits, misses, fStats.fCacheMisses);
return false;
}
@@ -168,6 +168,9 @@
auto threadSafeViewCache = this->threadSafeViewCache();
GrSurfaceProxyView view = threadSafeViewCache->find(key);
+ if (!view.proxy()) {
+ return false;
+ }
if (!view.proxy()->refCntGreaterThan(numRefs+1) || // +1 for 'view's ref
view.proxy()->refCntGreaterThan(numRefs+2)) {
@@ -206,13 +209,21 @@
return true;
}
+ size_t gpuSize(int wh) const {
+ GrBackendFormat format = fDContext->defaultBackendFormat(kRGBA_8888_SkColorType,
+ GrRenderable::kNo);
+
+ return GrSurface::ComputeSize(*fDContext->priv().caps(), format,
+ {wh, wh}, 1, GrMipMapped::kNo, false);
+ }
+
private:
static GrSurfaceProxyView AccessCachedView(GrRecordingContext*,
GrThreadSafeUniquelyKeyedProxyViewCache*,
- GrBackendTexture, int wh,
+ int wh,
bool failLookup, Stats*);
static GrSurfaceProxyView CreateViewOnCpu(GrRecordingContext*, int wh, Stats*);
- static GrSurfaceProxyView CreateViewOnGpu(GrDirectContext*, GrBackendTexture, int wh, Stats*);
+ static GrSurfaceProxyView CreateViewOnGpu(GrDirectContext*, int wh, Stats*);
Stats fStats;
GrDirectContext* fDContext = nullptr;
@@ -220,8 +231,6 @@
sk_sp<SkSurface> fDst;
std::unique_ptr<SkDeferredDisplayListRecorder> fRecorder1;
std::unique_ptr<SkDeferredDisplayListRecorder> fRecorder2;
-
- GrBackendTexture fBETex;
};
GrSurfaceProxyView TestHelper::CreateViewOnCpu(GrRecordingContext* rContext,
@@ -244,17 +253,15 @@
}
GrSurfaceProxyView TestHelper::CreateViewOnGpu(GrDirectContext* dContext,
- GrBackendTexture beTex,
int wh,
Stats* stats) {
- SkASSERT(!stats->fNumHWCreations); // we had better not do this more than once
-
GrProxyProvider* proxyProvider = dContext->priv().proxyProvider();
- sk_sp<GrTextureProxy> proxy = proxyProvider->wrapBackendTexture(beTex,
- kBorrow_GrWrapOwnership,
- GrWrapCacheable::kNo,
- kRead_GrIOType);
+ sk_sp<GrTextureProxy> proxy = proxyProvider->createProxyFromBitmap(create_up_arrow_bitmap(wh),
+ GrMipmapped::kNo,
+ SkBackingFit::kExact,
+ SkBudgeted::kYes);
+
GrSwizzle swizzle = dContext->priv().caps()->getReadSwizzle(proxy->backendFormat(),
GrColorType::kRGBA_8888);
++stats->fNumHWCreations;
@@ -266,7 +273,6 @@
GrSurfaceProxyView TestHelper::AccessCachedView(
GrRecordingContext* rContext,
GrThreadSafeUniquelyKeyedProxyViewCache* threadSafeViewCache,
- GrBackendTexture beTex,
int wh,
bool failLookup,
Stats* stats) {
@@ -283,7 +289,7 @@
GrSurfaceProxyView view;
if (GrDirectContext* dContext = rContext->asDirectContext()) {
- view = CreateViewOnGpu(dContext, beTex, wh, stats);
+ view = CreateViewOnGpu(dContext, wh, stats);
} else {
view = CreateViewOnCpu(rContext, wh, stats);
}
@@ -297,10 +303,12 @@
TestHelper helper(ctxInfo.directContext());
helper.accessCachedView(helper.ddlCanvas1(), kImageWH);
- helper.checkView(helper.ddlCanvas1(), kImageWH, /*hits*/ 0, /*misses*/ 1, /*refs*/ 1);
+ REPORTER_ASSERT(reporter, helper.checkView(helper.ddlCanvas1(), kImageWH,
+ /*hits*/ 0, /*misses*/ 1, /*refs*/ 1));
helper.accessCachedView(helper.ddlCanvas2(), kImageWH);
- helper.checkView(helper.ddlCanvas2(), kImageWH, /*hits*/ 1, /*misses*/ 1, /*refs*/ 2);
+ REPORTER_ASSERT(reporter, helper.checkView(helper.ddlCanvas2(), kImageWH,
+ /*hits*/ 1, /*misses*/ 1, /*refs*/ 2));
REPORTER_ASSERT(reporter, helper.numCacheEntries() == 1);
REPORTER_ASSERT(reporter, helper.stats()->fNumHWCreations == 0);
@@ -312,13 +320,16 @@
TestHelper helper(ctxInfo.directContext());
helper.accessCachedView(helper.liveCanvas(), kImageWH);
- helper.checkView(helper.liveCanvas(), kImageWH, /*hits*/ 0, /*misses*/ 1, /*refs*/ 1);
+ REPORTER_ASSERT(reporter, helper.checkView(helper.liveCanvas(), kImageWH,
+ /*hits*/ 0, /*misses*/ 1, /*refs*/ 1));
helper.accessCachedView(helper.ddlCanvas1(), kImageWH);
- helper.checkView(helper.ddlCanvas1(), kImageWH, /*hits*/ 1, /*misses*/ 1, /*refs*/ 2);
+ REPORTER_ASSERT(reporter, helper.checkView(helper.ddlCanvas1(), kImageWH,
+ /*hits*/ 1, /*misses*/ 1, /*refs*/ 2));
helper.accessCachedView(helper.ddlCanvas2(), kImageWH);
- helper.checkView(helper.ddlCanvas2(), kImageWH, /*hits*/ 2, /*misses*/ 1, /*refs*/ 3);
+ REPORTER_ASSERT(reporter, helper.checkView(helper.ddlCanvas2(), kImageWH,
+ /*hits*/ 2, /*misses*/ 1, /*refs*/ 3));
REPORTER_ASSERT(reporter, helper.numCacheEntries() == 1);
REPORTER_ASSERT(reporter, helper.stats()->fNumHWCreations == 1);
@@ -330,10 +341,12 @@
TestHelper helper(ctxInfo.directContext());
helper.accessCachedView(helper.ddlCanvas1(), kImageWH);
- helper.checkView(helper.ddlCanvas1(), kImageWH, /*hits*/ 0, /*misses*/ 1, /*refs*/ 1);
+ REPORTER_ASSERT(reporter, helper.checkView(helper.ddlCanvas1(), kImageWH,
+ /*hits*/ 0, /*misses*/ 1, /*refs*/ 1));
helper.accessCachedView(helper.liveCanvas(), kImageWH);
- helper.checkView(helper.liveCanvas(), kImageWH, /*hits*/ 1, /*misses*/ 1, /*refs*/ 2);
+ REPORTER_ASSERT(reporter, helper.checkView(helper.liveCanvas(), kImageWH,
+ /*hits*/ 1, /*misses*/ 1, /*refs*/ 2));
REPORTER_ASSERT(reporter, helper.numCacheEntries() == 1);
REPORTER_ASSERT(reporter, helper.stats()->fNumHWCreations == 0);
@@ -345,11 +358,13 @@
TestHelper helper(ctxInfo.directContext());
helper.accessCachedView(helper.ddlCanvas1(), kImageWH);
- helper.checkView(helper.ddlCanvas1(), kImageWH, /*hits*/ 0, /*misses*/ 1, /*refs*/ 1);
+ REPORTER_ASSERT(reporter, helper.checkView(helper.ddlCanvas1(), kImageWH,
+ /*hits*/ 0, /*misses*/ 1, /*refs*/ 1));
static const bool kFailLookup = true;
helper.accessCachedView(helper.ddlCanvas2(), kImageWH, kFailLookup);
- helper.checkView(helper.ddlCanvas2(), kImageWH, /*hits*/ 0, /*misses*/ 2, /*refs*/ 2);
+ REPORTER_ASSERT(reporter, helper.checkView(helper.ddlCanvas2(), kImageWH,
+ /*hits*/ 0, /*misses*/ 2, /*refs*/ 2));
REPORTER_ASSERT(reporter, helper.numCacheEntries() == 1);
REPORTER_ASSERT(reporter, helper.stats()->fNumHWCreations == 0);
@@ -379,51 +394,190 @@
helper.accessCachedView(helper.ddlCanvas1(), kImageWH);
sk_sp<SkDeferredDisplayList> ddl1 = helper.snap1();
- helper.checkView(nullptr, kImageWH, /*hits*/ 0, /*misses*/ 1, /*refs*/ 1);
+ REPORTER_ASSERT(reporter, helper.checkView(nullptr, kImageWH,
+ /*hits*/ 0, /*misses*/ 1, /*refs*/ 1));
helper.accessCachedView(helper.ddlCanvas2(), kImageWH);
sk_sp<SkDeferredDisplayList> ddl2 = helper.snap2();
- helper.checkView(nullptr, kImageWH, /*hits*/ 1, /*misses*/ 1, /*refs*/ 2);
+ REPORTER_ASSERT(reporter, helper.checkView(nullptr, kImageWH,
+ /*hits*/ 1, /*misses*/ 1, /*refs*/ 2));
REPORTER_ASSERT(reporter, helper.numCacheEntries() == 1);
ddl1 = nullptr;
- helper.checkView(nullptr, kImageWH, /*hits*/ 1, /*misses*/ 1, /*refs*/ 1);
+ REPORTER_ASSERT(reporter, helper.checkView(nullptr, kImageWH,
+ /*hits*/ 1, /*misses*/ 1, /*refs*/ 1));
ddl2 = nullptr;
- helper.checkView(nullptr, kImageWH, /*hits*/ 1, /*misses*/ 1, /*refs*/ 0);
+ REPORTER_ASSERT(reporter, helper.checkView(nullptr, kImageWH,
+ /*hits*/ 1, /*misses*/ 1, /*refs*/ 0));
// The cache still has its ref
REPORTER_ASSERT(reporter, helper.numCacheEntries() == 1);
- helper.checkView(nullptr, kImageWH, /*hits*/ 1, /*misses*/ 1, /*refs*/ 0);
+ REPORTER_ASSERT(reporter, helper.checkView(nullptr, kImageWH,
+ /*hits*/ 1, /*misses*/ 1, /*refs*/ 0));
}
-// Case 7: check that dropAllRefs() and dropAllUniqueRefs work as expected
+// Case 7: check that invoking dropAllRefs and dropAllUniqueRefs directly works as expected
DEF_GPUTEST_FOR_RENDERING_CONTEXTS(GrThreadSafeViewCache7, reporter, ctxInfo) {
TestHelper helper(ctxInfo.directContext());
helper.accessCachedView(helper.ddlCanvas1(), kImageWH);
sk_sp<SkDeferredDisplayList> ddl1 = helper.snap1();
- helper.checkView(nullptr, kImageWH, /*hits*/ 0, /*misses*/ 1, /*refs*/ 1);
+ REPORTER_ASSERT(reporter, helper.checkView(nullptr, kImageWH,
+ /*hits*/ 0, /*misses*/ 1, /*refs*/ 1));
helper.accessCachedView(helper.ddlCanvas2(), 2*kImageWH);
sk_sp<SkDeferredDisplayList> ddl2 = helper.snap2();
- helper.checkView(nullptr, 2*kImageWH, /*hits*/ 0, /*misses*/ 1, /*refs*/ 1);
+ REPORTER_ASSERT(reporter, helper.checkView(nullptr, 2*kImageWH,
+ /*hits*/ 0, /*misses*/ 2, /*refs*/ 1));
REPORTER_ASSERT(reporter, helper.numCacheEntries() == 2);
- helper.threadSafeViewCache()->dropAllUniqueRefs();
+ helper.threadSafeViewCache()->dropAllUniqueRefs(nullptr);
REPORTER_ASSERT(reporter, helper.numCacheEntries() == 2);
ddl1 = nullptr;
- helper.threadSafeViewCache()->dropAllUniqueRefs();
+ helper.threadSafeViewCache()->dropAllUniqueRefs(nullptr);
REPORTER_ASSERT(reporter, helper.numCacheEntries() == 1);
- helper.checkView(nullptr, 2*kImageWH, /*hits*/ 0, /*misses*/ 1, /*refs*/ 1);
+ REPORTER_ASSERT(reporter, helper.checkView(nullptr, 2*kImageWH,
+ /*hits*/ 0, /*misses*/ 2, /*refs*/ 1));
helper.threadSafeViewCache()->dropAllRefs();
REPORTER_ASSERT(reporter, helper.numCacheEntries() == 0);
ddl2 = nullptr;
}
+
+// Case 8: This checks that GrContext::abandonContext works as expected wrt the thread
+// safe cache. This simulates the case where we have one DDL that has finished
+// recording but one still recording when the abandonContext fires.
+DEF_GPUTEST_FOR_RENDERING_CONTEXTS(GrThreadSafeViewCache8, reporter, ctxInfo) {
+ TestHelper helper(ctxInfo.directContext());
+
+ helper.accessCachedView(helper.liveCanvas(), kImageWH);
+ REPORTER_ASSERT(reporter, helper.checkView(helper.liveCanvas(), kImageWH,
+ /*hits*/ 0, /*misses*/ 1, /*refs*/ 1));
+
+ helper.accessCachedView(helper.ddlCanvas1(), kImageWH);
+ sk_sp<SkDeferredDisplayList> ddl1 = helper.snap1();
+ REPORTER_ASSERT(reporter, helper.checkView(helper.ddlCanvas1(), kImageWH,
+ /*hits*/ 1, /*misses*/ 1, /*refs*/ 2));
+
+ helper.accessCachedView(helper.ddlCanvas2(), kImageWH);
+ REPORTER_ASSERT(reporter, helper.checkView(helper.ddlCanvas2(), kImageWH,
+ /*hits*/ 2, /*misses*/ 1, /*refs*/ 3));
+
+ REPORTER_ASSERT(reporter, helper.numCacheEntries() == 1);
+ REPORTER_ASSERT(reporter, helper.stats()->fNumHWCreations == 1);
+ REPORTER_ASSERT(reporter, helper.stats()->fNumSWCreations == 0);
+
+ ctxInfo.directContext()->abandonContext(); // This should exercise dropAllRefs
+
+ sk_sp<SkDeferredDisplayList> ddl2 = helper.snap2();
+
+ REPORTER_ASSERT(reporter, helper.numCacheEntries() == 0);
+
+ ddl1 = nullptr;
+ ddl2 = nullptr;
+}
+
+// Case 9: This checks that GrContext::releaseResourcesAndAbandonContext works as expected wrt
+// the thread safe cache. This simulates the case where we have one DDL that has finished
+// recording but one still recording when the releaseResourcesAndAbandonContext fires.
+DEF_GPUTEST_FOR_RENDERING_CONTEXTS(GrThreadSafeViewCache9, reporter, ctxInfo) {
+ TestHelper helper(ctxInfo.directContext());
+
+ helper.accessCachedView(helper.liveCanvas(), kImageWH);
+ REPORTER_ASSERT(reporter, helper.checkView(helper.liveCanvas(), kImageWH,
+ /*hits*/ 0, /*misses*/ 1, /*refs*/ 1));
+
+ helper.accessCachedView(helper.ddlCanvas1(), kImageWH);
+ sk_sp<SkDeferredDisplayList> ddl1 = helper.snap1();
+ REPORTER_ASSERT(reporter, helper.checkView(helper.ddlCanvas1(), kImageWH,
+ /*hits*/ 1, /*misses*/ 1, /*refs*/ 2));
+
+ helper.accessCachedView(helper.ddlCanvas2(), kImageWH);
+ REPORTER_ASSERT(reporter, helper.checkView(helper.ddlCanvas2(), kImageWH,
+ /*hits*/ 2, /*misses*/ 1, /*refs*/ 3));
+
+ REPORTER_ASSERT(reporter, helper.numCacheEntries() == 1);
+ REPORTER_ASSERT(reporter, helper.stats()->fNumHWCreations == 1);
+ REPORTER_ASSERT(reporter, helper.stats()->fNumSWCreations == 0);
+
+ ctxInfo.directContext()->releaseResourcesAndAbandonContext(); // This should hit dropAllRefs
+
+ sk_sp<SkDeferredDisplayList> ddl2 = helper.snap2();
+
+ REPORTER_ASSERT(reporter, helper.numCacheEntries() == 0);
+
+ ddl1 = nullptr;
+ ddl2 = nullptr;
+}
+
+// Case 10: This checks that the GrContext::purgeUnlockedResources(size_t) variant works as
+// expected wrt the thread safe cache. It, in particular, tests out the MRU behavior
+// of the shared cache.
+DEF_GPUTEST_FOR_RENDERING_CONTEXTS(GrThreadSafeViewCache10, reporter, ctxInfo) {
+ auto dContext = ctxInfo.directContext();
+
+ if (GrBackendApi::kOpenGL != dContext->backend()) {
+ // The lower-level backends have too much going on for the following simple purging
+ // test to work
+ return;
+ }
+
+ TestHelper helper(dContext);
+
+ helper.accessCachedView(helper.liveCanvas(), kImageWH);
+ REPORTER_ASSERT(reporter, helper.checkView(helper.liveCanvas(), kImageWH,
+ /*hits*/ 0, /*misses*/ 1, /*refs*/ 1));
+
+ helper.accessCachedView(helper.ddlCanvas1(), kImageWH);
+ sk_sp<SkDeferredDisplayList> ddl1 = helper.snap1();
+ REPORTER_ASSERT(reporter, helper.checkView(helper.ddlCanvas1(), kImageWH,
+ /*hits*/ 1, /*misses*/ 1, /*refs*/ 2));
+
+ helper.accessCachedView(helper.liveCanvas(), 2*kImageWH);
+ REPORTER_ASSERT(reporter, helper.checkView(helper.liveCanvas(), 2*kImageWH,
+ /*hits*/ 1, /*misses*/ 2, /*refs*/ 1));
+
+ helper.accessCachedView(helper.ddlCanvas2(), 2*kImageWH);
+ sk_sp<SkDeferredDisplayList> ddl2 = helper.snap2();
+ REPORTER_ASSERT(reporter, helper.checkView(helper.ddlCanvas2(), 2*kImageWH,
+ /*hits*/ 2, /*misses*/ 2, /*refs*/ 2));
+
+ dContext->flush();
+ dContext->submit(true);
+
+ // This should clear out everything but the textures locked in the thread-safe cache
+ dContext->purgeUnlockedResources(false);
+
+ ddl1 = nullptr;
+ ddl2 = nullptr;
+
+ REPORTER_ASSERT(reporter, helper.numCacheEntries() == 2);
+ REPORTER_ASSERT(reporter, helper.checkView(helper.liveCanvas(), kImageWH,
+ /*hits*/ 2, /*misses*/ 2, /*refs*/ 0));
+ REPORTER_ASSERT(reporter, helper.checkView(helper.liveCanvas(), 2*kImageWH,
+ /*hits*/ 2, /*misses*/ 2, /*refs*/ 0));
+
+ // Regardless of which image is MRU, this should force the other out
+ size_t desiredBytes = helper.gpuSize(2*kImageWH) + helper.gpuSize(kImageWH)/2;
+
+ auto cache = dContext->priv().getResourceCache();
+ size_t currentBytes = cache->getResourceBytes();
+
+ SkASSERT(currentBytes >= desiredBytes);
+ size_t amountToPurge = currentBytes - desiredBytes;
+
+ // The 2*kImageWH texture should be MRU.
+ dContext->purgeUnlockedResources(amountToPurge, true);
+
+ REPORTER_ASSERT(reporter, helper.numCacheEntries() == 1);
+
+ REPORTER_ASSERT(reporter, helper.checkView(helper.liveCanvas(), 2*kImageWH,
+ /*hits*/ 2, /*misses*/ 2, /*refs*/ 0));
+}