1/*
2 * Copyright 2020 Google Inc.
3 *
4 * Use of this source code is governed by a BSD-style license that can be
5 * found in the LICENSE file.
6 */
7
8#include "include/core/SkCanvas.h"
9#include "include/core/SkDeferredDisplayListRecorder.h"
10#include "include/core/SkSurfaceCharacterization.h"
11#include "include/private/SkMalloc.h"
12#include "include/utils/SkRandom.h"
13#include "src/core/SkCanvasPriv.h"
14#include "src/core/SkMessageBus.h"
15#include "src/gpu/GrDefaultGeoProcFactory.h"
16#include "src/gpu/GrDirectContextPriv.h"
17#include "src/gpu/GrGpu.h"
18#include "src/gpu/GrMemoryPool.h"
19#include "src/gpu/GrOpFlushState.h"
20#include "src/gpu/GrProxyProvider.h"
21#include "src/gpu/GrRecordingContextPriv.h"
22#include "src/gpu/GrResourceProvider.h"
23#include "src/gpu/GrStyle.h"
24#include "src/gpu/GrThreadSafeCache.h"
25#include "src/gpu/ops/GrDrawOp.h"
26#include "src/gpu/v1/SurfaceDrawContext_v1.h"
27#include "tests/Test.h"
28#include "tests/TestUtils.h"
29#include "tools/gpu/ProxyUtils.h"
30
31#include <thread>
32
33static constexpr int kImageWH = 32;
34static constexpr auto kImageOrigin = kBottomLeft_GrSurfaceOrigin;
35static constexpr int kNoID = -1;
36
37static SkImageInfo default_ii(int wh) {
38    return SkImageInfo::Make(wh, wh, kRGBA_8888_SkColorType, kPremul_SkAlphaType);
39}
40
41static std::unique_ptr<skgpu::v1::SurfaceDrawContext> new_SDC(GrRecordingContext* rContext,
42                                                              int wh) {
43    return skgpu::v1::SurfaceDrawContext::Make(rContext,
44                                               GrColorType::kRGBA_8888,
45                                               nullptr,
46                                               SkBackingFit::kExact,
47                                               {wh, wh},
48                                               SkSurfaceProps(),
49                                               1,
50                                               GrMipMapped::kNo,
51                                               GrProtected::kNo,
52                                               kImageOrigin,
53                                               SkBudgeted::kYes);
54}
55
56static void create_view_key(GrUniqueKey* key, int wh, int id) {
57    static const GrUniqueKey::Domain kViewDomain = GrUniqueKey::GenerateDomain();
58    GrUniqueKey::Builder builder(key, kViewDomain, 1);
59    builder[0] = wh;
60    builder.finish();
61
62    if (id != kNoID) {
63        key->setCustomData(SkData::MakeWithCopy(&id, sizeof(id)));
64    }
65}
66
67static void create_vert_key(GrUniqueKey* key, int wh, int id) {
68    static const GrUniqueKey::Domain kVertDomain = GrUniqueKey::GenerateDomain();
69    GrUniqueKey::Builder builder(key, kVertDomain, 1);
70    builder[0] = wh;
71    builder.finish();
72
73    if (id != kNoID) {
74        key->setCustomData(SkData::MakeWithCopy(&id, sizeof(id)));
75    }
76}
77
78static bool default_is_newer_better(SkData* incumbent, SkData* challenger) {
79    return false;
80}
81
82// When testing views we create a bitmap that covers the entire screen and has an inset blue rect
83// atop a field of white.
84// When testing verts we clear the background to white and simply draw an inset blur rect.
85static SkBitmap create_bitmap(int wh) {
86    SkBitmap bitmap;
87
88    bitmap.allocPixels(default_ii(wh));
89
90    SkCanvas tmp(bitmap);
91    tmp.clear(SK_ColorWHITE);
92
93    SkPaint blue;
94    blue.setColor(SK_ColorBLUE);
95    blue.setAntiAlias(false);
96
97    tmp.drawRect({10, 10, wh-10.0f, wh-10.0f}, blue);
98
99    bitmap.setImmutable();
100    return bitmap;
101}
102
103class GrThreadSafeVertexTestOp;
104
105class TestHelper {
106public:
107    struct Stats {
108        int fCacheHits = 0;
109        int fCacheMisses = 0;
110
111        int fNumSWCreations = 0;
112        int fNumLazyCreations = 0;
113        int fNumHWCreations = 0;
114    };
115
116    TestHelper(GrDirectContext* dContext,
117               GrThreadSafeCache::IsNewerBetter isNewerBetter = default_is_newer_better)
118            : fDContext(dContext)
119            , fIsNewerBetter(isNewerBetter) {
120
121        fDst = SkSurface::MakeRenderTarget(dContext, SkBudgeted::kNo, default_ii(kImageWH));
122        SkAssertResult(fDst);
123
124        SkSurfaceCharacterization characterization;
125        SkAssertResult(fDst->characterize(&characterization));
126
127        fRecorder1 = std::make_unique<SkDeferredDisplayListRecorder>(characterization);
128        this->ddlCanvas1()->clear(SkColors::kWhite);
129
130        fRecorder2 = std::make_unique<SkDeferredDisplayListRecorder>(characterization);
131        this->ddlCanvas2()->clear(SkColors::kWhite);
132
133        fDst->getCanvas()->clear(SkColors::kWhite);
134    }
135
136    ~TestHelper() {
137        fDContext->flush();
138        fDContext->submit(true);
139    }
140
141    Stats* stats() { return &fStats; }
142
143    int numCacheEntries() const { return this->threadSafeCache()->numEntries(); }
144
145    GrDirectContext* dContext() { return fDContext; }
146
147    SkCanvas* liveCanvas() { return fDst ? fDst->getCanvas() : nullptr; }
148    SkCanvas* ddlCanvas1() { return fRecorder1 ? fRecorder1->getCanvas() : nullptr; }
149    sk_sp<SkDeferredDisplayList> snap1() {
150        if (fRecorder1) {
151            sk_sp<SkDeferredDisplayList> tmp = fRecorder1->detach();
152            fRecorder1 = nullptr;
153            return tmp;
154        }
155
156        return nullptr;
157    }
158    SkCanvas* ddlCanvas2() { return fRecorder2 ? fRecorder2->getCanvas() : nullptr; }
159    sk_sp<SkDeferredDisplayList> snap2() {
160        if (fRecorder2) {
161            sk_sp<SkDeferredDisplayList> tmp = fRecorder2->detach();
162            fRecorder2 = nullptr;
163            return tmp;
164        }
165
166        return nullptr;
167    }
168
169    GrThreadSafeCache* threadSafeCache() { return fDContext->priv().threadSafeCache(); }
170    const GrThreadSafeCache* threadSafeCache() const { return fDContext->priv().threadSafeCache(); }
171
172    typedef void (TestHelper::*addAccessFP)(SkCanvas*, int wh, int id,
173                                            bool failLookUp, bool failFillingIn);
174    typedef bool (TestHelper::*checkFP)(SkCanvas*, int wh,
175                                        int expectedHits, int expectedMisses,
176                                        int expectedNumRefs, int expectedID);
177
178    // Add a draw on 'canvas' that will introduce a ref on the 'wh' view
179    void addViewAccess(SkCanvas* canvas,
180                       int wh,
181                       int id = kNoID,
182                       bool failLookup = false,
183                       bool failFillingIn = false) {
184        auto rContext = canvas->recordingContext();
185
186        auto view = AccessCachedView(rContext, this->threadSafeCache(),
187                                     wh, failLookup, failFillingIn, id, &fStats);
188        SkASSERT(view);
189
190        auto sdc = SkCanvasPriv::TopDeviceSurfaceDrawContext(canvas);
191
192        sdc->drawTexture(nullptr,
193                         view,
194                         kPremul_SkAlphaType,
195                         GrSamplerState::Filter::kNearest,
196                         GrSamplerState::MipmapMode::kNone,
197                         SkBlendMode::kSrcOver,
198                         {1.0f, 1.0f, 1.0f, 1.0f},
199                         SkRect::MakeWH(wh, wh),
200                         SkRect::MakeWH(wh, wh),
201                         GrAA::kNo,
202                         GrQuadAAFlags::kNone,
203                         SkCanvas::kFast_SrcRectConstraint,
204                         SkMatrix::I(),
205                         nullptr);
206    }
207
208    // Besides checking that the number of refs and cache hits and misses are as expected, this
209    // method also validates that the unique key doesn't appear in any of the other caches.
210    bool checkView(SkCanvas* canvas, int wh,
211                   int expectedHits, int expectedMisses, int expectedNumRefs, int expectedID) {
212        if (fStats.fCacheHits != expectedHits || fStats.fCacheMisses != expectedMisses) {
213            SkDebugf("Hits E: %d A: %d --- Misses E: %d A: %d\n",
214                     expectedHits, fStats.fCacheHits, expectedMisses, fStats.fCacheMisses);
215            return false;
216        }
217
218        GrUniqueKey key;
219        create_view_key(&key, wh, kNoID);
220
221        auto threadSafeCache = this->threadSafeCache();
222
223        auto [view, xtraData] = threadSafeCache->findWithData(key);
224        if (!view.proxy()) {
225            return false;
226        }
227
228        if (expectedID < 0) {
229            if (xtraData) {
230                return false;
231            }
232        } else {
233            if (!xtraData) {
234                return false;
235            }
236
237            const int* cachedID = static_cast<const int*>(xtraData->data());
238            if (*cachedID != expectedID) {
239                return false;
240            }
241        }
242
243        if (!view.proxy()->refCntGreaterThan(expectedNumRefs+1) ||  // +1 for 'view's ref
244            view.proxy()->refCntGreaterThan(expectedNumRefs+2)) {
245            return false;
246        }
247
248        if (canvas) {
249            GrRecordingContext* rContext = canvas->recordingContext();
250            GrProxyProvider* recordingProxyProvider = rContext->priv().proxyProvider();
251            sk_sp<GrTextureProxy> result = recordingProxyProvider->findProxyByUniqueKey(key);
252            if (result) {
253                // views in this cache should never appear in the recorder's cache
254                return false;
255            }
256        }
257
258        {
259            GrProxyProvider* directProxyProvider = fDContext->priv().proxyProvider();
260            sk_sp<GrTextureProxy> result = directProxyProvider->findProxyByUniqueKey(key);
261            if (result) {
262                // views in this cache should never appear in the main proxy cache
263                return false;
264            }
265        }
266
267        {
268            auto resourceProvider = fDContext->priv().resourceProvider();
269            sk_sp<GrSurface> surf = resourceProvider->findByUniqueKey<GrSurface>(key);
270            if (surf) {
271                // the textures backing the views in this cache should never be discoverable in the
272                // resource cache
273                return false;
274            }
275        }
276
277        return true;
278    }
279
280    void addVertAccess(SkCanvas* canvas,
281                       int wh,
282                       int id,
283                       bool failLookup,
284                       bool failFillingIn,
285                       GrThreadSafeVertexTestOp** createdOp);
286
287    // Add a draw on 'canvas' that will introduce a ref on a 'wh' vertex data
288    void addVertAccess(SkCanvas* canvas,
289                       int wh,
290                       int id = kNoID,
291                       bool failLookup = false,
292                       bool failFillingIn = false) {
293        this->addVertAccess(canvas, wh, id, failLookup, failFillingIn, nullptr);
294    }
295
296    bool checkVert(SkCanvas* canvas, int wh,
297                   int expectedHits, int expectedMisses, int expectedNumRefs, int expectedID) {
298        if (fStats.fCacheHits != expectedHits || fStats.fCacheMisses != expectedMisses) {
299            SkDebugf("Hits E: %d A: %d --- Misses E: %d A: %d\n",
300                     expectedHits, fStats.fCacheHits, expectedMisses, fStats.fCacheMisses);
301            return false;
302        }
303
304        GrUniqueKey key;
305        create_vert_key(&key, wh, kNoID);
306
307        auto threadSafeCache = this->threadSafeCache();
308
309        auto [vertData, xtraData] = threadSafeCache->findVertsWithData(key);
310        if (!vertData) {
311            return false;
312        }
313
314        if (expectedID < 0) {
315            if (xtraData) {
316                return false;
317            }
318        } else {
319            if (!xtraData) {
320                return false;
321            }
322
323            const int* cachedID = static_cast<const int*>(xtraData->data());
324            if (*cachedID != expectedID) {
325                return false;
326            }
327        }
328
329        if (!vertData->refCntGreaterThan(expectedNumRefs+1) ||  // +1 for 'vertData's ref
330            vertData->refCntGreaterThan(expectedNumRefs+2)) {
331            return false;
332        }
333
334        {
335            auto resourceProvider = fDContext->priv().resourceProvider();
336            sk_sp<GrGpuBuffer> buffer = resourceProvider->findByUniqueKey<GrGpuBuffer>(key);
337            if (buffer) {
338                // the buffer holding the vertex data in this cache should never be discoverable
339                // in the resource cache
340                return false;
341            }
342        }
343
344        return true;
345    }
346
347    bool checkImage(skiatest::Reporter* reporter, sk_sp<SkSurface> s) {
348        SkBitmap actual;
349
350        actual.allocPixels(default_ii(kImageWH));
351
352        if (!s->readPixels(actual, 0, 0)) {
353            return false;
354        }
355
356        SkBitmap expected = create_bitmap(kImageWH);
357
358        const float tols[4] = { 0.0f, 0.0f, 0.0f, 0.0f };
359
360        auto error = std::function<ComparePixmapsErrorReporter>(
361            [reporter](int x, int y, const float diffs[4]) {
362                SkASSERT(x >= 0 && y >= 0);
363                ERRORF(reporter, "mismatch at %d, %d (%f, %f, %f %f)",
364                       x, y, diffs[0], diffs[1], diffs[2], diffs[3]);
365            });
366
367        return ComparePixels(expected.pixmap(), actual.pixmap(), tols, error);
368    }
369
370    bool checkImage(skiatest::Reporter* reporter) {
371        return this->checkImage(reporter, fDst);
372    }
373
374    bool checkImage(skiatest::Reporter* reporter, sk_sp<SkDeferredDisplayList> ddl) {
375        sk_sp<SkSurface> tmp = SkSurface::MakeRenderTarget(fDContext,
376                                                           SkBudgeted::kNo,
377                                                           default_ii(kImageWH));
378        if (!tmp) {
379            return false;
380        }
381
382        if (!tmp->draw(std::move(ddl))) {
383            return false;
384        }
385
386        return this->checkImage(reporter, std::move(tmp));
387    }
388
389    size_t gpuSize(int wh) const {
390        GrBackendFormat format = fDContext->defaultBackendFormat(kRGBA_8888_SkColorType,
391                                                                 GrRenderable::kNo);
392
393        return GrSurface::ComputeSize(format, {wh, wh}, /*colorSamplesPerPixel=*/1,
394                                      GrMipMapped::kNo, /*binSize=*/false);
395    }
396
397private:
398    static GrSurfaceProxyView AccessCachedView(GrRecordingContext*,
399                                               GrThreadSafeCache*,
400                                               int wh,
401                                               bool failLookup, bool failFillingIn, int id,
402                                               Stats*);
403    static GrSurfaceProxyView CreateViewOnCpu(GrRecordingContext*, int wh, Stats*);
404    static bool FillInViewOnGpu(GrDirectContext*, int wh, Stats*,
405                                const GrSurfaceProxyView& lazyView,
406                                sk_sp<GrThreadSafeCache::Trampoline>);
407
408    Stats fStats;
409    GrDirectContext* fDContext = nullptr;
410    GrThreadSafeCache::IsNewerBetter fIsNewerBetter;
411
412    sk_sp<SkSurface> fDst;
413    std::unique_ptr<SkDeferredDisplayListRecorder> fRecorder1;
414    std::unique_ptr<SkDeferredDisplayListRecorder> fRecorder2;
415};
416
417class GrThreadSafeVertexTestOp : public GrDrawOp {
418public:
419    DEFINE_OP_CLASS_ID
420
421    static GrOp::Owner Make(GrRecordingContext* rContext, TestHelper::Stats* stats,
422                            int wh, int id, bool failLookup, bool failFillingIn,
423                            GrThreadSafeCache::IsNewerBetter isNewerBetter) {
424
425        return GrOp::Make<GrThreadSafeVertexTestOp>(
426                rContext, rContext, stats, wh, id, failLookup, failFillingIn, isNewerBetter);
427    }
428
429    const GrThreadSafeCache::VertexData* vertexData() const { return fVertexData.get(); }
430
431private:
432    friend class GrOp; // for ctor
433
434    GrThreadSafeVertexTestOp(GrRecordingContext* rContext, TestHelper::Stats* stats, int wh, int id,
435                             bool failLookup, bool failFillingIn,
436                             GrThreadSafeCache::IsNewerBetter isNewerBetter)
437            : INHERITED(ClassID())
438            , fStats(stats)
439            , fWH(wh)
440            , fID(id)
441            , fFailFillingIn(failFillingIn)
442            , fIsNewerBetter(isNewerBetter) {
443        this->setBounds(SkRect::MakeIWH(fWH, fWH), HasAABloat::kNo, IsHairline::kNo);
444
445        // Normally we wouldn't add a ref to the vertex data at this point. However, it is
446        // needed in this unit test to get the ref counts on the uniquely keyed resources
447        // to be as expected.
448        this->findOrCreateVertices(rContext, failLookup, fFailFillingIn);
449    }
450
451    const char* name() const override { return "GrThreadSafeVertexTestOp"; }
452    FixedFunctionFlags fixedFunctionFlags() const override { return FixedFunctionFlags::kNone; }
453    GrProcessorSet::Analysis finalize(const GrCaps&, const GrAppliedClip*, GrClampType) override {
454        return GrProcessorSet::EmptySetAnalysis();
455    }
456
457    GrProgramInfo* createProgramInfo(const GrCaps* caps,
458                                     SkArenaAlloc* arena,
459                                     const GrSurfaceProxyView& writeView,
460                                     bool usesMSAASurface,
461                                     GrAppliedClip&& appliedClip,
462                                     const GrDstProxyView& dstProxyView,
463                                     GrXferBarrierFlags renderPassXferBarriers,
464                                     GrLoadOp colorLoadOp) const {
465        using namespace GrDefaultGeoProcFactory;
466
467        Color color({ 0.0f, 0.0f, 1.0f, 1.0f });
468
469        auto gp = MakeForDeviceSpace(arena, color,
470                                     Coverage::kSolid_Type,
471                                     LocalCoords::kUnused_Type,
472                                     SkMatrix::I());
473
474        return sk_gpu_test::CreateProgramInfo(caps, arena, writeView, usesMSAASurface,
475                                              std::move(appliedClip), dstProxyView,
476                                              gp, SkBlendMode::kSrcOver,
477                                              GrPrimitiveType::kTriangleStrip,
478                                              renderPassXferBarriers, colorLoadOp);
479    }
480
481    GrProgramInfo* createProgramInfo(GrOpFlushState* flushState) const {
482        return this->createProgramInfo(&flushState->caps(),
483                                       flushState->allocator(),
484                                       flushState->writeView(),
485                                       flushState->usesMSAASurface(),
486                                       flushState->detachAppliedClip(),
487                                       flushState->dstProxyView(),
488                                       flushState->renderPassBarriers(),
489                                       flushState->colorLoadOp());
490    }
491
492    void findOrCreateVertices(GrRecordingContext* rContext, bool failLookup, bool failFillingIn) {
493
494        if (!fVertexData) {
495            auto threadSafeViewCache = rContext->priv().threadSafeCache();
496
497            if (rContext->asDirectContext()) {
498                // The vertex variant doesn't have a correlate to lazyProxies but increment this
499                // here to make the unit tests happy.
500                ++fStats->fNumLazyCreations;
501            }
502
503            GrUniqueKey key;
504            create_vert_key(&key, fWH, fID);
505
506            // We can "fail the lookup" to simulate a threaded race condition
507            auto [cachedVerts, data] = threadSafeViewCache->findVertsWithData(key);
508            if (cachedVerts && !failLookup) {
509                fVertexData = cachedVerts;
510                ++fStats->fCacheHits;
511                return;
512            }
513
514            ++fStats->fCacheMisses;
515            if (!rContext->asDirectContext()) {
516                ++fStats->fNumSWCreations;
517            }
518
519            constexpr size_t kVertSize = sizeof(SkPoint);
520            SkPoint* verts = static_cast<SkPoint*>(sk_malloc_throw(4 * kVertSize));
521
522            verts[0].set(10.0f, 10.0f);
523            verts[1].set(fWH-10.0f, 10.0f);
524            verts[2].set(10.0f, fWH-10.0f);
525            verts[3].set(fWH-10.0f, fWH-10.0f);
526
527            fVertexData = GrThreadSafeCache::MakeVertexData(verts, 4, kVertSize);
528
529            auto [tmpV, tmpD] = threadSafeViewCache->addVertsWithData(key, fVertexData,
530                                                                      fIsNewerBetter);
531            if (tmpV != fVertexData) {
532                // Someone beat us to creating the vertex data. Use that version.
533                fVertexData = tmpV;
534            }
535        }
536
537        if (auto dContext = rContext->asDirectContext(); dContext && !fVertexData->gpuBuffer()) {
538            auto rp = dContext->priv().resourceProvider();
539
540            if (!failFillingIn) {
541                ++fStats->fNumHWCreations;
542
543                sk_sp<GrGpuBuffer> tmp = rp->createBuffer(fVertexData->size(),
544                                                          GrGpuBufferType::kVertex,
545                                                          kStatic_GrAccessPattern,
546                                                          fVertexData->vertices());
547                fVertexData->setGpuBuffer(std::move(tmp));
548            }
549        }
550    }
551
552    void onPrePrepare(GrRecordingContext* rContext,
553                      const GrSurfaceProxyView& writeView,
554                      GrAppliedClip* clip,
555                      const GrDstProxyView& dstProxyView,
556                      GrXferBarrierFlags renderPassXferBarriers,
557                      GrLoadOp colorLoadOp) override {
558        SkArenaAlloc* arena = rContext->priv().recordTimeAllocator();
559
560        // DMSAA is not supported on DDL.
561        bool usesMSAASurface = writeView.asRenderTargetProxy()->numSamples() > 1;
562
563        // This is equivalent to a GrOpFlushState::detachAppliedClip
564        GrAppliedClip appliedClip = clip ? std::move(*clip) : GrAppliedClip::Disabled();
565
566        fProgramInfo = this->createProgramInfo(rContext->priv().caps(), arena, writeView,
567                                               usesMSAASurface, std::move(appliedClip),
568                                               dstProxyView, renderPassXferBarriers, colorLoadOp);
569
570        rContext->priv().recordProgramInfo(fProgramInfo);
571
572        // This is now a noop (bc it is always called in the ctor) but is where we would normally
573        // create the vertices.
574        this->findOrCreateVertices(rContext, false, fFailFillingIn);
575    }
576
577    void onPrepare(GrOpFlushState* flushState) override {
578        auto dContext = flushState->gpu()->getContext();
579
580        // This call site is not a noop bc this op could've been created on with DDL context
581        // and, therefore, could be lacking a gpu-side buffer
582        this->findOrCreateVertices(dContext, false, fFailFillingIn);
583    }
584
585    void onExecute(GrOpFlushState* flushState, const SkRect& chainBounds) override {
586        if (!fVertexData || !fVertexData->gpuBuffer()) {
587            return;
588        }
589
590        if (!fProgramInfo) {
591            fProgramInfo = this->createProgramInfo(flushState);
592        }
593
594        flushState->bindPipeline(*fProgramInfo, SkRect::MakeIWH(fWH, fWH));
595        flushState->bindBuffers(nullptr, nullptr, fVertexData->refGpuBuffer());
596        flushState->draw(4, 0);
597    }
598
599    TestHelper::Stats*               fStats;
600    int                              fWH;
601    int                              fID;
602    bool                             fFailFillingIn;
603    GrThreadSafeCache::IsNewerBetter fIsNewerBetter;
604
605    sk_sp<GrThreadSafeCache::VertexData> fVertexData;
606    GrProgramInfo*                   fProgramInfo = nullptr;
607
608    using INHERITED = GrDrawOp;
609};
610
611void TestHelper::addVertAccess(SkCanvas* canvas,
612                               int wh, int id,
613                               bool failLookup, bool failFillingIn,
614                               GrThreadSafeVertexTestOp** createdOp) {
615    auto rContext = canvas->recordingContext();
616    auto sdc = SkCanvasPriv::TopDeviceSurfaceDrawContext(canvas);
617
618    GrOp::Owner op = GrThreadSafeVertexTestOp::Make(rContext, &fStats,
619                                                    wh, id,
620                                                    failLookup, failFillingIn,
621                                                    fIsNewerBetter);
622    if (createdOp) {
623        *createdOp = (GrThreadSafeVertexTestOp*) op.get();
624    }
625
626    sdc->addDrawOp(std::move(op));
627}
628
629GrSurfaceProxyView TestHelper::CreateViewOnCpu(GrRecordingContext* rContext,
630                                               int wh,
631                                               Stats* stats) {
632    GrProxyProvider* proxyProvider = rContext->priv().proxyProvider();
633
634    sk_sp<GrTextureProxy> proxy = proxyProvider->createProxyFromBitmap(create_bitmap(wh),
635                                                                       GrMipmapped::kNo,
636                                                                       SkBackingFit::kExact,
637                                                                       SkBudgeted::kYes);
638    if (!proxy) {
639        return {};
640    }
641
642    GrSwizzle swizzle = rContext->priv().caps()->getReadSwizzle(proxy->backendFormat(),
643                                                                GrColorType::kRGBA_8888);
644    ++stats->fNumSWCreations;
645    return {std::move(proxy), kImageOrigin, swizzle};
646}
647
648bool TestHelper::FillInViewOnGpu(GrDirectContext* dContext, int wh, Stats* stats,
649                                 const GrSurfaceProxyView& lazyView,
650                                 sk_sp<GrThreadSafeCache::Trampoline> trampoline) {
651
652    std::unique_ptr<skgpu::v1::SurfaceDrawContext> sdc = new_SDC(dContext, wh);
653
654    GrPaint paint;
655    paint.setColor4f({0.0f, 0.0f, 1.0f, 1.0f});
656
657    sdc->clear(SkPMColor4f{1.0f, 1.0f, 1.0f, 1.0f});
658    sdc->drawRect(nullptr, std::move(paint), GrAA::kNo, SkMatrix::I(),
659                  { 10, 10, wh-10.0f, wh-10.0f }, &GrStyle::SimpleFill());
660
661    ++stats->fNumHWCreations;
662    auto view = sdc->readSurfaceView();
663
664    SkASSERT(view.swizzle() == lazyView.swizzle());
665    SkASSERT(view.origin() == lazyView.origin());
666    trampoline->fProxy = view.asTextureProxyRef();
667
668    return true;
669}
670
671GrSurfaceProxyView TestHelper::AccessCachedView(GrRecordingContext* rContext,
672                                                GrThreadSafeCache* threadSafeCache,
673                                                int wh,
674                                                bool failLookup, bool failFillingIn, int id,
675                                                Stats* stats) {
676    GrUniqueKey key;
677    create_view_key(&key, wh, id);
678
679    if (GrDirectContext* dContext = rContext->asDirectContext()) {
680        // The gpu thread gets priority over the recording threads. If the gpu thread is first,
681        // it crams a lazy proxy into the cache and then fills it in later.
682        auto [lazyView, trampoline] = GrThreadSafeCache::CreateLazyView(
683            dContext, GrColorType::kRGBA_8888, {wh, wh}, kImageOrigin, SkBackingFit::kExact);
684        ++stats->fNumLazyCreations;
685
686        auto [view, data] = threadSafeCache->findOrAddWithData(key, lazyView);
687        if (view != lazyView) {
688            ++stats->fCacheHits;
689            return view;
690        } else if (id != kNoID) {
691            // Make sure, in this case, that the customData stuck
692            SkASSERT(data);
693            SkDEBUGCODE(const int* cachedID = static_cast<const int*>(data->data());)
694            SkASSERT(*cachedID == id);
695        }
696
697        ++stats->fCacheMisses;
698
699        if (failFillingIn) {
700            // Simulate something going horribly wrong at flush-time so no GrTexture is
701            // available to fulfill the lazy proxy.
702            return view;
703        }
704
705        if (!FillInViewOnGpu(dContext, wh, stats, lazyView, std::move(trampoline))) {
706            // In this case something has gone disastrously wrong so set up to drop the draw
707            // that needed this resource and reduce future pollution of the cache.
708            threadSafeCache->remove(key);
709            return {};
710        }
711
712        return view;
713    } else {
714        GrSurfaceProxyView view;
715
716        // We can "fail the lookup" to simulate a threaded race condition
717        if (view = threadSafeCache->find(key); !failLookup && view) {
718            ++stats->fCacheHits;
719            return view;
720        }
721
722        ++stats->fCacheMisses;
723
724        view = CreateViewOnCpu(rContext, wh, stats);
725        SkASSERT(view);
726
727        auto [newView, data] = threadSafeCache->addWithData(key, view);
728        if (view == newView && id != kNoID) {
729            // Make sure, in this case, that the customData stuck
730            SkASSERT(data);
731            SkDEBUGCODE(const int* cachedID = static_cast<const int*>(data->data());)
732            SkASSERT(*cachedID == id);
733        }
734        return newView;
735    }
736}
737
738// Case 1: ensure two DDL recorders share the view/vertexData
739static void test_1(GrDirectContext* dContext, skiatest::Reporter* reporter,
740                   TestHelper::addAccessFP addAccess,
741                   TestHelper::checkFP check) {
742
743    TestHelper helper(dContext);
744
745    (helper.*addAccess)(helper.ddlCanvas1(), kImageWH, 1, false, false);
746    REPORTER_ASSERT(reporter, (helper.*check)(helper.ddlCanvas1(), kImageWH,
747                                              /*hits*/ 0, /*misses*/ 1, /*refs*/ 1, /*id*/ 1));
748
749    (helper.*addAccess)(helper.ddlCanvas2(), kImageWH, 2, false, false);
750    REPORTER_ASSERT(reporter, (helper.*check)(helper.ddlCanvas2(), kImageWH,
751                                              /*hits*/ 1, /*misses*/ 1, /*refs*/ 2, /*id*/ 1));
752
753    REPORTER_ASSERT(reporter, helper.numCacheEntries() == 1);
754    REPORTER_ASSERT(reporter, helper.stats()->fNumLazyCreations == 0);
755    REPORTER_ASSERT(reporter, helper.stats()->fNumHWCreations == 0);
756    REPORTER_ASSERT(reporter, helper.stats()->fNumSWCreations == 1);
757
758    helper.checkImage(reporter, helper.snap1());
759    helper.checkImage(reporter, helper.snap2());
760}
761
762DEF_GPUTEST_FOR_RENDERING_CONTEXTS(GrThreadSafeCache1View, reporter, ctxInfo) {
763    test_1(ctxInfo.directContext(), reporter, &TestHelper::addViewAccess, &TestHelper::checkView);
764}
765
766DEF_GPUTEST_FOR_RENDERING_CONTEXTS(GrThreadSafeCache1Verts, reporter, ctxInfo) {
767    test_1(ctxInfo.directContext(), reporter, &TestHelper::addVertAccess, &TestHelper::checkVert);
768}
769
770// Case 2: ensure that, if the direct context version wins, its result is reused by the
771//         DDL recorders
772static void test_2(GrDirectContext* dContext, skiatest::Reporter* reporter,
773                   TestHelper::addAccessFP addAccess,
774                   TestHelper::checkFP check) {
775
776    TestHelper helper(dContext);
777
778    (helper.*addAccess)(helper.liveCanvas(), kImageWH, 1, false, false);
779    REPORTER_ASSERT(reporter, (helper.*check)(helper.liveCanvas(), kImageWH,
780                                              /*hits*/ 0, /*misses*/ 1, /*refs*/ 1, /*id*/ 1));
781
782    (helper.*addAccess)(helper.ddlCanvas1(), kImageWH, 2, false, false);
783    REPORTER_ASSERT(reporter, (helper.*check)(helper.ddlCanvas1(), kImageWH,
784                                              /*hits*/ 1, /*misses*/ 1, /*refs*/ 2, /*id*/ 1));
785
786    (helper.*addAccess)(helper.ddlCanvas2(), kImageWH, 3, false, false);
787    REPORTER_ASSERT(reporter, (helper.*check)(helper.ddlCanvas2(), kImageWH,
788                                              /*hits*/ 2, /*misses*/ 1, /*refs*/ 3, /*id*/ 1));
789
790    REPORTER_ASSERT(reporter, helper.numCacheEntries() == 1);
791    REPORTER_ASSERT(reporter, helper.stats()->fNumLazyCreations == 1);
792    REPORTER_ASSERT(reporter, helper.stats()->fNumHWCreations == 1);
793    REPORTER_ASSERT(reporter, helper.stats()->fNumSWCreations == 0);
794
795    helper.checkImage(reporter);
796    helper.checkImage(reporter, helper.snap1());
797    helper.checkImage(reporter, helper.snap2());
798}
799
800DEF_GPUTEST_FOR_RENDERING_CONTEXTS(GrThreadSafeCache2View, reporter, ctxInfo) {
801    test_2(ctxInfo.directContext(), reporter, &TestHelper::addViewAccess, &TestHelper::checkView);
802}
803
804DEF_GPUTEST_FOR_RENDERING_CONTEXTS(GrThreadSafeCache2Verts, reporter, ctxInfo) {
805    test_2(ctxInfo.directContext(), reporter, &TestHelper::addVertAccess, &TestHelper::checkVert);
806}
807
808// Case 3: ensure that, if the cpu-version wins, its result is reused by the direct context
809static void test_3(GrDirectContext* dContext, skiatest::Reporter* reporter,
810                   TestHelper::addAccessFP addAccess,
811                   TestHelper::checkFP check) {
812
813    TestHelper helper(dContext);
814
815    (helper.*addAccess)(helper.ddlCanvas1(), kImageWH, 1, false, false);
816    REPORTER_ASSERT(reporter, (helper.*check)(helper.ddlCanvas1(), kImageWH,
817                                              /*hits*/ 0, /*misses*/ 1, /*refs*/ 1, /*id*/ 1));
818
819    (helper.*addAccess)(helper.liveCanvas(), kImageWH, 2, false, false);
820    REPORTER_ASSERT(reporter, (helper.*check)(helper.liveCanvas(), kImageWH,
821                                              /*hits*/ 1, /*misses*/ 1, /*refs*/ 2, /*id*/ 1));
822
823    REPORTER_ASSERT(reporter, helper.numCacheEntries() == 1);
824    REPORTER_ASSERT(reporter, helper.stats()->fNumLazyCreations == 1);
825    REPORTER_ASSERT(reporter, helper.stats()->fNumHWCreations == 0);
826    REPORTER_ASSERT(reporter, helper.stats()->fNumSWCreations == 1);
827
828    helper.checkImage(reporter);
829    helper.checkImage(reporter, helper.snap1());
830}
831
832DEF_GPUTEST_FOR_RENDERING_CONTEXTS(GrThreadSafeCache3View, reporter, ctxInfo) {
833    test_3(ctxInfo.directContext(), reporter, &TestHelper::addViewAccess, &TestHelper::checkView);
834}
835
836DEF_GPUTEST_FOR_RENDERING_CONTEXTS(GrThreadSafeCache3Verts, reporter, ctxInfo) {
837    test_3(ctxInfo.directContext(), reporter, &TestHelper::addVertAccess, &TestHelper::checkVert);
838}
839
840// Case 4: ensure that, if two DDL recorders get in a race, they still end up sharing a single
841//         view/vertexData
842static void test_4(GrDirectContext* dContext, skiatest::Reporter* reporter,
843                   TestHelper::addAccessFP addAccess,
844                   TestHelper::checkFP check) {
845
846    TestHelper helper(dContext);
847
848    (helper.*addAccess)(helper.ddlCanvas1(), kImageWH, 1, false, false);
849    REPORTER_ASSERT(reporter, (helper.*check)(helper.ddlCanvas1(), kImageWH,
850                                              /*hits*/ 0, /*misses*/ 1, /*refs*/ 1, /*id*/ 1));
851
852    static const bool kFailLookup = true;
853    (helper.*addAccess)(helper.ddlCanvas2(), kImageWH, 2, kFailLookup, false);
854    REPORTER_ASSERT(reporter, (helper.*check)(helper.ddlCanvas2(), kImageWH,
855                                              /*hits*/ 0, /*misses*/ 2, /*refs*/ 2, /*id*/ 1));
856
857    REPORTER_ASSERT(reporter, helper.numCacheEntries() == 1);
858    REPORTER_ASSERT(reporter, helper.stats()->fNumLazyCreations == 0);
859    REPORTER_ASSERT(reporter, helper.stats()->fNumHWCreations == 0);
860    REPORTER_ASSERT(reporter, helper.stats()->fNumSWCreations == 2);
861
862    helper.checkImage(reporter, helper.snap1());
863    helper.checkImage(reporter, helper.snap2());
864}
865
866DEF_GPUTEST_FOR_RENDERING_CONTEXTS(GrThreadSafeCache4View, reporter, ctxInfo) {
867    test_4(ctxInfo.directContext(), reporter, &TestHelper::addViewAccess, &TestHelper::checkView);
868}
869
870DEF_GPUTEST_FOR_RENDERING_CONTEXTS(GrThreadSafeCache4Verts, reporter, ctxInfo) {
871    test_4(ctxInfo.directContext(), reporter, &TestHelper::addVertAccess, &TestHelper::checkVert);
872}
873
874// Case 4.5: check that, if a live rendering and a DDL recording get into a race, the live
875//           rendering takes precedence.
876static void test_4_5(GrDirectContext* dContext, skiatest::Reporter* reporter,
877                     TestHelper::addAccessFP addAccess,
878                     TestHelper::checkFP check) {
879
880    TestHelper helper(dContext);
881
882    (helper.*addAccess)(helper.liveCanvas(), kImageWH, 1, false, false);
883    REPORTER_ASSERT(reporter, (helper.*check)(helper.liveCanvas(), kImageWH,
884                                              /*hits*/ 0, /*misses*/ 1, /*refs*/ 1, /*id*/ 1));
885
886    REPORTER_ASSERT(reporter, helper.numCacheEntries() == 1);
887    REPORTER_ASSERT(reporter, helper.stats()->fNumLazyCreations == 1);
888    REPORTER_ASSERT(reporter, helper.stats()->fNumHWCreations == 1);
889    REPORTER_ASSERT(reporter, helper.stats()->fNumSWCreations == 0);
890
891    static const bool kFailLookup = true;
892    (helper.*addAccess)(helper.ddlCanvas1(), kImageWH, 2, kFailLookup, false);
893    REPORTER_ASSERT(reporter, (helper.*check)(helper.ddlCanvas1(), kImageWH,
894                                              /*hits*/ 0, /*misses*/ 2, /*refs*/ 2, /*id*/ 1));
895
896    REPORTER_ASSERT(reporter, helper.numCacheEntries() == 1);
897    REPORTER_ASSERT(reporter, helper.stats()->fNumLazyCreations == 1);
898    REPORTER_ASSERT(reporter, helper.stats()->fNumHWCreations == 1);
899    REPORTER_ASSERT(reporter, helper.stats()->fNumSWCreations == 1);
900
901    helper.checkImage(reporter);
902    helper.checkImage(reporter, helper.snap1());
903}
904
905DEF_GPUTEST_FOR_RENDERING_CONTEXTS(GrThreadSafeCache4_5View, reporter, ctxInfo) {
906    test_4_5(ctxInfo.directContext(), reporter,
907             &TestHelper::addViewAccess, &TestHelper::checkView);
908}
909
910DEF_GPUTEST_FOR_RENDERING_CONTEXTS(GrThreadSafeCache4_5Verts, reporter, ctxInfo) {
911    test_4_5(ctxInfo.directContext(), reporter,
912             &TestHelper::addVertAccess, &TestHelper::checkVert);
913}
914
915// Case 4.75: check that, if a live rendering fails to generate the content needed to instantiate
916//            its lazy proxy, life goes on
917static void test_4_75(GrDirectContext* dContext, skiatest::Reporter* reporter,
918                      TestHelper::addAccessFP addAccess,
919                      TestHelper::checkFP check) {
920
921    TestHelper helper(dContext);
922
923    static const bool kFailFillingIn = true;
924    (helper.*addAccess)(helper.liveCanvas(), kImageWH, kNoID, false, kFailFillingIn);
925    REPORTER_ASSERT(reporter, (helper.*check)(helper.liveCanvas(), kImageWH,
926                                              /*hits*/ 0, /*misses*/ 1, /*refs*/ 1, kNoID));
927
928    REPORTER_ASSERT(reporter, helper.numCacheEntries() == 1);
929    REPORTER_ASSERT(reporter, helper.stats()->fNumLazyCreations == 1);
930    REPORTER_ASSERT(reporter, helper.stats()->fNumHWCreations == 0);
931    REPORTER_ASSERT(reporter, helper.stats()->fNumSWCreations == 0);
932
933    dContext->flush();
934    dContext->submit(true);
935
936    REPORTER_ASSERT(reporter, (helper.*check)(helper.liveCanvas(), kImageWH,
937                                              /*hits*/ 0, /*misses*/ 1, /*refs*/ 0, kNoID));
938
939    REPORTER_ASSERT(reporter, helper.numCacheEntries() == 1);
940    REPORTER_ASSERT(reporter, helper.stats()->fNumLazyCreations == 1);
941    REPORTER_ASSERT(reporter, helper.stats()->fNumHWCreations == 0);
942    REPORTER_ASSERT(reporter, helper.stats()->fNumSWCreations == 0);
943}
944
945DEF_GPUTEST_FOR_RENDERING_CONTEXTS(GrThreadSafeCache4_75View, reporter, ctxInfo) {
946    test_4_75(ctxInfo.directContext(), reporter,
947              &TestHelper::addViewAccess, &TestHelper::checkView);
948}
949
950DEF_GPUTEST_FOR_RENDERING_CONTEXTS(GrThreadSafeCache4_75Verts, reporter, ctxInfo) {
951    test_4_75(ctxInfo.directContext(), reporter,
952              &TestHelper::addVertAccess, &TestHelper::checkVert);
953}
954
955// Case 5: ensure that expanding the map works (esp. wrt custom data)
956static void test_5(GrDirectContext* dContext, skiatest::Reporter* reporter,
957                   TestHelper::addAccessFP addAccess,
958                   TestHelper::checkFP check) {
959
960    TestHelper helper(dContext);
961
962    auto threadSafeCache = helper.threadSafeCache();
963
964    int size = 16;
965    (helper.*addAccess)(helper.ddlCanvas1(), size, /*id*/ size, false, false);
966
967    size_t initialSize = threadSafeCache->approxBytesUsedForHash();
968
969    while (initialSize == threadSafeCache->approxBytesUsedForHash()) {
970        size *= 2;
971        (helper.*addAccess)(helper.ddlCanvas1(), size, /*id*/ size, false, false);
972    }
973
974    for (int i = 16; i <= size; i *= 2) {
975        REPORTER_ASSERT(reporter, (helper.*check)(helper.ddlCanvas1(),
976                                                  /*wh*/ i,
977                                                  /*hits*/ 0,
978                                                  /*misses*/ threadSafeCache->numEntries(),
979                                                  /*refs*/ 1,
980                                                  /*id*/ i));
981    }
982}
983
984DEF_GPUTEST_FOR_RENDERING_CONTEXTS(GrThreadSafeCache5View, reporter, ctxInfo) {
985    test_5(ctxInfo.directContext(), reporter, &TestHelper::addViewAccess, &TestHelper::checkView);
986}
987
988DEF_GPUTEST_FOR_RENDERING_CONTEXTS(GrThreadSafeCache5Verts, reporter, ctxInfo) {
989    test_5(ctxInfo.directContext(), reporter, &TestHelper::addVertAccess, &TestHelper::checkVert);
990}
991
992// Case 6: Check on dropping refs. In particular, that the cache has its own ref to keep
993//         the backing resource alive and locked.
994static void test_6(GrDirectContext* dContext, skiatest::Reporter* reporter,
995                   TestHelper::addAccessFP addAccess,
996                   TestHelper::checkFP check) {
997
998    TestHelper helper(dContext);
999
1000    (helper.*addAccess)(helper.ddlCanvas1(), kImageWH, kNoID, false, false);
1001    sk_sp<SkDeferredDisplayList> ddl1 = helper.snap1();
1002    REPORTER_ASSERT(reporter, (helper.*check)(nullptr, kImageWH,
1003                                              /*hits*/ 0, /*misses*/ 1, /*refs*/ 1, kNoID));
1004
1005    (helper.*addAccess)(helper.ddlCanvas2(), kImageWH, kNoID, false, false);
1006    sk_sp<SkDeferredDisplayList> ddl2 = helper.snap2();
1007    REPORTER_ASSERT(reporter, (helper.*check)(nullptr, kImageWH,
1008                                              /*hits*/ 1, /*misses*/ 1, /*refs*/ 2, kNoID));
1009
1010    REPORTER_ASSERT(reporter, helper.numCacheEntries() == 1);
1011
1012    ddl1 = nullptr;
1013    REPORTER_ASSERT(reporter, (helper.*check)(nullptr, kImageWH,
1014                                              /*hits*/ 1, /*misses*/ 1, /*refs*/ 1, kNoID));
1015
1016    ddl2 = nullptr;
1017    REPORTER_ASSERT(reporter, (helper.*check)(nullptr, kImageWH,
1018                                              /*hits*/ 1, /*misses*/ 1, /*refs*/ 0, kNoID));
1019
1020    // The cache still has its ref
1021    REPORTER_ASSERT(reporter, helper.numCacheEntries() == 1);
1022
1023    REPORTER_ASSERT(reporter, (helper.*check)(nullptr, kImageWH,
1024                                              /*hits*/ 1, /*misses*/ 1, /*refs*/ 0, kNoID));
1025}
1026
1027DEF_GPUTEST_FOR_RENDERING_CONTEXTS(GrThreadSafeCache6View, reporter, ctxInfo) {
1028    test_6(ctxInfo.directContext(), reporter, &TestHelper::addViewAccess, &TestHelper::checkView);
1029}
1030
1031DEF_GPUTEST_FOR_RENDERING_CONTEXTS(GrThreadSafeCache6Verts, reporter, ctxInfo) {
1032    test_6(ctxInfo.directContext(), reporter, &TestHelper::addVertAccess, &TestHelper::checkVert);
1033}
1034
1035// Case 7: Check that invoking dropAllRefs and dropUniqueRefs directly works as expected; i.e.,
1036//         dropAllRefs removes everything while dropUniqueRefs is more measured.
1037static void test_7(GrDirectContext* dContext, skiatest::Reporter* reporter,
1038                   TestHelper::addAccessFP addAccess,
1039                   TestHelper::checkFP check) {
1040
1041    TestHelper helper(dContext);
1042
1043    (helper.*addAccess)(helper.ddlCanvas1(), kImageWH, kNoID, false, false);
1044    sk_sp<SkDeferredDisplayList> ddl1 = helper.snap1();
1045    REPORTER_ASSERT(reporter, (helper.*check)(nullptr, kImageWH,
1046                                              /*hits*/ 0, /*misses*/ 1, /*refs*/ 1, kNoID));
1047
1048    (helper.*addAccess)(helper.ddlCanvas2(), 2*kImageWH, kNoID, false, false);
1049    sk_sp<SkDeferredDisplayList> ddl2 = helper.snap2();
1050    REPORTER_ASSERT(reporter, (helper.*check)(nullptr, 2*kImageWH,
1051                                              /*hits*/ 0, /*misses*/ 2, /*refs*/ 1, kNoID));
1052
1053    REPORTER_ASSERT(reporter, helper.numCacheEntries() == 2);
1054
1055    helper.threadSafeCache()->dropUniqueRefs(nullptr);
1056    REPORTER_ASSERT(reporter, helper.numCacheEntries() == 2);
1057
1058    ddl1 = nullptr;
1059
1060    helper.threadSafeCache()->dropUniqueRefs(nullptr);
1061    REPORTER_ASSERT(reporter, helper.numCacheEntries() == 1);
1062    REPORTER_ASSERT(reporter, (helper.*check)(nullptr, 2*kImageWH,
1063                                              /*hits*/ 0, /*misses*/ 2, /*refs*/ 1, kNoID));
1064
1065    helper.threadSafeCache()->dropAllRefs();
1066    REPORTER_ASSERT(reporter, helper.numCacheEntries() == 0);
1067
1068    ddl2 = nullptr;
1069}
1070
1071DEF_GPUTEST_FOR_RENDERING_CONTEXTS(GrThreadSafeCache7View, reporter, ctxInfo) {
1072    test_7(ctxInfo.directContext(), reporter, &TestHelper::addViewAccess, &TestHelper::checkView);
1073}
1074
1075DEF_GPUTEST_FOR_RENDERING_CONTEXTS(GrThreadSafeCache7Verts, reporter, ctxInfo) {
1076    test_7(ctxInfo.directContext(), reporter, &TestHelper::addVertAccess, &TestHelper::checkVert);
1077}
1078
1079// Case 8: This checks that GrContext::abandonContext works as expected wrt the thread
1080//         safe cache. This simulates the case where we have one DDL that has finished
1081//         recording but one still recording when the abandonContext fires.
1082static void test_8(GrDirectContext* dContext, skiatest::Reporter* reporter,
1083                   TestHelper::addAccessFP addAccess,
1084                   TestHelper::checkFP check) {
1085
1086    TestHelper helper(dContext);
1087
1088    (helper.*addAccess)(helper.liveCanvas(), kImageWH, kNoID, false, false);
1089    REPORTER_ASSERT(reporter, (helper.*check)(helper.liveCanvas(), kImageWH,
1090                                              /*hits*/ 0, /*misses*/ 1, /*refs*/ 1, kNoID));
1091
1092    (helper.*addAccess)(helper.ddlCanvas1(), kImageWH, kNoID, false, false);
1093    sk_sp<SkDeferredDisplayList> ddl1 = helper.snap1();
1094    REPORTER_ASSERT(reporter, (helper.*check)(helper.ddlCanvas1(), kImageWH,
1095                                              /*hits*/ 1, /*misses*/ 1, /*refs*/ 2, kNoID));
1096
1097    (helper.*addAccess)(helper.ddlCanvas2(), kImageWH, kNoID, false, false);
1098    REPORTER_ASSERT(reporter, (helper.*check)(helper.ddlCanvas2(), kImageWH,
1099                                              /*hits*/ 2, /*misses*/ 1, /*refs*/ 3, kNoID));
1100
1101    REPORTER_ASSERT(reporter, helper.numCacheEntries() == 1);
1102    REPORTER_ASSERT(reporter, helper.stats()->fNumLazyCreations == 1);
1103    REPORTER_ASSERT(reporter, helper.stats()->fNumHWCreations == 1);
1104    REPORTER_ASSERT(reporter, helper.stats()->fNumSWCreations == 0);
1105
1106    dContext->abandonContext(); // This should exercise dropAllRefs
1107
1108    sk_sp<SkDeferredDisplayList> ddl2 = helper.snap2();
1109
1110    REPORTER_ASSERT(reporter, helper.numCacheEntries() == 0);
1111
1112    ddl1 = nullptr;
1113    ddl2 = nullptr;
1114}
1115
1116DEF_GPUTEST_FOR_RENDERING_CONTEXTS(GrThreadSafeCache8View, reporter, ctxInfo) {
1117    test_8(ctxInfo.directContext(), reporter, &TestHelper::addViewAccess, &TestHelper::checkView);
1118}
1119
1120DEF_GPUTEST_FOR_RENDERING_CONTEXTS(GrThreadSafeCache8Verts, reporter, ctxInfo) {
1121    test_8(ctxInfo.directContext(), reporter, &TestHelper::addVertAccess, &TestHelper::checkVert);
1122}
1123
1124// Case 9: This checks that GrContext::releaseResourcesAndAbandonContext works as expected wrt
1125//         the thread safe cache. This simulates the case where we have one DDL that has finished
1126//         recording but one still recording when the releaseResourcesAndAbandonContext fires.
1127static void test_9(GrDirectContext* dContext, skiatest::Reporter* reporter,
1128                   TestHelper::addAccessFP addAccess,
1129                   TestHelper::checkFP check) {
1130
1131    TestHelper helper(dContext);
1132
1133    (helper.*addAccess)(helper.liveCanvas(), kImageWH, kNoID, false, false);
1134    REPORTER_ASSERT(reporter, (helper.*check)(helper.liveCanvas(), kImageWH,
1135                                              /*hits*/ 0, /*misses*/ 1, /*refs*/ 1, kNoID));
1136
1137    (helper.*addAccess)(helper.ddlCanvas1(), kImageWH, kNoID, false, false);
1138    sk_sp<SkDeferredDisplayList> ddl1 = helper.snap1();
1139    REPORTER_ASSERT(reporter, (helper.*check)(helper.ddlCanvas1(), kImageWH,
1140                                              /*hits*/ 1, /*misses*/ 1, /*refs*/ 2, kNoID));
1141
1142    (helper.*addAccess)(helper.ddlCanvas2(), kImageWH, kNoID, false, false);
1143    REPORTER_ASSERT(reporter, (helper.*check)(helper.ddlCanvas2(), kImageWH,
1144                                              /*hits*/ 2, /*misses*/ 1, /*refs*/ 3, kNoID));
1145
1146    REPORTER_ASSERT(reporter, helper.numCacheEntries() == 1);
1147    REPORTER_ASSERT(reporter, helper.stats()->fNumLazyCreations == 1);
1148    REPORTER_ASSERT(reporter, helper.stats()->fNumHWCreations == 1);
1149    REPORTER_ASSERT(reporter, helper.stats()->fNumSWCreations == 0);
1150
1151    dContext->releaseResourcesAndAbandonContext(); // This should hit dropAllRefs
1152
1153    sk_sp<SkDeferredDisplayList> ddl2 = helper.snap2();
1154
1155    REPORTER_ASSERT(reporter, helper.numCacheEntries() == 0);
1156
1157    ddl1 = nullptr;
1158    ddl2 = nullptr;
1159}
1160
1161DEF_GPUTEST_FOR_RENDERING_CONTEXTS(GrThreadSafeCache9View, reporter, ctxInfo) {
1162    test_9(ctxInfo.directContext(), reporter, &TestHelper::addViewAccess, &TestHelper::checkView);
1163}
1164
1165DEF_GPUTEST_FOR_RENDERING_CONTEXTS(GrThreadSafeCache9Verts, reporter, ctxInfo) {
1166    test_9(ctxInfo.directContext(), reporter, &TestHelper::addVertAccess, &TestHelper::checkVert);
1167}
1168
1169// Case 10: This checks that the GrContext::purgeUnlockedResources(size_t) variant works as
1170//          expected wrt the thread safe cache. It, in particular, tests out the MRU behavior
1171//          of the shared cache.
1172static void test_10(GrDirectContext* dContext, skiatest::Reporter* reporter,
1173                    TestHelper::addAccessFP addAccess,
1174                    TestHelper::checkFP check) {
1175
1176    if (GrBackendApi::kOpenGL != dContext->backend()) {
1177        // The lower-level backends have too much going on for the following simple purging
1178        // test to work
1179        return;
1180    }
1181
1182    TestHelper helper(dContext);
1183
1184    (helper.*addAccess)(helper.liveCanvas(), kImageWH, kNoID, false, false);
1185    REPORTER_ASSERT(reporter, (helper.*check)(helper.liveCanvas(), kImageWH,
1186                                              /*hits*/ 0, /*misses*/ 1, /*refs*/ 1, kNoID));
1187
1188    (helper.*addAccess)(helper.ddlCanvas1(), kImageWH, kNoID, false, false);
1189    sk_sp<SkDeferredDisplayList> ddl1 = helper.snap1();
1190    REPORTER_ASSERT(reporter, (helper.*check)(helper.ddlCanvas1(), kImageWH,
1191                                              /*hits*/ 1, /*misses*/ 1, /*refs*/ 2, kNoID));
1192
1193    (helper.*addAccess)(helper.liveCanvas(), 2*kImageWH, kNoID, false, false);
1194    REPORTER_ASSERT(reporter, (helper.*check)(helper.liveCanvas(), 2*kImageWH,
1195                                              /*hits*/ 1, /*misses*/ 2, /*refs*/ 1, kNoID));
1196
1197    (helper.*addAccess)(helper.ddlCanvas2(), 2*kImageWH, kNoID, false, false);
1198    sk_sp<SkDeferredDisplayList> ddl2 = helper.snap2();
1199    REPORTER_ASSERT(reporter, (helper.*check)(helper.ddlCanvas2(), 2*kImageWH,
1200                                              /*hits*/ 2, /*misses*/ 2, /*refs*/ 2, kNoID));
1201
1202    dContext->flush();
1203    dContext->submit(true);
1204
1205    // This should clear out everything but the textures locked in the thread-safe cache
1206    dContext->purgeUnlockedResources(false);
1207
1208    ddl1 = nullptr;
1209    ddl2 = nullptr;
1210
1211    REPORTER_ASSERT(reporter, helper.numCacheEntries() == 2);
1212    REPORTER_ASSERT(reporter, (helper.*check)(helper.liveCanvas(), kImageWH,
1213                                              /*hits*/ 2, /*misses*/ 2, /*refs*/ 0, kNoID));
1214    REPORTER_ASSERT(reporter, (helper.*check)(helper.liveCanvas(), 2*kImageWH,
1215                                              /*hits*/ 2, /*misses*/ 2, /*refs*/ 0, kNoID));
1216
1217    // Regardless of which image is MRU, this should force the other out
1218    size_t desiredBytes = helper.gpuSize(2*kImageWH) + helper.gpuSize(kImageWH)/2;
1219
1220    auto cache = dContext->priv().getResourceCache();
1221    size_t currentBytes = cache->getResourceBytes();
1222
1223    SkASSERT(currentBytes >= desiredBytes);
1224    size_t amountToPurge = currentBytes - desiredBytes;
1225
1226    // The 2*kImageWH texture should be MRU.
1227    dContext->purgeUnlockedResources(amountToPurge, true);
1228
1229    REPORTER_ASSERT(reporter, helper.numCacheEntries() == 1);
1230
1231    REPORTER_ASSERT(reporter, (helper.*check)(helper.liveCanvas(), 2*kImageWH,
1232                                              /*hits*/ 2, /*misses*/ 2, /*refs*/ 0, kNoID));
1233}
1234
1235DEF_GPUTEST_FOR_RENDERING_CONTEXTS(GrThreadSafeCache10View, reporter, ctxInfo) {
1236    test_10(ctxInfo.directContext(), reporter, &TestHelper::addViewAccess, &TestHelper::checkView);
1237}
1238
1239// To enable test_10 with verts would require a bit more work, namely:
1240//    have a different # of verts based on size
1241//    also pass in a gpuSize function to 'test_10'
1242//DEF_GPUTEST_FOR_RENDERING_CONTEXTS(GrThreadSafeCache10Verts, reporter, ctxInfo) {
1243//    test_10(ctxInfo.directContext(), reporter, &TestHelper::addVertAccess, &TestHelper::checkVert);
1244//}
1245
1246// Case 11: This checks that scratch-only variant of GrContext::purgeUnlockedResources works as
1247//          expected wrt the thread safe cache. In particular, that when 'scratchResourcesOnly'
1248//          is true, the call has no effect on the cache.
1249static void test_11(GrDirectContext* dContext, skiatest::Reporter* reporter,
1250                    TestHelper::addAccessFP addAccess,
1251                    TestHelper::checkFP check) {
1252
1253    TestHelper helper(dContext);
1254
1255    (helper.*addAccess)(helper.liveCanvas(), kImageWH, kNoID, false, false);
1256    REPORTER_ASSERT(reporter, (helper.*check)(helper.liveCanvas(), kImageWH,
1257                                              /*hits*/ 0, /*misses*/ 1, /*refs*/ 1, kNoID));
1258
1259    (helper.*addAccess)(helper.liveCanvas(), 2*kImageWH, kNoID, false, false);
1260    REPORTER_ASSERT(reporter, (helper.*check)(helper.liveCanvas(), 2*kImageWH,
1261                                              /*hits*/ 0, /*misses*/ 2, /*refs*/ 1, kNoID));
1262
1263    dContext->flush();
1264    dContext->submit(true);
1265
1266    REPORTER_ASSERT(reporter, helper.numCacheEntries() == 2);
1267    REPORTER_ASSERT(reporter, (helper.*check)(helper.liveCanvas(), kImageWH,
1268                                              /*hits*/ 0, /*misses*/ 2, /*refs*/ 0, kNoID));
1269    REPORTER_ASSERT(reporter, (helper.*check)(helper.liveCanvas(), 2*kImageWH,
1270                                              /*hits*/ 0, /*misses*/ 2, /*refs*/ 0, kNoID));
1271
1272    // This shouldn't remove anything from the cache
1273    dContext->purgeUnlockedResources(/* scratchResourcesOnly */ true);
1274
1275    REPORTER_ASSERT(reporter, helper.numCacheEntries() == 2);
1276
1277    dContext->purgeUnlockedResources(/* scratchResourcesOnly */ false);
1278
1279    REPORTER_ASSERT(reporter, helper.numCacheEntries() == 0);
1280}
1281
1282DEF_GPUTEST_FOR_RENDERING_CONTEXTS(GrThreadSafeCache11View, reporter, ctxInfo) {
1283    test_11(ctxInfo.directContext(), reporter, &TestHelper::addViewAccess, &TestHelper::checkView);
1284}
1285
1286DEF_GPUTEST_FOR_RENDERING_CONTEXTS(GrThreadSafeCache11Verts, reporter, ctxInfo) {
1287    test_11(ctxInfo.directContext(), reporter, &TestHelper::addVertAccess, &TestHelper::checkVert);
1288}
1289
1290// Case 12: Test out purges caused by resetting the cache budget to 0. Note that, due to
1291//          the how the cache operates (i.e., not directly driven by ref/unrefs) there
1292//          needs to be an explicit kick to purge the cache.
1293static void test_12(GrDirectContext* dContext, skiatest::Reporter* reporter,
1294                    TestHelper::addAccessFP addAccess,
1295                    TestHelper::checkFP check) {
1296
1297    TestHelper helper(dContext);
1298
1299    (helper.*addAccess)(helper.liveCanvas(), kImageWH, kNoID, false, false);
1300    REPORTER_ASSERT(reporter, (helper.*check)(helper.liveCanvas(), kImageWH,
1301                                              /*hits*/ 0, /*misses*/ 1, /*refs*/ 1, kNoID));
1302    (helper.*addAccess)(helper.ddlCanvas1(), kImageWH, kNoID, false, false);
1303    sk_sp<SkDeferredDisplayList> ddl1 = helper.snap1();
1304    REPORTER_ASSERT(reporter, (helper.*check)(helper.ddlCanvas1(), kImageWH,
1305                                              /*hits*/ 1, /*misses*/ 1, /*refs*/ 2, kNoID));
1306
1307    (helper.*addAccess)(helper.liveCanvas(), 2*kImageWH, kNoID, false, false);
1308    REPORTER_ASSERT(reporter, (helper.*check)(helper.liveCanvas(), 2*kImageWH,
1309                                              /*hits*/ 1, /*misses*/ 2, /*refs*/ 1, kNoID));
1310
1311    dContext->flush();
1312    dContext->submit(true);
1313
1314    REPORTER_ASSERT(reporter, helper.numCacheEntries() == 2);
1315    REPORTER_ASSERT(reporter, (helper.*check)(helper.liveCanvas(), kImageWH,
1316                                              /*hits*/ 1, /*misses*/ 2, /*refs*/ 1, kNoID));
1317    REPORTER_ASSERT(reporter, (helper.*check)(helper.liveCanvas(), 2*kImageWH,
1318                                              /*hits*/ 1, /*misses*/ 2, /*refs*/ 0, kNoID));
1319
1320    dContext->setResourceCacheLimit(0);
1321
1322    REPORTER_ASSERT(reporter, helper.numCacheEntries() == 1);
1323
1324    ddl1 = nullptr;
1325
1326    // Explicitly kick off the purge - it won't happen automatically on unref
1327    dContext->performDeferredCleanup(std::chrono::milliseconds(0));
1328
1329    REPORTER_ASSERT(reporter, helper.numCacheEntries() == 0);
1330}
1331
1332DEF_GPUTEST_FOR_RENDERING_CONTEXTS(GrThreadSafeCache12View, reporter, ctxInfo) {
1333    test_12(ctxInfo.directContext(), reporter, &TestHelper::addViewAccess, &TestHelper::checkView);
1334}
1335
1336DEF_GPUTEST_FOR_RENDERING_CONTEXTS(GrThreadSafeCache12Verts, reporter, ctxInfo) {
1337    test_12(ctxInfo.directContext(), reporter, &TestHelper::addVertAccess, &TestHelper::checkVert);
1338}
1339
1340// Case 13: Test out the 'msNotUsed' parameter to GrContext::performDeferredCleanup.
1341static void test_13(GrDirectContext* dContext, skiatest::Reporter* reporter,
1342                    TestHelper::addAccessFP addAccess,
1343                    TestHelper::checkFP check) {
1344
1345    TestHelper helper(dContext);
1346
1347    (helper.*addAccess)(helper.ddlCanvas1(), kImageWH, kNoID, false, false);
1348    REPORTER_ASSERT(reporter, (helper.*check)(helper.ddlCanvas1(), kImageWH,
1349                                              /*hits*/ 0, /*misses*/ 1, /*refs*/ 1, kNoID));
1350    sk_sp<SkDeferredDisplayList> ddl1 = helper.snap1();
1351
1352    std::this_thread::sleep_for(std::chrono::milliseconds(5));
1353    auto firstTime = GrStdSteadyClock::now();
1354    std::this_thread::sleep_for(std::chrono::milliseconds(5));
1355
1356    (helper.*addAccess)(helper.ddlCanvas2(), 2*kImageWH, kNoID, false, false);
1357
1358    REPORTER_ASSERT(reporter, (helper.*check)(helper.ddlCanvas2(), 2*kImageWH,
1359                                              /*hits*/ 0, /*misses*/ 2, /*refs*/ 1, kNoID));
1360    sk_sp<SkDeferredDisplayList> ddl2 = helper.snap2();
1361
1362    ddl1 = nullptr;
1363    ddl2 = nullptr;
1364
1365    REPORTER_ASSERT(reporter, helper.numCacheEntries() == 2);
1366
1367    auto secondTime = GrStdSteadyClock::now();
1368
1369    auto msecs = std::chrono::duration_cast<std::chrono::milliseconds>(secondTime - firstTime);
1370    dContext->performDeferredCleanup(msecs);
1371
1372    REPORTER_ASSERT(reporter, helper.numCacheEntries() == 1);
1373    REPORTER_ASSERT(reporter, (helper.*check)(helper.liveCanvas(), 2*kImageWH,
1374                                              /*hits*/ 0, /*misses*/ 2, /*refs*/ 0, kNoID));
1375}
1376
1377DEF_GPUTEST_FOR_RENDERING_CONTEXTS(GrThreadSafeCache13View, reporter, ctxInfo) {
1378    test_13(ctxInfo.directContext(), reporter, &TestHelper::addViewAccess, &TestHelper::checkView);
1379}
1380
1381DEF_GPUTEST_FOR_RENDERING_CONTEXTS(GrThreadSafeCache13Verts, reporter, ctxInfo) {
1382    test_13(ctxInfo.directContext(), reporter, &TestHelper::addVertAccess, &TestHelper::checkVert);
1383}
1384
1385// Case 14: Test out mixing & matching view & vertex data w/ recycling of the cache entries to
1386//          wring out the anonymous union code. This is mainly for the MSAN bot's consumption.
1387DEF_GPUTEST_FOR_RENDERING_CONTEXTS(GrThreadSafeCache14, reporter, ctxInfo) {
1388    constexpr int kBestPrimeNumber = 73; // palindromic in binary
1389    SkRandom rand(kBestPrimeNumber);
1390
1391    TestHelper helper(ctxInfo.directContext());
1392
1393    for (int i = 0; i < 2; ++i) {
1394        SkCanvas* ddlCanvas = (!i) ? helper.ddlCanvas1() : helper.ddlCanvas2();
1395
1396        for (int j = 0; j < 10; ++j) {
1397            int numResources = 10*i + j + 1;
1398            int wh = numResources;
1399
1400            if (rand.nextBool()) {
1401                helper.addViewAccess(ddlCanvas, wh, kNoID, false, false);
1402                REPORTER_ASSERT(reporter, helper.checkView(ddlCanvas, wh,
1403                                                           /*hits*/ 0, /*misses*/ numResources,
1404                                                           /*refs*/ 1, kNoID));
1405            } else {
1406                helper.addVertAccess(ddlCanvas, wh, kNoID, false, false);
1407                REPORTER_ASSERT(reporter, helper.checkVert(ddlCanvas, wh,
1408                                                           /*hits*/ 0, /*misses*/ numResources,
1409                                                           /*refs*/ 1, kNoID));
1410            }
1411        }
1412
1413        if (!i) {
1414            // Drop all the accumulated resources from the thread-safe cache
1415            helper.snap1();
1416            ctxInfo.directContext()->purgeUnlockedResources(/* scratchResourcesOnly */ false);
1417        }
1418    }
1419}
1420
1421// Case 15: Test out posting invalidation messages that involve the thread safe cache
1422static void test_15(GrDirectContext* dContext, skiatest::Reporter* reporter,
1423                    TestHelper::addAccessFP addAccess,
1424                    TestHelper::checkFP check,
1425                    void (*create_key)(GrUniqueKey*, int wh, int id)) {
1426
1427    TestHelper helper(dContext);
1428
1429    (helper.*addAccess)(helper.ddlCanvas1(), kImageWH, kNoID, false, false);
1430    REPORTER_ASSERT(reporter, (helper.*check)(helper.ddlCanvas1(), kImageWH,
1431                                              /*hits*/ 0, /*misses*/ 1, /*refs*/ 1, kNoID));
1432    sk_sp<SkDeferredDisplayList> ddl1 = helper.snap1();
1433
1434    REPORTER_ASSERT(reporter, helper.numCacheEntries() == 1);
1435
1436    GrUniqueKey key;
1437    (*create_key)(&key, kImageWH, kNoID);
1438
1439    GrUniqueKeyInvalidatedMessage msg(key, dContext->priv().contextID(),
1440                                      /* inThreadSafeCache */ true);
1441
1442    SkMessageBus<GrUniqueKeyInvalidatedMessage, uint32_t>::Post(msg);
1443
1444    // This purge call is needed to process the invalidation messages
1445    dContext->purgeUnlockedResources(/* scratchResourcesOnly */ true);
1446
1447    REPORTER_ASSERT(reporter, helper.numCacheEntries() == 0);
1448
1449    (helper.*addAccess)(helper.ddlCanvas2(), kImageWH, kNoID, false, false);
1450    REPORTER_ASSERT(reporter, (helper.*check)(helper.ddlCanvas2(), kImageWH,
1451                                              /*hits*/ 0, /*misses*/ 2, /*refs*/ 1, kNoID));
1452    sk_sp<SkDeferredDisplayList> ddl2 = helper.snap2();
1453
1454    REPORTER_ASSERT(reporter, helper.numCacheEntries() == 1);
1455
1456    helper.checkImage(reporter, std::move(ddl1));
1457    helper.checkImage(reporter, std::move(ddl2));
1458}
1459
1460DEF_GPUTEST_FOR_RENDERING_CONTEXTS(GrThreadSafeCache15View, reporter, ctxInfo) {
1461    test_15(ctxInfo.directContext(), reporter, &TestHelper::addViewAccess, &TestHelper::checkView,
1462            create_view_key);
1463}
1464
1465DEF_GPUTEST_FOR_RENDERING_CONTEXTS(GrThreadSafeCache15Verts, reporter, ctxInfo) {
1466    test_15(ctxInfo.directContext(), reporter, &TestHelper::addVertAccess, &TestHelper::checkVert,
1467            create_vert_key);
1468}
1469
1470// Case 16: Test out pre-emption of an existing vertex-data cache entry. This test simulates
1471//          the case where there is a race to create vertex data. However, the second one
1472//          to finish is better and usurps the first's position in the cache.
1473//
1474//          This capability isn't available for views.
1475
1476static bool newer_is_always_better(SkData* /* incumbent */, SkData* /* challenger */) {
1477    return true;
1478};
1479
1480DEF_GPUTEST_FOR_RENDERING_CONTEXTS(GrThreadSafeCache16Verts, reporter, ctxInfo) {
1481    GrUniqueKey key;
1482    create_vert_key(&key, kImageWH, kNoID);
1483
1484    TestHelper helper(ctxInfo.directContext(), newer_is_always_better);
1485
1486    GrThreadSafeVertexTestOp* op1 = nullptr, *op2 = nullptr;
1487
1488    helper.addVertAccess(helper.ddlCanvas1(), kImageWH, kNoID, false, false, &op1);
1489    REPORTER_ASSERT(reporter, helper.checkVert(helper.ddlCanvas1(), kImageWH,
1490                                               /*hits*/ 0, /*misses*/ 1, /*refs*/ 1, kNoID));
1491    sk_sp<SkDeferredDisplayList> ddl1 = helper.snap1();
1492
1493    {
1494        REPORTER_ASSERT(reporter, helper.numCacheEntries() == 1);
1495        auto [vertexData, xtraData] = helper.threadSafeCache()->findVertsWithData(key);
1496        REPORTER_ASSERT(reporter, vertexData.get() == op1->vertexData());
1497    }
1498
1499    helper.addVertAccess(helper.ddlCanvas2(), kImageWH, kNoID, /* failLookup */ true, false, &op2);
1500    REPORTER_ASSERT(reporter, helper.checkVert(helper.ddlCanvas2(), kImageWH,
1501                                               /*hits*/ 0, /*misses*/ 2, /*refs*/ 1, kNoID));
1502    sk_sp<SkDeferredDisplayList> ddl2 = helper.snap2();
1503
1504    REPORTER_ASSERT(reporter, op1->vertexData() != op2->vertexData());
1505
1506    {
1507        REPORTER_ASSERT(reporter, helper.numCacheEntries() == 1);
1508        auto [vertexData, xtraData] = helper.threadSafeCache()->findVertsWithData(key);
1509        REPORTER_ASSERT(reporter, vertexData.get() == op2->vertexData());
1510    }
1511
1512    helper.checkImage(reporter, std::move(ddl1));
1513    helper.checkImage(reporter, std::move(ddl2));
1514}
1515