Flutter Engine
The Flutter Engine
StrokeTessellator.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
16#include "src/core/SkGeometry.h"
17#include "src/gpu/ResourceKey.h"
30
31#include <algorithm>
32#include <cmath>
33#include <utility>
34
35namespace skgpu::ganesh {
36
37namespace {
38
39using namespace skgpu::tess;
40
41using StrokeWriter = PatchWriter<VertexChunkPatchAllocator,
49
50void write_fixed_count_patches(StrokeWriter&& patchWriter,
51 const SkMatrix& shaderMatrix,
52 StrokeTessellator::PathStrokeList* pathStrokeList) {
53 // The vector xform approximates how the control points are transformed by the shader to
54 // more accurately compute how many *parametric* segments are needed.
55 // getMaxScale() returns -1 if it can't compute a scale factor (e.g. perspective), taking the
56 // absolute value automatically converts that to an identity scale factor for our purposes.
57 patchWriter.setShaderTransform(wangs_formula::VectorXform{shaderMatrix},
58 std::abs(shaderMatrix.getMaxScale()));
59 if (!(patchWriter.attribs() & PatchAttribs::kStrokeParams)) {
60 // Strokes are static. Calculate tolerances once.
61 patchWriter.updateUniformStrokeParams(pathStrokeList->fStroke);
62 }
63
64 for (auto* pathStroke = pathStrokeList; pathStroke; pathStroke = pathStroke->fNext) {
65 const SkStrokeRec& stroke = pathStroke->fStroke;
66 if (patchWriter.attribs() & PatchAttribs::kStrokeParams) {
67 // Strokes are dynamic. Calculate tolerances every time.
68 patchWriter.updateStrokeParamsAttrib(stroke);
69 }
70 if (patchWriter.attribs() & PatchAttribs::kColor) {
71 patchWriter.updateColorAttrib(pathStroke->fColor);
72 }
73
74 StrokeIterator strokeIter(pathStroke->fPath, &pathStroke->fStroke, &shaderMatrix);
75 while (strokeIter.next()) {
76 using Verb = StrokeIterator::Verb;
77 const SkPoint* p = strokeIter.pts();
78 int numChops;
79 switch (strokeIter.verb()) {
80 case Verb::kContourFinished:
81 patchWriter.writeDeferredStrokePatch();
82 break;
83 case Verb::kCircle:
84 // Round cap or else an empty stroke that is specified to be drawn as a circle.
85 patchWriter.writeCircle(p[0]);
86 [[fallthrough]];
87 case Verb::kMoveWithinContour:
88 // A regular kMove invalidates the previous control point; the stroke iterator
89 // tells us a new value to use.
90 patchWriter.updateJoinControlPointAttrib(p[0]);
91 break;
92 case Verb::kLine:
93 patchWriter.writeLine(p[0], p[1]);
94 break;
95 case Verb::kQuad:
96 if (ConicHasCusp(p)) {
97 // The cusp is always at the midtandent.
99 patchWriter.writeCircle(cusp);
100 // A quad can only have a cusp if it's flat with a 180-degree turnaround.
101 patchWriter.writeLine(p[0], cusp);
102 patchWriter.writeLine(cusp, p[2]);
103 } else {
104 patchWriter.writeQuadratic(p);
105 }
106 break;
107 case Verb::kConic:
108 if (ConicHasCusp(p)) {
109 // The cusp is always at the midtandent.
110 SkConic conic(p, strokeIter.w());
111 SkPoint cusp = conic.evalAt(conic.findMidTangent());
112 patchWriter.writeCircle(cusp);
113 // A conic can only have a cusp if it's flat with a 180-degree turnaround.
114 patchWriter.writeLine(p[0], cusp);
115 patchWriter.writeLine(cusp, p[2]);
116 } else {
117 patchWriter.writeConic(p, strokeIter.w());
118 }
119 break;
120 case Verb::kCubic:
121 SkPoint chops[10];
122 float T[2];
123 bool areCusps;
124 numChops = FindCubicConvex180Chops(p, T, &areCusps);
125 if (numChops == 0) {
126 patchWriter.writeCubic(p);
127 } else if (numChops == 1) {
128 SkChopCubicAt(p, chops, T[0]);
129 if (areCusps) {
130 patchWriter.writeCircle(chops[3]);
131 // In a perfect world, these 3 points would be be equal after chopping
132 // on a cusp.
133 chops[2] = chops[4] = chops[3];
134 }
135 patchWriter.writeCubic(chops);
136 patchWriter.writeCubic(chops + 3);
137 } else {
138 SkASSERT(numChops == 2);
139 SkChopCubicAt(p, chops, T[0], T[1]);
140 if (areCusps) {
141 patchWriter.writeCircle(chops[3]);
142 patchWriter.writeCircle(chops[6]);
143 // Two cusps are only possible if it's a flat line with two 180-degree
144 // turnarounds.
145 patchWriter.writeLine(chops[0], chops[3]);
146 patchWriter.writeLine(chops[3], chops[6]);
147 patchWriter.writeLine(chops[6], chops[9]);
148 } else {
149 patchWriter.writeCubic(chops);
150 patchWriter.writeCubic(chops + 3);
151 patchWriter.writeCubic(chops + 6);
152 }
153 }
154 break;
155 }
156 }
157 }
158}
159
160} // namespace
161
162
163SKGPU_DECLARE_STATIC_UNIQUE_KEY(gVertexIDFallbackBufferKey);
164
166 const SkMatrix& shaderMatrix,
167 PathStrokeList* pathStrokeList,
168 int totalCombinedStrokeVerbCnt) {
169 LinearTolerances worstCase;
170 const int preallocCount = FixedCountStrokes::PreallocCount(totalCombinedStrokeVerbCnt);
171 StrokeWriter patchWriter{fAttribs, &worstCase, target, &fVertexChunkArray, preallocCount};
172
173 write_fixed_count_patches(std::move(patchWriter), shaderMatrix, pathStrokeList);
175
176 if (!target->caps().shaderCaps()->fVertexIDSupport) {
177 // Our shader won't be able to use sk_VertexID. Bind a fallback vertex buffer with the IDs
178 // in it instead.
180
181 SKGPU_DEFINE_STATIC_UNIQUE_KEY(gVertexIDFallbackBufferKey);
182
183 fVertexBufferIfNoIDSupport = target->resourceProvider()->findOrMakeStaticBuffer(
186 gVertexIDFallbackBufferKey,
188 }
189}
190
192 if (fVertexChunkArray.empty() || fVertexCount <= 0) {
193 return;
194 }
195 if (!flushState->caps().shaderCaps()->fVertexIDSupport &&
197 return;
198 }
199 for (const auto& instanceChunk : fVertexChunkArray) {
200 flushState->bindBuffers(nullptr, instanceChunk.fBuffer, fVertexBufferIfNoIDSupport);
201 flushState->drawInstanced(instanceChunk.fCount,
202 instanceChunk.fBase,
204 0);
205 }
206}
207
208} // namespace skgpu::ganesh
#define SKGPU_DEFINE_STATIC_UNIQUE_KEY(name)
Definition: ResourceKey.h:324
#define SkASSERT(cond)
Definition: SkAssert.h:116
float SkFindQuadMidTangent(const SkPoint src[3])
Definition: SkGeometry.cpp:231
void SkChopCubicAt(const SkPoint src[4], SkPoint dst[7], SkScalar t)
Definition: SkGeometry.cpp:473
void SkEvalQuadAt(const SkPoint src[3], SkScalar t, SkPoint *pt, SkVector *tangent)
Definition: SkGeometry.cpp:132
const GrShaderCaps * shaderCaps() const
Definition: GrCaps.h:63
void bindBuffers(sk_sp< const GrBuffer > indexBuffer, sk_sp< const GrBuffer > instanceBuffer, sk_sp< const GrBuffer > vertexBuffer, GrPrimitiveRestart primitiveRestart=GrPrimitiveRestart::kNo)
void drawInstanced(int instanceCount, int baseInstance, int vertexCount, int baseVertex)
const GrCaps & caps() const final
SkScalar getMaxScale() const
Definition: SkMatrix.cpp:1531
void draw(GrOpFlushState *) const
void prepare(GrMeshDrawTarget *, const SkMatrix &shaderMatrix, PathStrokeList *, int totalCombinedStrokeVerbCnt)
sk_sp< const GrGpuBuffer > fVertexBufferIfNoIDSupport
static constexpr int PreallocCount(int totalCombinedPathVerbCnt)
static void WriteVertexBuffer(VertexWriter, size_t bufferSize)
static constexpr int kMaxEdgesNoVertexIDs
static int VertexCount(const LinearTolerances &tolerances)
static constexpr size_t VertexBufferSize()
bool empty() const
Definition: SkTArray.h:199
uint32_t * target
static float min(float r, float g, float b)
Definition: hsl.cpp:48
StrokeTessellator::PathStrokeList PathStrokeList
SKGPU_DECLARE_STATIC_UNIQUE_KEY(gUnitQuadBufferKey)
const SkPoint kQuad[4]
bool ConicHasCusp(const SkPoint p[3])
Definition: Tessellation.h:131
int FindCubicConvex180Chops(const SkPoint pts[], float T[2], bool *areCusps)
AI float conic(float tolerance, const SkPoint pts[], float w, const VectorXform &vectorXform=VectorXform())
Definition: WangsFormula.h:287
SIN Vec< N, float > abs(const Vec< N, float > &x)
Definition: SkVx.h:707
#define T
Definition: precompiler.cc:65
bool fVertexIDSupport
Definition: GrShaderCaps.h:36