blob: 0c7835eccda98171cc8e96918c2aa44d00c6886b [file] [log] [blame]
/*
* 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,
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())
, fSDC(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 (!fSDC) {
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 && fSDC; }
void clear() const { fSDC->clear(SK_PMColor4fTRANSPARENT); }
void destroyGrContext() {
SkASSERT(fDContext->unique());
fSDC.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);
fSDC->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> fSDC;
};
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 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)