From bb995e60a3c8fb4609fefd6febb42d473afe0cea Mon Sep 17 00:00:00 2001 From: Chris Dalton Date: Thu, 1 Jul 2021 10:58:55 -0600 Subject: [PATCH] Add an sk_VertexID workaround for tessellated stroking Tessellation should now be completely free of its dependence on sk_VertexID. Bug: chromium:1220246 Change-Id: I4027099392b92e45aee7d8417945335352e3416e Reviewed-on: https://skia-review.googlesource.com/c/skia/+/423496 Commit-Queue: Chris Dalton Reviewed-by: Brian Salomon --- bench/TessellateBench.cpp | 5 +- .../GrStrokeFixedCountTessellator.cpp | 30 +++++++- .../GrStrokeFixedCountTessellator.h | 5 +- .../GrStrokeHardwareTessellator.cpp | 7 +- src/gpu/tessellate/GrStrokeTessellateOp.cpp | 3 +- src/gpu/tessellate/GrStrokeTessellator.h | 4 +- .../tessellate/GrTessellationPathRenderer.cpp | 3 +- .../shaders/GrStrokeTessellationShader.cpp | 77 +++++++++++++++++++ .../shaders/GrStrokeTessellationShader.h | 76 ++---------------- ...StrokeTessellationShader_InstancedImpl.cpp | 28 +++++-- 10 files changed, 150 insertions(+), 88 deletions(-) diff --git a/bench/TessellateBench.cpp b/bench/TessellateBench.cpp index 2c46c1dd25..f5e9e3f5a0 100644 --- a/bench/TessellateBench.cpp +++ b/bench/TessellateBench.cpp @@ -222,11 +222,12 @@ static std::unique_ptr make_hw_tessellator( } static std::unique_ptr make_fixed_count_tessellator( - ShaderFlags shaderFlags, const GrShaderCaps&, const SkMatrix& viewMatrix, + ShaderFlags shaderFlags, const GrShaderCaps& shaderCaps, const SkMatrix& viewMatrix, PathStrokeList* pathStrokeList, std::array matrixMinMaxScales, const SkRect& strokeCullBounds) { return std::make_unique(shaderFlags, viewMatrix, pathStrokeList, - matrixMinMaxScales, strokeCullBounds); + matrixMinMaxScales, strokeCullBounds, + shaderCaps); } using MakePathStrokesFn = std::vector(*)(); diff --git a/src/gpu/tessellate/GrStrokeFixedCountTessellator.cpp b/src/gpu/tessellate/GrStrokeFixedCountTessellator.cpp index e28c3ebdf9..15cad27084 100644 --- a/src/gpu/tessellate/GrStrokeFixedCountTessellator.cpp +++ b/src/gpu/tessellate/GrStrokeFixedCountTessellator.cpp @@ -8,6 +8,7 @@ #include "src/gpu/tessellate/GrStrokeFixedCountTessellator.h" #include "src/core/SkGeometry.h" +#include "src/gpu/GrResourceProvider.h" #include "src/gpu/geometry/GrPathUtils.h" #include "src/gpu/geometry/GrWangsFormula.h" #include "src/gpu/tessellate/GrCullTest.h" @@ -238,12 +239,15 @@ GrStrokeFixedCountTessellator::GrStrokeFixedCountTessellator(ShaderFlags shaderF const SkMatrix& viewMatrix, PathStrokeList* pathStrokeList, std::array matrixMinMaxScales, - const SkRect& strokeCullBounds) + const SkRect& strokeCullBounds, + const GrShaderCaps& shaderCaps) : GrStrokeTessellator(GrStrokeTessellationShader::Mode::kFixedCount, shaderFlags, kMaxParametricSegments_log2, viewMatrix, pathStrokeList, - matrixMinMaxScales, strokeCullBounds) { + matrixMinMaxScales, strokeCullBounds, shaderCaps) { } +GR_DECLARE_STATIC_UNIQUE_KEY(gVertexIDFallbackBufferKey); + void GrStrokeFixedCountTessellator::prepare(GrMeshDrawTarget* target, int totalCombinedVerbCnt) { int maxEdgesInJoin = 0; @@ -404,6 +408,26 @@ void GrStrokeFixedCountTessellator::prepare(GrMeshDrawTarget* target, // emit both because the join's edge is half-width and the stroke's is full-width. int fixedEdgeCount = 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); + + 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); + + GR_DEFINE_STATIC_UNIQUE_KEY(gVertexIDFallbackBufferKey); + + fVertexBufferIfNoIDSupport = target->resourceProvider()->findOrMakeStaticBuffer( + GrGpuBufferType::kVertex, + kMaxVerticesInFallbackBuffer * sizeof(float), + gVertexIDFallbackBufferKey, + GrStrokeTessellationShader::InitializeVertexIDFallbackBuffer); + } + fShader.setFixedCountNumTotalEdges(fixedEdgeCount); fFixedVertexCount = fixedEdgeCount * 2; } @@ -413,7 +437,7 @@ void GrStrokeFixedCountTessellator::draw(GrOpFlushState* flushState) const { return; } for (const auto& instanceChunk : fInstanceChunks) { - flushState->bindBuffers(nullptr, instanceChunk.fBuffer, nullptr); + flushState->bindBuffers(nullptr, instanceChunk.fBuffer, fVertexBufferIfNoIDSupport); flushState->drawInstanced(instanceChunk.fCount, instanceChunk.fBase, fFixedVertexCount, 0); } } diff --git a/src/gpu/tessellate/GrStrokeFixedCountTessellator.h b/src/gpu/tessellate/GrStrokeFixedCountTessellator.h index d7498e537b..112de91603 100644 --- a/src/gpu/tessellate/GrStrokeFixedCountTessellator.h +++ b/src/gpu/tessellate/GrStrokeFixedCountTessellator.h @@ -17,7 +17,7 @@ class GrStrokeFixedCountTessellator : public GrStrokeTessellator { public: GrStrokeFixedCountTessellator(ShaderFlags, const SkMatrix&, PathStrokeList*, std::array matrixMinMaxScales, - const SkRect& strokeCullBounds); + const SkRect& strokeCullBounds, const GrShaderCaps&); void prepare(GrMeshDrawTarget*, int totalCombinedVerbCnt) override; void draw(GrOpFlushState*) const override; @@ -25,6 +25,9 @@ public: private: GrVertexChunkArray fInstanceChunks; int fFixedVertexCount = 0; + + // Only used if sk_VertexID is not supported. + sk_sp fVertexBufferIfNoIDSupport; }; #endif diff --git a/src/gpu/tessellate/GrStrokeHardwareTessellator.cpp b/src/gpu/tessellate/GrStrokeHardwareTessellator.cpp index e87fa9c12f..65c15c51b0 100644 --- a/src/gpu/tessellate/GrStrokeHardwareTessellator.cpp +++ b/src/gpu/tessellate/GrStrokeHardwareTessellator.cpp @@ -709,10 +709,9 @@ GrStrokeHardwareTessellator::GrStrokeHardwareTessellator(ShaderFlags shaderFlags PathStrokeList* pathStrokeList, std::array matrixMinMaxScales, const SkRect& strokeCullBounds) - : GrStrokeTessellator(GrStrokeTessellationShader::Mode::kHardwareTessellation, - shaderFlags, SkNextLog2(shaderCaps.maxTessellationSegments()), - viewMatrix, pathStrokeList, matrixMinMaxScales, - strokeCullBounds) { + : GrStrokeTessellator(GrStrokeTessellationShader::Mode::kHardwareTessellation, shaderFlags, + SkNextLog2(shaderCaps.maxTessellationSegments()), viewMatrix, + pathStrokeList, matrixMinMaxScales, strokeCullBounds, shaderCaps) { } void GrStrokeHardwareTessellator::prepare(GrMeshDrawTarget* target, int totalCombinedVerbCnt) { diff --git a/src/gpu/tessellate/GrStrokeTessellateOp.cpp b/src/gpu/tessellate/GrStrokeTessellateOp.cpp index 24341f459b..e8c9342c95 100644 --- a/src/gpu/tessellate/GrStrokeTessellateOp.cpp +++ b/src/gpu/tessellate/GrStrokeTessellateOp.cpp @@ -198,7 +198,8 @@ void GrStrokeTessellateOp::prePrepareTessellator(GrTessellationShader::ProgramAr fTessellator = arena->make(fShaderFlags, fViewMatrix, &fPathStrokeList, matrixMinMaxScales, - strokeCullBounds); + strokeCullBounds, + *caps.shaderCaps()); } auto fillStencil = &GrUserStencilSettings::kUnused; diff --git a/src/gpu/tessellate/GrStrokeTessellator.h b/src/gpu/tessellate/GrStrokeTessellator.h index 59aa1e2826..2bf7cfccf9 100644 --- a/src/gpu/tessellate/GrStrokeTessellator.h +++ b/src/gpu/tessellate/GrStrokeTessellator.h @@ -28,9 +28,9 @@ public: GrStrokeTessellator(GrStrokeTessellationShader::Mode shaderMode, ShaderFlags shaderFlags, int8_t maxParametricSegments_log2, const SkMatrix& viewMatrix, PathStrokeList* pathStrokeList, std::array matrixMinMaxScales, - const SkRect& strokeCullBounds) + const SkRect& strokeCullBounds, const GrShaderCaps& shaderCaps) : fShader(shaderMode, shaderFlags, viewMatrix, pathStrokeList->fStroke, - pathStrokeList->fColor, maxParametricSegments_log2) + pathStrokeList->fColor, maxParametricSegments_log2, shaderCaps) , fPathStrokeList(pathStrokeList) , fMatrixMinMaxScales(matrixMinMaxScales) , fStrokeCullBounds(strokeCullBounds) { diff --git a/src/gpu/tessellate/GrTessellationPathRenderer.cpp b/src/gpu/tessellate/GrTessellationPathRenderer.cpp index a04b5f80fd..fec78d5ec5 100644 --- a/src/gpu/tessellate/GrTessellationPathRenderer.cpp +++ b/src/gpu/tessellate/GrTessellationPathRenderer.cpp @@ -78,8 +78,7 @@ GrPathRenderer::CanDrawPath GrTessellationPathRenderer::onCanDrawPath( return CanDrawPath::kNo; } if (!shape.style().isSimpleFill()) { - if (shape.inverseFilled() || - !args.fCaps->shaderCaps()->vertexIDSupport()) { + if (shape.inverseFilled()) { return CanDrawPath::kNo; } } diff --git a/src/gpu/tessellate/shaders/GrStrokeTessellationShader.cpp b/src/gpu/tessellate/shaders/GrStrokeTessellationShader.cpp index b1e62327aa..c415a514e7 100644 --- a/src/gpu/tessellate/shaders/GrStrokeTessellationShader.cpp +++ b/src/gpu/tessellate/shaders/GrStrokeTessellationShader.cpp @@ -13,6 +13,83 @@ #include "src/gpu/glsl/GrGLSLVertexGeoBuilder.h" #include "src/gpu/tessellate/GrStrokeTessellator.h" +GrStrokeTessellationShader::GrStrokeTessellationShader(Mode mode, ShaderFlags shaderFlags, + const SkMatrix& viewMatrix, + const SkStrokeRec& stroke, SkPMColor4f color, + int8_t maxParametricSegments_log2, + const GrShaderCaps& shaderCaps) + : GrTessellationShader(kTessellate_GrStrokeTessellationShader_ClassID, + (mode == Mode::kHardwareTessellation) + ? GrPrimitiveType::kPatches + : GrPrimitiveType::kTriangleStrip, + (mode == Mode::kHardwareTessellation) ? 1 : 0, viewMatrix, color) + , fMode(mode) + , fShaderFlags(shaderFlags) + , fStroke(stroke) + , fMaxParametricSegments_log2(maxParametricSegments_log2) { + if (fMode == Mode::kHardwareTessellation) { + // A join calculates its starting angle using prevCtrlPtAttr. + fAttribs.emplace_back("prevCtrlPtAttr", kFloat2_GrVertexAttribType, kFloat2_GrSLType); + // pts 0..3 define the stroke as a cubic bezier. If p3.y is infinity, then it's a conic + // with w=p3.x. + // + // If p0 == prevCtrlPtAttr, then no join is emitted. + // + // pts=[p0, p3, p3, p3] is a reserved pattern that means this patch is a join only, + // whose start and end tangents are (p0 - inputPrevCtrlPt) and (p3 - p0). + // + // pts=[p0, p0, p0, p3] is a reserved pattern that means this patch is a "bowtie", or + // double-sided round join, anchored on p0 and rotating from (p0 - prevCtrlPtAttr) to + // (p3 - p0). + fAttribs.emplace_back("pts01Attr", kFloat4_GrVertexAttribType, kFloat4_GrSLType); + fAttribs.emplace_back("pts23Attr", kFloat4_GrVertexAttribType, kFloat4_GrSLType); + } else { + // pts 0..3 define the stroke as a cubic bezier. If p3.y is infinity, then it's a conic + // with w=p3.x. + // + // An empty stroke (p0==p1==p2==p3) is a special case that denotes a circle, or + // 180-degree point stroke. + fAttribs.emplace_back("pts01Attr", kFloat4_GrVertexAttribType, kFloat4_GrSLType); + fAttribs.emplace_back("pts23Attr", kFloat4_GrVertexAttribType, kFloat4_GrSLType); + if (fMode == Mode::kLog2Indirect) { + // argsAttr.xy contains the lastControlPoint for setting up the join. + // + // "argsAttr.z=numTotalEdges" tells the shader the literal number of edges in the + // triangle strip being rendered (i.e., it should be vertexCount/2). If + // numTotalEdges is negative and the join type is "kRound", it also instructs the + // shader to only allocate one segment the preceding round join. + fAttribs.emplace_back("argsAttr", kFloat3_GrVertexAttribType, kFloat3_GrSLType); + } else { + SkASSERT(fMode == Mode::kFixedCount); + // argsAttr contains the lastControlPoint for setting up the join. + fAttribs.emplace_back("argsAttr", kFloat2_GrVertexAttribType, kFloat2_GrSLType); + } + } + if (fShaderFlags & ShaderFlags::kDynamicStroke) { + fAttribs.emplace_back("dynamicStrokeAttr", kFloat2_GrVertexAttribType, + kFloat2_GrSLType); + } + if (fShaderFlags & ShaderFlags::kDynamicColor) { + fAttribs.emplace_back("dynamicColorAttr", + (fShaderFlags & ShaderFlags::kWideColor) + ? kFloat4_GrVertexAttribType + : kUByte4_norm_GrVertexAttribType, + kHalf4_GrSLType); + } + if (fMode == Mode::kHardwareTessellation) { + this->setVertexAttributes(fAttribs.data(), fAttribs.count()); + } else { + this->setInstanceAttributes(fAttribs.data(), fAttribs.count()); + if (!shaderCaps.vertexIDSupport()) { + constexpr static Attribute kVertexAttrib("edgeID", kFloat_GrVertexAttribType, + kFloat_GrSLType); + this->setVertexAttributes(&kVertexAttrib, 1); + } + } + SkASSERT(fAttribs.count() <= kMaxAttribCount); +} + + // The built-in atan() is undefined when x==0. This method relieves that restriction, but also can // return values larger than 2*PI. This shouldn't matter for our purposes. const char* GrStrokeTessellationShader::Impl::kAtan2Fn = R"( diff --git a/src/gpu/tessellate/shaders/GrStrokeTessellationShader.h b/src/gpu/tessellate/shaders/GrStrokeTessellationShader.h index 88a8d39fc0..0f4af3c5a2 100644 --- a/src/gpu/tessellate/shaders/GrStrokeTessellationShader.h +++ b/src/gpu/tessellate/shaders/GrStrokeTessellationShader.h @@ -98,74 +98,8 @@ public: }; // 'viewMatrix' is applied to the geometry post tessellation. It cannot have perspective. - GrStrokeTessellationShader(Mode mode, ShaderFlags shaderFlags, const SkMatrix& viewMatrix, - const SkStrokeRec& stroke, SkPMColor4f color, - int8_t maxParametricSegments_log2) - : GrTessellationShader(kTessellate_GrStrokeTessellationShader_ClassID, - (mode == Mode::kHardwareTessellation) - ? GrPrimitiveType::kPatches - : GrPrimitiveType::kTriangleStrip, - (mode == Mode::kHardwareTessellation) ? 1 : 0, viewMatrix, color) - , fMode(mode) - , fShaderFlags(shaderFlags) - , fStroke(stroke) - , fMaxParametricSegments_log2(maxParametricSegments_log2) { - if (fMode == Mode::kHardwareTessellation) { - // A join calculates its starting angle using prevCtrlPtAttr. - fAttribs.emplace_back("prevCtrlPtAttr", kFloat2_GrVertexAttribType, kFloat2_GrSLType); - // pts 0..3 define the stroke as a cubic bezier. If p3.y is infinity, then it's a conic - // with w=p3.x. - // - // If p0 == prevCtrlPtAttr, then no join is emitted. - // - // pts=[p0, p3, p3, p3] is a reserved pattern that means this patch is a join only, - // whose start and end tangents are (p0 - inputPrevCtrlPt) and (p3 - p0). - // - // pts=[p0, p0, p0, p3] is a reserved pattern that means this patch is a "bowtie", or - // double-sided round join, anchored on p0 and rotating from (p0 - prevCtrlPtAttr) to - // (p3 - p0). - fAttribs.emplace_back("pts01Attr", kFloat4_GrVertexAttribType, kFloat4_GrSLType); - fAttribs.emplace_back("pts23Attr", kFloat4_GrVertexAttribType, kFloat4_GrSLType); - } else { - // pts 0..3 define the stroke as a cubic bezier. If p3.y is infinity, then it's a conic - // with w=p3.x. - // - // An empty stroke (p0==p1==p2==p3) is a special case that denotes a circle, or - // 180-degree point stroke. - fAttribs.emplace_back("pts01Attr", kFloat4_GrVertexAttribType, kFloat4_GrSLType); - fAttribs.emplace_back("pts23Attr", kFloat4_GrVertexAttribType, kFloat4_GrSLType); - if (fMode == Mode::kLog2Indirect) { - // argsAttr.xy contains the lastControlPoint for setting up the join. - // - // "argsAttr.z=numTotalEdges" tells the shader the literal number of edges in the - // triangle strip being rendered (i.e., it should be vertexCount/2). If - // numTotalEdges is negative and the join type is "kRound", it also instructs the - // shader to only allocate one segment the preceding round join. - fAttribs.emplace_back("argsAttr", kFloat3_GrVertexAttribType, kFloat3_GrSLType); - } else { - SkASSERT(fMode == Mode::kFixedCount); - // argsAttr contains the lastControlPoint for setting up the join. - fAttribs.emplace_back("argsAttr", kFloat2_GrVertexAttribType, kFloat2_GrSLType); - } - } - if (fShaderFlags & ShaderFlags::kDynamicStroke) { - fAttribs.emplace_back("dynamicStrokeAttr", kFloat2_GrVertexAttribType, - kFloat2_GrSLType); - } - if (fShaderFlags & ShaderFlags::kDynamicColor) { - fAttribs.emplace_back("dynamicColorAttr", - (fShaderFlags & ShaderFlags::kWideColor) - ? kFloat4_GrVertexAttribType - : kUByte4_norm_GrVertexAttribType, - kHalf4_GrSLType); - } - if (fMode == Mode::kHardwareTessellation) { - this->setVertexAttributes(fAttribs.data(), fAttribs.count()); - } else { - this->setInstanceAttributes(fAttribs.data(), fAttribs.count()); - } - SkASSERT(fAttribs.count() <= kMaxAttribCount); - } + GrStrokeTessellationShader(Mode, ShaderFlags, const SkMatrix& viewMatrix, const SkStrokeRec&, + SkPMColor4f, int8_t maxParametricSegments_log2, const GrShaderCaps&); Mode mode() const { return fMode; } ShaderFlags flags() const { return fShaderFlags; } @@ -182,6 +116,12 @@ 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(GrVertexWriter 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 12e9132183..53776e3cd7 100644 --- a/src/gpu/tessellate/shaders/GrStrokeTessellationShader_InstancedImpl.cpp +++ b/src/gpu/tessellate/shaders/GrStrokeTessellationShader_InstancedImpl.cpp @@ -11,7 +11,6 @@ #include "src/gpu/glsl/GrGLSLFragmentShaderBuilder.h" #include "src/gpu/glsl/GrGLSLGeometryProcessor.h" #include "src/gpu/glsl/GrGLSLVertexGeoBuilder.h" -#include "src/gpu/tessellate/GrStrokeTessellator.h" void GrStrokeTessellationShader::InstancedImpl::onEmitCode(EmitArgs& args, GrGPArgs* gpArgs) { const auto& shader = args.fGeomProc.cast(); @@ -126,6 +125,15 @@ void GrStrokeTessellationShader::InstancedImpl::onEmitCode(EmitArgs& args, GrGPA tan1 = float2(-1,0); })"); + if (args.fShaderCaps->vertexIDSupport()) { + // If we don't have sk_VertexID support then "edgeID" already came in as a vertex attrib. + args.fVertBuilder->codeAppend(R"( + float edgeID = float(sk_VertexID >> 1); + if ((sk_VertexID & 1) != 0) { + edgeID = -edgeID; + })"); + } + // Potential optimization: (shader.hasDynamicStroke() && shader.hasRoundJoins())? if (shader.stroke().getJoin() == SkPaint::kRound_Join || shader.hasDynamicStroke()) { args.fVertBuilder->codeAppend(R"( @@ -167,7 +175,7 @@ void GrStrokeTessellationShader::InstancedImpl::onEmitCode(EmitArgs& args, GrGPA // NOTE: Since the curve is not allowed to inflect, we can just check F'(.5) x F''(.5). // NOTE: F'(.5) x F''(.5) has the same sign as (P2 - P0) x (P3 - P1) float turn = cross(P[2] - P[0], P[3] - P[1]); - float combinedEdgeID = float(sk_VertexID >> 1) - numEdgesInJoin; + float combinedEdgeID = abs(edgeID) - numEdgesInJoin; if (combinedEdgeID < 0) { tan1 = tan0; // Don't let tan0 become zero. The code as-is isn't built to handle that case. tan0=0 @@ -188,7 +196,7 @@ void GrStrokeTessellationShader::InstancedImpl::onEmitCode(EmitArgs& args, GrGPA } float numRadialSegments; - float strokeOutset = ((sk_VertexID & 1) == 0) ? +1 : -1; + float strokeOutset = sign(edgeID); if (combinedEdgeID < 0) { // We belong to the preceding join. The first and final edges get duplicated, so we only // have "numEdgesInJoin - 2" segments. @@ -235,8 +243,8 @@ void GrStrokeTessellationShader::InstancedImpl::onEmitCode(EmitArgs& args, GrGPA if (joinType == SkPaint::kMiter_Join || shader.hasDynamicStroke()) { args.fVertBuilder->codeAppendf(R"( - // Vertices #4 and #5 belong to the edge of the join that extends to the miter point. - if ((sk_VertexID | 1) == (4 | 5) && %s) { + // Edge #2 extends to the miter point. + if (abs(edgeID) == 2 && %s) { strokeOutset *= miter_extent(cosTheta, JOIN_TYPE/*miterLimit*/); })", shader.hasDynamicStroke() ? "JOIN_TYPE > 0/*Is the join a miter type?*/" : "true"); } @@ -245,3 +253,13 @@ void GrStrokeTessellationShader::InstancedImpl::onEmitCode(EmitArgs& args, GrGPA this->emitFragmentCode(shader, args); } + +void GrStrokeTessellationShader::InitializeVertexIDFallbackBuffer(GrVertexWriter vertexWriter, + size_t bufferSize) { + SkASSERT(bufferSize % (sizeof(float) * 2) == 0); + int edgeCount = bufferSize / (sizeof(float) * 2); + for (int i = 0; i < edgeCount; ++i) { + vertexWriter.write(i); + vertexWriter.write(-i); + } +}