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