skia2/tests/GrCCPRTest.cpp
Chris Dalton 4ac0cbe769 Remove the scary raw pointer from GrCCClipProcessor into GrCCClipPath
This also allows us to rip out extra work ccpr was doing to maintain
the lifetimes of these objects.

Bug: chromium:928984
Change-Id: I094784235e23f00fb2ed10756a5eaa22a6028c2e
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/387356
Commit-Queue: Chris Dalton <csmartdalton@google.com>
Reviewed-by: Adlai Holler <adlai@google.com>
Reviewed-by: Robert Phillips <robertphillips@google.com>
2021-03-22 21:45:17 +00:00

347 lines
12 KiB
C++

/*
* 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 <cmath>
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,
bool hasUserStencilSettings, 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<GrDirectContext> dContext, skiatest::Reporter* reporter)
: fDContext(dContext)
, fCCPR(fDContext->priv().drawingManager()->getCoverageCountingPathRenderer())
, fRTC(GrSurfaceDrawContext::Make(
fDContext.get(), GrColorType::kRGBA_8888, nullptr, SkBackingFit::kExact,
{kCanvasSize, kCanvasSize})) {
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<GrDirectContext> fDContext;
GrCoverageCountingPathRenderer* fCCPR;
std::unique_ptr<GrSurfaceDrawContext> 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<GrDirectContext> 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.
for (int i = 0; i < 10; ++i) {
ccpr.clipFullscreenRect(fPath);
}
REPORTER_ASSERT(reporter, !SkPathPriv::TestingOnly_unique(fPath));
ccpr.flush();
REPORTER_ASSERT(reporter, SkPathPriv::TestingOnly_unique(fPath));
// Ensure clip paths get unreffed.
for (int i = 0; i < 10; ++i) {
ccpr.clipFullscreenRect(fPath);
}
REPORTER_ASSERT(reporter, !SkPathPriv::TestingOnly_unique(fPath));
ccpr.flush();
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 CCPR_unrefPerOpsTaskPathsBeforeOps : public CCPRTest {
void onRun(skiatest::Reporter* reporter, CCPRPathDrawer& ccpr) override {
REPORTER_ASSERT(reporter, SkPathPriv::TestingOnly_unique(fPath));
for (int i = 0; i < 10000; ++i) {
// Draw enough paths to make the arena allocator hit the heap.
ccpr.clipFullscreenRect(fPath);
}
// Unref the GrCCPerOpsTaskPaths object.
auto perOpsTaskPathsMap = ccpr.ccpr()->detachPendingPaths();
perOpsTaskPathsMap.clear();
// Now delete the Op and all its draws.
REPORTER_ASSERT(reporter, !SkPathPriv::TestingOnly_unique(fPath));
ccpr.flush();
REPORTER_ASSERT(reporter, SkPathPriv::TestingOnly_unique(fPath));
}
};
DEF_CCPR_TEST(CCPR_unrefPerOpsTaskPathsBeforeOps)
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)