0378307904
The static functionality for writing vertex buffers, constants, and utility functions that were in the StrokeTessellator and PathTessellator classes have been moved into Tessellation.h and a new FixedCountBufferUtils.h. The tessellator hierarchy has been moved into src/gpu/ops and all the v1 guards are removed since they were already solely v1 after the static functions were lifted out. The hierarchy and subclasses are preserved but have been combined into just StrokeTessellator.h and PathTessellator.h instead of separate files for the subclasses. All the rest of the little changes are updating references and includes. Bug: skia:13012 Change-Id: I90f2f53538349cf9ad1823a0c42fbdc772190a49 Reviewed-on: https://skia-review.googlesource.com/c/skia/+/523189 Reviewed-by: Robert Phillips <robertphillips@google.com> Commit-Queue: Michael Ludwig <michaelludwig@google.com>
346 lines
13 KiB
C++
346 lines
13 KiB
C++
/*
|
|
* Copyright 2019 Google LLC.
|
|
*
|
|
* Use of this source code is governed by a BSD-style license that can be
|
|
* found in the LICENSE file.
|
|
*/
|
|
|
|
#include "include/core/SkCanvas.h"
|
|
#include "samplecode/Sample.h"
|
|
#include "src/core/SkPathPriv.h"
|
|
|
|
#if SK_SUPPORT_GPU
|
|
|
|
#include "src/core/SkCanvasPriv.h"
|
|
#include "src/gpu/GrOpFlushState.h"
|
|
#include "src/gpu/GrRecordingContextPriv.h"
|
|
#include "src/gpu/ops/GrDrawOp.h"
|
|
#include "src/gpu/ops/GrSimpleMeshDrawOpHelper.h"
|
|
#include "src/gpu/ops/PathTessellator.h"
|
|
#include "src/gpu/ops/TessellationPathRenderer.h"
|
|
#include "src/gpu/tessellate/AffineMatrix.h"
|
|
#include "src/gpu/tessellate/MiddleOutPolygonTriangulator.h"
|
|
#include "src/gpu/tessellate/shaders/GrPathTessellationShader.h"
|
|
#include "src/gpu/v1/SurfaceDrawContext_v1.h"
|
|
|
|
namespace skgpu::v1 {
|
|
|
|
namespace {
|
|
|
|
enum class Mode {
|
|
kWedgeMiddleOut,
|
|
kCurveMiddleOut,
|
|
kWedgeTessellate,
|
|
kCurveTessellate
|
|
};
|
|
|
|
static const char* ModeName(Mode mode) {
|
|
switch (mode) {
|
|
case Mode::kWedgeMiddleOut:
|
|
return "MiddleOutShader (kWedges)";
|
|
case Mode::kCurveMiddleOut:
|
|
return "MiddleOutShader (kCurves)";
|
|
case Mode::kWedgeTessellate:
|
|
return "HardwareWedgeShader";
|
|
case Mode::kCurveTessellate:
|
|
return "HardwareCurveShader";
|
|
}
|
|
SkUNREACHABLE;
|
|
}
|
|
|
|
// Draws a path directly to the screen using a specific tessellator.
|
|
class SamplePathTessellatorOp : public GrDrawOp {
|
|
private:
|
|
DEFINE_OP_CLASS_ID
|
|
|
|
SamplePathTessellatorOp(const SkRect& drawBounds, const SkPath& path, const SkMatrix& m,
|
|
GrPipeline::InputFlags pipelineFlags, Mode mode)
|
|
: GrDrawOp(ClassID())
|
|
, fPath(path)
|
|
, fMatrix(m)
|
|
, fPipelineFlags(pipelineFlags)
|
|
, fMode(mode) {
|
|
this->setBounds(drawBounds, HasAABloat::kNo, IsHairline::kNo);
|
|
}
|
|
const char* name() const override { return "SamplePathTessellatorOp"; }
|
|
void visitProxies(const GrVisitProxyFunc&) const override {}
|
|
FixedFunctionFlags fixedFunctionFlags() const override {
|
|
return FixedFunctionFlags::kUsesHWAA;
|
|
}
|
|
GrProcessorSet::Analysis finalize(const GrCaps& caps, const GrAppliedClip* clip,
|
|
GrClampType clampType) override {
|
|
SkPMColor4f color;
|
|
return fProcessors.finalize(SK_PMColor4fWHITE, GrProcessorAnalysisCoverage::kNone, clip,
|
|
nullptr, caps, clampType, &color);
|
|
}
|
|
void onPrePrepare(GrRecordingContext*, const GrSurfaceProxyView&, GrAppliedClip*,
|
|
const GrDstProxyView&, GrXferBarrierFlags, GrLoadOp colorLoadOp) override {}
|
|
void onPrepare(GrOpFlushState* flushState) override {
|
|
constexpr static SkPMColor4f kCyan = {0,1,1,1};
|
|
auto alloc = flushState->allocator();
|
|
const SkMatrix& shaderMatrix = SkMatrix::I();
|
|
const SkMatrix& pathMatrix = fMatrix;
|
|
const GrCaps& caps = flushState->caps();
|
|
const GrShaderCaps& shaderCaps = *caps.shaderCaps();
|
|
int numVerbsToGetMiddleOut = 0;
|
|
int numVerbsToGetTessellation = caps.minPathVerbsForHwTessellation();
|
|
auto pipeline = GrSimpleMeshDrawOpHelper::CreatePipeline(flushState, std::move(fProcessors),
|
|
fPipelineFlags);
|
|
int numVerbs = (fMode == Mode::kWedgeMiddleOut || fMode == Mode::kCurveMiddleOut) ?
|
|
numVerbsToGetMiddleOut : numVerbsToGetTessellation;
|
|
auto* tessShader = GrPathTessellationShader::Make(alloc,
|
|
shaderMatrix,
|
|
kCyan,
|
|
numVerbs,
|
|
*pipeline,
|
|
fTessellator->patchAttribs(),
|
|
caps);
|
|
fProgram = GrTessellationShader::MakeProgram({alloc, flushState->writeView(),
|
|
flushState->usesMSAASurface(),
|
|
&flushState->dstProxyView(),
|
|
flushState->renderPassBarriers(),
|
|
GrLoadOp::kClear, &flushState->caps()},
|
|
tessShader,
|
|
pipeline,
|
|
&GrUserStencilSettings::kUnused);
|
|
|
|
|
|
int maxSegments = tessShader->maxTessellationSegments(*caps.shaderCaps());
|
|
PathTessellator::PathDrawList pathList{pathMatrix, fPath, kCyan};
|
|
|
|
if (fMode == Mode::kCurveTessellate || fMode == Mode::kCurveMiddleOut) {
|
|
// This emulates what PathStencilCoverOp does when using curves, except we include the
|
|
// middle-out triangles directly in the written patches for convenience (normally they
|
|
// use a simple triangle pipeline). But PathCurveTessellator only knows how to read
|
|
// extra triangles from BreadcrumbTriangleList, so build on from the middle-out stack.
|
|
SkArenaAlloc storage{256};
|
|
GrInnerFanTriangulator::BreadcrumbTriangleList triangles;
|
|
for (PathMiddleOutFanIter it(fPath); !it.done();) {
|
|
for (auto [p0, p1, p2] : it.nextStack()) {
|
|
triangles.append(&storage,
|
|
pathMatrix.mapPoint(p0),
|
|
pathMatrix.mapPoint(p1),
|
|
pathMatrix.mapPoint(p2),
|
|
/*winding=*/1);
|
|
}
|
|
}
|
|
|
|
auto* tess = PathCurveTessellator::Make(alloc, shaderCaps.infinitySupport());
|
|
tess->prepareWithTriangles(flushState, maxSegments, shaderMatrix, &triangles, pathList,
|
|
fPath.countVerbs(),tessShader->willUseTessellationShaders());
|
|
fTessellator = tess;
|
|
} else {
|
|
// This emulates what PathStencilCoverOp does when using wedges.
|
|
fTessellator = PathWedgeTessellator::Make(alloc, shaderCaps.infinitySupport());
|
|
fTessellator->prepare(flushState, maxSegments, shaderMatrix, pathList,
|
|
fPath.countVerbs(), tessShader->willUseTessellationShaders());
|
|
}
|
|
}
|
|
|
|
void onExecute(GrOpFlushState* flushState, const SkRect& chainBounds) override {
|
|
flushState->bindPipeline(*fProgram, chainBounds);
|
|
fTessellator->draw(flushState, fProgram->geomProc().willUseTessellationShaders());
|
|
}
|
|
|
|
const SkPath fPath;
|
|
const SkMatrix fMatrix;
|
|
const GrPipeline::InputFlags fPipelineFlags;
|
|
const Mode fMode;
|
|
PathTessellator* fTessellator = nullptr;
|
|
GrProgramInfo* fProgram;
|
|
GrProcessorSet fProcessors{SkBlendMode::kSrcOver};
|
|
|
|
friend class GrOp; // For ctor.
|
|
};
|
|
|
|
} // namespace
|
|
|
|
// This sample enables wireframe and visualizes the triangles generated by path tessellators.
|
|
class SamplePathTessellators : public Sample {
|
|
public:
|
|
SamplePathTessellators() {
|
|
#if 0
|
|
// For viewing middle-out triangulations of the inner fan.
|
|
fPath.moveTo(1, 0);
|
|
int numSides = 32 * 3;
|
|
for (int i = 1; i < numSides; ++i) {
|
|
float theta = 2*3.1415926535897932384626433832785 * i / numSides;
|
|
fPath.lineTo(std::cos(theta), std::sin(theta));
|
|
}
|
|
fPath.transform(SkMatrix::Scale(200, 200));
|
|
fPath.transform(SkMatrix::Translate(300, 300));
|
|
#else
|
|
fPath.moveTo(100, 500);
|
|
fPath.cubicTo(300, 400, -100, 300, 100, 200);
|
|
fPath.quadTo(250, 0, 400, 200);
|
|
fPath.conicTo(600, 350, 400, 500, fConicWeight);
|
|
fPath.close();
|
|
#endif
|
|
}
|
|
|
|
private:
|
|
void onDrawContent(SkCanvas*) override;
|
|
Sample::Click* onFindClickHandler(SkScalar x, SkScalar y, skui::ModifierKey) override;
|
|
bool onClick(Sample::Click*) override;
|
|
bool onChar(SkUnichar) override;
|
|
|
|
SkString name() override { return SkString("PathTessellators"); }
|
|
|
|
SkPath fPath;
|
|
GrPipeline::InputFlags fPipelineFlags = GrPipeline::InputFlags::kWireframe;
|
|
Mode fMode = Mode::kWedgeMiddleOut;
|
|
|
|
float fConicWeight = .5;
|
|
|
|
class Click;
|
|
};
|
|
|
|
void SamplePathTessellators::onDrawContent(SkCanvas* canvas) {
|
|
canvas->clear(SK_ColorBLACK);
|
|
|
|
auto ctx = canvas->recordingContext();
|
|
auto sdc = SkCanvasPriv::TopDeviceSurfaceDrawContext(canvas);
|
|
|
|
SkString error;
|
|
if (!sdc || !ctx) {
|
|
error = "GPU Only.";
|
|
} else if (!skgpu::v1::TessellationPathRenderer::IsSupported(*ctx->priv().caps())) {
|
|
error = "TessellationPathRenderer not supported.";
|
|
} else if (fMode >= Mode::kWedgeTessellate &&
|
|
!ctx->priv().caps()->shaderCaps()->tessellationSupport()) {
|
|
error.printf("%s requires hardware tessellation support.", ModeName(fMode));
|
|
}
|
|
if (!error.isEmpty()) {
|
|
canvas->clear(SK_ColorRED);
|
|
SkFont font(nullptr, 20);
|
|
SkPaint captionPaint;
|
|
captionPaint.setColor(SK_ColorWHITE);
|
|
canvas->drawString(error.c_str(), 10, 30, font, captionPaint);
|
|
return;
|
|
}
|
|
|
|
sdc->addDrawOp(GrOp::Make<SamplePathTessellatorOp>(ctx,
|
|
sdc->asRenderTargetProxy()->getBoundsRect(),
|
|
fPath, canvas->getTotalMatrix(),
|
|
fPipelineFlags, fMode));
|
|
|
|
// Draw the path points.
|
|
SkPaint pointsPaint;
|
|
pointsPaint.setColor(SK_ColorBLUE);
|
|
pointsPaint.setStrokeWidth(8);
|
|
SkPath devPath = fPath;
|
|
devPath.transform(canvas->getTotalMatrix());
|
|
{
|
|
SkAutoCanvasRestore acr(canvas, true);
|
|
canvas->setMatrix(SkMatrix::I());
|
|
SkString caption(ModeName(fMode));
|
|
caption.appendf(" (w=%g)", fConicWeight);
|
|
SkFont font(nullptr, 20);
|
|
SkPaint captionPaint;
|
|
captionPaint.setColor(SK_ColorWHITE);
|
|
canvas->drawString(caption, 10, 30, font, captionPaint);
|
|
canvas->drawPoints(SkCanvas::kPoints_PointMode, devPath.countPoints(),
|
|
SkPathPriv::PointData(devPath), pointsPaint);
|
|
}
|
|
}
|
|
|
|
class SamplePathTessellators::Click : public Sample::Click {
|
|
public:
|
|
Click(int ptIdx) : fPtIdx(ptIdx) {}
|
|
|
|
void doClick(SkPath* path) {
|
|
SkPoint pt = path->getPoint(fPtIdx);
|
|
SkPathPriv::UpdatePathPoint(path, fPtIdx, pt + fCurr - fPrev);
|
|
}
|
|
|
|
private:
|
|
int fPtIdx;
|
|
};
|
|
|
|
Sample::Click* SamplePathTessellators::onFindClickHandler(SkScalar x, SkScalar y,
|
|
skui::ModifierKey) {
|
|
const SkPoint* pts = SkPathPriv::PointData(fPath);
|
|
float fuzz = 30;
|
|
for (int i = 0; i < fPath.countPoints(); ++i) {
|
|
if (fabs(x - pts[i].x()) < fuzz && fabsf(y - pts[i].y()) < fuzz) {
|
|
return new Click(i);
|
|
}
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
bool SamplePathTessellators::onClick(Sample::Click* click) {
|
|
Click* myClick = (Click*)click;
|
|
myClick->doClick(&fPath);
|
|
return true;
|
|
}
|
|
|
|
static SkPath update_weight(const SkPath& path, float w) {
|
|
SkPath path_;
|
|
for (auto [verb, pts, _] : SkPathPriv::Iterate(path)) {
|
|
switch (verb) {
|
|
case SkPathVerb::kMove:
|
|
path_.moveTo(pts[0]);
|
|
break;
|
|
case SkPathVerb::kLine:
|
|
path_.lineTo(pts[1]);
|
|
break;
|
|
case SkPathVerb::kQuad:
|
|
path_.quadTo(pts[1], pts[2]);
|
|
break;
|
|
case SkPathVerb::kCubic:
|
|
path_.cubicTo(pts[1], pts[2], pts[3]);
|
|
break;
|
|
case SkPathVerb::kConic:
|
|
path_.conicTo(pts[1], pts[2], (w != 1) ? w : .99f);
|
|
break;
|
|
case SkPathVerb::kClose:
|
|
break;
|
|
}
|
|
}
|
|
return path_;
|
|
}
|
|
|
|
bool SamplePathTessellators::onChar(SkUnichar unichar) {
|
|
switch (unichar) {
|
|
case 'w':
|
|
fPipelineFlags = (GrPipeline::InputFlags)(
|
|
(int)fPipelineFlags ^ (int)GrPipeline::InputFlags::kWireframe);
|
|
return true;
|
|
case 'D': {
|
|
fPath.dump();
|
|
return true;
|
|
}
|
|
case '+':
|
|
fConicWeight *= 2;
|
|
fPath = update_weight(fPath, fConicWeight);
|
|
return true;
|
|
case '=':
|
|
fConicWeight *= 5/4.f;
|
|
fPath = update_weight(fPath, fConicWeight);
|
|
return true;
|
|
case '_':
|
|
fConicWeight *= .5f;
|
|
fPath = update_weight(fPath, fConicWeight);
|
|
return true;
|
|
case '-':
|
|
fConicWeight *= 4/5.f;
|
|
fPath = update_weight(fPath, fConicWeight);
|
|
return true;
|
|
case '1':
|
|
case '2':
|
|
case '3':
|
|
case '4':
|
|
fMode = (Mode)(unichar - '1');
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
Sample* MakeTessellatedPathSample() { return new SamplePathTessellators; }
|
|
static SampleRegistry gTessellatedPathSample(MakeTessellatedPathSample);
|
|
|
|
} // namespace skgpu::v1
|
|
|
|
#endif // SK_SUPPORT_GPU
|