|  | /* | 
|  | * 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/SkAlphaType.h" | 
|  | #include "include/core/SkBitmap.h" | 
|  | #include "include/core/SkBlendMode.h" | 
|  | #include "include/core/SkCanvas.h" | 
|  | #include "include/core/SkColor.h" | 
|  | #include "include/core/SkColorSpace.h" | 
|  | #include "include/core/SkColorType.h" | 
|  | #include "include/core/SkData.h" | 
|  | #include "include/core/SkImageInfo.h" | 
|  | #include "include/core/SkMatrix.h" | 
|  | #include "include/core/SkPaint.h" | 
|  | #include "include/core/SkPoint.h" | 
|  | #include "include/core/SkRect.h" | 
|  | #include "include/core/SkRefCnt.h" | 
|  | #include "include/core/SkSamplingOptions.h" | 
|  | #include "include/core/SkSurface.h" | 
|  | #include "include/core/SkSurfaceProps.h" | 
|  | #include "include/core/SkTypes.h" | 
|  | #include "include/gpu/GpuTypes.h" | 
|  | #include "include/gpu/GrBackendSurface.h" | 
|  | #include "include/gpu/GrDirectContext.h" | 
|  | #include "include/gpu/GrRecordingContext.h" | 
|  | #include "include/gpu/GrTypes.h" | 
|  | #include "include/gpu/ganesh/SkSurfaceGanesh.h" | 
|  | #include "include/private/SkColorData.h" | 
|  | #include "include/private/base/SkDebug.h" | 
|  | #include "include/private/base/SkMalloc.h" | 
|  | #include "include/private/chromium/GrDeferredDisplayList.h" | 
|  | #include "include/private/chromium/GrDeferredDisplayListRecorder.h" | 
|  | #include "include/private/chromium/GrSurfaceCharacterization.h" | 
|  | #include "include/private/gpu/ganesh/GrTypesPriv.h" | 
|  | #include "src/base/SkRandom.h" | 
|  | #include "src/core/SkMessageBus.h" | 
|  | #include "src/gpu/GpuTypesPriv.h" | 
|  | #include "src/gpu/ResourceKey.h" | 
|  | #include "src/gpu/SkBackingFit.h" | 
|  | #include "src/gpu/Swizzle.h" | 
|  | #include "src/gpu/ganesh/GrAppliedClip.h" | 
|  | #include "src/gpu/ganesh/GrBuffer.h" | 
|  | #include "src/gpu/ganesh/GrCanvas.h" | 
|  | #include "src/gpu/ganesh/GrCaps.h" | 
|  | #include "src/gpu/ganesh/GrColorSpaceXform.h" | 
|  | #include "src/gpu/ganesh/GrDefaultGeoProcFactory.h" | 
|  | #include "src/gpu/ganesh/GrDirectContextPriv.h" | 
|  | #include "src/gpu/ganesh/GrGpu.h" | 
|  | #include "src/gpu/ganesh/GrGpuBuffer.h" | 
|  | #include "src/gpu/ganesh/GrOpFlushState.h" | 
|  | #include "src/gpu/ganesh/GrPaint.h" | 
|  | #include "src/gpu/ganesh/GrProcessorSet.h" | 
|  | #include "src/gpu/ganesh/GrProxyProvider.h" | 
|  | #include "src/gpu/ganesh/GrRecordingContextPriv.h" | 
|  | #include "src/gpu/ganesh/GrRenderTargetProxy.h" | 
|  | #include "src/gpu/ganesh/GrResourceCache.h" | 
|  | #include "src/gpu/ganesh/GrResourceProvider.h" | 
|  | #include "src/gpu/ganesh/GrSamplerState.h" | 
|  | #include "src/gpu/ganesh/GrStyle.h" | 
|  | #include "src/gpu/ganesh/GrSurface.h" | 
|  | #include "src/gpu/ganesh/GrSurfaceProxy.h" | 
|  | #include "src/gpu/ganesh/GrSurfaceProxyView.h" | 
|  | #include "src/gpu/ganesh/GrTextureProxy.h" | 
|  | #include "src/gpu/ganesh/GrThreadSafeCache.h" | 
|  | #include "src/gpu/ganesh/SurfaceDrawContext.h" | 
|  | #include "src/gpu/ganesh/ops/GrDrawOp.h" | 
|  | #include "src/gpu/ganesh/ops/GrOp.h" | 
|  | #include "tests/CtsEnforcement.h" | 
|  | #include "tests/Test.h" | 
|  | #include "tests/TestUtils.h" | 
|  | #include "tools/gpu/ProxyUtils.h" | 
|  |  | 
|  | #include <chrono> | 
|  | #include <cstddef> | 
|  | #include <cstdint> | 
|  | #include <functional> | 
|  | #include <memory> | 
|  | #include <thread> | 
|  | #include <utility> | 
|  |  | 
|  | class GrDstProxyView; | 
|  | class GrProgramInfo; | 
|  | class GrThreadSafeVertexTestOp; | 
|  | class SkArenaAlloc; | 
|  | enum class GrXferBarrierFlags; | 
|  | struct GrContextOptions; | 
|  |  | 
|  | 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::ganesh::SurfaceDrawContext> new_SDC(GrRecordingContext* rContext, | 
|  | int wh) { | 
|  | return skgpu::ganesh::SurfaceDrawContext::Make(rContext, | 
|  | GrColorType::kRGBA_8888, | 
|  | nullptr, | 
|  | SkBackingFit::kExact, | 
|  | {wh, wh}, | 
|  | SkSurfaceProps(), | 
|  | /* label= */ {}, | 
|  | /* sampleCnt= */ 1, | 
|  | skgpu::Mipmapped::kNo, | 
|  | GrProtected::kNo, | 
|  | kImageOrigin, | 
|  | skgpu::Budgeted::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 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 = SkSurfaces::RenderTarget(dContext, skgpu::Budgeted::kNo, default_ii(kImageWH)); | 
|  | SkAssertResult(fDst); | 
|  |  | 
|  | GrSurfaceCharacterization characterization; | 
|  | SkAssertResult(fDst->characterize(&characterization)); | 
|  |  | 
|  | fRecorder1 = std::make_unique<GrDeferredDisplayListRecorder>(characterization); | 
|  | this->ddlCanvas1()->clear(SkColors::kWhite); | 
|  |  | 
|  | fRecorder2 = std::make_unique<GrDeferredDisplayListRecorder>(characterization); | 
|  | this->ddlCanvas2()->clear(SkColors::kWhite); | 
|  |  | 
|  | fDst->getCanvas()->clear(SkColors::kWhite); | 
|  | } | 
|  |  | 
|  | ~TestHelper() { | 
|  | fDContext->flush(); | 
|  | fDContext->submit(GrSyncCpu::kYes); | 
|  | } | 
|  |  | 
|  | 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<GrDeferredDisplayList> snap1() { | 
|  | if (fRecorder1) { | 
|  | sk_sp<GrDeferredDisplayList> tmp = fRecorder1->detach(); | 
|  | fRecorder1 = nullptr; | 
|  | return tmp; | 
|  | } | 
|  |  | 
|  | return nullptr; | 
|  | } | 
|  | SkCanvas* ddlCanvas2() { return fRecorder2 ? fRecorder2->getCanvas() : nullptr; } | 
|  | sk_sp<GrDeferredDisplayList> snap2() { | 
|  | if (fRecorder2) { | 
|  | sk_sp<GrDeferredDisplayList> 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 = skgpu::ganesh::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, const 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<GrDeferredDisplayList> ddl) { | 
|  | sk_sp<SkSurface> tmp = | 
|  | SkSurfaces::RenderTarget(fDContext, skgpu::Budgeted::kNo, default_ii(kImageWH)); | 
|  | if (!tmp) { | 
|  | return false; | 
|  | } | 
|  |  | 
|  | if (!skgpu::ganesh::DrawDDL(tmp, 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, | 
|  | skgpu::Mipmapped::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<GrDeferredDisplayListRecorder> fRecorder1; | 
|  | std::unique_ptr<GrDeferredDisplayListRecorder> 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->vertices(), | 
|  | fVertexData->size(), | 
|  | GrGpuBufferType::kVertex, | 
|  | kStatic_GrAccessPattern); | 
|  | 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 = skgpu::ganesh::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), skgpu::Mipmapped::kNo, SkBackingFit::kExact, skgpu::Budgeted::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::ganesh::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_GANESH_TEST_FOR_RENDERING_CONTEXTS(GrThreadSafeCache1View, | 
|  | reporter, | 
|  | ctxInfo, | 
|  | CtsEnforcement::kNever) { | 
|  | test_1(ctxInfo.directContext(), reporter, &TestHelper::addViewAccess, &TestHelper::checkView); | 
|  | } | 
|  |  | 
|  | DEF_GANESH_TEST_FOR_RENDERING_CONTEXTS(GrThreadSafeCache1Verts, | 
|  | reporter, | 
|  | ctxInfo, | 
|  | CtsEnforcement::kNever) { | 
|  | 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_GANESH_TEST_FOR_RENDERING_CONTEXTS(GrThreadSafeCache2View, | 
|  | reporter, | 
|  | ctxInfo, | 
|  | CtsEnforcement::kNever) { | 
|  | test_2(ctxInfo.directContext(), reporter, &TestHelper::addViewAccess, &TestHelper::checkView); | 
|  | } | 
|  |  | 
|  | DEF_GANESH_TEST_FOR_RENDERING_CONTEXTS(GrThreadSafeCache2Verts, | 
|  | reporter, | 
|  | ctxInfo, | 
|  | CtsEnforcement::kNever) { | 
|  | 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_GANESH_TEST_FOR_RENDERING_CONTEXTS(GrThreadSafeCache3View, | 
|  | reporter, | 
|  | ctxInfo, | 
|  | CtsEnforcement::kNever) { | 
|  | test_3(ctxInfo.directContext(), reporter, &TestHelper::addViewAccess, &TestHelper::checkView); | 
|  | } | 
|  |  | 
|  | DEF_GANESH_TEST_FOR_RENDERING_CONTEXTS(GrThreadSafeCache3Verts, | 
|  | reporter, | 
|  | ctxInfo, | 
|  | CtsEnforcement::kNever) { | 
|  | 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_GANESH_TEST_FOR_RENDERING_CONTEXTS(GrThreadSafeCache4View, | 
|  | reporter, | 
|  | ctxInfo, | 
|  | CtsEnforcement::kNever) { | 
|  | test_4(ctxInfo.directContext(), reporter, &TestHelper::addViewAccess, &TestHelper::checkView); | 
|  | } | 
|  |  | 
|  | DEF_GANESH_TEST_FOR_RENDERING_CONTEXTS(GrThreadSafeCache4Verts, | 
|  | reporter, | 
|  | ctxInfo, | 
|  | CtsEnforcement::kNever) { | 
|  | 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_GANESH_TEST_FOR_RENDERING_CONTEXTS(GrThreadSafeCache4_5View, | 
|  | reporter, | 
|  | ctxInfo, | 
|  | CtsEnforcement::kNever) { | 
|  | test_4_5(ctxInfo.directContext(), reporter, | 
|  | &TestHelper::addViewAccess, &TestHelper::checkView); | 
|  | } | 
|  |  | 
|  | DEF_GANESH_TEST_FOR_RENDERING_CONTEXTS(GrThreadSafeCache4_5Verts, | 
|  | reporter, | 
|  | ctxInfo, | 
|  | CtsEnforcement::kNever) { | 
|  | 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(GrSyncCpu::kYes); | 
|  |  | 
|  | 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_GANESH_TEST_FOR_RENDERING_CONTEXTS(GrThreadSafeCache4_75View, | 
|  | reporter, | 
|  | ctxInfo, | 
|  | CtsEnforcement::kNever) { | 
|  | test_4_75(ctxInfo.directContext(), reporter, | 
|  | &TestHelper::addViewAccess, &TestHelper::checkView); | 
|  | } | 
|  |  | 
|  | DEF_GANESH_TEST_FOR_RENDERING_CONTEXTS(GrThreadSafeCache4_75Verts, | 
|  | reporter, | 
|  | ctxInfo, | 
|  | CtsEnforcement::kNever) { | 
|  | 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_GANESH_TEST_FOR_RENDERING_CONTEXTS(GrThreadSafeCache5View, | 
|  | reporter, | 
|  | ctxInfo, | 
|  | CtsEnforcement::kNever) { | 
|  | test_5(ctxInfo.directContext(), reporter, &TestHelper::addViewAccess, &TestHelper::checkView); | 
|  | } | 
|  |  | 
|  | DEF_GANESH_TEST_FOR_RENDERING_CONTEXTS(GrThreadSafeCache5Verts, | 
|  | reporter, | 
|  | ctxInfo, | 
|  | CtsEnforcement::kNever) { | 
|  | 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<GrDeferredDisplayList> 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<GrDeferredDisplayList> 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_GANESH_TEST_FOR_RENDERING_CONTEXTS(GrThreadSafeCache6View, | 
|  | reporter, | 
|  | ctxInfo, | 
|  | CtsEnforcement::kNever) { | 
|  | test_6(ctxInfo.directContext(), reporter, &TestHelper::addViewAccess, &TestHelper::checkView); | 
|  | } | 
|  |  | 
|  | DEF_GANESH_TEST_FOR_RENDERING_CONTEXTS(GrThreadSafeCache6Verts, | 
|  | reporter, | 
|  | ctxInfo, | 
|  | CtsEnforcement::kNever) { | 
|  | 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<GrDeferredDisplayList> 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<GrDeferredDisplayList> 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_GANESH_TEST_FOR_RENDERING_CONTEXTS(GrThreadSafeCache7View, | 
|  | reporter, | 
|  | ctxInfo, | 
|  | CtsEnforcement::kNever) { | 
|  | test_7(ctxInfo.directContext(), reporter, &TestHelper::addViewAccess, &TestHelper::checkView); | 
|  | } | 
|  |  | 
|  | DEF_GANESH_TEST_FOR_RENDERING_CONTEXTS(GrThreadSafeCache7Verts, | 
|  | reporter, | 
|  | ctxInfo, | 
|  | CtsEnforcement::kNever) { | 
|  | 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<GrDeferredDisplayList> 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<GrDeferredDisplayList> ddl2 = helper.snap2(); | 
|  |  | 
|  | REPORTER_ASSERT(reporter, helper.numCacheEntries() == 0); | 
|  |  | 
|  | ddl1 = nullptr; | 
|  | ddl2 = nullptr; | 
|  | } | 
|  |  | 
|  | DEF_GANESH_TEST_FOR_RENDERING_CONTEXTS(GrThreadSafeCache8View, | 
|  | reporter, | 
|  | ctxInfo, | 
|  | CtsEnforcement::kNever) { | 
|  | test_8(ctxInfo.directContext(), reporter, &TestHelper::addViewAccess, &TestHelper::checkView); | 
|  | } | 
|  |  | 
|  | DEF_GANESH_TEST_FOR_RENDERING_CONTEXTS(GrThreadSafeCache8Verts, | 
|  | reporter, | 
|  | ctxInfo, | 
|  | CtsEnforcement::kNever) { | 
|  | 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<GrDeferredDisplayList> 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<GrDeferredDisplayList> ddl2 = helper.snap2(); | 
|  |  | 
|  | REPORTER_ASSERT(reporter, helper.numCacheEntries() == 0); | 
|  |  | 
|  | ddl1 = nullptr; | 
|  | ddl2 = nullptr; | 
|  | } | 
|  |  | 
|  | DEF_GANESH_TEST_FOR_RENDERING_CONTEXTS(GrThreadSafeCache9View, | 
|  | reporter, | 
|  | ctxInfo, | 
|  | CtsEnforcement::kNever) { | 
|  | test_9(ctxInfo.directContext(), reporter, &TestHelper::addViewAccess, &TestHelper::checkView); | 
|  | } | 
|  |  | 
|  | DEF_GANESH_TEST_FOR_RENDERING_CONTEXTS(GrThreadSafeCache9Verts, | 
|  | reporter, | 
|  | ctxInfo, | 
|  | CtsEnforcement::kNever) { | 
|  | 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<GrDeferredDisplayList> 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<GrDeferredDisplayList> ddl2 = helper.snap2(); | 
|  | REPORTER_ASSERT(reporter, (helper.*check)(helper.ddlCanvas2(), 2*kImageWH, | 
|  | /*hits*/ 2, /*misses*/ 2, /*refs*/ 2, kNoID)); | 
|  |  | 
|  | dContext->flush(); | 
|  | dContext->submit(GrSyncCpu::kYes); | 
|  |  | 
|  | // This should clear out everything but the textures locked in the thread-safe cache | 
|  | dContext->purgeUnlockedResources(GrPurgeResourceOptions::kAllResources); | 
|  |  | 
|  | 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_GANESH_TEST_FOR_RENDERING_CONTEXTS(GrThreadSafeCache10View, | 
|  | reporter, | 
|  | ctxInfo, | 
|  | CtsEnforcement::kNever) { | 
|  | 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_GANESH_TEST_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(GrSyncCpu::kYes); | 
|  |  | 
|  | 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(GrPurgeResourceOptions::kScratchResourcesOnly); | 
|  |  | 
|  | REPORTER_ASSERT(reporter, helper.numCacheEntries() == 2); | 
|  |  | 
|  | dContext->purgeUnlockedResources(GrPurgeResourceOptions::kAllResources); | 
|  |  | 
|  | REPORTER_ASSERT(reporter, helper.numCacheEntries() == 0); | 
|  | } | 
|  |  | 
|  | DEF_GANESH_TEST_FOR_RENDERING_CONTEXTS(GrThreadSafeCache11View, | 
|  | reporter, | 
|  | ctxInfo, | 
|  | CtsEnforcement::kNever) { | 
|  | test_11(ctxInfo.directContext(), reporter, &TestHelper::addViewAccess, &TestHelper::checkView); | 
|  | } | 
|  |  | 
|  | DEF_GANESH_TEST_FOR_RENDERING_CONTEXTS(GrThreadSafeCache11Verts, | 
|  | reporter, | 
|  | ctxInfo, | 
|  | CtsEnforcement::kNever) { | 
|  | 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<GrDeferredDisplayList> 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(GrSyncCpu::kYes); | 
|  |  | 
|  | 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_GANESH_TEST_FOR_RENDERING_CONTEXTS(GrThreadSafeCache12View, | 
|  | reporter, | 
|  | ctxInfo, | 
|  | CtsEnforcement::kNever) { | 
|  | test_12(ctxInfo.directContext(), reporter, &TestHelper::addViewAccess, &TestHelper::checkView); | 
|  | } | 
|  |  | 
|  | DEF_GANESH_TEST_FOR_RENDERING_CONTEXTS(GrThreadSafeCache12Verts, | 
|  | reporter, | 
|  | ctxInfo, | 
|  | CtsEnforcement::kNever) { | 
|  | 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<GrDeferredDisplayList> ddl1 = helper.snap1(); | 
|  |  | 
|  | std::this_thread::sleep_for(std::chrono::milliseconds(5)); | 
|  | auto firstTime = skgpu::StdSteadyClock::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<GrDeferredDisplayList> ddl2 = helper.snap2(); | 
|  |  | 
|  | ddl1 = nullptr; | 
|  | ddl2 = nullptr; | 
|  |  | 
|  | REPORTER_ASSERT(reporter, helper.numCacheEntries() == 2); | 
|  |  | 
|  | auto secondTime = skgpu::StdSteadyClock::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_GANESH_TEST_FOR_RENDERING_CONTEXTS(GrThreadSafeCache13View, | 
|  | reporter, | 
|  | ctxInfo, | 
|  | CtsEnforcement::kNever) { | 
|  | test_13(ctxInfo.directContext(), reporter, &TestHelper::addViewAccess, &TestHelper::checkView); | 
|  | } | 
|  |  | 
|  | DEF_GANESH_TEST_FOR_RENDERING_CONTEXTS(GrThreadSafeCache13Verts, | 
|  | reporter, | 
|  | ctxInfo, | 
|  | CtsEnforcement::kNever) { | 
|  | 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_GANESH_TEST_FOR_RENDERING_CONTEXTS(GrThreadSafeCache14, | 
|  | reporter, | 
|  | ctxInfo, | 
|  | CtsEnforcement::kNever) { | 
|  | 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(GrPurgeResourceOptions::kAllResources); | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | // 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<GrDeferredDisplayList> 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(GrPurgeResourceOptions::kScratchResourcesOnly); | 
|  |  | 
|  | 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<GrDeferredDisplayList> ddl2 = helper.snap2(); | 
|  |  | 
|  | REPORTER_ASSERT(reporter, helper.numCacheEntries() == 1); | 
|  |  | 
|  | helper.checkImage(reporter, std::move(ddl1)); | 
|  | helper.checkImage(reporter, std::move(ddl2)); | 
|  | } | 
|  |  | 
|  | DEF_GANESH_TEST_FOR_RENDERING_CONTEXTS(GrThreadSafeCache15View, | 
|  | reporter, | 
|  | ctxInfo, | 
|  | CtsEnforcement::kNever) { | 
|  | test_15(ctxInfo.directContext(), reporter, &TestHelper::addViewAccess, &TestHelper::checkView, | 
|  | create_view_key); | 
|  | } | 
|  |  | 
|  | DEF_GANESH_TEST_FOR_RENDERING_CONTEXTS(GrThreadSafeCache15Verts, | 
|  | reporter, | 
|  | ctxInfo, | 
|  | CtsEnforcement::kNever) { | 
|  | 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_GANESH_TEST_FOR_RENDERING_CONTEXTS(GrThreadSafeCache16Verts, | 
|  | reporter, | 
|  | ctxInfo, | 
|  | CtsEnforcement::kNever) { | 
|  | 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<GrDeferredDisplayList> 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<GrDeferredDisplayList> 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)); | 
|  | } |