skia2/bench/TessellateBench.cpp
Robert Phillips 5edf510941 Add plumbing for access to the flush-time GrSmallPathAtlasMgr
This isn't connected to anything yet and parallels what is done
for the text GrAtlasManager.

The rough pattern is that the GrContext hierarchy implements creating
the atlas but it is only exposed/made available via the flush state
(which derives from GrDrawMeshOp::Target).

TBR=bsalomon@google.com
Bug: 1108408
Change-Id: Iad7b5648877dbd20840e8d47c1d24131098ded94
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/309128
Reviewed-by: Robert Phillips <robertphillips@google.com>
Reviewed-by: Jim Van Verth <jvanverth@google.com>
Commit-Queue: Robert Phillips <robertphillips@google.com>
2020-08-11 12:53:55 +00:00

242 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/GrDirectContext.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/GrPathTessellateOp.h"
#include "src/gpu/tessellate/GrResolveLevelCounter.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 = GrDirectContext::MakeMock(&mockOptions, ctxOptions);
}
const GrDirectContext* mockContext() const { return fMockContext.get(); }
const GrCaps& caps() const override { return *fMockContext->priv().caps(); }
GrResourceProvider* resourceProvider() const override {
return fMockContext->priv().resourceProvider();
}
GrSmallPathAtlasMgr* smallPathAtlasManager() const override { return nullptr; }
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("FATAL: wanted %zu bytes of static vertex data; only have %zu.\n",
vertexSize * vertexCount, SK_ARRAY_COUNT(fStaticVertexData));
}
*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("FATAL: wanted %i static drawIndexedIndirect elements; only have %i.\n",
drawCount, staticBufferCount);
}
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<GrDirectContext> fMockContext;
SkPoint fStaticVertexData[(kNumCubicsInChalkboard + 2) * 8];
GrDrawIndexedIndirectCommand fStaticDrawIndexedIndirectData[32];
SkSTArenaAllocWithReset<1024 * 1024> fAllocator;
};
// This serves as a base class for benchmarking individual methods on GrPathTessellateOp.
class GrPathTessellateOp::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*, GrPathTessellateOp*) = 0;
GrPathTessellateOp fOp;
BenchmarkTarget fTarget;
SkString fName;
};
#define DEF_TESS_BENCH(NAME, PATH, MATRIX, TARGET, OP) \
class GrPathTessellateOp::TestingOnly_Benchmark::NAME \
: public GrPathTessellateOp::TestingOnly_Benchmark { \
public: \
NAME() : TestingOnly_Benchmark(#NAME, (PATH), (MATRIX)) {} \
void runBench(GrMeshDrawOp::Target* target, GrPathTessellateOp* op) override; \
}; \
DEF_BENCH( return new GrPathTessellateOp::TestingOnly_Benchmark::NAME(); ); \
void GrPathTessellateOp::TestingOnly_Benchmark::NAME::runBench( \
GrMeshDrawOp::Target* TARGET, GrPathTessellateOp* 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]);
}
}