Yank GrStrokeTessellationShader out of StrokeTessellators

Bug: skia:12524
Change-Id: I208f257bca46c8fcb15cf58baea4c3e058310327
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/471216
Commit-Queue: Chris Dalton <csmartdalton@google.com>
Reviewed-by: Greg Daniel <egdaniel@google.com>
This commit is contained in:
Chris Dalton 2021-11-12 21:18:47 -07:00 committed by SkCQ
parent b339d59fdb
commit 9aca7f7dd0
16 changed files with 249 additions and 253 deletions

View File

@ -244,30 +244,14 @@ DEF_PATH_TESS_BENCH(middle_out_triangulation,
}
using PathStrokeList = StrokeTessellator::PathStrokeList;
using MakeTessellatorFn = std::unique_ptr<StrokeTessellator>(*)(PatchAttribs,
const GrShaderCaps&,
const SkMatrix&,
PathStrokeList*,
std::array<float, 2>);
using MakeTessellatorFn = std::unique_ptr<StrokeTessellator>(*)(PatchAttribs);
static std::unique_ptr<StrokeTessellator> make_hw_tessellator(
PatchAttribs attribs,
const GrShaderCaps& shaderCaps,
const SkMatrix& viewMatrix,
PathStrokeList* pathStrokeList,
std::array<float,2> matrixMinMaxScales) {
return std::make_unique<StrokeHardwareTessellator>(shaderCaps, attribs, viewMatrix,
pathStrokeList, matrixMinMaxScales);
static std::unique_ptr<StrokeTessellator> make_hw_tessellator(PatchAttribs attribs) {
return std::make_unique<StrokeHardwareTessellator>(attribs);
}
static std::unique_ptr<StrokeTessellator> make_fixed_count_tessellator(
PatchAttribs attribs,
const GrShaderCaps& shaderCaps,
const SkMatrix& viewMatrix,
PathStrokeList* pathStrokeList,
std::array<float, 2> matrixMinMaxScales) {
return std::make_unique<StrokeFixedCountTessellator>(shaderCaps, attribs, viewMatrix,
pathStrokeList, matrixMinMaxScales);
static std::unique_ptr<StrokeTessellator> make_fixed_count_tessellator(PatchAttribs attribs) {
return std::make_unique<StrokeFixedCountTessellator>(attribs);
}
using MakePathStrokesFn = std::vector<PathStrokeList>(*)();
@ -367,14 +351,16 @@ private:
fTotalVerbCount += fPathStrokes[i].fPath.countVerbs();
}
fTessellator = fMakeTessellatorFn(fPatchAttribs, *fTarget->caps().shaderCaps(),
SkMatrix::Scale(fMatrixScale, fMatrixScale),
fPathStrokes.data(), {fMatrixScale, fMatrixScale});
fTessellator = fMakeTessellatorFn(fPatchAttribs);
}
void onDraw(int loops, SkCanvas*) final {
for (int i = 0; i < loops; ++i) {
fTessellator->prepare(fTarget.get(), fTotalVerbCount);
fTessellator->prepare(fTarget.get(),
SkMatrix::Scale(fMatrixScale, fMatrixScale),
{fMatrixScale, fMatrixScale},
fPathStrokes.data(),
fTotalVerbCount);
fTarget->resetAllocator();
}
}

View File

