027b9c0d38
Bug: skia:13114 Change-Id: I653ab746927abdd1491e070e2e27252bf056d233 Reviewed-on: https://skia-review.googlesource.com/c/skia/+/526024 Reviewed-by: Greg Daniel <egdaniel@google.com> Commit-Queue: Michael Ludwig <michaelludwig@google.com>
1514 lines
63 KiB
C++
1514 lines
63 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/GrResourceProvider.h"
|
|
#include "src/gpu/GrStyle.h"
|
|
#include "src/gpu/GrThreadSafeCache.h"
|
|
#include "src/gpu/ops/GrDrawOp.h"
|
|
#include "src/gpu/v1/SurfaceDrawContext_v1.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<skgpu::v1::SurfaceDrawContext> new_SDC(GrRecordingContext* rContext,
|
|
int wh) {
|
|
return skgpu::v1::SurfaceDrawContext::Make(rContext,
|
|
GrColorType::kRGBA_8888,
|
|
nullptr,
|
|
SkBackingFit::kExact,
|
|
{wh, wh},
|
|
SkSurfaceProps(),
|
|
1,
|
|
GrMipMapped::kNo,
|
|
GrProtected::kNo,
|
|
kImageOrigin,
|
|
SkBudgeted::kYes);
|
|
}
|
|
|
|
static void create_view_key(skgpu::UniqueKey* key, int wh, int id) {
|
|
static const skgpu::UniqueKey::Domain kViewDomain = skgpu::UniqueKey::GenerateDomain();
|
|
skgpu::UniqueKey::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(skgpu::UniqueKey* key, int wh, int id) {
|
|
static const skgpu::UniqueKey::Domain kVertDomain = skgpu::UniqueKey::GenerateDomain();
|
|
skgpu::UniqueKey::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),
|
|
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;
|
|
}
|
|
|
|
skgpu::UniqueKey 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;
|
|
}
|
|
|
|
skgpu::UniqueKey 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,
|
|
bool usesMSAASurface,
|
|
GrAppliedClip&& appliedClip,
|
|
const GrDstProxyView& 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, usesMSAASurface,
|
|
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->usesMSAASurface(),
|
|
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;
|
|
}
|
|
|
|
skgpu::UniqueKey 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 GrDstProxyView& dstProxyView,
|
|
GrXferBarrierFlags renderPassXferBarriers,
|
|
GrLoadOp colorLoadOp) override {
|
|
SkArenaAlloc* arena = rContext->priv().recordTimeAllocator();
|
|
|
|
// DMSAA is not supported on DDL.
|
|
bool usesMSAASurface = writeView.asRenderTargetProxy()->numSamples() > 1;
|
|
|
|
// This is equivalent to a GrOpFlushState::detachAppliedClip
|
|
GrAppliedClip appliedClip = clip ? std::move(*clip) : GrAppliedClip::Disabled();
|
|
|
|
fProgramInfo = this->createProgramInfo(rContext->priv().caps(), arena, writeView,
|
|
usesMSAASurface, 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 {};
|
|
}
|
|
|
|
skgpu::Swizzle 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<skgpu::v1::SurfaceDrawContext> sdc = new_SDC(dContext, wh);
|
|
|
|
GrPaint paint;
|
|
paint.setColor4f({0.0f, 0.0f, 1.0f, 1.0f});
|
|
|
|
sdc->clear(SkPMColor4f{1.0f, 1.0f, 1.0f, 1.0f});
|
|
sdc->drawRect(nullptr, std::move(paint), GrAA::kNo, SkMatrix::I(),
|
|
{ 10, 10, wh-10.0f, wh-10.0f }, &GrStyle::SimpleFill());
|
|
|
|
++stats->fNumHWCreations;
|
|
auto view = sdc->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) {
|
|
skgpu::UniqueKey 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)(skgpu::UniqueKey*, 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);
|
|
|
|
skgpu::UniqueKey key;
|
|
(*create_key)(&key, kImageWH, kNoID);
|
|
|
|
skgpu::UniqueKeyInvalidatedMessage msg(key, dContext->priv().contextID(),
|
|
/* inThreadSafeCache */ true);
|
|
|
|
SkMessageBus<skgpu::UniqueKeyInvalidatedMessage, 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) {
|
|
skgpu::UniqueKey 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));
|
|
}
|