93250090c2
This is a reland of f00d6a8efd
This change is significantly different from the original.
The lifetime of GrArenas on the GrSurfaceFillContext is from
first GrOpsTask creation to the completion of the first call
to GrOpsTask::onExecute. The overall lifetime of the GrArenas
is from first GrOpsTask creation until the last dtor of the
GrOpsTask.
There are checks in the GrArena to make sure that nothing is
allocated after it is removed from the GrSurfaceFillContext.
Original change's description:
> remove fAllocators from GrOpsTask
>
> Change-Id: I5901f005c2758a92692e5cd70ba46a2b5ad797fd
> Reviewed-on: https://skia-review.googlesource.com/c/skia/+/393116
> Commit-Queue: Herb Derby <herb@google.com>
> Reviewed-by: Adlai Holler <adlai@google.com>
> Reviewed-by: Chris Dalton <csmartdalton@google.com>
> Reviewed-by: Robert Phillips <robertphillips@google.com>
Change-Id: I9ca2ae09ddd3b3eb95981013f54ab4168b5c5b49
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/397141
Commit-Queue: Herb Derby <herb@google.com>
Reviewed-by: Robert Phillips <robertphillips@google.com>
311 lines
11 KiB
C++
311 lines
11 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}, 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<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 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)
|