skia2/tests/GrThreadSafeCacheTest.cpp
Chris Dalton 57ab06c14e Delete mixed samples
Mixed samples is no longer relevant for Ganesh. DMSAA and the new
Ganesh architecture both rely on full MSAA, and any platform where
mixed samples is supported will ultimately not use the old
architecture.

Change-Id: I5acc745010e090ef26310d92ec6240be2cd494cf
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/399837
Reviewed-by: Brian Salomon <bsalomon@google.com>
Commit-Queue: Chris Dalton <csmartdalton@google.com>
2021-04-22 20:11:34 +00:00

1507 lines
62 KiB
C++

/*
* Copyright 2020 Google Inc.
*
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#include "include/core/SkCanvas.h"
#include "include/core/SkDeferredDisplayListRecorder.h"
#include "include/core/SkSurfaceCharacterization.h"
#include "include/private/SkMalloc.h"
#include "include/utils/SkRandom.h"
#include "src/core/SkCanvasPriv.h"
#include "src/core/SkMessageBus.h"
#include "src/gpu/GrDefaultGeoProcFactory.h"
#include "src/gpu/GrDirectContextPriv.h"
#include "src/gpu/GrGpu.h"
#include "src/gpu/GrMemoryPool.h"
#include "src/gpu/GrOpFlushState.h"
#include "src/gpu/GrProxyProvider.h"
#include "src/gpu/GrRecordingContextPriv.h"
#include "src/gpu/GrStyle.h"
#include "src/gpu/GrSurfaceDrawContext.h"
#include "src/gpu/GrThreadSafeCache.h"
#include "tests/Test.h"
#include "tests/TestUtils.h"
#include "tools/gpu/ProxyUtils.h"
#include <thread>
static constexpr int kImageWH = 32;
static constexpr auto kImageOrigin = kBottomLeft_GrSurfaceOrigin;
static constexpr int kNoID = -1;
static SkImageInfo default_ii(int wh) {
return SkImageInfo::Make(wh, wh, kRGBA_8888_SkColorType, kPremul_SkAlphaType);
}
static std::unique_ptr<GrSurfaceDrawContext> new_RTC(GrRecordingContext* rContext, int wh) {
return GrSurfaceDrawContext::Make(rContext,
GrColorType::kRGBA_8888,
nullptr,
SkBackingFit::kExact,
{wh, wh},
SkSurfaceProps(),
1,
GrMipMapped::kNo,
GrProtected::kNo,
kImageOrigin,
SkBudgeted::kYes);
}
static void create_view_key(GrUniqueKey* key, int wh, int id) {
static const GrUniqueKey::Domain kViewDomain = GrUniqueKey::GenerateDomain();
GrUniqueKey::Builder builder(key, kViewDomain, 1);
builder[0] = wh;
builder.finish();
if (id != kNoID) {
key->setCustomData(SkData::MakeWithCopy(&id, sizeof(id)));
}
}
static void create_vert_key(GrUniqueKey* key, int wh, int id) {
static const GrUniqueKey::Domain kVertDomain = GrUniqueKey::GenerateDomain();
GrUniqueKey::Builder builder(key, kVertDomain, 1);
builder[0] = wh;
builder.finish();
if (id != kNoID) {
key->setCustomData(SkData::MakeWithCopy(&id, sizeof(id)));
}
}
static bool default_is_newer_better(SkData* incumbent, SkData* challenger) {
return false;
}
// When testing views we create a bitmap that covers the entire screen and has an inset blue rect
// atop a field of white.
// When testing verts we clear the background to white and simply draw an inset blur rect.
static SkBitmap create_bitmap(int wh) {
SkBitmap bitmap;
bitmap.allocPixels(default_ii(wh));
SkCanvas tmp(bitmap);
tmp.clear(SK_ColorWHITE);
SkPaint blue;
blue.setColor(SK_ColorBLUE);
blue.setAntiAlias(false);
tmp.drawRect({10, 10, wh-10.0f, wh-10.0f}, blue);
bitmap.setImmutable();
return bitmap;
}
class GrThreadSafeVertexTestOp;
class TestHelper {
public:
struct Stats {
int fCacheHits = 0;
int fCacheMisses = 0;
int fNumSWCreations = 0;
int fNumLazyCreations = 0;
int fNumHWCreations = 0;
};
TestHelper(GrDirectContext* dContext,
GrThreadSafeCache::IsNewerBetter isNewerBetter = default_is_newer_better)
: fDContext(dContext)
, fIsNewerBetter(isNewerBetter) {
fDst = SkSurface::MakeRenderTarget(dContext, SkBudgeted::kNo, default_ii(kImageWH));
SkAssertResult(fDst);
SkSurfaceCharacterization characterization;
SkAssertResult(fDst->characterize(&characterization));
fRecorder1 = std::make_unique<SkDeferredDisplayListRecorder>(characterization);
this->ddlCanvas1()->clear(SkColors::kWhite);
fRecorder2 = std::make_unique<SkDeferredDisplayListRecorder>(characterization);
this->ddlCanvas2()->clear(SkColors::kWhite);
fDst->getCanvas()->clear(SkColors::kWhite);
}
~TestHelper() {
fDContext->flush();
fDContext->submit(true);
}
Stats* stats() { return &fStats; }
int numCacheEntries() const { return this->threadSafeCache()->numEntries(); }
GrDirectContext* dContext() { return fDContext; }
SkCanvas* liveCanvas() { return fDst ? fDst->getCanvas() : nullptr; }
SkCanvas* ddlCanvas1() { return fRecorder1 ? fRecorder1->getCanvas() : nullptr; }
sk_sp<SkDeferredDisplayList> snap1() {
if (fRecorder1) {
sk_sp<SkDeferredDisplayList> tmp = fRecorder1->detach();
fRecorder1 = nullptr;
return tmp;
}
return nullptr;
}
SkCanvas* ddlCanvas2() { return fRecorder2 ? fRecorder2->getCanvas() : nullptr; }
sk_sp<SkDeferredDisplayList> snap2() {
if (fRecorder2) {
sk_sp<SkDeferredDisplayList> tmp = fRecorder2->detach();
fRecorder2 = nullptr;
return tmp;
}
return nullptr;
}
GrThreadSafeCache* threadSafeCache() { return fDContext->priv().threadSafeCache(); }
const GrThreadSafeCache* threadSafeCache() const { return fDContext->priv().threadSafeCache(); }
typedef void (TestHelper::*addAccessFP)(SkCanvas*, int wh, int id,
bool failLookUp, bool failFillingIn);
typedef bool (TestHelper::*checkFP)(SkCanvas*, int wh,
int expectedHits, int expectedMisses,
int expectedNumRefs, int expectedID);
// Add a draw on 'canvas' that will introduce a ref on the 'wh' view
void addViewAccess(SkCanvas* canvas,
int wh,
int id = kNoID,
bool failLookup = false,
bool failFillingIn = false) {
auto rContext = canvas->recordingContext();
auto view = AccessCachedView(rContext, this->threadSafeCache(),
wh, failLookup, failFillingIn, id, &fStats);
SkASSERT(view);
auto sdc = SkCanvasPriv::TopDeviceSurfaceDrawContext(canvas);
sdc->drawTexture(nullptr,
view,
kPremul_SkAlphaType,
GrSamplerState::Filter::kNearest,
GrSamplerState::MipmapMode::kNone,
SkBlendMode::kSrcOver,
{1.0f, 1.0f, 1.0f, 1.0f},
SkRect::MakeWH(wh, wh),
SkRect::MakeWH(wh, wh),
GrAA::kNo,
GrQuadAAFlags::kNone,
SkCanvas::kFast_SrcRectConstraint,
SkMatrix::I(),
nullptr);
}
// Besides checking that the number of refs and cache hits and misses are as expected, this
// method also validates that the unique key doesn't appear in any of the other caches.
bool checkView(SkCanvas* canvas, int wh,
int expectedHits, int expectedMisses, int expectedNumRefs, int expectedID) {
if (fStats.fCacheHits != expectedHits || fStats.fCacheMisses != expectedMisses) {
SkDebugf("Hits E: %d A: %d --- Misses E: %d A: %d\n",
expectedHits, fStats.fCacheHits, expectedMisses, fStats.fCacheMisses);
return false;
}
GrUniqueKey key;
create_view_key(&key, wh, kNoID);
auto threadSafeCache = this->threadSafeCache();
auto [view, xtraData] = threadSafeCache->findWithData(key);
if (!view.proxy()) {
return false;
}
if (expectedID < 0) {
if (xtraData) {
return false;
}
} else {
if (!xtraData) {
return false;
}
const int* cachedID = static_cast<const int*>(xtraData->data());
if (*cachedID != expectedID) {
return false;
}
}
if (!view.proxy()->refCntGreaterThan(expectedNumRefs+1) || // +1 for 'view's ref
view.proxy()->refCntGreaterThan(expectedNumRefs+2)) {
return false;
}
if (canvas) {
GrRecordingContext* rContext = canvas->recordingContext();
GrProxyProvider* recordingProxyProvider = rContext->priv().proxyProvider();
sk_sp<GrTextureProxy> result = recordingProxyProvider->findProxyByUniqueKey(key);
if (result) {
// views in this cache should never appear in the recorder's cache
return false;
}
}
{
GrProxyProvider* directProxyProvider = fDContext->priv().proxyProvider();
sk_sp<GrTextureProxy> result = directProxyProvider->findProxyByUniqueKey(key);
if (result) {
// views in this cache should never appear in the main proxy cache
return false;
}
}
{
auto resourceProvider = fDContext->priv().resourceProvider();
sk_sp<GrSurface> surf = resourceProvider->findByUniqueKey<GrSurface>(key);
if (surf) {
// the textures backing the views in this cache should never be discoverable in the
// resource cache
return false;
}
}
return true;
}
void addVertAccess(SkCanvas* canvas,
int wh,
int id,
bool failLookup,
bool failFillingIn,
GrThreadSafeVertexTestOp** createdOp);
// Add a draw on 'canvas' that will introduce a ref on a 'wh' vertex data
void addVertAccess(SkCanvas* canvas,
int wh,
int id = kNoID,
bool failLookup = false,
bool failFillingIn = false) {
this->addVertAccess(canvas, wh, id, failLookup, failFillingIn, nullptr);
}
bool checkVert(SkCanvas* canvas, int wh,
int expectedHits, int expectedMisses, int expectedNumRefs, int expectedID) {
if (fStats.fCacheHits != expectedHits || fStats.fCacheMisses != expectedMisses) {
SkDebugf("Hits E: %d A: %d --- Misses E: %d A: %d\n",
expectedHits, fStats.fCacheHits, expectedMisses, fStats.fCacheMisses);
return false;
}
GrUniqueKey key;
create_vert_key(&key, wh, kNoID);
auto threadSafeCache = this->threadSafeCache();
auto [vertData, xtraData] = threadSafeCache->findVertsWithData(key);
if (!vertData) {
return false;
}
if (expectedID < 0) {
if (xtraData) {
return false;
}
} else {
if (!xtraData) {
return false;
}
const int* cachedID = static_cast<const int*>(xtraData->data());
if (*cachedID != expectedID) {
return false;
}
}
if (!vertData->refCntGreaterThan(expectedNumRefs+1) || // +1 for 'vertData's ref
vertData->refCntGreaterThan(expectedNumRefs+2)) {
return false;
}
{
auto resourceProvider = fDContext->priv().resourceProvider();
sk_sp<GrGpuBuffer> buffer = resourceProvider->findByUniqueKey<GrGpuBuffer>(key);
if (buffer) {
// the buffer holding the vertex data in this cache should never be discoverable
// in the resource cache
return false;
}
}
return true;
}
bool checkImage(skiatest::Reporter* reporter, sk_sp<SkSurface> s) {
SkBitmap actual;
actual.allocPixels(default_ii(kImageWH));
if (!s->readPixels(actual, 0, 0)) {
return false;
}
SkBitmap expected = create_bitmap(kImageWH);
const float tols[4] = { 0.0f, 0.0f, 0.0f, 0.0f };
auto error = std::function<ComparePixmapsErrorReporter>(
[reporter](int x, int y, const float diffs[4]) {
SkASSERT(x >= 0 && y >= 0);
ERRORF(reporter, "mismatch at %d, %d (%f, %f, %f %f)",
x, y, diffs[0], diffs[1], diffs[2], diffs[3]);
});
return ComparePixels(expected.pixmap(), actual.pixmap(), tols, error);
}
bool checkImage(skiatest::Reporter* reporter) {
return this->checkImage(reporter, fDst);
}
bool checkImage(skiatest::Reporter* reporter, sk_sp<SkDeferredDisplayList> ddl) {
sk_sp<SkSurface> tmp = SkSurface::MakeRenderTarget(fDContext,
SkBudgeted::kNo,
default_ii(kImageWH));
if (!tmp) {
return false;
}
if (!tmp->draw(std::move(ddl))) {
return false;
}
return this->checkImage(reporter, std::move(tmp));
}
size_t gpuSize(int wh) const {
GrBackendFormat format = fDContext->defaultBackendFormat(kRGBA_8888_SkColorType,
GrRenderable::kNo);
return GrSurface::ComputeSize(format, {wh, wh}, /*colorSamplesPerPixel=*/1,
GrMipMapped::kNo, /*binSize=*/false);
}
private:
static GrSurfaceProxyView AccessCachedView(GrRecordingContext*,
GrThreadSafeCache*,
int wh,
bool failLookup, bool failFillingIn, int id,
Stats*);
static GrSurfaceProxyView CreateViewOnCpu(GrRecordingContext*, int wh, Stats*);
static bool FillInViewOnGpu(GrDirectContext*, int wh, Stats*,
const GrSurfaceProxyView& lazyView,
sk_sp<GrThreadSafeCache::Trampoline>);
Stats fStats;
GrDirectContext* fDContext = nullptr;
GrThreadSafeCache::IsNewerBetter fIsNewerBetter;
sk_sp<SkSurface> fDst;
std::unique_ptr<SkDeferredDisplayListRecorder> fRecorder1;
std::unique_ptr<SkDeferredDisplayListRecorder> fRecorder2;
};
class GrThreadSafeVertexTestOp : public GrDrawOp {
public:
DEFINE_OP_CLASS_ID
static GrOp::Owner Make(GrRecordingContext* rContext, TestHelper::Stats* stats,
int wh, int id, bool failLookup, bool failFillingIn,
GrThreadSafeCache::IsNewerBetter isNewerBetter) {
return GrOp::Make<GrThreadSafeVertexTestOp>(
rContext, rContext, stats, wh, id, failLookup, failFillingIn, isNewerBetter);
}
const GrThreadSafeCache::VertexData* vertexData() const { return fVertexData.get(); }
private:
friend class GrOp; // for ctor
GrThreadSafeVertexTestOp(GrRecordingContext* rContext, TestHelper::Stats* stats, int wh, int id,
bool failLookup, bool failFillingIn,
GrThreadSafeCache::IsNewerBetter isNewerBetter)
: INHERITED(ClassID())
, fStats(stats)
, fWH(wh)
, fID(id)
, fFailFillingIn(failFillingIn)
, fIsNewerBetter(isNewerBetter) {
this->setBounds(SkRect::MakeIWH(fWH, fWH), HasAABloat::kNo, IsHairline::kNo);
// Normally we wouldn't add a ref to the vertex data at this point. However, it is
// needed in this unit test to get the ref counts on the uniquely keyed resources
// to be as expected.
this->findOrCreateVertices(rContext, failLookup, fFailFillingIn);
}
const char* name() const override { return "GrThreadSafeVertexTestOp"; }
FixedFunctionFlags fixedFunctionFlags() const override { return FixedFunctionFlags::kNone; }
GrProcessorSet::Analysis finalize(const GrCaps&, const GrAppliedClip*, GrClampType) override {
return GrProcessorSet::EmptySetAnalysis();
}
GrProgramInfo* createProgramInfo(const GrCaps* caps,
SkArenaAlloc* arena,
const GrSurfaceProxyView& writeView,
GrAppliedClip&& appliedClip,
const GrXferProcessor::DstProxyView& dstProxyView,
GrXferBarrierFlags renderPassXferBarriers,
GrLoadOp colorLoadOp) const {
using namespace GrDefaultGeoProcFactory;
Color color({ 0.0f, 0.0f, 1.0f, 1.0f });
auto gp = MakeForDeviceSpace(arena, color,
Coverage::kSolid_Type,
LocalCoords::kUnused_Type,
SkMatrix::I());
return sk_gpu_test::CreateProgramInfo(caps, arena, writeView,
std::move(appliedClip), dstProxyView,
gp, SkBlendMode::kSrcOver,
GrPrimitiveType::kTriangleStrip,
renderPassXferBarriers, colorLoadOp);
}
GrProgramInfo* createProgramInfo(GrOpFlushState* flushState) const {
return this->createProgramInfo(&flushState->caps(),
flushState->allocator(),
flushState->writeView(),
flushState->detachAppliedClip(),
flushState->dstProxyView(),
flushState->renderPassBarriers(),
flushState->colorLoadOp());
}
void findOrCreateVertices(GrRecordingContext* rContext, bool failLookup, bool failFillingIn) {
if (!fVertexData) {
auto threadSafeViewCache = rContext->priv().threadSafeCache();
if (rContext->asDirectContext()) {
// The vertex variant doesn't have a correlate to lazyProxies but increment this
// here to make the unit tests happy.
++fStats->fNumLazyCreations;
}
GrUniqueKey key;
create_vert_key(&key, fWH, fID);
// We can "fail the lookup" to simulate a threaded race condition
auto [cachedVerts, data] = threadSafeViewCache->findVertsWithData(key);
if (cachedVerts && !failLookup) {
fVertexData = cachedVerts;
++fStats->fCacheHits;
return;
}
++fStats->fCacheMisses;
if (!rContext->asDirectContext()) {
++fStats->fNumSWCreations;
}
constexpr size_t kVertSize = sizeof(SkPoint);
SkPoint* verts = static_cast<SkPoint*>(sk_malloc_throw(4 * kVertSize));
verts[0].set(10.0f, 10.0f);
verts[1].set(fWH-10.0f, 10.0f);
verts[2].set(10.0f, fWH-10.0f);
verts[3].set(fWH-10.0f, fWH-10.0f);
fVertexData = GrThreadSafeCache::MakeVertexData(verts, 4, kVertSize);
auto [tmpV, tmpD] = threadSafeViewCache->addVertsWithData(key, fVertexData,
fIsNewerBetter);
if (tmpV != fVertexData) {
// Someone beat us to creating the vertex data. Use that version.
fVertexData = tmpV;
}
}
if (auto dContext = rContext->asDirectContext(); dContext && !fVertexData->gpuBuffer()) {
auto rp = dContext->priv().resourceProvider();
if (!failFillingIn) {
++fStats->fNumHWCreations;
sk_sp<GrGpuBuffer> tmp = rp->createBuffer(fVertexData->size(),
GrGpuBufferType::kVertex,
kStatic_GrAccessPattern,
fVertexData->vertices());
fVertexData->setGpuBuffer(std::move(tmp));
}
}
}
void onPrePrepare(GrRecordingContext* rContext,
const GrSurfaceProxyView& writeView,
GrAppliedClip* clip,
const GrXferProcessor::DstProxyView& dstProxyView,
GrXferBarrierFlags renderPassXferBarriers,
GrLoadOp colorLoadOp) override {
SkArenaAlloc* arena = rContext->priv().recordTimeAllocator();
// This is equivalent to a GrOpFlushState::detachAppliedClip
GrAppliedClip appliedClip = clip ? std::move(*clip) : GrAppliedClip::Disabled();
fProgramInfo = this->createProgramInfo(rContext->priv().caps(), arena, writeView,
std::move(appliedClip), dstProxyView,
renderPassXferBarriers, colorLoadOp);
rContext->priv().recordProgramInfo(fProgramInfo);
// This is now a noop (bc it is always called in the ctor) but is where we would normally
// create the vertices.
this->findOrCreateVertices(rContext, false, fFailFillingIn);
}
void onPrepare(GrOpFlushState* flushState) override {
auto dContext = flushState->gpu()->getContext();
// This call site is not a noop bc this op could've been created on with DDL context
// and, therefore, could be lacking a gpu-side buffer
this->findOrCreateVertices(dContext, false, fFailFillingIn);
}
void onExecute(GrOpFlushState* flushState, const SkRect& chainBounds) override {
if (!fVertexData || !fVertexData->gpuBuffer()) {
return;
}
if (!fProgramInfo) {
fProgramInfo = this->createProgramInfo(flushState);
}
flushState->bindPipeline(*fProgramInfo, SkRect::MakeIWH(fWH, fWH));
flushState->bindBuffers(nullptr, nullptr, fVertexData->refGpuBuffer());
flushState->draw(4, 0);
}
TestHelper::Stats* fStats;
int fWH;
int fID;
bool fFailFillingIn;
GrThreadSafeCache::IsNewerBetter fIsNewerBetter;
sk_sp<GrThreadSafeCache::VertexData> fVertexData;
GrProgramInfo* fProgramInfo = nullptr;
using INHERITED = GrDrawOp;
};
void TestHelper::addVertAccess(SkCanvas* canvas,
int wh, int id,
bool failLookup, bool failFillingIn,
GrThreadSafeVertexTestOp** createdOp) {
auto rContext = canvas->recordingContext();
auto sdc = SkCanvasPriv::TopDeviceSurfaceDrawContext(canvas);
GrOp::Owner op = GrThreadSafeVertexTestOp::Make(rContext, &fStats,
wh, id,
failLookup, failFillingIn,
fIsNewerBetter);
if (createdOp) {
*createdOp = (GrThreadSafeVertexTestOp*) op.get();
}
sdc->addDrawOp(std::move(op));
}
GrSurfaceProxyView TestHelper::CreateViewOnCpu(GrRecordingContext* rContext,
int wh,
Stats* stats) {
GrProxyProvider* proxyProvider = rContext->priv().proxyProvider();
sk_sp<GrTextureProxy> proxy = proxyProvider->createProxyFromBitmap(create_bitmap(wh),
GrMipmapped::kNo,
SkBackingFit::kExact,
SkBudgeted::kYes);
if (!proxy) {
return {};
}
GrSwizzle swizzle = rContext->priv().caps()->getReadSwizzle(proxy->backendFormat(),
GrColorType::kRGBA_8888);
++stats->fNumSWCreations;
return {std::move(proxy), kImageOrigin, swizzle};
}
bool TestHelper::FillInViewOnGpu(GrDirectContext* dContext, int wh, Stats* stats,
const GrSurfaceProxyView& lazyView,
sk_sp<GrThreadSafeCache::Trampoline> trampoline) {
std::unique_ptr<GrSurfaceDrawContext> rtc = new_RTC(dContext, wh);
GrPaint paint;
paint.setColor4f({0.0f, 0.0f, 1.0f, 1.0f});
rtc->clear(SkPMColor4f{1.0f, 1.0f, 1.0f, 1.0f});
rtc->drawRect(nullptr, std::move(paint), GrAA::kNo, SkMatrix::I(),
{ 10, 10, wh-10.0f, wh-10.0f }, &GrStyle::SimpleFill());
++stats->fNumHWCreations;
auto view = rtc->readSurfaceView();
SkASSERT(view.swizzle() == lazyView.swizzle());
SkASSERT(view.origin() == lazyView.origin());
trampoline->fProxy = view.asTextureProxyRef();
return true;
}
GrSurfaceProxyView TestHelper::AccessCachedView(GrRecordingContext* rContext,
GrThreadSafeCache* threadSafeCache,
int wh,
bool failLookup, bool failFillingIn, int id,
Stats* stats) {
GrUniqueKey key;
create_view_key(&key, wh, id);
if (GrDirectContext* dContext = rContext->asDirectContext()) {
// The gpu thread gets priority over the recording threads. If the gpu thread is first,
// it crams a lazy proxy into the cache and then fills it in later.
auto [lazyView, trampoline] = GrThreadSafeCache::CreateLazyView(
dContext, GrColorType::kRGBA_8888, {wh, wh}, kImageOrigin, SkBackingFit::kExact);
++stats->fNumLazyCreations;
auto [view, data] = threadSafeCache->findOrAddWithData(key, lazyView);
if (view != lazyView) {
++stats->fCacheHits;
return view;
} else if (id != kNoID) {
// Make sure, in this case, that the customData stuck
SkASSERT(data);
SkDEBUGCODE(const int* cachedID = static_cast<const int*>(data->data());)
SkASSERT(*cachedID == id);
}
++stats->fCacheMisses;
if (failFillingIn) {
// Simulate something going horribly wrong at flush-time so no GrTexture is
// available to fulfill the lazy proxy.
return view;
}
if (!FillInViewOnGpu(dContext, wh, stats, lazyView, std::move(trampoline))) {
// In this case something has gone disastrously wrong so set up to drop the draw
// that needed this resource and reduce future pollution of the cache.
threadSafeCache->remove(key);
return {};
}
return view;
} else {
GrSurfaceProxyView view;
// We can "fail the lookup" to simulate a threaded race condition
if (view = threadSafeCache->find(key); !failLookup && view) {
++stats->fCacheHits;
return view;
}
++stats->fCacheMisses;
view = CreateViewOnCpu(rContext, wh, stats);
SkASSERT(view);
auto [newView, data] = threadSafeCache->addWithData(key, view);
if (view == newView && id != kNoID) {
// Make sure, in this case, that the customData stuck
SkASSERT(data);
SkDEBUGCODE(const int* cachedID = static_cast<const int*>(data->data());)
SkASSERT(*cachedID == id);
}
return newView;
}
}
// Case 1: ensure two DDL recorders share the view/vertexData
static void test_1(GrDirectContext* dContext, skiatest::Reporter* reporter,
TestHelper::addAccessFP addAccess,
TestHelper::checkFP check) {
TestHelper helper(dContext);
(helper.*addAccess)(helper.ddlCanvas1(), kImageWH, 1, false, false);
REPORTER_ASSERT(reporter, (helper.*check)(helper.ddlCanvas1(), kImageWH,
/*hits*/ 0, /*misses*/ 1, /*refs*/ 1, /*id*/ 1));
(helper.*addAccess)(helper.ddlCanvas2(), kImageWH, 2, false, false);
REPORTER_ASSERT(reporter, (helper.*check)(helper.ddlCanvas2(), kImageWH,
/*hits*/ 1, /*misses*/ 1, /*refs*/ 2, /*id*/ 1));
REPORTER_ASSERT(reporter, helper.numCacheEntries() == 1);
REPORTER_ASSERT(reporter, helper.stats()->fNumLazyCreations == 0);
REPORTER_ASSERT(reporter, helper.stats()->fNumHWCreations == 0);
REPORTER_ASSERT(reporter, helper.stats()->fNumSWCreations == 1);
helper.checkImage(reporter, helper.snap1());
helper.checkImage(reporter, helper.snap2());
}
DEF_GPUTEST_FOR_RENDERING_CONTEXTS(GrThreadSafeCache1View, reporter, ctxInfo) {
test_1(ctxInfo.directContext(), reporter, &TestHelper::addViewAccess, &TestHelper::checkView);
}
DEF_GPUTEST_FOR_RENDERING_CONTEXTS(GrThreadSafeCache1Verts, reporter, ctxInfo) {
test_1(ctxInfo.directContext(), reporter, &TestHelper::addVertAccess, &TestHelper::checkVert);
}
// Case 2: ensure that, if the direct context version wins, its result is reused by the
// DDL recorders
static void test_2(GrDirectContext* dContext, skiatest::Reporter* reporter,
TestHelper::addAccessFP addAccess,
TestHelper::checkFP check) {
TestHelper helper(dContext);
(helper.*addAccess)(helper.liveCanvas(), kImageWH, 1, false, false);
REPORTER_ASSERT(reporter, (helper.*check)(helper.liveCanvas(), kImageWH,
/*hits*/ 0, /*misses*/ 1, /*refs*/ 1, /*id*/ 1));
(helper.*addAccess)(helper.ddlCanvas1(), kImageWH, 2, false, false);
REPORTER_ASSERT(reporter, (helper.*check)(helper.ddlCanvas1(), kImageWH,
/*hits*/ 1, /*misses*/ 1, /*refs*/ 2, /*id*/ 1));
(helper.*addAccess)(helper.ddlCanvas2(), kImageWH, 3, false, false);
REPORTER_ASSERT(reporter, (helper.*check)(helper.ddlCanvas2(), kImageWH,
/*hits*/ 2, /*misses*/ 1, /*refs*/ 3, /*id*/ 1));
REPORTER_ASSERT(reporter, helper.numCacheEntries() == 1);
REPORTER_ASSERT(reporter, helper.stats()->fNumLazyCreations == 1);
REPORTER_ASSERT(reporter, helper.stats()->fNumHWCreations == 1);
REPORTER_ASSERT(reporter, helper.stats()->fNumSWCreations == 0);
helper.checkImage(reporter);
helper.checkImage(reporter, helper.snap1());
helper.checkImage(reporter, helper.snap2());
}
DEF_GPUTEST_FOR_RENDERING_CONTEXTS(GrThreadSafeCache2View, reporter, ctxInfo) {
test_2(ctxInfo.directContext(), reporter, &TestHelper::addViewAccess, &TestHelper::checkView);
}
DEF_GPUTEST_FOR_RENDERING_CONTEXTS(GrThreadSafeCache2Verts, reporter, ctxInfo) {
test_2(ctxInfo.directContext(), reporter, &TestHelper::addVertAccess, &TestHelper::checkVert);
}
// Case 3: ensure that, if the cpu-version wins, its result is reused by the direct context
static void test_3(GrDirectContext* dContext, skiatest::Reporter* reporter,
TestHelper::addAccessFP addAccess,
TestHelper::checkFP check) {
TestHelper helper(dContext);
(helper.*addAccess)(helper.ddlCanvas1(), kImageWH, 1, false, false);
REPORTER_ASSERT(reporter, (helper.*check)(helper.ddlCanvas1(), kImageWH,
/*hits*/ 0, /*misses*/ 1, /*refs*/ 1, /*id*/ 1));
(helper.*addAccess)(helper.liveCanvas(), kImageWH, 2, false, false);
REPORTER_ASSERT(reporter, (helper.*check)(helper.liveCanvas(), kImageWH,
/*hits*/ 1, /*misses*/ 1, /*refs*/ 2, /*id*/ 1));
REPORTER_ASSERT(reporter, helper.numCacheEntries() == 1);
REPORTER_ASSERT(reporter, helper.stats()->fNumLazyCreations == 1);
REPORTER_ASSERT(reporter, helper.stats()->fNumHWCreations == 0);
REPORTER_ASSERT(reporter, helper.stats()->fNumSWCreations == 1);
helper.checkImage(reporter);
helper.checkImage(reporter, helper.snap1());
}
DEF_GPUTEST_FOR_RENDERING_CONTEXTS(GrThreadSafeCache3View, reporter, ctxInfo) {
test_3(ctxInfo.directContext(), reporter, &TestHelper::addViewAccess, &TestHelper::checkView);
}
DEF_GPUTEST_FOR_RENDERING_CONTEXTS(GrThreadSafeCache3Verts, reporter, ctxInfo) {
test_3(ctxInfo.directContext(), reporter, &TestHelper::addVertAccess, &TestHelper::checkVert);
}
// Case 4: ensure that, if two DDL recorders get in a race, they still end up sharing a single
// view/vertexData
static void test_4(GrDirectContext* dContext, skiatest::Reporter* reporter,
TestHelper::addAccessFP addAccess,
TestHelper::checkFP check) {
TestHelper helper(dContext);
(helper.*addAccess)(helper.ddlCanvas1(), kImageWH, 1, false, false);
REPORTER_ASSERT(reporter, (helper.*check)(helper.ddlCanvas1(), kImageWH,
/*hits*/ 0, /*misses*/ 1, /*refs*/ 1, /*id*/ 1));
static const bool kFailLookup = true;
(helper.*addAccess)(helper.ddlCanvas2(), kImageWH, 2, kFailLookup, false);
REPORTER_ASSERT(reporter, (helper.*check)(helper.ddlCanvas2(), kImageWH,
/*hits*/ 0, /*misses*/ 2, /*refs*/ 2, /*id*/ 1));
REPORTER_ASSERT(reporter, helper.numCacheEntries() == 1);
REPORTER_ASSERT(reporter, helper.stats()->fNumLazyCreations == 0);
REPORTER_ASSERT(reporter, helper.stats()->fNumHWCreations == 0);
REPORTER_ASSERT(reporter, helper.stats()->fNumSWCreations == 2);
helper.checkImage(reporter, helper.snap1());
helper.checkImage(reporter, helper.snap2());
}
DEF_GPUTEST_FOR_RENDERING_CONTEXTS(GrThreadSafeCache4View, reporter, ctxInfo) {
test_4(ctxInfo.directContext(), reporter, &TestHelper::addViewAccess, &TestHelper::checkView);
}
DEF_GPUTEST_FOR_RENDERING_CONTEXTS(GrThreadSafeCache4Verts, reporter, ctxInfo) {
test_4(ctxInfo.directContext(), reporter, &TestHelper::addVertAccess, &TestHelper::checkVert);
}
// Case 4.5: check that, if a live rendering and a DDL recording get into a race, the live
// rendering takes precedence.
static void test_4_5(GrDirectContext* dContext, skiatest::Reporter* reporter,
TestHelper::addAccessFP addAccess,
TestHelper::checkFP check) {
TestHelper helper(dContext);
(helper.*addAccess)(helper.liveCanvas(), kImageWH, 1, false, false);
REPORTER_ASSERT(reporter, (helper.*check)(helper.liveCanvas(), kImageWH,
/*hits*/ 0, /*misses*/ 1, /*refs*/ 1, /*id*/ 1));
REPORTER_ASSERT(reporter, helper.numCacheEntries() == 1);
REPORTER_ASSERT(reporter, helper.stats()->fNumLazyCreations == 1);
REPORTER_ASSERT(reporter, helper.stats()->fNumHWCreations == 1);
REPORTER_ASSERT(reporter, helper.stats()->fNumSWCreations == 0);
static const bool kFailLookup = true;
(helper.*addAccess)(helper.ddlCanvas1(), kImageWH, 2, kFailLookup, false);
REPORTER_ASSERT(reporter, (helper.*check)(helper.ddlCanvas1(), kImageWH,
/*hits*/ 0, /*misses*/ 2, /*refs*/ 2, /*id*/ 1));
REPORTER_ASSERT(reporter, helper.numCacheEntries() == 1);
REPORTER_ASSERT(reporter, helper.stats()->fNumLazyCreations == 1);
REPORTER_ASSERT(reporter, helper.stats()->fNumHWCreations == 1);
REPORTER_ASSERT(reporter, helper.stats()->fNumSWCreations == 1);
helper.checkImage(reporter);
helper.checkImage(reporter, helper.snap1());
}
DEF_GPUTEST_FOR_RENDERING_CONTEXTS(GrThreadSafeCache4_5View, reporter, ctxInfo) {
test_4_5(ctxInfo.directContext(), reporter,
&TestHelper::addViewAccess, &TestHelper::checkView);
}
DEF_GPUTEST_FOR_RENDERING_CONTEXTS(GrThreadSafeCache4_5Verts, reporter, ctxInfo) {
test_4_5(ctxInfo.directContext(), reporter,
&TestHelper::addVertAccess, &TestHelper::checkVert);
}
// Case 4.75: check that, if a live rendering fails to generate the content needed to instantiate
// its lazy proxy, life goes on
static void test_4_75(GrDirectContext* dContext, skiatest::Reporter* reporter,
TestHelper::addAccessFP addAccess,
TestHelper::checkFP check) {
TestHelper helper(dContext);
static const bool kFailFillingIn = true;
(helper.*addAccess)(helper.liveCanvas(), kImageWH, kNoID, false, kFailFillingIn);
REPORTER_ASSERT(reporter, (helper.*check)(helper.liveCanvas(), kImageWH,
/*hits*/ 0, /*misses*/ 1, /*refs*/ 1, kNoID));
REPORTER_ASSERT(reporter, helper.numCacheEntries() == 1);
REPORTER_ASSERT(reporter, helper.stats()->fNumLazyCreations == 1);
REPORTER_ASSERT(reporter, helper.stats()->fNumHWCreations == 0);
REPORTER_ASSERT(reporter, helper.stats()->fNumSWCreations == 0);
dContext->flush();
dContext->submit(true);
REPORTER_ASSERT(reporter, (helper.*check)(helper.liveCanvas(), kImageWH,
/*hits*/ 0, /*misses*/ 1, /*refs*/ 0, kNoID));
REPORTER_ASSERT(reporter, helper.numCacheEntries() == 1);
REPORTER_ASSERT(reporter, helper.stats()->fNumLazyCreations == 1);
REPORTER_ASSERT(reporter, helper.stats()->fNumHWCreations == 0);
REPORTER_ASSERT(reporter, helper.stats()->fNumSWCreations == 0);
}
DEF_GPUTEST_FOR_RENDERING_CONTEXTS(GrThreadSafeCache4_75View, reporter, ctxInfo) {
test_4_75(ctxInfo.directContext(), reporter,
&TestHelper::addViewAccess, &TestHelper::checkView);
}
DEF_GPUTEST_FOR_RENDERING_CONTEXTS(GrThreadSafeCache4_75Verts, reporter, ctxInfo) {
test_4_75(ctxInfo.directContext(), reporter,
&TestHelper::addVertAccess, &TestHelper::checkVert);
}
// Case 5: ensure that expanding the map works (esp. wrt custom data)
static void test_5(GrDirectContext* dContext, skiatest::Reporter* reporter,
TestHelper::addAccessFP addAccess,
TestHelper::checkFP check) {
TestHelper helper(dContext);
auto threadSafeCache = helper.threadSafeCache();
int size = 16;
(helper.*addAccess)(helper.ddlCanvas1(), size, /*id*/ size, false, false);
size_t initialSize = threadSafeCache->approxBytesUsedForHash();
while (initialSize == threadSafeCache->approxBytesUsedForHash()) {
size *= 2;
(helper.*addAccess)(helper.ddlCanvas1(), size, /*id*/ size, false, false);
}
for (int i = 16; i <= size; i *= 2) {
REPORTER_ASSERT(reporter, (helper.*check)(helper.ddlCanvas1(),
/*wh*/ i,
/*hits*/ 0,
/*misses*/ threadSafeCache->numEntries(),
/*refs*/ 1,
/*id*/ i));
}
}
DEF_GPUTEST_FOR_RENDERING_CONTEXTS(GrThreadSafeCache5View, reporter, ctxInfo) {
test_5(ctxInfo.directContext(), reporter, &TestHelper::addViewAccess, &TestHelper::checkView);
}
DEF_GPUTEST_FOR_RENDERING_CONTEXTS(GrThreadSafeCache5Verts, reporter, ctxInfo) {
test_5(ctxInfo.directContext(), reporter, &TestHelper::addVertAccess, &TestHelper::checkVert);
}
// Case 6: Check on dropping refs. In particular, that the cache has its own ref to keep
// the backing resource alive and locked.
static void test_6(GrDirectContext* dContext, skiatest::Reporter* reporter,
TestHelper::addAccessFP addAccess,
TestHelper::checkFP check) {
TestHelper helper(dContext);
(helper.*addAccess)(helper.ddlCanvas1(), kImageWH, kNoID, false, false);
sk_sp<SkDeferredDisplayList> ddl1 = helper.snap1();
REPORTER_ASSERT(reporter, (helper.*check)(nullptr, kImageWH,
/*hits*/ 0, /*misses*/ 1, /*refs*/ 1, kNoID));
(helper.*addAccess)(helper.ddlCanvas2(), kImageWH, kNoID, false, false);
sk_sp<SkDeferredDisplayList> ddl2 = helper.snap2();
REPORTER_ASSERT(reporter, (helper.*check)(nullptr, kImageWH,
/*hits*/ 1, /*misses*/ 1, /*refs*/ 2, kNoID));
REPORTER_ASSERT(reporter, helper.numCacheEntries() == 1);
ddl1 = nullptr;
REPORTER_ASSERT(reporter, (helper.*check)(nullptr, kImageWH,
/*hits*/ 1, /*misses*/ 1, /*refs*/ 1, kNoID));
ddl2 = nullptr;
REPORTER_ASSERT(reporter, (helper.*check)(nullptr, kImageWH,
/*hits*/ 1, /*misses*/ 1, /*refs*/ 0, kNoID));
// The cache still has its ref
REPORTER_ASSERT(reporter, helper.numCacheEntries() == 1);
REPORTER_ASSERT(reporter, (helper.*check)(nullptr, kImageWH,
/*hits*/ 1, /*misses*/ 1, /*refs*/ 0, kNoID));
}
DEF_GPUTEST_FOR_RENDERING_CONTEXTS(GrThreadSafeCache6View, reporter, ctxInfo) {
test_6(ctxInfo.directContext(), reporter, &TestHelper::addViewAccess, &TestHelper::checkView);
}
DEF_GPUTEST_FOR_RENDERING_CONTEXTS(GrThreadSafeCache6Verts, reporter, ctxInfo) {
test_6(ctxInfo.directContext(), reporter, &TestHelper::addVertAccess, &TestHelper::checkVert);
}
// Case 7: Check that invoking dropAllRefs and dropUniqueRefs directly works as expected; i.e.,
// dropAllRefs removes everything while dropUniqueRefs is more measured.
static void test_7(GrDirectContext* dContext, skiatest::Reporter* reporter,
TestHelper::addAccessFP addAccess,
TestHelper::checkFP check) {
TestHelper helper(dContext);
(helper.*addAccess)(helper.ddlCanvas1(), kImageWH, kNoID, false, false);
sk_sp<SkDeferredDisplayList> ddl1 = helper.snap1();
REPORTER_ASSERT(reporter, (helper.*check)(nullptr, kImageWH,
/*hits*/ 0, /*misses*/ 1, /*refs*/ 1, kNoID));
(helper.*addAccess)(helper.ddlCanvas2(), 2*kImageWH, kNoID, false, false);
sk_sp<SkDeferredDisplayList> ddl2 = helper.snap2();
REPORTER_ASSERT(reporter, (helper.*check)(nullptr, 2*kImageWH,
/*hits*/ 0, /*misses*/ 2, /*refs*/ 1, kNoID));
REPORTER_ASSERT(reporter, helper.numCacheEntries() == 2);
helper.threadSafeCache()->dropUniqueRefs(nullptr);
REPORTER_ASSERT(reporter, helper.numCacheEntries() == 2);
ddl1 = nullptr;
helper.threadSafeCache()->dropUniqueRefs(nullptr);
REPORTER_ASSERT(reporter, helper.numCacheEntries() == 1);
REPORTER_ASSERT(reporter, (helper.*check)(nullptr, 2*kImageWH,
/*hits*/ 0, /*misses*/ 2, /*refs*/ 1, kNoID));
helper.threadSafeCache()->dropAllRefs();
REPORTER_ASSERT(reporter, helper.numCacheEntries() == 0);
ddl2 = nullptr;
}
DEF_GPUTEST_FOR_RENDERING_CONTEXTS(GrThreadSafeCache7View, reporter, ctxInfo) {
test_7(ctxInfo.directContext(), reporter, &TestHelper::addViewAccess, &TestHelper::checkView);
}
DEF_GPUTEST_FOR_RENDERING_CONTEXTS(GrThreadSafeCache7Verts, reporter, ctxInfo) {
test_7(ctxInfo.directContext(), reporter, &TestHelper::addVertAccess, &TestHelper::checkVert);
}
// 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.
static void test_8(GrDirectContext* dContext, skiatest::Reporter* reporter,
TestHelper::addAccessFP addAccess,
TestHelper::checkFP check) {
TestHelper helper(dContext);
(helper.*addAccess)(helper.liveCanvas(), kImageWH, kNoID, false, false);
REPORTER_ASSERT(reporter, (helper.*check)(helper.liveCanvas(), kImageWH,
/*hits*/ 0, /*misses*/ 1, /*refs*/ 1, kNoID));
(helper.*addAccess)(helper.ddlCanvas1(), kImageWH, kNoID, false, false);
sk_sp<SkDeferredDisplayList> ddl1 = helper.snap1();
REPORTER_ASSERT(reporter, (helper.*check)(helper.ddlCanvas1(), kImageWH,
/*hits*/ 1, /*misses*/ 1, /*refs*/ 2, kNoID));
(helper.*addAccess)(helper.ddlCanvas2(), kImageWH, kNoID, false, false);
REPORTER_ASSERT(reporter, (helper.*check)(helper.ddlCanvas2(), kImageWH,
/*hits*/ 2, /*misses*/ 1, /*refs*/ 3, kNoID));
REPORTER_ASSERT(reporter, helper.numCacheEntries() == 1);
REPORTER_ASSERT(reporter, helper.stats()->fNumLazyCreations == 1);
REPORTER_ASSERT(reporter, helper.stats()->fNumHWCreations == 1);
REPORTER_ASSERT(reporter, helper.stats()->fNumSWCreations == 0);
dContext->abandonContext(); // This should exercise dropAllRefs
sk_sp<SkDeferredDisplayList> ddl2 = helper.snap2();
REPORTER_ASSERT(reporter, helper.numCacheEntries() == 0);
ddl1 = nullptr;
ddl2 = nullptr;
}
DEF_GPUTEST_FOR_RENDERING_CONTEXTS(GrThreadSafeCache8View, reporter, ctxInfo) {
test_8(ctxInfo.directContext(), reporter, &TestHelper::addViewAccess, &TestHelper::checkView);
}
DEF_GPUTEST_FOR_RENDERING_CONTEXTS(GrThreadSafeCache8Verts, reporter, ctxInfo) {
test_8(ctxInfo.directContext(), reporter, &TestHelper::addVertAccess, &TestHelper::checkVert);
}
// 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.
static void test_9(GrDirectContext* dContext, skiatest::Reporter* reporter,
TestHelper::addAccessFP addAccess,
TestHelper::checkFP check) {
TestHelper helper(dContext);
(helper.*addAccess)(helper.liveCanvas(), kImageWH, kNoID, false, false);
REPORTER_ASSERT(reporter, (helper.*check)(helper.liveCanvas(), kImageWH,
/*hits*/ 0, /*misses*/ 1, /*refs*/ 1, kNoID));
(helper.*addAccess)(helper.ddlCanvas1(), kImageWH, kNoID, false, false);
sk_sp<SkDeferredDisplayList> ddl1 = helper.snap1();
REPORTER_ASSERT(reporter, (helper.*check)(helper.ddlCanvas1(), kImageWH,
/*hits*/ 1, /*misses*/ 1, /*refs*/ 2, kNoID));
(helper.*addAccess)(helper.ddlCanvas2(), kImageWH, kNoID, false, false);
REPORTER_ASSERT(reporter, (helper.*check)(helper.ddlCanvas2(), kImageWH,
/*hits*/ 2, /*misses*/ 1, /*refs*/ 3, kNoID));
REPORTER_ASSERT(reporter, helper.numCacheEntries() == 1);
REPORTER_ASSERT(reporter, helper.stats()->fNumLazyCreations == 1);
REPORTER_ASSERT(reporter, helper.stats()->fNumHWCreations == 1);
REPORTER_ASSERT(reporter, helper.stats()->fNumSWCreations == 0);
dContext->releaseResourcesAndAbandonContext(); // This should hit dropAllRefs
sk_sp<SkDeferredDisplayList> ddl2 = helper.snap2();
REPORTER_ASSERT(reporter, helper.numCacheEntries() == 0);
ddl1 = nullptr;
ddl2 = nullptr;
}
DEF_GPUTEST_FOR_RENDERING_CONTEXTS(GrThreadSafeCache9View, reporter, ctxInfo) {
test_9(ctxInfo.directContext(), reporter, &TestHelper::addViewAccess, &TestHelper::checkView);
}
DEF_GPUTEST_FOR_RENDERING_CONTEXTS(GrThreadSafeCache9Verts, reporter, ctxInfo) {
test_9(ctxInfo.directContext(), reporter, &TestHelper::addVertAccess, &TestHelper::checkVert);
}
// 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.
static void test_10(GrDirectContext* dContext, skiatest::Reporter* reporter,
TestHelper::addAccessFP addAccess,
TestHelper::checkFP check) {
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.*addAccess)(helper.liveCanvas(), kImageWH, kNoID, false, false);
REPORTER_ASSERT(reporter, (helper.*check)(helper.liveCanvas(), kImageWH,
/*hits*/ 0, /*misses*/ 1, /*refs*/ 1, kNoID));
(helper.*addAccess)(helper.ddlCanvas1(), kImageWH, kNoID, false, false);
sk_sp<SkDeferredDisplayList> ddl1 = helper.snap1();
REPORTER_ASSERT(reporter, (helper.*check)(helper.ddlCanvas1(), kImageWH,
/*hits*/ 1, /*misses*/ 1, /*refs*/ 2, kNoID));
(helper.*addAccess)(helper.liveCanvas(), 2*kImageWH, kNoID, false, false);
REPORTER_ASSERT(reporter, (helper.*check)(helper.liveCanvas(), 2*kImageWH,
/*hits*/ 1, /*misses*/ 2, /*refs*/ 1, kNoID));
(helper.*addAccess)(helper.ddlCanvas2(), 2*kImageWH, kNoID, false, false);
sk_sp<SkDeferredDisplayList> ddl2 = helper.snap2();
REPORTER_ASSERT(reporter, (helper.*check)(helper.ddlCanvas2(), 2*kImageWH,
/*hits*/ 2, /*misses*/ 2, /*refs*/ 2, kNoID));
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.*check)(helper.liveCanvas(), kImageWH,
/*hits*/ 2, /*misses*/ 2, /*refs*/ 0, kNoID));
REPORTER_ASSERT(reporter, (helper.*check)(helper.liveCanvas(), 2*kImageWH,
/*hits*/ 2, /*misses*/ 2, /*refs*/ 0, kNoID));
// 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.*check)(helper.liveCanvas(), 2*kImageWH,
/*hits*/ 2, /*misses*/ 2, /*refs*/ 0, kNoID));
}
DEF_GPUTEST_FOR_RENDERING_CONTEXTS(GrThreadSafeCache10View, reporter, ctxInfo) {
test_10(ctxInfo.directContext(), reporter, &TestHelper::addViewAccess, &TestHelper::checkView);
}
// To enable test_10 with verts would require a bit more work, namely:
// have a different # of verts based on size
// also pass in a gpuSize function to 'test_10'
//DEF_GPUTEST_FOR_RENDERING_CONTEXTS(GrThreadSafeCache10Verts, reporter, ctxInfo) {
// test_10(ctxInfo.directContext(), reporter, &TestHelper::addVertAccess, &TestHelper::checkVert);
//}
// Case 11: This checks that scratch-only variant of GrContext::purgeUnlockedResources works as
// expected wrt the thread safe cache. In particular, that when 'scratchResourcesOnly'
// is true, the call has no effect on the cache.
static void test_11(GrDirectContext* dContext, skiatest::Reporter* reporter,
TestHelper::addAccessFP addAccess,
TestHelper::checkFP check) {
TestHelper helper(dContext);
(helper.*addAccess)(helper.liveCanvas(), kImageWH, kNoID, false, false);
REPORTER_ASSERT(reporter, (helper.*check)(helper.liveCanvas(), kImageWH,
/*hits*/ 0, /*misses*/ 1, /*refs*/ 1, kNoID));
(helper.*addAccess)(helper.liveCanvas(), 2*kImageWH, kNoID, false, false);
REPORTER_ASSERT(reporter, (helper.*check)(helper.liveCanvas(), 2*kImageWH,
/*hits*/ 0, /*misses*/ 2, /*refs*/ 1, kNoID));
dContext->flush();
dContext->submit(true);
REPORTER_ASSERT(reporter, helper.numCacheEntries() == 2);
REPORTER_ASSERT(reporter, (helper.*check)(helper.liveCanvas(), kImageWH,
/*hits*/ 0, /*misses*/ 2, /*refs*/ 0, kNoID));
REPORTER_ASSERT(reporter, (helper.*check)(helper.liveCanvas(), 2*kImageWH,
/*hits*/ 0, /*misses*/ 2, /*refs*/ 0, kNoID));
// This shouldn't remove anything from the cache
dContext->purgeUnlockedResources(/* scratchResourcesOnly */ true);
REPORTER_ASSERT(reporter, helper.numCacheEntries() == 2);
dContext->purgeUnlockedResources(/* scratchResourcesOnly */ false);
REPORTER_ASSERT(reporter, helper.numCacheEntries() == 0);
}
DEF_GPUTEST_FOR_RENDERING_CONTEXTS(GrThreadSafeCache11View, reporter, ctxInfo) {
test_11(ctxInfo.directContext(), reporter, &TestHelper::addViewAccess, &TestHelper::checkView);
}
DEF_GPUTEST_FOR_RENDERING_CONTEXTS(GrThreadSafeCache11Verts, reporter, ctxInfo) {
test_11(ctxInfo.directContext(), reporter, &TestHelper::addVertAccess, &TestHelper::checkVert);
}
// Case 12: Test out purges caused by resetting the cache budget to 0. Note that, due to
// the how the cache operates (i.e., not directly driven by ref/unrefs) there
// needs to be an explicit kick to purge the cache.
static void test_12(GrDirectContext* dContext, skiatest::Reporter* reporter,
TestHelper::addAccessFP addAccess,
TestHelper::checkFP check) {
TestHelper helper(dContext);
(helper.*addAccess)(helper.liveCanvas(), kImageWH, kNoID, false, false);
REPORTER_ASSERT(reporter, (helper.*check)(helper.liveCanvas(), kImageWH,
/*hits*/ 0, /*misses*/ 1, /*refs*/ 1, kNoID));
(helper.*addAccess)(helper.ddlCanvas1(), kImageWH, kNoID, false, false);
sk_sp<SkDeferredDisplayList> ddl1 = helper.snap1();
REPORTER_ASSERT(reporter, (helper.*check)(helper.ddlCanvas1(), kImageWH,
/*hits*/ 1, /*misses*/ 1, /*refs*/ 2, kNoID));
(helper.*addAccess)(helper.liveCanvas(), 2*kImageWH, kNoID, false, false);
REPORTER_ASSERT(reporter, (helper.*check)(helper.liveCanvas(), 2*kImageWH,
/*hits*/ 1, /*misses*/ 2, /*refs*/ 1, kNoID));
dContext->flush();
dContext->submit(true);
REPORTER_ASSERT(reporter, helper.numCacheEntries() == 2);
REPORTER_ASSERT(reporter, (helper.*check)(helper.liveCanvas(), kImageWH,
/*hits*/ 1, /*misses*/ 2, /*refs*/ 1, kNoID));
REPORTER_ASSERT(reporter, (helper.*check)(helper.liveCanvas(), 2*kImageWH,
/*hits*/ 1, /*misses*/ 2, /*refs*/ 0, kNoID));
dContext->setResourceCacheLimit(0);
REPORTER_ASSERT(reporter, helper.numCacheEntries() == 1);
ddl1 = nullptr;
// Explicitly kick off the purge - it won't happen automatically on unref
dContext->performDeferredCleanup(std::chrono::milliseconds(0));
REPORTER_ASSERT(reporter, helper.numCacheEntries() == 0);
}
DEF_GPUTEST_FOR_RENDERING_CONTEXTS(GrThreadSafeCache12View, reporter, ctxInfo) {
test_12(ctxInfo.directContext(), reporter, &TestHelper::addViewAccess, &TestHelper::checkView);
}
DEF_GPUTEST_FOR_RENDERING_CONTEXTS(GrThreadSafeCache12Verts, reporter, ctxInfo) {
test_12(ctxInfo.directContext(), reporter, &TestHelper::addVertAccess, &TestHelper::checkVert);
}
// Case 13: Test out the 'msNotUsed' parameter to GrContext::performDeferredCleanup.
static void test_13(GrDirectContext* dContext, skiatest::Reporter* reporter,
TestHelper::addAccessFP addAccess,
TestHelper::checkFP check) {
TestHelper helper(dContext);
(helper.*addAccess)(helper.ddlCanvas1(), kImageWH, kNoID, false, false);
REPORTER_ASSERT(reporter, (helper.*check)(helper.ddlCanvas1(), kImageWH,
/*hits*/ 0, /*misses*/ 1, /*refs*/ 1, kNoID));
sk_sp<SkDeferredDisplayList> ddl1 = helper.snap1();
std::this_thread::sleep_for(std::chrono::milliseconds(5));
auto firstTime = GrStdSteadyClock::now();
std::this_thread::sleep_for(std::chrono::milliseconds(5));
(helper.*addAccess)(helper.ddlCanvas2(), 2*kImageWH, kNoID, false, false);
REPORTER_ASSERT(reporter, (helper.*check)(helper.ddlCanvas2(), 2*kImageWH,
/*hits*/ 0, /*misses*/ 2, /*refs*/ 1, kNoID));
sk_sp<SkDeferredDisplayList> ddl2 = helper.snap2();
ddl1 = nullptr;
ddl2 = nullptr;
REPORTER_ASSERT(reporter, helper.numCacheEntries() == 2);
auto secondTime = GrStdSteadyClock::now();
auto msecs = std::chrono::duration_cast<std::chrono::milliseconds>(secondTime - firstTime);
dContext->performDeferredCleanup(msecs);
REPORTER_ASSERT(reporter, helper.numCacheEntries() == 1);
REPORTER_ASSERT(reporter, (helper.*check)(helper.liveCanvas(), 2*kImageWH,
/*hits*/ 0, /*misses*/ 2, /*refs*/ 0, kNoID));
}
DEF_GPUTEST_FOR_RENDERING_CONTEXTS(GrThreadSafeCache13View, reporter, ctxInfo) {
test_13(ctxInfo.directContext(), reporter, &TestHelper::addViewAccess, &TestHelper::checkView);
}
DEF_GPUTEST_FOR_RENDERING_CONTEXTS(GrThreadSafeCache13Verts, reporter, ctxInfo) {
test_13(ctxInfo.directContext(), reporter, &TestHelper::addVertAccess, &TestHelper::checkVert);
}
// Case 14: Test out mixing & matching view & vertex data w/ recycling of the cache entries to
// wring out the anonymous union code. This is mainly for the MSAN bot's consumption.
DEF_GPUTEST_FOR_RENDERING_CONTEXTS(GrThreadSafeCache14, reporter, ctxInfo) {
constexpr int kBestPrimeNumber = 73; // palindromic in binary
SkRandom rand(kBestPrimeNumber);
TestHelper helper(ctxInfo.directContext());
for (int i = 0; i < 2; ++i) {
SkCanvas* ddlCanvas = (!i) ? helper.ddlCanvas1() : helper.ddlCanvas2();
for (int j = 0; j < 10; ++j) {
int numResources = 10*i + j + 1;
int wh = numResources;
if (rand.nextBool()) {
helper.addViewAccess(ddlCanvas, wh, kNoID, false, false);
REPORTER_ASSERT(reporter, helper.checkView(ddlCanvas, wh,
/*hits*/ 0, /*misses*/ numResources,
/*refs*/ 1, kNoID));
} else {
helper.addVertAccess(ddlCanvas, wh, kNoID, false, false);
REPORTER_ASSERT(reporter, helper.checkVert(ddlCanvas, wh,
/*hits*/ 0, /*misses*/ numResources,
/*refs*/ 1, kNoID));
}
}
if (!i) {
// Drop all the accumulated resources from the thread-safe cache
helper.snap1();
ctxInfo.directContext()->purgeUnlockedResources(/* scratchResourcesOnly */ false);
}
}
}
// Case 15: Test out posting invalidation messages that involve the thread safe cache
static void test_15(GrDirectContext* dContext, skiatest::Reporter* reporter,
TestHelper::addAccessFP addAccess,
TestHelper::checkFP check,
void (*create_key)(GrUniqueKey*, int wh, int id)) {
TestHelper helper(dContext);
(helper.*addAccess)(helper.ddlCanvas1(), kImageWH, kNoID, false, false);
REPORTER_ASSERT(reporter, (helper.*check)(helper.ddlCanvas1(), kImageWH,
/*hits*/ 0, /*misses*/ 1, /*refs*/ 1, kNoID));
sk_sp<SkDeferredDisplayList> ddl1 = helper.snap1();
REPORTER_ASSERT(reporter, helper.numCacheEntries() == 1);
GrUniqueKey key;
(*create_key)(&key, kImageWH, kNoID);
GrUniqueKeyInvalidatedMessage msg(key, dContext->priv().contextID(),
/* inThreadSafeCache */ true);
SkMessageBus<GrUniqueKeyInvalidatedMessage, uint32_t>::Post(msg);
// This purge call is needed to process the invalidation messages
dContext->purgeUnlockedResources(/* scratchResourcesOnly */ true);
REPORTER_ASSERT(reporter, helper.numCacheEntries() == 0);
(helper.*addAccess)(helper.ddlCanvas2(), kImageWH, kNoID, false, false);
REPORTER_ASSERT(reporter, (helper.*check)(helper.ddlCanvas2(), kImageWH,
/*hits*/ 0, /*misses*/ 2, /*refs*/ 1, kNoID));
sk_sp<SkDeferredDisplayList> ddl2 = helper.snap2();
REPORTER_ASSERT(reporter, helper.numCacheEntries() == 1);
helper.checkImage(reporter, std::move(ddl1));
helper.checkImage(reporter, std::move(ddl2));
}
DEF_GPUTEST_FOR_RENDERING_CONTEXTS(GrThreadSafeCache15View, reporter, ctxInfo) {
test_15(ctxInfo.directContext(), reporter, &TestHelper::addViewAccess, &TestHelper::checkView,
create_view_key);
}
DEF_GPUTEST_FOR_RENDERING_CONTEXTS(GrThreadSafeCache15Verts, reporter, ctxInfo) {
test_15(ctxInfo.directContext(), reporter, &TestHelper::addVertAccess, &TestHelper::checkVert,
create_vert_key);
}
// Case 16: Test out pre-emption of an existing vertex-data cache entry. This test simulates
// the case where there is a race to create vertex data. However, the second one
// to finish is better and usurps the first's position in the cache.
//
// This capability isn't available for views.
static bool newer_is_always_better(SkData* /* incumbent */, SkData* /* challenger */) {
return true;
};
DEF_GPUTEST_FOR_RENDERING_CONTEXTS(GrThreadSafeCache16Verts, reporter, ctxInfo) {
GrUniqueKey key;
create_vert_key(&key, kImageWH, kNoID);
TestHelper helper(ctxInfo.directContext(), newer_is_always_better);
GrThreadSafeVertexTestOp* op1 = nullptr, *op2 = nullptr;
helper.addVertAccess(helper.ddlCanvas1(), kImageWH, kNoID, false, false, &op1);
REPORTER_ASSERT(reporter, helper.checkVert(helper.ddlCanvas1(), kImageWH,
/*hits*/ 0, /*misses*/ 1, /*refs*/ 1, kNoID));
sk_sp<SkDeferredDisplayList> ddl1 = helper.snap1();
{
REPORTER_ASSERT(reporter, helper.numCacheEntries() == 1);
auto [vertexData, xtraData] = helper.threadSafeCache()->findVertsWithData(key);
REPORTER_ASSERT(reporter, vertexData.get() == op1->vertexData());
}
helper.addVertAccess(helper.ddlCanvas2(), kImageWH, kNoID, /* failLookup */ true, false, &op2);
REPORTER_ASSERT(reporter, helper.checkVert(helper.ddlCanvas2(), kImageWH,
/*hits*/ 0, /*misses*/ 2, /*refs*/ 1, kNoID));
sk_sp<SkDeferredDisplayList> ddl2 = helper.snap2();
REPORTER_ASSERT(reporter, op1->vertexData() != op2->vertexData());
{
REPORTER_ASSERT(reporter, helper.numCacheEntries() == 1);
auto [vertexData, xtraData] = helper.threadSafeCache()->findVertsWithData(key);
REPORTER_ASSERT(reporter, vertexData.get() == op2->vertexData());
}
helper.checkImage(reporter, std::move(ddl1));
helper.checkImage(reporter, std::move(ddl2));
}