@ -7,15 +7,14 @@
#include "src/gpu/ops/StrokeTessellateOp.h"
#include "src/core/SkMathPriv.h"
#include "src/core/SkPathPriv.h"
#include "src/gpu/GrAppliedClip.h"
#include "src/gpu/GrOpFlushState.h"
#include "src/gpu/GrRecordingContextPriv.h"
#include "src/gpu/tessellate/StrokeFixedCountTessellator.h"
#include "src/gpu/tessellate/StrokeHardwareTessellator.h"
#include "src/gpu/tessellate/shaders/GrTessellationShader.h"
using DynamicStroke = GrStrokeTessellationShader::DynamicStroke;
#include "src/gpu/tessellate/shaders/GrStrokeTessellationShader.h"
namespace {
@ -114,7 +113,7 @@ GrOp::CombineResult StrokeTessellateOp::onCombineIfPossible(GrOp* grOp, SkArenaA
auto combinedAttribs = fPatchAttribs | op->fPatchAttribs;
if (!(combinedAttribs & PatchAttribs::kStrokeParams) &&
!DynamicStroke::StrokesHaveEqualDynamicState(this->headStroke(), op->headStroke())) {
!StrokeParams::StrokesHaveEqualParams(this->headStroke(), op->headStroke())) {
// The paths have different stroke properties. We will need to enable dynamic stroke if we
// still decide to combine them.
if (this->headStroke().isHairlineStyle()) {
@ -186,39 +185,42 @@ void StrokeTessellateOp::prePrepareTessellator(GrTessellationShader::ProgramArgs
const GrCaps& caps = *args.fCaps;
SkArenaAlloc* arena = args.fArena;
std::array<float, 2> matrixMinMaxScales;
if (!fViewMatrix.getMinMaxScales(matrixMinMaxScales.data())) {
matrixMinMaxScales.fill(1);
}
auto* pipeline = GrTessellationShader::MakePipeline(args, fAAType, std::move(clip),
std::move(fProcessors));
GrStrokeTessellationShader::Mode shaderMode;
int maxParametricSegments_log2;
if (can_use_hardware_tessellation(fTotalCombinedVerbCnt, *pipeline, caps)) {
// Only use hardware tessellation if we're drawing a somewhat large number of verbs.
// Otherwise we seem to be better off using instanced draws.
fTessellator = arena->make<StrokeHardwareTessellator>(*caps.shaderCaps(),
fPatchAttribs,
fViewMatrix,
&fPathStrokeList,
matrixMinMaxScales);
fTessellator = arena->make<StrokeHardwareTessellator>(fPatchAttribs);
shaderMode = GrStrokeTessellationShader::Mode::kHardwareTessellation;
// This sets a limit on the number of binary search iterations inside the shader, so we
// round up to the next log2 to guarantee it makes enough.
maxParametricSegments_log2 = SkNextLog2(caps.shaderCaps()->maxTessellationSegments());
} else {
fTessellator = arena->make<StrokeFixedCountTessellator>(*caps.shaderCaps(),
fPatchAttribs,
fViewMatrix,
&fPathStrokeList,
matrixMinMaxScales);
fTessellator = arena->make<StrokeFixedCountTessellator>(fPatchAttribs);
shaderMode = GrStrokeTessellationShader::Mode::kFixedCount;
maxParametricSegments_log2 = StrokeFixedCountTessellator::kMaxParametricSegments_log2;
}
fTessellationShader = args.fArena->make<GrStrokeTessellationShader>(*caps.shaderCaps(),
shaderMode,
fPatchAttribs,
fViewMatrix,
this->headStroke(),
this->headColor(),
maxParametricSegments_log2);
auto fillStencil = &GrUserStencilSettings::kUnused;
if (fNeedsStencil) {
fStencilProgram = GrTessellationShader::MakeProgram(args, fTessellator->shader(), pipeline,
fStencilProgram = GrTessellationShader::MakeProgram(args, fTessellationShader, pipeline,
&kMarkStencil);
fillStencil = &kTestAndResetStencil;
args.fXferBarrierFlags = GrXferBarrierFlags::kNone;
}
fFillProgram = GrTessellationShader::MakeProgram(args, fTessellator->shader(), pipeline,
fFillProgram = GrTessellationShader::MakeProgram(args, fTessellationShader, pipeline,
fillStencil);
}
@ -249,7 +251,18 @@ void StrokeTessellateOp::onPrepare(GrOpFlushState* flushState) {
&flushState->caps()}, flushState->detachAppliedClip());
}
SkASSERT(fTessellator);
fTessellator->prepare(flushState, fTotalCombinedVerbCnt);
std::array<float, 2> matrixMinMaxScales;
if (!fViewMatrix.getMinMaxScales(matrixMinMaxScales.data())) {
matrixMinMaxScales.fill(1);
}
int fixedEdgeCount = fTessellator->prepare(flushState,
fViewMatrix,
matrixMinMaxScales,
&fPathStrokeList,
fTotalCombinedVerbCnt);
if (!fTessellationShader->willUseTessellationShaders()) {
fTessellationShader->setFixedCountNumTotalEdges(fixedEdgeCount);
}
}
void StrokeTessellateOp::onExecute(GrOpFlushState* flushState, const SkRect& chainBounds) {

View File

@ -14,6 +14,7 @@
#include "src/gpu/tessellate/shaders/GrTessellationShader.h"
class GrRecordingContext;
class GrStrokeTessellationShader;
namespace skgpu::v1 {
@ -73,6 +74,7 @@ private:
bool fNeedsStencil;
StrokeTessellator* fTessellator = nullptr;
GrStrokeTessellationShader* fTessellationShader;
const GrProgramInfo* fStencilProgram = nullptr; // Only used if the stroke has transparency.
const GrProgramInfo* fFillProgram = nullptr;
};

View File

@ -20,6 +20,7 @@ PatchWriter::PatchWriter(GrMeshDrawTarget* target,
: PatchWriter(target,
&tessellator->fVertexChunkArray,
tessellator->fAttribs,
sizeof(SkPoint) * 4 + PatchAttribsStride(tessellator->fAttribs),
initialPatchAllocCount) {
}
#endif

View File

@ -25,9 +25,10 @@ public:
PatchWriter(GrMeshDrawTarget* target,
GrVertexChunkArray* vertexChunkArray,
PatchAttribs attribs,
size_t patchStride,
int initialAllocCount)
: fPatchAttribs(attribs)
, fChunker(target, vertexChunkArray, PatchStride(fPatchAttribs), initialAllocCount) {
, fChunker(target, vertexChunkArray, patchStride, initialAllocCount) {
}
#if SK_GPU_V1

View File

@ -13,6 +13,7 @@
#include "src/gpu/geometry/GrPathUtils.h"
#include "src/gpu/tessellate/StrokeIterator.h"
#include "src/gpu/tessellate/WangsFormula.h"
#include "src/gpu/tessellate/shaders/GrTessellationShader.h"
#if SK_GPU_V1
#include "src/gpu/GrOpFlushState.h"
@ -22,8 +23,8 @@ namespace skgpu {
namespace {
constexpr static float kMaxParametricSegments_pow4 = 32*32*32*32; // 32^4
constexpr static int8_t kMaxParametricSegments_log2 = 5; // log2(32)
constexpr static float kMaxParametricSegments_pow4 =
StrokeFixedCountTessellator::kMaxParametricSegments_pow4;
// Writes out strokes to the given instance chunk array, chopping if necessary so that all instances
// require 32 parametric segments or less. (We don't consider radial segments here. The tessellator
@ -215,13 +216,13 @@ private:
bool fHasLastControlPoint = false;
// Values for the current dynamic state (if any) that will get written out with each instance.
GrStrokeTessellationShader::DynamicStroke fDynamicStroke;
StrokeParams fDynamicStroke;
GrVertexColor fDynamicColor;
};
// Returns the worst-case number of edges we will need in order to draw a join of the given type.
int worst_case_edges_in_join(SkPaint::Join joinType, float numRadialSegmentsPerRadian) {
int numEdges = GrStrokeTessellationShader::NumFixedEdgesInJoin(joinType);
int numEdges = StrokeFixedCountTessellator::NumFixedEdgesInJoin(joinType);
if (joinType == SkPaint::kRound_Join) {
// For round joins we need to count the radial edges on our own. Account for a worst-case
// join of 180 degrees (SK_ScalarPI radians).
@ -233,23 +234,13 @@ int worst_case_edges_in_join(SkPaint::Join joinType, float numRadialSegmentsPerR
} // namespace
StrokeFixedCountTessellator::StrokeFixedCountTessellator(const GrShaderCaps& shaderCaps,
PatchAttribs attribs,
const SkMatrix& viewMatrix,
PathStrokeList* pathStrokeList,
std::array<float,2> matrixMinMaxScales)
: StrokeTessellator(shaderCaps,
GrStrokeTessellationShader::Mode::kFixedCount,
attribs,
kMaxParametricSegments_log2,
viewMatrix,
pathStrokeList,
matrixMinMaxScales) {
}
GR_DECLARE_STATIC_UNIQUE_KEY(gVertexIDFallbackBufferKey);
void StrokeFixedCountTessellator::prepare(GrMeshDrawTarget* target, int totalCombinedVerbCnt) {
int StrokeFixedCountTessellator::prepare(GrMeshDrawTarget* target,
const SkMatrix& shaderMatrix,
std::array<float,2> matrixMinMaxScales,
PathStrokeList* pathStrokeList,
int totalCombinedVerbCnt) {
int maxEdgesInJoin = 0;
float maxRadialSegmentsPerRadian = 0;
@ -261,16 +252,16 @@ void StrokeFixedCountTessellator::prepare(GrMeshDrawTarget* target, int totalCom
int minInstancesPerChunk = strokePreallocCount + capPreallocCount;
InstanceWriter instanceWriter(fAttribs,
target,
fMatrixMinMaxScales[1],
fShader.viewMatrix(),
matrixMinMaxScales[1],
shaderMatrix,
&fInstanceChunks,
fShader.instanceStride(),
sizeof(SkPoint) * 5 + PatchAttribsStride(fAttribs),
minInstancesPerChunk);
if (!fShader.hasDynamicStroke()) {
if (!(fAttribs & PatchAttribs::kStrokeParams)) {
// Strokes are static. Calculate tolerances once.
const SkStrokeRec& stroke = fPathStrokeList->fStroke;
float localStrokeWidth = StrokeTolerances::GetLocalStrokeWidth(fMatrixMinMaxScales.data(),
const SkStrokeRec& stroke = pathStrokeList->fStroke;
float localStrokeWidth = StrokeTolerances::GetLocalStrokeWidth(matrixMinMaxScales.data(),
stroke.getWidth());
float numRadialSegmentsPerRadian = StrokeTolerances::CalcNumRadialSegmentsPerRadian(
instanceWriter.parametricPrecision(), localStrokeWidth);
@ -282,9 +273,9 @@ void StrokeFixedCountTessellator::prepare(GrMeshDrawTarget* target, int totalCom
// have dynamic stroke.
StrokeToleranceBuffer toleranceBuffer(instanceWriter.parametricPrecision());
for (PathStrokeList* pathStroke = fPathStrokeList; pathStroke; pathStroke = pathStroke->fNext) {
for (PathStrokeList* pathStroke = pathStrokeList; pathStroke; pathStroke = pathStroke->fNext) {
const SkStrokeRec& stroke = pathStroke->fStroke;
if (fShader.hasDynamicStroke()) {
if (fAttribs & PatchAttribs::kStrokeParams) {
// Strokes are dynamic. Calculate tolerances every time.
float numRadialSegmentsPerRadian =
toleranceBuffer.fetchRadialSegmentsPerRadian(pathStroke);
@ -295,10 +286,10 @@ void StrokeFixedCountTessellator::prepare(GrMeshDrawTarget* target, int totalCom
maxRadialSegmentsPerRadian);
instanceWriter.updateDynamicStroke(stroke);
}
if (fShader.hasDynamicColor()) {
if (fAttribs & PatchAttribs::kColor) {
instanceWriter.updateDynamicColor(pathStroke->fColor);
}
StrokeIterator strokeIter(pathStroke->fPath, &pathStroke->fStroke, &fShader.viewMatrix());
StrokeIterator strokeIter(pathStroke->fPath, &pathStroke->fStroke, &shaderMatrix);
while (strokeIter.next()) {
const SkPoint* p = strokeIter.pts();
switch (strokeIter.verb()) {
@ -410,18 +401,18 @@ void StrokeFixedCountTessellator::prepare(GrMeshDrawTarget* target, int totalCom
// number of edges in an instance is the sum of edges from the join and stroke sections both.
// NOTE: The final join edge and the first stroke edge are co-located, however we still need to
// emit both because the join's edge is half-width and the stroke's is full-width.
int fixedEdgeCount = maxEdgesInJoin + maxEdgesInStroke;
fFixedEdgeCount = maxEdgesInJoin + maxEdgesInStroke;
// Don't draw more vertices than can be indexed by a signed short. We just have to draw the line
// somewhere and this seems reasonable enough. (There are two vertices per edge, so 2^14 edges
// make 2^15 vertices.)
fixedEdgeCount = std::min(fixedEdgeCount, (1 << 14) - 1);
fFixedEdgeCount = std::min(fFixedEdgeCount, (1 << 14) - 1);
if (!target->caps().shaderCaps()->vertexIDSupport()) {
// Our shader won't be able to use sk_VertexID. Bind a fallback vertex buffer with the IDs
// in it instead.
constexpr static int kMaxVerticesInFallbackBuffer = 2048;
fixedEdgeCount = std::min(fixedEdgeCount, kMaxVerticesInFallbackBuffer/2);
fFixedEdgeCount = std::min(fFixedEdgeCount, kMaxVerticesInFallbackBuffer/2);
GR_DEFINE_STATIC_UNIQUE_KEY(gVertexIDFallbackBufferKey);
@ -429,16 +420,24 @@ void StrokeFixedCountTessellator::prepare(GrMeshDrawTarget* target, int totalCom
GrGpuBufferType::kVertex,
kMaxVerticesInFallbackBuffer * sizeof(float),
gVertexIDFallbackBufferKey,
GrStrokeTessellationShader::InitializeVertexIDFallbackBuffer);
InitializeVertexIDFallbackBuffer);
}
fShader.setFixedCountNumTotalEdges(fixedEdgeCount);
fFixedVertexCount = fixedEdgeCount * 2;
return fFixedEdgeCount;
}
void StrokeFixedCountTessellator::InitializeVertexIDFallbackBuffer(VertexWriter vertexWriter,
size_t bufferSize) {
SkASSERT(bufferSize % (sizeof(float) * 2) == 0);
int edgeCount = bufferSize / (sizeof(float) * 2);
for (int i = 0; i < edgeCount; ++i) {
vertexWriter << (float)i << (float)-i;
}
}
#if SK_GPU_V1
void StrokeFixedCountTessellator::draw(GrOpFlushState* flushState) const {
if (fInstanceChunks.empty() || fFixedVertexCount <= 0) {
if (fInstanceChunks.empty() || fFixedEdgeCount <= 0) {
return;
}
if (!flushState->caps().shaderCaps()->vertexIDSupport() &&
@ -447,7 +446,10 @@ void StrokeFixedCountTessellator::draw(GrOpFlushState* flushState) const {
}
for (const auto& instanceChunk : fInstanceChunks) {
flushState->bindBuffers(nullptr, instanceChunk.fBuffer, fVertexBufferIfNoIDSupport);
flushState->drawInstanced(instanceChunk.fCount, instanceChunk.fBase, fFixedVertexCount, 0);
flushState->drawInstanced(instanceChunk.fCount,
instanceChunk.fBase,
fFixedEdgeCount * 2,
0);
}
}
#endif

View File

@ -8,6 +8,7 @@
#ifndef tessellate_StrokeFixedCountTessellator_DEFINED
#define tessellate_StrokeFixedCountTessellator_DEFINED
#include "src/gpu/GrGpuBuffer.h"
#include "src/gpu/GrVertexChunkArray.h"
#include "src/gpu/tessellate/StrokeTessellator.h"
@ -17,20 +18,57 @@ namespace skgpu {
// instance are emitted as degenerate triangles.
class StrokeFixedCountTessellator : public StrokeTessellator {
public:
StrokeFixedCountTessellator(const GrShaderCaps&,
PatchAttribs,
const SkMatrix&,
PathStrokeList*,
std::array<float, 2> matrixMinMaxScales);
constexpr static float kMaxParametricSegments_pow4 = 32*32*32*32; // 32^4
constexpr static int8_t kMaxParametricSegments_log2 = 5; // log2(32)
StrokeFixedCountTessellator(PatchAttribs attribs) : StrokeTessellator(attribs) {}
int prepare(GrMeshDrawTarget*,
const SkMatrix& shaderMatrix,
std::array<float,2> matrixMinMaxScales,
PathStrokeList*,
int totalCombinedVerbCnt) override;
void prepare(GrMeshDrawTarget*, int totalCombinedVerbCnt) override;
#if SK_GPU_V1
void draw(GrOpFlushState*) const override;
#endif
// Initializes the fallback vertex buffer that should be bound when sk_VertexID is not
// supported. Each vertex is a single float and each edge is composed of two vertices, so the
// desired edge count in the buffer is presumed to be "bufferSize / (sizeof(float) * 2)". The
// caller cannot draw more vertices than edgeCount * 2.
static void InitializeVertexIDFallbackBuffer(VertexWriter vertexWriter, size_t bufferSize);
// Returns the fixed number of edges that are always emitted with the given join type. If the
// join is round, the caller needs to account for the additional radial edges on their own.
// Specifically, each join always emits:
//
// * Two colocated edges at the beginning (a full-width edge to seam with the preceding stroke
// and a half-width edge to begin the join).
//
// * An extra edge in the middle for miter joins, or else a variable number of radial edges
// for round joins (the caller is responsible for counting radial edges from round joins).
//
// * A half-width edge at the end of the join that will be colocated with the first
// (full-width) edge of the stroke.
//
constexpr static int NumFixedEdgesInJoin(SkPaint::Join joinType) {
switch (joinType) {
case SkPaint::kMiter_Join:
return 4;
case SkPaint::kRound_Join:
// The caller is responsible for counting the variable number of middle, radial
// segments on round joins.
[[fallthrough]];
case SkPaint::kBevel_Join:
return 3;
}
SkUNREACHABLE;
}
private:
GrVertexChunkArray fInstanceChunks;
int fFixedVertexCount = 0;
int fFixedEdgeCount = 0;
// Only used if sk_VertexID is not supported.
sk_sp<const GrGpuBuffer> fVertexBufferIfNoIDSupport;

View File

@ -7,12 +7,12 @@
#include "src/gpu/tessellate/StrokeHardwareTessellator.h"
#include "src/core/SkMathPriv.h"
#include "src/core/SkPathPriv.h"
#include "src/gpu/GrMeshDrawTarget.h"
#include "src/gpu/GrRecordingContextPriv.h"
#include "src/gpu/geometry/GrPathUtils.h"
#include "src/gpu/tessellate/WangsFormula.h"
#include "src/gpu/tessellate/shaders/GrTessellationShader.h"
#if SK_GPU_V1
#include "src/gpu/GrOpFlushState.h"
@ -657,7 +657,7 @@ private:
SkPoint fLastControlPoint;
// Values for the current dynamic state (if any) that will get written out with each patch.
GrStrokeTessellationShader::DynamicStroke fDynamicStroke;
StrokeParams fDynamicStroke;
GrVertexColor fDynamicColor;
};
@ -696,34 +696,29 @@ SK_ALWAYS_INLINE bool cubic_has_cusp(const SkPoint p[4]) {
} // namespace
StrokeHardwareTessellator::StrokeHardwareTessellator(const GrShaderCaps& shaderCaps,
PatchAttribs attribs,
const SkMatrix& viewMatrix,
PathStrokeList* pathStrokeList,
std::array<float,2> matrixMinMaxScales)
: StrokeTessellator(shaderCaps,
GrStrokeTessellationShader::Mode::kHardwareTessellation,
attribs,
SkNextLog2(shaderCaps.maxTessellationSegments()),
viewMatrix,
pathStrokeList,
matrixMinMaxScales) {
}
void StrokeHardwareTessellator::prepare(GrMeshDrawTarget* target, int totalCombinedVerbCnt) {
int StrokeHardwareTessellator::prepare(GrMeshDrawTarget* target,
const SkMatrix& shaderMatrix,
std::array<float,2> matrixMinMaxScales,
PathStrokeList* pathStrokeList,
int totalCombinedVerbCnt) {
using JoinType = PatchWriter::JoinType;
// Over-allocate enough patches for 1 in 4 strokes to chop and for 8 extra caps.
int strokePreallocCount = totalCombinedVerbCnt * 5/4;
int capPreallocCount = 8;
int minPatchesPerChunk = strokePreallocCount + capPreallocCount;
PatchWriter patchWriter(fAttribs, target, fShader.viewMatrix(), fMatrixMinMaxScales[1],
&fPatchChunks, fShader.vertexStride(), minPatchesPerChunk);
PatchWriter patchWriter(fAttribs,
target,
shaderMatrix,
matrixMinMaxScales[1],
&fPatchChunks,
sizeof(SkPoint) * 5 + PatchAttribsStride(fAttribs),
minPatchesPerChunk);
if (!fShader.hasDynamicStroke()) {
if (!(fAttribs & PatchAttribs::kStrokeParams)) {
// Strokes are static. Calculate tolerances once.
const SkStrokeRec& stroke = fPathStrokeList->fStroke;
float localStrokeWidth = StrokeTolerances::GetLocalStrokeWidth(fMatrixMinMaxScales.data(),
const SkStrokeRec& stroke = pathStrokeList->fStroke;
float localStrokeWidth = StrokeTolerances::GetLocalStrokeWidth(matrixMinMaxScales.data(),
stroke.getWidth());
float numRadialSegmentsPerRadian = StrokeTolerances::CalcNumRadialSegmentsPerRadian(
patchWriter.parametricPrecision(), localStrokeWidth);
@ -734,15 +729,15 @@ void StrokeHardwareTessellator::prepare(GrMeshDrawTarget* target, int totalCombi
// have dynamic strokes.
StrokeToleranceBuffer toleranceBuffer(patchWriter.parametricPrecision());
for (PathStrokeList* pathStroke = fPathStrokeList; pathStroke; pathStroke = pathStroke->fNext) {
for (PathStrokeList* pathStroke = pathStrokeList; pathStroke; pathStroke = pathStroke->fNext) {
const SkStrokeRec& stroke = pathStroke->fStroke;
if (fShader.hasDynamicStroke()) {
if (fAttribs & PatchAttribs::kStrokeParams) {
// Strokes are dynamic. Update tolerances with every new stroke.
patchWriter.updateTolerances(toleranceBuffer.fetchRadialSegmentsPerRadian(pathStroke),
stroke.getJoin());
patchWriter.updateDynamicStroke(stroke);
}
if (fShader.hasDynamicColor()) {
if (fAttribs & PatchAttribs::kColor) {
patchWriter.updateDynamicColor(pathStroke->fColor);
}
@ -758,13 +753,13 @@ void StrokeHardwareTessellator::prepare(GrMeshDrawTarget* target, int totalCombi
// "A subpath ... consisting of a single moveto shall not be stroked."
// https://www.w3.org/TR/SVG11/painting.html#StrokeProperties
if (!contourIsEmpty) {
patchWriter.writeCaps(p[-1], fShader.viewMatrix(), stroke);
patchWriter.writeCaps(p[-1], shaderMatrix, stroke);
}
patchWriter.moveTo(p[0]);
contourIsEmpty = true;
continue;
case SkPathVerb::kClose:
patchWriter.writeClose(p[0], fShader.viewMatrix(), stroke);
patchWriter.writeClose(p[0], shaderMatrix, stroke);
contourIsEmpty = true;
continue;
case SkPathVerb::kLine:
@ -886,9 +881,10 @@ void StrokeHardwareTessellator::prepare(GrMeshDrawTarget* target, int totalCombi
}
if (!contourIsEmpty) {
const SkPoint* p = SkPathPriv::PointData(path);
patchWriter.writeCaps(p[path.countPoints() - 1], fShader.viewMatrix(), stroke);
patchWriter.writeCaps(p[path.countPoints() - 1], shaderMatrix, stroke);
}
}
return 0;
}
#if SK_GPU_V1

View File

@ -18,13 +18,13 @@ namespace skgpu {
// MSAA if antialiasing is desired.
class StrokeHardwareTessellator : public StrokeTessellator {
public:
StrokeHardwareTessellator(const GrShaderCaps& shaderCaps,
PatchAttribs,
const SkMatrix& viewMatrix,
PathStrokeList* pathStrokeList,
std::array<float,2> matrixMinMaxScales);
StrokeHardwareTessellator(PatchAttribs attribs) : StrokeTessellator(attribs) {}
void prepare(GrMeshDrawTarget*, int totalCombinedVerbCnt) override;
int prepare(GrMeshDrawTarget*,
const SkMatrix& shaderMatrix,
std::array<float,2> matrixMinMaxScales,
PathStrokeList*,
int totalCombinedVerbCnt) override;
#if SK_GPU_V1
void draw(GrOpFlushState*) const override;
#endif

View File

@ -8,8 +8,10 @@
#ifndef tessellate_StrokeTessellator_DEFINED
#define tessellate_StrokeTessellator_DEFINED
#include "include/core/SkPath.h"
#include "include/core/SkStrokeRec.h"
#include "include/private/SkColorData.h"
#include "src/gpu/tessellate/Tessellation.h"
#include "src/gpu/tessellate/shaders/GrStrokeTessellationShader.h"
class GrMeshDrawTarget;
class GrOpFlushState;
@ -28,24 +30,17 @@ public:
PathStrokeList* fNext = nullptr;
};
StrokeTessellator(const GrShaderCaps& shaderCaps,
GrStrokeTessellationShader::Mode shaderMode,
PatchAttribs attribs,
int8_t maxParametricSegments_log2,
const SkMatrix& viewMatrix,
PathStrokeList* pathStrokeList,
std::array<float, 2> matrixMinMaxScales)
: fAttribs(attribs)
, fShader(shaderCaps, shaderMode, fAttribs, viewMatrix, pathStrokeList->fStroke,
pathStrokeList->fColor, maxParametricSegments_log2)
, fPathStrokeList(pathStrokeList)
, fMatrixMinMaxScales(matrixMinMaxScales) {
}
const GrTessellationShader* shader() const { return &fShader; }
StrokeTessellator(PatchAttribs attribs) : fAttribs(attribs) {}
// Called before draw(). Prepares GPU buffers containing the geometry to tessellate.
virtual void prepare(GrMeshDrawTarget*, int totalCombinedVerbCnt) = 0;
//
// Returns the fixed number of edges the tessellator will draw per patch, if using fixed-count
// rendering, otherwise 0.
virtual int prepare(GrMeshDrawTarget*,
const SkMatrix& shaderMatrix,
std::array<float,2> matrixMinMaxScales,
PathStrokeList*,
int totalCombinedVerbCnt) = 0;
#if SK_GPU_V1
// Issues draw calls for the tessellated stroke. The caller is responsible for creating and
@ -57,9 +52,6 @@ public:
protected:
PatchAttribs fAttribs;
GrStrokeTessellationShader fShader;
PathStrokeList* fPathStrokeList;
const std::array<float,2> fMatrixMinMaxScales;
};
// These tolerances decide the number of parametric and radial segments the tessellator will

View File

@ -8,7 +8,7 @@
#ifndef tessellate_Tessellation_DEFINED
#define tessellate_Tessellation_DEFINED
#include "include/core/SkTypes.h"
#include "include/core/SkStrokeRec.h"
#include "include/gpu/GrTypes.h"
#include "include/private/SkVx.h"
@ -20,35 +20,6 @@ namespace skgpu {
struct VertexWriter;
// Don't allow linearized segments to be off by more than 1/4th of a pixel from the true curve.
SK_MAYBE_UNUSED constexpr static float kTessellationPrecision = 4;
// Optional attribs that are included in tessellation patches, following the control points and in
// the same order as they appear here.
enum class PatchAttribs {
// Attribs.
kNone = 0,
kFanPoint = 1 << 0, // [float2] Used by wedges. This is the center point the wedges fan around.
kStrokeParams = 1 << 1, // [float2] Used when strokes have different widths or join types.
kColor = 1 << 2, // [ubyte4 or float4] Used when patches have different colors.
kExplicitCurveType = 1 << 3, // [float] Used when GPU can't infer curve type based on infinity.
// Extra flags.
kWideColorIfEnabled = 1 << 4, // If kColor is set, specifies it to be float4 wide color.
};
GR_MAKE_BITFIELD_CLASS_OPS(PatchAttribs)
// Returns the packed size in bytes of a tessellation patch (or instance) in GPU buffers.
constexpr size_t PatchStride(PatchAttribs attribs) {
return sizeof(float) * 8 + // 4 control points
(attribs & PatchAttribs::kFanPoint ? sizeof(float) * 2 : 0) +
(attribs & PatchAttribs::kColor
? (attribs & PatchAttribs::kWideColorIfEnabled ? sizeof(float)
: sizeof(uint8_t)) * 4 : 0) +
(attribs & PatchAttribs::kExplicitCurveType ? sizeof(float) : 0);
}
// Use familiar type names from SkSL.
template<int N> using vec = skvx::Vec<N, float>;
using float2 = vec<2>;
@ -93,6 +64,65 @@ AI constexpr float pow4(float x) { return pow2(x*x); }
#undef AI
// Don't allow linearized segments to be off by more than 1/4th of a pixel from the true curve.
SK_MAYBE_UNUSED constexpr static float kTessellationPrecision = 4;
// Optional attribs that are included in tessellation patches, following the control points and in
// the same order as they appear here.
enum class PatchAttribs {
// Attribs.
kNone = 0,
kFanPoint = 1 << 0, // [float2] Used by wedges. This is the center point the wedges fan around.
kStrokeParams = 1 << 1, // [float2] Used when strokes have different widths or join types.
kColor = 1 << 2, // [ubyte4 or float4] Used when patches have different colors.
kExplicitCurveType = 1 << 3, // [float] Used when GPU can't infer curve type based on infinity.
// Extra flags.
kWideColorIfEnabled = 1 << 4, // If kColor is set, specifies it to be float4 wide color.
};
GR_MAKE_BITFIELD_CLASS_OPS(PatchAttribs)
// We encode all of a join's information in a single float value:
//
// Negative => Round Join
// Zero => Bevel Join
// Positive => Miter join, and the value is also the miter limit
//
static float GetJoinType(const SkStrokeRec& stroke) {
switch (stroke.getJoin()) {
case SkPaint::kRound_Join: return -1;
case SkPaint::kBevel_Join: return 0;
case SkPaint::kMiter_Join: SkASSERT(stroke.getMiter() >= 0); return stroke.getMiter();
}
SkUNREACHABLE;
}
// This float2 gets written out with each patch/instance if PatchAttribs::kStrokeParams is enabled.
struct StrokeParams {
static bool StrokesHaveEqualParams(const SkStrokeRec& a, const SkStrokeRec& b) {
return a.getWidth() == b.getWidth() && a.getJoin() == b.getJoin() &&
(a.getJoin() != SkPaint::kMiter_Join || a.getMiter() == b.getMiter());
}
void set(const SkStrokeRec& stroke) {
fRadius = stroke.getWidth() * .5f;
fJoinType = GetJoinType(stroke);
}
float fRadius;
float fJoinType; // See GetJoinType().
};
// Returns the packed size in bytes of the attribs portion of tessellation patches (or instances) in
// GPU buffers.
constexpr size_t PatchAttribsStride(PatchAttribs attribs) {
return (attribs & PatchAttribs::kFanPoint ? sizeof(float) * 2 : 0) +
(attribs & PatchAttribs::kStrokeParams ? sizeof(float) * 2 : 0) +
(attribs & PatchAttribs::kColor
? (attribs & PatchAttribs::kWideColorIfEnabled ? sizeof(float)
: sizeof(uint8_t)) * 4 : 0) +
(attribs & PatchAttribs::kExplicitCurveType ? sizeof(float) : 0);
}
// Don't tessellate paths that might have an individual curve that requires more than 1024 segments.
// (See wangs_formula::worst_case_cubic). If this is the case, call "PreChopPathCurves" first.
constexpr static float kMaxTessellationSegmentsPerCurve SK_MAYBE_UNUSED = 1024;

View File

@ -40,7 +40,8 @@ public:
constexpr static Attribute kInputPointAttrib{"inputPoint", kFloat2_GrVertexAttribType,
kFloat2_GrSLType};
this->setVertexAttributes(&kInputPointAttrib, 1);
SkASSERT(this->vertexStride() * 5 == skgpu::PatchStride(fAttribs));
SkASSERT(this->vertexStride() * 5 ==
sizeof(SkPoint) * 4 + skgpu::PatchAttribsStride(fAttribs));
}
int maxTessellationSegments(const GrShaderCaps& shaderCaps) const override {
@ -180,7 +181,8 @@ public:
constexpr static Attribute kInputPointAttrib{"inputPoint", kFloat2_GrVertexAttribType,
kFloat2_GrSLType};
this->setVertexAttributes(&kInputPointAttrib, 1);
SkASSERT(this->vertexStride() * 4 == skgpu::PatchStride(fAttribs));
SkASSERT(this->vertexStride() * 4 ==
sizeof(SkPoint) * 4 + skgpu::PatchAttribsStride(fAttribs));
}
int maxTessellationSegments(const GrShaderCaps& shaderCaps) const override {

View File

@ -58,7 +58,8 @@ public:
}
this->setInstanceAttributes(fInstanceAttribs.data(), fInstanceAttribs.count());
SkASSERT(fInstanceAttribs.count() <= kMaxInstanceAttribCount);
SkASSERT(this->instanceStride() == skgpu::PatchStride(fAttribs));
SkASSERT(this->instanceStride() ==
sizeof(SkPoint) * 4 + skgpu::PatchAttribsStride(fAttribs));
constexpr static Attribute kVertexAttrib("resolveLevel_and_idx", kFloat2_GrVertexAttribType,
kFloat2_GrSLType);

View File

@ -92,8 +92,10 @@ GrStrokeTessellationShader::GrStrokeTessellationShader(const GrShaderCaps& shade
}
if (fMode == Mode::kHardwareTessellation) {
this->setVertexAttributes(fAttribs.data(), fAttribs.count());
SkASSERT(this->vertexStride() == sizeof(SkPoint) * 5 + PatchAttribsStride(fPatchAttribs));
} else {
this->setInstanceAttributes(fAttribs.data(), fAttribs.count());
SkASSERT(this->instanceStride() == sizeof(SkPoint) * 5 + PatchAttribsStride(fPatchAttribs));
if (!shaderCaps.vertexIDSupport()) {
constexpr static Attribute kVertexAttrib("edgeID", kFloat_GrVertexAttribType,
kFloat_GrSLType);
@ -371,7 +373,7 @@ void GrStrokeTessellationShader::Impl::setData(const GrGLSLProgramDataManager& p
pdman.set4f(fTessControlArgsUniform,
tolerances.fParametricPrecision, // PARAMETRIC_PRECISION
tolerances.fNumRadialSegmentsPerRadian, // NUM_RADIAL_SEGMENTS_PER_RADIAN
GrStrokeTessellationShader::GetJoinType(stroke), // JOIN_TYPE
skgpu::GetJoinType(stroke), // JOIN_TYPE
strokeRadius); // STROKE_RADIUS
} else {
SkASSERT(!stroke.isHairlineStyle());

View File

@ -33,62 +33,6 @@ public:
kFixedCount
};
// Returns the fixed number of edges that are always emitted with the given join type. If the
// join is round, the caller needs to account for the additional radial edges on their own.
// Specifically, each join always emits:
//
// * Two colocated edges at the beginning (a full-width edge to seam with the preceding stroke
// and a half-width edge to begin the join).
//
// * An extra edge in the middle for miter joins, or else a variable number of radial edges
// for round joins (the caller is responsible for counting radial edges from round joins).
//
// * A half-width edge at the end of the join that will be colocated with the first
// (full-width) edge of the stroke.
//
constexpr static int NumFixedEdgesInJoin(SkPaint::Join joinType) {
switch (joinType) {
case SkPaint::kMiter_Join:
return 4;
case SkPaint::kRound_Join:
// The caller is responsible for counting the variable number of middle, radial
// segments on round joins.
[[fallthrough]];
case SkPaint::kBevel_Join:
return 3;
}
SkUNREACHABLE;
}
// We encode all of a join's information in a single float value:
//
// Negative => Round Join
// Zero => Bevel Join
// Positive => Miter join, and the value is also the miter limit
//
static float GetJoinType(const SkStrokeRec& stroke) {
switch (stroke.getJoin()) {
case SkPaint::kRound_Join: return -1;
case SkPaint::kBevel_Join: return 0;
case SkPaint::kMiter_Join: SkASSERT(stroke.getMiter() >= 0); return stroke.getMiter();
}
SkUNREACHABLE;
}
// This struct gets written out to each patch or instance if kDynamicStroke is enabled.
struct DynamicStroke {
static bool StrokesHaveEqualDynamicState(const SkStrokeRec& a, const SkStrokeRec& b) {
return a.getWidth() == b.getWidth() && a.getJoin() == b.getJoin() &&
(a.getJoin() != SkPaint::kMiter_Join || a.getMiter() == b.getMiter());
}
void set(const SkStrokeRec& stroke) {
fRadius = stroke.getWidth() * .5f;
fJoinType = GetJoinType(stroke);
}
float fRadius;
float fJoinType; // See GetJoinType().
};
// 'viewMatrix' is applied to the geometry post tessellation. It cannot have perspective.
GrStrokeTessellationShader(const GrShaderCaps&, Mode, PatchAttribs, const SkMatrix& viewMatrix,
const SkStrokeRec&, SkPMColor4f, int8_t maxParametricSegments_log2);
@ -109,13 +53,6 @@ public:
fFixedCountNumTotalEdges = value;
}
// Initializes the fallback vertex buffer that should be bound when drawing in Mode::kFixedCount
// and sk_VertexID is not supported. Each vertex is a single float and each edge is composed of
// two vertices, so the desired edge count in the buffer is presumed to be
// "bufferSize / (sizeof(float) * 2)". The caller cannot draw more vertices than edgeCount * 2.
static void InitializeVertexIDFallbackBuffer(skgpu::VertexWriter vertexWriter,
size_t bufferSize);
private:
const char* name() const override {
switch (fMode) {

View File

@ -9,6 +9,7 @@
#include "src/gpu/glsl/GrGLSLFragmentShaderBuilder.h"
#include "src/gpu/glsl/GrGLSLVertexGeoBuilder.h"
#include "src/gpu/tessellate/StrokeFixedCountTessellator.h"
#include "src/gpu/tessellate/WangsFormula.h"
using skgpu::VertexWriter;
@ -180,7 +181,8 @@ void GrStrokeTessellationShader::InstancedImpl::onEmitCode(EmitArgs& args, GrGPA
}
} else {
args.fVertBuilder->codeAppendf(R"(
float numEdgesInJoin = %i;)", GrStrokeTessellationShader::NumFixedEdgesInJoin(joinType));
float numEdgesInJoin = %i;)",
skgpu::StrokeFixedCountTessellator::NumFixedEdgesInJoin(joinType));
}
args.fVertBuilder->codeAppend(R"(
@ -265,12 +267,3 @@ void GrStrokeTessellationShader::InstancedImpl::onEmitCode(EmitArgs& args, GrGPA
this->emitFragmentCode(shader, args);
}
void GrStrokeTessellationShader::InitializeVertexIDFallbackBuffer(VertexWriter vertexWriter,
size_t bufferSize) {
SkASSERT(bufferSize % (sizeof(float) * 2) == 0);
int edgeCount = bufferSize / (sizeof(float) * 2);
for (int i = 0; i < edgeCount; ++i) {
vertexWriter << (float)i << (float)-i;
}
}