From 9aca7f7dd005a3a3494fda66aa954063366933d5 Mon Sep 17 00:00:00 2001 From: Chris Dalton Date: Fri, 12 Nov 2021 21:18:47 -0700 Subject: [PATCH] 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 Reviewed-by: Greg Daniel --- bench/TessellateBench.cpp | 36 +++----- src/gpu/ops/StrokeTessellateOp.cpp | 57 +++++++----- src/gpu/ops/StrokeTessellateOp.h | 2 + src/gpu/tessellate/PatchWriter.cpp | 1 + src/gpu/tessellate/PatchWriter.h | 3 +- .../StrokeFixedCountTessellator.cpp | 76 ++++++++-------- .../tessellate/StrokeFixedCountTessellator.h | 52 +++++++++-- .../tessellate/StrokeHardwareTessellator.cpp | 52 +++++------ .../tessellate/StrokeHardwareTessellator.h | 12 +-- src/gpu/tessellate/StrokeTessellator.h | 32 +++---- src/gpu/tessellate/Tessellation.h | 90 ++++++++++++------- .../GrPathTessellationShader_Hardware.cpp | 6 +- .../GrPathTessellationShader_MiddleOut.cpp | 3 +- .../shaders/GrStrokeTessellationShader.cpp | 4 +- .../shaders/GrStrokeTessellationShader.h | 63 ------------- ...StrokeTessellationShader_InstancedImpl.cpp | 13 +-- 16 files changed, 249 insertions(+), 253 deletions(-) diff --git a/bench/TessellateBench.cpp b/bench/TessellateBench.cpp index d0e05be0cc..b55d0516c8 100644 --- a/bench/TessellateBench.cpp +++ b/bench/TessellateBench.cpp @@ -244,30 +244,14 @@ DEF_PATH_TESS_BENCH(middle_out_triangulation, } using PathStrokeList = StrokeTessellator::PathStrokeList; -using MakeTessellatorFn = std::unique_ptr(*)(PatchAttribs, - const GrShaderCaps&, - const SkMatrix&, - PathStrokeList*, - std::array); +using MakeTessellatorFn = std::unique_ptr(*)(PatchAttribs); -static std::unique_ptr make_hw_tessellator( - PatchAttribs attribs, - const GrShaderCaps& shaderCaps, - const SkMatrix& viewMatrix, - PathStrokeList* pathStrokeList, - std::array matrixMinMaxScales) { - return std::make_unique(shaderCaps, attribs, viewMatrix, - pathStrokeList, matrixMinMaxScales); +static std::unique_ptr make_hw_tessellator(PatchAttribs attribs) { + return std::make_unique(attribs); } -static std::unique_ptr make_fixed_count_tessellator( - PatchAttribs attribs, - const GrShaderCaps& shaderCaps, - const SkMatrix& viewMatrix, - PathStrokeList* pathStrokeList, - std::array matrixMinMaxScales) { - return std::make_unique(shaderCaps, attribs, viewMatrix, - pathStrokeList, matrixMinMaxScales); +static std::unique_ptr make_fixed_count_tessellator(PatchAttribs attribs) { + return std::make_unique(attribs); } using MakePathStrokesFn = std::vector(*)(); @@ -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(); } } diff --git a/src/gpu/ops/StrokeTessellateOp.cpp b/src/gpu/ops/StrokeTessellateOp.cpp index b7081d0f6c..9e0d3e93ee 100644 --- a/src/gpu/ops/StrokeTessellateOp.cpp +++ b/src/gpu/ops/StrokeTessellateOp.cpp @@ -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 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(*caps.shaderCaps(), - fPatchAttribs, - fViewMatrix, - &fPathStrokeList, - matrixMinMaxScales); + fTessellator = arena->make(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(*caps.shaderCaps(), - fPatchAttribs, - fViewMatrix, - &fPathStrokeList, - matrixMinMaxScales); + fTessellator = arena->make(fPatchAttribs); + shaderMode = GrStrokeTessellationShader::Mode::kFixedCount; + maxParametricSegments_log2 = StrokeFixedCountTessellator::kMaxParametricSegments_log2; } + fTessellationShader = args.fArena->make(*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 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) { diff --git a/src/gpu/ops/StrokeTessellateOp.h b/src/gpu/ops/StrokeTessellateOp.h index f5e2034b85..8eecd24873 100644 --- a/src/gpu/ops/StrokeTessellateOp.h +++ b/src/gpu/ops/StrokeTessellateOp.h @@ -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; }; diff --git a/src/gpu/tessellate/PatchWriter.cpp b/src/gpu/tessellate/PatchWriter.cpp index 8d122375b4..405397616d 100644 --- a/src/gpu/tessellate/PatchWriter.cpp +++ b/src/gpu/tessellate/PatchWriter.cpp @@ -20,6 +20,7 @@ PatchWriter::PatchWriter(GrMeshDrawTarget* target, : PatchWriter(target, &tessellator->fVertexChunkArray, tessellator->fAttribs, + sizeof(SkPoint) * 4 + PatchAttribsStride(tessellator->fAttribs), initialPatchAllocCount) { } #endif diff --git a/src/gpu/tessellate/PatchWriter.h b/src/gpu/tessellate/PatchWriter.h index 220600a964..3d7745b489 100644 --- a/src/gpu/tessellate/PatchWriter.h +++ b/src/gpu/tessellate/PatchWriter.h @@ -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 diff --git a/src/gpu/tessellate/StrokeFixedCountTessellator.cpp b/src/gpu/tessellate/StrokeFixedCountTessellator.cpp index 7ae581c48e..1134e1f370 100644 --- a/src/gpu/tessellate/StrokeFixedCountTessellator.cpp +++ b/src/gpu/tessellate/StrokeFixedCountTessellator.cpp @@ -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 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 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 diff --git a/src/gpu/tessellate/StrokeFixedCountTessellator.h b/src/gpu/tessellate/StrokeFixedCountTessellator.h index 58ebc1b5e8..530d662af7 100644 --- a/src/gpu/tessellate/StrokeFixedCountTessellator.h +++ b/src/gpu/tessellate/StrokeFixedCountTessellator.h @@ -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 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 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 fVertexBufferIfNoIDSupport; diff --git a/src/gpu/tessellate/StrokeHardwareTessellator.cpp b/src/gpu/tessellate/StrokeHardwareTessellator.cpp index a626da6f26..d0338779e9 100644 --- a/src/gpu/tessellate/StrokeHardwareTessellator.cpp +++ b/src/gpu/tessellate/StrokeHardwareTessellator.cpp @@ -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 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 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 diff --git a/src/gpu/tessellate/StrokeHardwareTessellator.h b/src/gpu/tessellate/StrokeHardwareTessellator.h index dc07df46ed..74e72a2a89 100644 --- a/src/gpu/tessellate/StrokeHardwareTessellator.h +++ b/src/gpu/tessellate/StrokeHardwareTessellator.h @@ -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 matrixMinMaxScales); + StrokeHardwareTessellator(PatchAttribs attribs) : StrokeTessellator(attribs) {} - void prepare(GrMeshDrawTarget*, int totalCombinedVerbCnt) override; + int prepare(GrMeshDrawTarget*, + const SkMatrix& shaderMatrix, + std::array matrixMinMaxScales, + PathStrokeList*, + int totalCombinedVerbCnt) override; #if SK_GPU_V1 void draw(GrOpFlushState*) const override; #endif diff --git a/src/gpu/tessellate/StrokeTessellator.h b/src/gpu/tessellate/StrokeTessellator.h index 35e9cdf26d..5e15d44cfe 100644 --- a/src/gpu/tessellate/StrokeTessellator.h +++ b/src/gpu/tessellate/StrokeTessellator.h @@ -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 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 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 fMatrixMinMaxScales; }; // These tolerances decide the number of parametric and radial segments the tessellator will diff --git a/src/gpu/tessellate/Tessellation.h b/src/gpu/tessellate/Tessellation.h index 1f596518ca..36412a9c23 100644 --- a/src/gpu/tessellate/Tessellation.h +++ b/src/gpu/tessellate/Tessellation.h @@ -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 using vec = skvx::Vec; 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; diff --git a/src/gpu/tessellate/shaders/GrPathTessellationShader_Hardware.cpp b/src/gpu/tessellate/shaders/GrPathTessellationShader_Hardware.cpp index 76a177c47d..b312f5dd76 100644 --- a/src/gpu/tessellate/shaders/GrPathTessellationShader_Hardware.cpp +++ b/src/gpu/tessellate/shaders/GrPathTessellationShader_Hardware.cpp @@ -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 { diff --git a/src/gpu/tessellate/shaders/GrPathTessellationShader_MiddleOut.cpp b/src/gpu/tessellate/shaders/GrPathTessellationShader_MiddleOut.cpp index df1afce6a0..3989e9e7f5 100644 --- a/src/gpu/tessellate/shaders/GrPathTessellationShader_MiddleOut.cpp +++ b/src/gpu/tessellate/shaders/GrPathTessellationShader_MiddleOut.cpp @@ -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); diff --git a/src/gpu/tessellate/shaders/GrStrokeTessellationShader.cpp b/src/gpu/tessellate/shaders/GrStrokeTessellationShader.cpp index 5e509a2f18..baf705576c 100644 --- a/src/gpu/tessellate/shaders/GrStrokeTessellationShader.cpp +++ b/src/gpu/tessellate/shaders/GrStrokeTessellationShader.cpp @@ -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()); diff --git a/src/gpu/tessellate/shaders/GrStrokeTessellationShader.h b/src/gpu/tessellate/shaders/GrStrokeTessellationShader.h index b00605b3d1..3b88119967 100644 --- a/src/gpu/tessellate/shaders/GrStrokeTessellationShader.h +++ b/src/gpu/tessellate/shaders/GrStrokeTessellationShader.h @@ -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) { diff --git a/src/gpu/tessellate/shaders/GrStrokeTessellationShader_InstancedImpl.cpp b/src/gpu/tessellate/shaders/GrStrokeTessellationShader_InstancedImpl.cpp index 2dd1470c73..ecfbbf269e 100644 --- a/src/gpu/tessellate/shaders/GrStrokeTessellationShader_InstancedImpl.cpp +++ b/src/gpu/tessellate/shaders/GrStrokeTessellationShader_InstancedImpl.cpp @@ -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; - } -}