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