/* * Copyright 2017 Google Inc. * * Use of this source code is governed by a BSD-style license that can be * found in the LICENSE file. */ #include "include/core/SkTypes.h" #include "tests/Test.h" #include "include/core/SkMatrix.h" #include "include/core/SkPathBuilder.h" #include "include/core/SkRect.h" #include "include/gpu/GrDirectContext.h" #include "include/gpu/GrRecordingContext.h" #include "include/gpu/mock/GrMockTypes.h" #include "src/core/SkPathPriv.h" #include "src/gpu/GrClip.h" #include "src/gpu/GrDirectContextPriv.h" #include "src/gpu/GrDrawingManager.h" #include "src/gpu/GrPaint.h" #include "src/gpu/GrPathRenderer.h" #include "src/gpu/GrRecordingContextPriv.h" #include "src/gpu/GrSurfaceDrawContext.h" #include "src/gpu/GrTexture.h" #include "src/gpu/ccpr/GrCoverageCountingPathRenderer.h" #include "src/gpu/geometry/GrStyledShape.h" #include "tools/ToolUtils.h" #include static constexpr int kCanvasSize = 100; class CCPRClip : public GrClip { public: CCPRClip(GrCoverageCountingPathRenderer* ccpr, const SkPath& path) : fCCPR(ccpr), fPath(path) {} private: SkIRect getConservativeBounds() const final { return fPath.getBounds().roundOut(); } Effect apply(GrRecordingContext* context, GrSurfaceDrawContext* rtc, GrAAType, GrAppliedClip* out, SkRect* bounds) const override { auto [success, fp] = fCCPR->makeClipProcessor(/*inputFP=*/nullptr, rtc->getOpsTask()->uniqueID(), fPath, SkIRect::MakeWH(rtc->width(), rtc->height()), *context->priv().caps()); if (success) { out->addCoverageFP(std::move(fp)); return Effect::kClipped; } else { return Effect::kClippedOut; } } GrCoverageCountingPathRenderer* const fCCPR; const SkPath fPath; }; class CCPRPathDrawer { public: CCPRPathDrawer(sk_sp dContext, skiatest::Reporter* reporter) : fDContext(dContext) , fCCPR(fDContext->priv().drawingManager()->getCoverageCountingPathRenderer()) , fRTC(GrSurfaceDrawContext::Make( fDContext.get(), GrColorType::kRGBA_8888, nullptr, SkBackingFit::kExact, {kCanvasSize, kCanvasSize}, SkSurfaceProps())) { if (!fCCPR) { ERRORF(reporter, "ccpr not enabled in GrDirectContext for ccpr tests"); } if (!fRTC) { ERRORF(reporter, "failed to create GrSurfaceDrawContext for ccpr tests"); } } GrDirectContext* dContext() const { return fDContext.get(); } GrCoverageCountingPathRenderer* ccpr() const { return fCCPR; } bool valid() const { return fCCPR && fRTC; } void clear() const { fRTC->clear(SK_PMColor4fTRANSPARENT); } void destroyGrContext() { SkASSERT(fDContext->unique()); fRTC.reset(); fCCPR = nullptr; fDContext.reset(); } void clipFullscreenRect(SkPath clipPath, const SkMatrix& matrix = SkMatrix::I()) const { SkASSERT(this->valid()); GrPaint paint; paint.setColor4f({0, 1, 0, 1}); CCPRClip clip(fCCPR, clipPath); fRTC->drawRect(&clip, std::move(paint), GrAA::kYes, SkMatrix::I(), SkRect::MakeIWH(kCanvasSize, kCanvasSize)); } void flush() const { SkASSERT(this->valid()); fDContext->flushAndSubmit(); } private: sk_sp fDContext; GrCoverageCountingPathRenderer* fCCPR; std::unique_ptr fRTC; }; class CCPRTest { public: void run(skiatest::Reporter* reporter) { GrMockOptions mockOptions; mockOptions.fDrawInstancedSupport = true; mockOptions.fHalfFloatVertexAttributeSupport = true; mockOptions.fMapBufferFlags = GrCaps::kCanMap_MapFlag; mockOptions.fConfigOptions[(int)GrColorType::kAlpha_F16].fRenderability = GrMockOptions::ConfigOptions::Renderability::kNonMSAA; mockOptions.fConfigOptions[(int)GrColorType::kAlpha_F16].fTexturable = true; mockOptions.fConfigOptions[(int)GrColorType::kAlpha_8].fRenderability = GrMockOptions::ConfigOptions::Renderability::kMSAA; mockOptions.fConfigOptions[(int)GrColorType::kAlpha_8].fTexturable = true; mockOptions.fGeometryShaderSupport = true; mockOptions.fIntegerSupport = true; mockOptions.fFlatInterpolationSupport = true; GrContextOptions ctxOptions; ctxOptions.fAllowPathMaskCaching = false; ctxOptions.fGpuPathRenderers = GpuPathRenderers::kCoverageCounting; this->customizeOptions(&mockOptions, &ctxOptions); sk_sp mockContext = GrDirectContext::MakeMock(&mockOptions, ctxOptions); if (!mockContext) { ERRORF(reporter, "could not create mock context"); return; } if (!mockContext->unique()) { ERRORF(reporter, "mock context is not unique"); return; } CCPRPathDrawer ccpr(std::exchange(mockContext, nullptr), reporter); if (!ccpr.valid()) { return; } fPath.moveTo(0, 0); fPath.cubicTo(50, 50, 0, 50, 50, 0); this->onRun(reporter, ccpr); } virtual ~CCPRTest() {} protected: virtual void customizeOptions(GrMockOptions*, GrContextOptions*) {} virtual void onRun(skiatest::Reporter* reporter, CCPRPathDrawer& ccpr) = 0; SkPath fPath; }; #define DEF_CCPR_TEST(name) \ DEF_GPUTEST(name, reporter, /* options */) { \ name test; \ test.run(reporter); \ } class CCPR_cleanup : public CCPRTest { protected: void onRun(skiatest::Reporter* reporter, CCPRPathDrawer& ccpr) override { REPORTER_ASSERT(reporter, SkPathPriv::TestingOnly_unique(fPath)); // Ensure paths get unreffed when we delete the context without flushing. for (int i = 0; i < 10; ++i) { ccpr.clipFullscreenRect(fPath); ccpr.clipFullscreenRect(fPath); } REPORTER_ASSERT(reporter, !SkPathPriv::TestingOnly_unique(fPath)); ccpr.destroyGrContext(); REPORTER_ASSERT(reporter, SkPathPriv::TestingOnly_unique(fPath)); } }; DEF_CCPR_TEST(CCPR_cleanup) class CCPR_cleanupWithTexAllocFail : public CCPR_cleanup { void customizeOptions(GrMockOptions* mockOptions, GrContextOptions*) override { mockOptions->fFailTextureAllocations = true; } void onRun(skiatest::Reporter* reporter, CCPRPathDrawer& ccpr) override { ((GrRecordingContext*)ccpr.dContext())->priv().incrSuppressWarningMessages(); this->CCPR_cleanup::onRun(reporter, ccpr); } }; DEF_CCPR_TEST(CCPR_cleanupWithTexAllocFail) class CCPR_parseEmptyPath : public CCPRTest { void onRun(skiatest::Reporter* reporter, CCPRPathDrawer& ccpr) override { REPORTER_ASSERT(reporter, SkPathPriv::TestingOnly_unique(fPath)); // Make a path large enough that ccpr chooses to crop it by the RT bounds, and ends up with // an empty path. SkPath largeOutsidePath = SkPath::Polygon({ {-1e30f, -1e30f}, {-1e30f, +1e30f}, {-1e10f, +1e30f}, }, false); ccpr.clipFullscreenRect(largeOutsidePath); // Normally an empty path is culled before reaching ccpr, however we use a back door for // testing so this path will make it. SkPath emptyPath; SkASSERT(emptyPath.isEmpty()); ccpr.clipFullscreenRect(emptyPath); // This is the test. It will exercise various internal asserts and verify we do not crash. ccpr.flush(); // Now try again with clips. ccpr.clipFullscreenRect(largeOutsidePath); ccpr.clipFullscreenRect(emptyPath); ccpr.flush(); // ... and both. ccpr.clipFullscreenRect(largeOutsidePath); ccpr.clipFullscreenRect(largeOutsidePath); ccpr.clipFullscreenRect(emptyPath); ccpr.clipFullscreenRect(emptyPath); ccpr.flush(); } }; DEF_CCPR_TEST(CCPR_parseEmptyPath) class CCPRRenderingTest { public: void run(skiatest::Reporter* reporter, GrDirectContext* dContext) const { if (dContext->priv().drawingManager()->getCoverageCountingPathRenderer()) { CCPRPathDrawer drawer(sk_ref_sp(dContext), reporter); if (!drawer.valid()) { return; } this->onRun(reporter, drawer); } } virtual ~CCPRRenderingTest() {} protected: virtual void onRun(skiatest::Reporter* reporter, const CCPRPathDrawer& ccpr) const = 0; }; #define DEF_CCPR_RENDERING_TEST(name) \ DEF_GPUTEST_FOR_RENDERING_CONTEXTS(name, reporter, ctxInfo) { \ name test; \ test.run(reporter, ctxInfo.directContext()); \ } class CCPR_busyPath : public CCPRRenderingTest { void onRun(skiatest::Reporter* reporter, const CCPRPathDrawer& ccpr) const override { static constexpr int kNumBusyVerbs = 1 << 17; ccpr.clear(); SkPathBuilder busyPath; busyPath.moveTo(0, 0); // top left busyPath.lineTo(kCanvasSize, kCanvasSize); // bottom right for (int i = 2; i < kNumBusyVerbs; ++i) { float offset = i * ((float)kCanvasSize / kNumBusyVerbs); busyPath.lineTo(kCanvasSize - offset, kCanvasSize + offset); // offscreen } ccpr.clipFullscreenRect(busyPath.detach()); ccpr.flush(); // If this doesn't crash, the test passed. // If it does, maybe fiddle with fMaxInstancesPerDrawArraysWithoutCrashing in // your platform's GrGLCaps. } }; DEF_CCPR_RENDERING_TEST(CCPR_busyPath) // https://bugs.chromium.org/p/chromium/issues/detail?id=1102117 class CCPR_evictCacheEntryForPendingDrawOp : public CCPRRenderingTest { void onRun(skiatest::Reporter* reporter, const CCPRPathDrawer& ccpr) const override { static constexpr SkRect kRect = SkRect::MakeWH(50, 50); ccpr.clear(); // make sure path is cached. for (int i = 0; i < 2; i++) { SkPath path; path.addRect(kRect); ccpr.clipFullscreenRect(path); ccpr.flush(); } // make enough cached draws to make DoCopies happen. for (int i = 0; i <= GrCoverageCountingPathRenderer::kDoCopiesThreshold; i++) { SkPath path; path.addRect(kRect); ccpr.clipFullscreenRect(path); } // now draw the path in an incompatible matrix. Previous draw's cached atlas should // not be invalidated. otherwise, this flush would render more paths than allocated for. auto m = SkMatrix::Translate(0.1f, 0.1f); SkPath path; path.addRect(kRect); ccpr.clipFullscreenRect(path, m); ccpr.flush(); // if this test does not crash, it is passed. } }; DEF_CCPR_RENDERING_TEST(CCPR_evictCacheEntryForPendingDrawOp)