b96995d05f
Adds a method to determine the worst-case number of tessellated line segments that a path might require, and disables hardware tessellation if it is more segments than are supported (falling back on indirect draw shaders). If the path requires even more segments than are supported by the indirect draw shaders (1024), we crop the path to the viewport. The required number of segments is proportional to the square root of the bounding box's diagonal, so we won't start cropping paths until their device-space bounding box diagonal is nearly 175,000 pixels long. Change-Id: I8a9435e70bb93dda3464cc11a3e44fbe511744ae Reviewed-on: https://skia-review.googlesource.com/c/skia/+/293691 Reviewed-by: Greg Daniel <egdaniel@google.com> Reviewed-by: Brian Salomon <bsalomon@google.com> Commit-Queue: Chris Dalton <csmartdalton@google.com>
243 lines
9.9 KiB
C++
243 lines
9.9 KiB
C++
/*
|
|
* Copyright 2020 Google Inc.
|
|
*
|
|
* Use of this source code is governed by a BSD-style license that can be
|
|
* found in the LICENSE file.
|
|
*/
|
|
|
|
#include "bench/Benchmark.h"
|
|
#include "include/gpu/GrContext.h"
|
|
#include "src/core/SkPathPriv.h"
|
|
#include "src/gpu/GrContextPriv.h"
|
|
#include "src/gpu/GrOpFlushState.h"
|
|
#include "src/gpu/tessellate/GrMiddleOutPolygonTriangulator.h"
|
|
#include "src/gpu/tessellate/GrResolveLevelCounter.h"
|
|
#include "src/gpu/tessellate/GrTessellatePathOp.h"
|
|
#include "src/gpu/tessellate/GrWangsFormula.h"
|
|
#include "tools/ToolUtils.h"
|
|
|
|
// This is the number of cubics in desk_chalkboard.skp. (There are no quadratics in the chalkboard.)
|
|
constexpr static int kNumCubicsInChalkboard = 47182;
|
|
|
|
static SkPath make_cubic_path() {
|
|
SkRandom rand;
|
|
SkPath path;
|
|
for (int i = 0; i < kNumCubicsInChalkboard/2; ++i) {
|
|
float x = std::ldexp(rand.nextF(), (i % 18)) / 1e3f;
|
|
path.cubicTo(111.625f*x, 308.188f*x, 764.62f*x, -435.688f*x, 742.63f*x, 85.187f*x);
|
|
path.cubicTo(764.62f*x, -435.688f*x, 111.625f*x, 308.188f*x, 0, 0);
|
|
}
|
|
return path;
|
|
}
|
|
|
|
// This is a dummy GrMeshDrawOp::Target implementation that just gives back pointers into
|
|
// pre-allocated CPU buffers, rather than allocating and mapping GPU buffers.
|
|
class BenchmarkTarget : public GrMeshDrawOp::Target {
|
|
public:
|
|
BenchmarkTarget() {
|
|
GrMockOptions mockOptions;
|
|
mockOptions.fDrawInstancedSupport = true;
|
|
mockOptions.fMaxTessellationSegments = 64;
|
|
mockOptions.fMapBufferFlags = GrCaps::kCanMap_MapFlag;
|
|
mockOptions.fConfigOptions[(int)GrColorType::kAlpha_8].fRenderability =
|
|
GrMockOptions::ConfigOptions::Renderability::kMSAA;
|
|
mockOptions.fConfigOptions[(int)GrColorType::kAlpha_8].fTexturable = true;
|
|
mockOptions.fIntegerSupport = true;
|
|
|
|
GrContextOptions ctxOptions;
|
|
ctxOptions.fGpuPathRenderers = GpuPathRenderers::kTessellation;
|
|
|
|
fMockContext = GrContext::MakeMock(&mockOptions, ctxOptions);
|
|
}
|
|
const GrContext* mockContext() const { return fMockContext.get(); }
|
|
const GrCaps& caps() const override { return *fMockContext->priv().caps(); }
|
|
GrResourceProvider* resourceProvider() const override {
|
|
return fMockContext->priv().resourceProvider();
|
|
}
|
|
void resetAllocator() { fAllocator.reset(); }
|
|
SkArenaAlloc* allocator() override { return &fAllocator; }
|
|
void putBackVertices(int vertices, size_t vertexStride) override { /* no-op */ }
|
|
|
|
void* makeVertexSpace(size_t vertexSize, int vertexCount, sk_sp<const GrBuffer>*,
|
|
int* startVertex) override {
|
|
if (vertexSize * vertexCount > sizeof(fStaticVertexData)) {
|
|
SK_ABORT(SkStringPrintf(
|
|
"FATAL: wanted %zu bytes of static vertex data; only have %zu.\n",
|
|
vertexSize * vertexCount, SK_ARRAY_COUNT(fStaticVertexData)).c_str());
|
|
}
|
|
*startVertex = 0;
|
|
return fStaticVertexData;
|
|
}
|
|
|
|
GrDrawIndexedIndirectCommand* makeDrawIndexedIndirectSpace(
|
|
int drawCount, sk_sp<const GrBuffer>* buffer, size_t* offsetInBytes) override {
|
|
int staticBufferCount = (int)SK_ARRAY_COUNT(fStaticDrawIndexedIndirectData);
|
|
if (drawCount > staticBufferCount) {
|
|
SK_ABORT(SkStringPrintf(
|
|
"FATAL: wanted %i static drawIndexedIndirect elements; only have %i.\n",
|
|
drawCount, staticBufferCount).c_str());
|
|
}
|
|
return fStaticDrawIndexedIndirectData;
|
|
}
|
|
|
|
#define UNIMPL(...) __VA_ARGS__ override { SK_ABORT("unimplemented."); }
|
|
UNIMPL(void recordDraw(const GrGeometryProcessor*, const GrSimpleMesh[], int,
|
|
const GrSurfaceProxy* const[], GrPrimitiveType))
|
|
UNIMPL(uint16_t* makeIndexSpace(int, sk_sp<const GrBuffer>*, int*))
|
|
UNIMPL(void* makeVertexSpaceAtLeast(size_t, int, int, sk_sp<const GrBuffer>*, int*, int*))
|
|
UNIMPL(uint16_t* makeIndexSpaceAtLeast(int, int, sk_sp<const GrBuffer>*, int*, int*))
|
|
UNIMPL(GrDrawIndirectCommand* makeDrawIndirectSpace(int, sk_sp<const GrBuffer>*, size_t*))
|
|
UNIMPL(void putBackIndices(int))
|
|
UNIMPL(GrRenderTargetProxy* proxy() const)
|
|
UNIMPL(const GrSurfaceProxyView* writeView() const)
|
|
UNIMPL(const GrAppliedClip* appliedClip() const)
|
|
UNIMPL(GrAppliedClip detachAppliedClip())
|
|
UNIMPL(const GrXferProcessor::DstProxyView& dstProxyView() const)
|
|
UNIMPL(GrStrikeCache* strikeCache() const)
|
|
UNIMPL(GrAtlasManager* atlasManager() const)
|
|
UNIMPL(SkTArray<GrSurfaceProxy*, true>* sampledProxyArray())
|
|
UNIMPL(GrDeferredUploadTarget* deferredUploadTarget())
|
|
#undef UNIMPL
|
|
|
|
private:
|
|
sk_sp<GrContext> fMockContext;
|
|
SkPoint fStaticVertexData[(kNumCubicsInChalkboard + 2) * 8];
|
|
GrDrawIndexedIndirectCommand fStaticDrawIndexedIndirectData[32];
|
|
SkSTArenaAlloc<1024 * 1024> fAllocator;
|
|
};
|
|
|
|
// This serves as a base class for benchmarking individual methods on GrTessellatePathOp.
|
|
class GrTessellatePathOp::TestingOnly_Benchmark : public Benchmark {
|
|
public:
|
|
TestingOnly_Benchmark(const char* subName, SkPath path, const SkMatrix& m)
|
|
: fOp(m, path, GrPaint(), GrAAType::kMSAA, GrTessellationPathRenderer::OpFlags::kNone) {
|
|
fName.printf("tessellate_%s", subName);
|
|
}
|
|
|
|
const char* onGetName() override { return fName.c_str(); }
|
|
bool isSuitableFor(Backend backend) final { return backend == kNonRendering_Backend; }
|
|
|
|
class prepareMiddleOutStencilGeometry;
|
|
class prepareMiddleOutStencilGeometry_indirect;
|
|
class prepareIndirectOuterCubics;
|
|
class prepareTessellatedOuterCubics;
|
|
class prepareTessellatedCubicWedges;
|
|
class wangs_formula_cubic_log2;
|
|
class wangs_formula_cubic_log2_scale;
|
|
class wangs_formula_cubic_log2_affine;
|
|
class middle_out_triangulation;
|
|
|
|
private:
|
|
void onDraw(int loops, SkCanvas*) final {
|
|
if (!fTarget.mockContext()) {
|
|
SkDebugf("ERROR: could not create mock context.");
|
|
return;
|
|
}
|
|
for (int i = 0; i < loops; ++i) {
|
|
fOp.fTriangleBuffer.reset();
|
|
fOp.fDoStencilTriangleBuffer = false;
|
|
fOp.fDoFillTriangleBuffer = false;
|
|
fOp.fCubicBuffer.reset();
|
|
fOp.fStencilCubicsShader = nullptr;
|
|
this->runBench(&fTarget, &fOp);
|
|
fTarget.resetAllocator();
|
|
}
|
|
}
|
|
|
|
virtual void runBench(GrMeshDrawOp::Target*, GrTessellatePathOp*) = 0;
|
|
|
|
GrTessellatePathOp fOp;
|
|
BenchmarkTarget fTarget;
|
|
SkString fName;
|
|
};
|
|
|
|
#define DEF_TESS_BENCH(NAME, PATH, MATRIX, TARGET, OP) \
|
|
class GrTessellatePathOp::TestingOnly_Benchmark::NAME \
|
|
: public GrTessellatePathOp::TestingOnly_Benchmark { \
|
|
public: \
|
|
NAME() : TestingOnly_Benchmark(#NAME, (PATH), (MATRIX)) {} \
|
|
void runBench(GrMeshDrawOp::Target* target, GrTessellatePathOp* op) override; \
|
|
}; \
|
|
DEF_BENCH( return new GrTessellatePathOp::TestingOnly_Benchmark::NAME(); ); \
|
|
void GrTessellatePathOp::TestingOnly_Benchmark::NAME::runBench( \
|
|
GrMeshDrawOp::Target* TARGET, GrTessellatePathOp* op)
|
|
|
|
DEF_TESS_BENCH(prepareMiddleOutStencilGeometry, make_cubic_path(), SkMatrix::I(), target, op) {
|
|
op->prepareMiddleOutTrianglesAndCubics(target);
|
|
}
|
|
|
|
DEF_TESS_BENCH(prepareMiddleOutStencilGeometry_indirect, make_cubic_path(), SkMatrix::I(), target,
|
|
op) {
|
|
GrResolveLevelCounter resolveLevelCounter;
|
|
op->prepareMiddleOutTrianglesAndCubics(target, &resolveLevelCounter, true);
|
|
}
|
|
|
|
DEF_TESS_BENCH(prepareIndirectOuterCubics, make_cubic_path(), SkMatrix::I(), target, op) {
|
|
GrResolveLevelCounter resolveLevelCounter;
|
|
resolveLevelCounter.reset(op->fPath, SkMatrix::I(), 4);
|
|
op->prepareIndirectOuterCubics(target, resolveLevelCounter);
|
|
}
|
|
|
|
DEF_TESS_BENCH(prepareTessellatedOuterCubics, make_cubic_path(), SkMatrix::I(), target, op) {
|
|
op->prepareTessellatedOuterCubics(target, kNumCubicsInChalkboard);
|
|
}
|
|
|
|
DEF_TESS_BENCH(prepareTessellatedCubicWedges, make_cubic_path(), SkMatrix::I(), target, op) {
|
|
op->prepareTessellatedCubicWedges(target);
|
|
}
|
|
|
|
static void benchmark_wangs_formula_cubic_log2(const SkMatrix& matrix, const SkPath& path) {
|
|
int sum = 0;
|
|
GrVectorXform xform(matrix);
|
|
for (auto [verb, pts, w] : SkPathPriv::Iterate(path)) {
|
|
if (verb == SkPathVerb::kCubic) {
|
|
sum += GrWangsFormula::cubic_log2(4, pts, xform);
|
|
}
|
|
}
|
|
// Don't let the compiler optimize away GrWangsFormula::cubic_log2.
|
|
if (sum <= 0) {
|
|
SK_ABORT("sum should be > 0.");
|
|
}
|
|
}
|
|
|
|
DEF_TESS_BENCH(wangs_formula_cubic_log2, make_cubic_path(), SkMatrix::I(), target, op) {
|
|
benchmark_wangs_formula_cubic_log2(op->fViewMatrix, op->fPath);
|
|
}
|
|
|
|
DEF_TESS_BENCH(wangs_formula_cubic_log2_scale, make_cubic_path(), SkMatrix::Scale(1.1f, 0.9f),
|
|
target, op) {
|
|
benchmark_wangs_formula_cubic_log2(op->fViewMatrix, op->fPath);
|
|
}
|
|
|
|
DEF_TESS_BENCH(wangs_formula_cubic_log2_affine, make_cubic_path(),
|
|
SkMatrix::MakeAll(.9f,0.9f,0, 1.1f,1.1f,0, 0,0,1), target, op) {
|
|
benchmark_wangs_formula_cubic_log2(op->fViewMatrix, op->fPath);
|
|
}
|
|
|
|
DEF_TESS_BENCH(middle_out_triangulation,
|
|
ToolUtils::make_star(SkRect::MakeWH(500, 500), kNumCubicsInChalkboard),
|
|
SkMatrix::I(), target, op) {
|
|
int baseVertex;
|
|
auto vertexData = static_cast<SkPoint*>(target->makeVertexSpace(
|
|
sizeof(SkPoint), kNumCubicsInChalkboard, nullptr, &baseVertex));
|
|
GrMiddleOutPolygonTriangulator middleOut(vertexData, 3, kNumCubicsInChalkboard + 2);
|
|
for (auto [verb, pts, w] : SkPathPriv::Iterate(op->fPath)) {
|
|
switch (verb) {
|
|
case SkPathVerb::kMove:
|
|
middleOut.closeAndMove(pts[0]);
|
|
break;
|
|
case SkPathVerb::kLine:
|
|
middleOut.pushVertex(pts[1]);
|
|
break;
|
|
case SkPathVerb::kClose:
|
|
middleOut.close();
|
|
break;
|
|
case SkPathVerb::kQuad:
|
|
case SkPathVerb::kConic:
|
|
case SkPathVerb::kCubic:
|
|
SkUNREACHABLE;
|
|
}
|
|
middleOut.closeAndMove(pts[0]);
|
|
}
|
|
}
|