Flutter Engine
The Flutter Engine
Loading...
Searching...
No Matches
TessellateStrokesRenderStep.cpp
Go to the documentation of this file.
1/*
2 * Copyright 2022 Google LLC
3 *
4 * Use of this source code is governed by a BSD-style license that can be
5 * found in the LICENSE file.
6 */
7
9
10#include "include/core/SkM44.h"
11#include "src/base/SkVx.h"
12#include "src/core/SkGeometry.h"
13#include "src/sksl/SkSLString.h"
14
21
25
26
27namespace skgpu::graphite {
28
29namespace {
30
31using namespace skgpu::tess;
32
33// Always use dynamic stroke params and join control points, track the join control point in
34// PatchWriter and replicate line end points (match Ganesh's shader behavior).
35//
36// No explicit curve type on platforms that support infinity.
37// No color or wide color attribs, since it might always be part of the PaintParams
38// or we'll add a color-only fast path to RenderStep later.
39static constexpr PatchAttribs kAttribs = PatchAttribs::kJoinControlPoint |
40 PatchAttribs::kStrokeParams |
41 PatchAttribs::kPaintDepth |
42 PatchAttribs::kSsboIndex;
43static constexpr PatchAttribs kAttribsWithCurveType = kAttribs | PatchAttribs::kExplicitCurveType;
52
53// The order of the attribute declarations must match the order used by
54// PatchWriter::emitPatchAttribs, i.e.:
55// join << fanPoint << stroke << color << depth << curveType << ssboIndices
56static constexpr Attribute kBaseAttributes[] = {
63
64static constexpr Attribute kAttributesWithCurveType[] = {
72
73static constexpr SkSpan<const Attribute> kAttributes[2] = {kAttributesWithCurveType,
74 kBaseAttributes};
75
76} // namespace
77
79 : RenderStep("TessellateStrokeRenderStep",
80 "",
81 Flags::kRequiresMSAA | Flags::kPerformsShading,
82 /*uniforms=*/{{"affineMatrix", SkSLType::kFloat4},
83 {"translate", SkSLType::kFloat2},
84 {"maxScale", SkSLType::kFloat}},
87 /*vertexAttrs=*/ {},
88 /*instanceAttrs=*/kAttributes[infinitySupport])
89 , fInfinitySupport(infinitySupport) {}
90
92
94 // TODO: Assumes vertex ID support for now, max edges must equal
95 // skgpu::tess::FixedCountStrokes::kMaxEdges -> (2^14 - 1) -> 16383
97 R"(
98 float edgeID = float(sk_VertexID >> 1);
99 if ((sk_VertexID & 1) != 0) {
100 edgeID = -edgeID;
102 float2x2 affine = float2x2(affineMatrix.xy, affineMatrix.zw);
103 float4 devAndLocalCoords = tessellate_stroked_curve(
104 edgeID, 16383, affine, translate, maxScale, p01, p23, prevPoint,
105 stroke, %s);
106 float4 devPosition = float4(devAndLocalCoords.xy, depth, 1.0);
107 stepLocalCoords = devAndLocalCoords.zw;
108 )",
109 fInfinitySupport ? "curve_type_using_inf_support(p23)" : "curveType");
110}
111
113 const DrawParams& params,
114 skvx::ushort2 ssboIndices) const {
115 SkPath path = params.geometry().shape().asPath(); // TODO: Iterate the Shape directly
116
117 int patchReserveCount = FixedCountStrokes::PreallocCount(path.countVerbs());
118 // Stroke tessellation does not use fixed indices or vertex data, and only needs the vertex ID
119 static const BindBufferInfo kNullBinding = {};
120 // TODO: All HW that Graphite will run on should support instancing ith sk_VertexID, but when
121 // we support Vulkan+Swiftshader, we will need the vertex buffer ID fallback unless Swiftshader
122 // has figured out how to support vertex IDs before then.
123 Writer writer{fInfinitySupport ? kAttribs : kAttribsWithCurveType,
124 *dw,
125 kNullBinding,
126 kNullBinding,
127 patchReserveCount};
128 writer.updatePaintDepthAttrib(params.order().depthAsFloat());
129 writer.updateSsboIndexAttrib(ssboIndices);
130
131 // The vector xform approximates how the control points are transformed by the shader to
132 // more accurately compute how many *parametric* segments are needed.
133 // getMaxScale() returns -1 if it can't compute a scale factor (e.g. perspective), taking the
134 // absolute value automatically converts that to an identity scale factor for our purposes.
135 writer.setShaderTransform(wangs_formula::VectorXform{params.transform().matrix()},
136 params.transform().maxScaleFactor());
137
138 SkASSERT(params.isStroke());
139 writer.updateStrokeParamsAttrib({params.strokeStyle().halfWidth(),
140 params.strokeStyle().joinLimit()});
141
142 // TODO: If PatchWriter can handle adding caps to its deferred patches, and we can convert
143 // hairlines to use round caps instead of square, then StrokeIterator can be deleted entirely.
144 // Besides being simpler, PatchWriter already has what it needs from the shader matrix and
145 // stroke params, so we don't have to re-extract them here.
146 SkMatrix shaderMatrix = params.transform();
148 stroke.setStrokeStyle(params.strokeStyle().width());
149 stroke.setStrokeParams(params.strokeStyle().cap(),
150 params.strokeStyle().join(),
151 params.strokeStyle().miterLimit());
152 StrokeIterator strokeIter(path, &stroke, &shaderMatrix);
153 while (strokeIter.next()) {
154 using Verb = StrokeIterator::Verb;
155 const SkPoint* p = strokeIter.pts();
156 int numChops;
157
158 // TODO: The cusp detection logic should be moved into PatchWriter and shared between
159 // this and StrokeTessellator.cpp, but that will require updating a lot of SkGeometry to
160 // operate on float2 (skvx) instead of the legacy SkNx or SkPoint.
161 switch (strokeIter.verb()) {
162 case Verb::kContourFinished:
163 writer.writeDeferredStrokePatch();
164 break;
165 case Verb::kCircle:
166 // Round cap or else an empty stroke that is specified to be drawn as a circle.
167 writer.writeCircle(p[0]);
168 [[fallthrough]];
169 case Verb::kMoveWithinContour:
170 // A regular kMove invalidates the previous control point; the stroke iterator
171 // tells us a new value to use.
172 writer.updateJoinControlPointAttrib(p[0]);
173 break;
174 case Verb::kLine:
175 writer.writeLine(p[0], p[1]);
176 break;
177 case Verb::kQuad:
178 if (ConicHasCusp(p)) {
179 // The cusp is always at the midtandent.
181 writer.writeCircle(cusp);
182 // A quad can only have a cusp if it's flat with a 180-degree turnaround.
183 writer.writeLine(p[0], cusp);
184 writer.writeLine(cusp, p[2]);
185 } else {
186 writer.writeQuadratic(p);
187 }
188 break;
189 case Verb::kConic:
190 if (ConicHasCusp(p)) {
191 // The cusp is always at the midtandent.
192 SkConic conic(p, strokeIter.w());
193 SkPoint cusp = conic.evalAt(conic.findMidTangent());
194 writer.writeCircle(cusp);
195 // A conic can only have a cusp if it's flat with a 180-degree turnaround.
196 writer.writeLine(p[0], cusp);
197 writer.writeLine(cusp, p[2]);
198 } else {
199 writer.writeConic(p, strokeIter.w());
200 }
201 break;
202 case Verb::kCubic:
203 SkPoint chops[10];
204 float T[2];
205 bool areCusps;
206 numChops = FindCubicConvex180Chops(p, T, &areCusps);
207 if (numChops == 0) {
208 writer.writeCubic(p);
209 } else if (numChops == 1) {
210 SkChopCubicAt(p, chops, T[0]);
211 if (areCusps) {
212 writer.writeCircle(chops[3]);
213 // In a perfect world, these 3 points would be be equal after chopping
214 // on a cusp.
215 chops[2] = chops[4] = chops[3];
216 }
217 writer.writeCubic(chops);
218 writer.writeCubic(chops + 3);
219 } else {
220 SkASSERT(numChops == 2);
221 SkChopCubicAt(p, chops, T[0], T[1]);
222 if (areCusps) {
223 writer.writeCircle(chops[3]);
224 writer.writeCircle(chops[6]);
225 // Two cusps are only possible if it's a flat line with two 180-degree
226 // turnarounds.
227 writer.writeLine(chops[0], chops[3]);
228 writer.writeLine(chops[3], chops[6]);
229 writer.writeLine(chops[6], chops[9]);
230 } else {
231 writer.writeCubic(chops);
232 writer.writeCubic(chops + 3);
233 writer.writeCubic(chops + 6);
234 }
235 }
236 break;
237 }
238 }
239}
240
242 PipelineDataGatherer* gatherer) const {
243 // TODO: Implement perspective
244 SkASSERT(params.transform().type() < Transform::Type::kPerspective);
245
246 SkDEBUGCODE(UniformExpectationsValidator uev(gatherer, this->uniforms());)
247
248 // affineMatrix = float4 (2x2 of transform), translate = float2, maxScale = float
249 // Column-major 2x2 of the transform.
250 SkV4 upper = {params.transform().matrix().rc(0, 0), params.transform().matrix().rc(1, 0),
251 params.transform().matrix().rc(0, 1), params.transform().matrix().rc(1, 1)};
252 gatherer->write(upper);
253
254 gatherer->write(SkPoint{params.transform().matrix().rc(0, 3),
255 params.transform().matrix().rc(1, 3)});
256
257 gatherer->write(params.transform().maxScaleFactor());
258}
259
260} // namespace skgpu::graphite
#define SkASSERT(cond)
Definition SkAssert.h:116
#define SkDEBUGCODE(...)
Definition SkDebug.h:23
float SkFindQuadMidTangent(const SkPoint src[3])
void SkChopCubicAt(const SkPoint src[4], SkPoint dst[7], SkScalar t)
void SkEvalQuadAt(const SkPoint src[3], SkScalar t, SkPoint *pt, SkVector *tangent)
@ kHairline_InitStyle
Definition SkStrokeRec.h:25
void setStrokeStyle(SkScalar width, bool strokeAndFill=false)
SkSpan< const Uniform > uniforms() const
Definition Renderer.h:143
void writeUniformsAndTextures(const DrawParams &, PipelineDataGatherer *) const override
void writeVertices(DrawWriter *, const DrawParams &, skvx::ushort2 ssboIndices) const override
static constexpr int PreallocCount(int totalCombinedPathVerbCnt)
const EmbeddedViewParams * params
unsigned useCenter Optional< SkMatrix > matrix
Definition SkRecords.h:258
std::string printf(const char *fmt,...) SK_PRINTF_LIKE(1
static constexpr DepthStencilSettings kDirectDepthGreaterPass
bool ConicHasCusp(const SkPoint p[3])
int FindCubicConvex180Chops(const SkPoint pts[], float T[2], bool *areCusps)
AI float conic(float tolerance, const SkPoint pts[], float w, const VectorXform &vectorXform=VectorXform())
#define T
Definition SkM44.h:98