skia2/bench/TessellateBench.cpp
Robert Phillips 83c38a8fd6 Allow uniqueKey invalidation messages to reach the thread safe cache
With the addition of vertex data to the thread safe cache we also have
to handle the case where a given SkPath becomes inaccessible and
proactively invalidate the matching entry.

Bug: 1108408
Change-Id: Id11ce2aa10517f7c0772a253634d3c0d13e13460
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/330261
Reviewed-by: Adlai Holler <adlai@google.com>
Commit-Queue: Robert Phillips <robertphillips@google.com>
2020-10-29 12:36:13 +00:00

306 lines
13 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/GrDirectContextPriv.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/GrStrokePatchBuilder.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(); }
GrThreadSafeCache* threadSafeCache() const override {
return fMockContext->priv().threadSafeCache();
}
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, sizeof(fStaticVertexData));
}
*startVertex = 0;
return fStaticVertexData;
}
void* makeVertexSpaceAtLeast(size_t vertexSize, int minVertexCount, int fallbackVertexCount,
sk_sp<const GrBuffer>*, int* startVertex,
int* actualVertexCount) override {
if (vertexSize * minVertexCount > sizeof(fStaticVertexData)) {
SK_ABORT("FATAL: wanted %zu bytes of static vertex data; only have %zu.\n",
vertexSize * minVertexCount, sizeof(fStaticVertexData));
}
*startVertex = 0;
*actualVertexCount = sizeof(fStaticVertexData) / vertexSize;
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(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(GrXferBarrierFlags renderPassBarriers() 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.fTriangleVertexCount = 0;
fOp.fPipelineForStencils = nullptr;
fOp.fPipelineForFills = nullptr;
fOp.fStencilTrianglesProgram = nullptr;
fOp.fFillTrianglesProgram = nullptr;
fOp.fCubicBuffer.reset();
fOp.fCubicVertexCount = 0;
// Make fStencilCubicsProgram non-null to keep assertions happy.
fOp.fStencilCubicsProgram = (GrProgramInfo*)-1;
fOp.fFillPathProgram = nullptr;
this->runBench(&fTarget, &fOp);
fTarget.resetAllocator();
}
}
virtual void runBench(GrMeshDrawOp::Target*, GrPathTessellateOp*) = 0;
GrPathTessellateOp fOp;
BenchmarkTarget fTarget;
SkString fName;
};
#define DEF_PATH_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_PATH_TESS_BENCH(prepareMiddleOutStencilGeometry, make_cubic_path(), SkMatrix::I(), target, op) {
// Make fStencilTrianglesProgram non-null so we benchmark the tessellation path with separate
// triangles.
op->fStencilTrianglesProgram = (GrProgramInfo*)-1;
op->prepareMiddleOutTrianglesAndCubics(target);
}
DEF_PATH_TESS_BENCH(prepareMiddleOutStencilGeometry_indirect, make_cubic_path(), SkMatrix::I(),
target, op) {
GrResolveLevelCounter resolveLevelCounter;
op->prepareMiddleOutTrianglesAndCubics(target, &resolveLevelCounter);
}
DEF_PATH_TESS_BENCH(prepareIndirectOuterCubics, make_cubic_path(), SkMatrix::I(), target, op) {
GrResolveLevelCounter resolveLevelCounter;
resolveLevelCounter.reset(op->fPath, SkMatrix::I(), 4);
op->prepareIndirectOuterCubics(target, resolveLevelCounter);
}
DEF_PATH_TESS_BENCH(prepareTessellatedOuterCubics, make_cubic_path(), SkMatrix::I(), target, op) {
op->prepareTessellatedOuterCubics(target, kNumCubicsInChalkboard);
}
DEF_PATH_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_PATH_TESS_BENCH(wangs_formula_cubic_log2, make_cubic_path(), SkMatrix::I(), target, op) {
benchmark_wangs_formula_cubic_log2(op->fViewMatrix, op->fPath);
}
DEF_PATH_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_PATH_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_PATH_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]);
}
}
class StrokePatchBuilderBench : public Benchmark {
const char* onGetName() override { return "tessellate_StrokePatchBuilder"; }
bool isSuitableFor(Backend backend) final { return backend == kNonRendering_Backend; }
void onDelayedSetup() override {
fPath.reset().moveTo(0, 0);
for (int i = 0; i < kNumCubicsInChalkboard/2; ++i) {
fPath.cubicTo(100, 0, 0, 100, 100, 100);
fPath.cubicTo(100, 0, 0, 100, 0, 0);
}
fStrokeRec.setStrokeStyle(8);
fStrokeRec.setStrokeParams(SkPaint::kButt_Cap, SkPaint::kMiter_Join, 4);
}
void onDraw(int loops, SkCanvas*) final {
if (!fTarget.mockContext()) {
SkDebugf("ERROR: could not create mock context.");
return;
}
for (int i = 0; i < loops; ++i) {
fPatchChunks.reset();
// TODO: Combine GrStrokePatchBuilder with GrStrokeTessellateOp.
float parametricIntolerance = GrTessellationPathRenderer::kLinearizationIntolerance;
float numRadialSegmentsPerRadian = .5f /
acosf(1 - 2/(parametricIntolerance * fStrokeRec.getWidth()));
GrStrokePatchBuilder builder(&fTarget, &fPatchChunks, fStrokeRec, parametricIntolerance,
numRadialSegmentsPerRadian, fPath.countVerbs());
builder.addPath(fPath);
}
}
BenchmarkTarget fTarget;
SkPath fPath;
SkStrokeRec fStrokeRec = SkStrokeRec(SkStrokeRec::kFill_InitStyle);
SkSTArray<8, GrStrokePatchBuilder::PatchChunk> fPatchChunks;
};
DEF_BENCH( return new StrokePatchBuilderBench(); )