• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2021 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 
8 #ifndef tessellate_PatchWriter_DEFINED
9 #define tessellate_PatchWriter_DEFINED
10 
11 #include "include/private/SkColorData.h"
12 #include "src/gpu/GrVertexChunkArray.h"
13 #include "src/gpu/tessellate/Tessellation.h"
14 #include "src/gpu/tessellate/WangsFormula.h"
15 
16 #define AI SK_ALWAYS_INLINE
17 
18 namespace skgpu {
19 
20 #if SK_GPU_V1
21 class PathTessellator;
22 class StrokeTessellator;
23 #endif
24 
25 // Writes out tessellation patches, formatted with their specific attribs, to a GPU buffer.
26 class PatchWriter {
27     using VectorXform = wangs_formula::VectorXform;
28 public:
PatchWriter(GrMeshDrawTarget * target,GrVertexChunkArray * vertexChunkArray,PatchAttribs attribs,int maxTessellationSegments,size_t patchStride,int initialAllocCount)29     PatchWriter(GrMeshDrawTarget* target,
30                 GrVertexChunkArray* vertexChunkArray,
31                 PatchAttribs attribs,
32                 int maxTessellationSegments,
33                 size_t patchStride,
34                 int initialAllocCount)
35             : fAttribs(attribs)
36             , fMaxSegments_pow2(pow2(maxTessellationSegments))
37             , fMaxSegments_pow4(pow2(fMaxSegments_pow2))
38             , fChunker(target, vertexChunkArray, patchStride, initialAllocCount) {
39         // For fans or strokes, the minimum required segment count is 1 (making either a triangle
40         // with the fan point, or a stroked line). Otherwise, we need 2 segments to represent
41         // triangles purely from the tessellated vertices.
42         fCurrMinSegments_pow4 = (attribs & PatchAttribs::kFanPoint ||
43                                  attribs & PatchAttribs::kJoinControlPoint) ? 1.f : 16.f; // 2^4
44     }
45 
46 #if SK_GPU_V1
47     // Create PatchWriters that write directly to the GrVertexChunkArrays stored on the provided
48     // tessellators.
49     PatchWriter(GrMeshDrawTarget*, PathTessellator*,
50                 int maxTessellationSegments, int initialPatchAllocCount);
51     PatchWriter(GrMeshDrawTarget*, StrokeTessellator*,
52                 int maxTessellationSegments, int initialPatchAllocCount);
53 #endif
54 
~PatchWriter()55     ~PatchWriter() {
56         // finishStrokeContour() should have been called before this was deleted (or never used).
57         SkASSERT(!fHasDeferredPatch && !fHasJoinControlPoint);
58     }
59 
attribs()60     PatchAttribs attribs() const { return fAttribs; }
61 
62     // Fast log2 of minimum required # of segments per tracked Wang's formula calculations.
requiredResolveLevel()63     int requiredResolveLevel() const {
64         return wangs_formula::nextlog16(fCurrMinSegments_pow4); // log16(n^4) == log2(n)
65     }
66     // Fast minimum required # of segments from tracked Wang's formula calculations.
requiredFixedSegments()67     int requiredFixedSegments() const {
68         return SkScalarCeilToInt(wangs_formula::root4(fCurrMinSegments_pow4));
69     }
70 
71     // Updates the stroke's join control point that will be written out with each patch. This is
72     // automatically adjusted when appending various geometries (e.g. Conic/Cubic), but sometimes
73     // must be set explicitly.
74     //
75     // PatchAttribs::kJoinControlPoint must be enabled.
updateJoinControlPointAttrib(SkPoint lastControlPoint)76     void updateJoinControlPointAttrib(SkPoint lastControlPoint) {
77         SkASSERT(fAttribs & PatchAttribs::kJoinControlPoint && lastControlPoint.isFinite());
78         fJoinControlPointAttrib = lastControlPoint;
79         fHasJoinControlPoint = true;
80     }
81     // Completes a closed contour of a stroke by rewriting a deferred patch with now-available
82     // join control point information. Automatically resets the join control point attribute.
83     //
84     // PatchAttribs::kJoinControlPoint must be enabled.
writeDeferredStrokePatch()85     void writeDeferredStrokePatch() {
86         SkASSERT(fAttribs & PatchAttribs::kJoinControlPoint);
87         if (fHasDeferredPatch) {
88             SkASSERT(fHasJoinControlPoint);
89             // Overwrite join control point with updated value, which is the first attribute
90             // after the 4 control points.
91             memcpy(SkTAddOffset<void>(fDeferredPatchStorage, 4 * sizeof(SkPoint)),
92                    &fJoinControlPointAttrib, sizeof(SkPoint));
93             if (VertexWriter vw = fChunker.appendVertex()) {
94                 vw << VertexWriter::Array<char>(fDeferredPatchStorage, fChunker.stride());
95             }
96         }
97 
98         fHasDeferredPatch = false;
99         fHasJoinControlPoint = false;
100     }
101     // TODO: These are only used by StrokeHardwareTessellator and ideally its patch writing logic
102     // should be simplified like StrokeFixedCountTessellator's, and then this can go away.
resetJoinControlPointAttrib()103     void resetJoinControlPointAttrib() {
104         SkASSERT(fAttribs & PatchAttribs::kJoinControlPoint);
105         // Should have already been written or caller should manually defer
106         SkASSERT(!fHasDeferredPatch);
107         fHasJoinControlPoint = false;
108     }
joinControlPoint()109     SkPoint joinControlPoint() const { return fJoinControlPointAttrib; }
hasJoinControlPoint()110     bool hasJoinControlPoint() const { return fHasJoinControlPoint; }
111 
112     // Updates the fan point that will be written out with each patch (i.e., the point that wedges
113     // fan around).
114     // PatchAttribs::kFanPoint must be enabled.
updateFanPointAttrib(SkPoint fanPoint)115     void updateFanPointAttrib(SkPoint fanPoint) {
116         SkASSERT(fAttribs & PatchAttribs::kFanPoint);
117         fFanPointAttrib = fanPoint;
118     }
119 
120     // Updates the stroke params that are written out with each patch.
121     // PatchAttribs::kStrokeParams must be enabled.
updateStrokeParamsAttrib(StrokeParams strokeParams)122     void updateStrokeParamsAttrib(StrokeParams strokeParams) {
123         SkASSERT(fAttribs & PatchAttribs::kStrokeParams);
124         fStrokeParamsAttrib = strokeParams;
125     }
126 
127     // Updates the color that will be written out with each patch.
128     // PatchAttribs::kColor must be enabled.
updateColorAttrib(const SkPMColor4f & color)129     void updateColorAttrib(const SkPMColor4f& color) {
130         SkASSERT(fAttribs & PatchAttribs::kColor);
131         fColorAttrib.set(color, fAttribs & PatchAttribs::kWideColorIfEnabled);
132     }
133 
134     /**
135      * writeX functions for supported patch geometry types. Every geometric type is converted to an
136      * equivalent cubic or conic, so this will always write at minimum 8 floats for the four control
137      * points (cubic) or three control points and {w, inf} (conics). The PatchWriter additionally
138      * writes the current values of all attributes enabled in its PatchAttribs flags.
139      */
140 
141     // Writes four control points manually prepared.
142     // TODO: Only used by StrokeHardwareTessellator
writeHwPatch(const SkPoint p[4])143     AI void writeHwPatch(const SkPoint p[4]) {
144         float4 p0p1 = float4::Load(p);
145         float4 p2p3 = float4::Load(p + 2);
146         this->writePatch(p0p1.lo, p0p1.hi, p2p3.lo, p2p3.hi, /*unused*/0.f);
147     }
148 
149     // Write a cubic curve with its four control points.
150     AI void writeCubic(float2 p0, float2 p1, float2 p2, float2 p3,
151                        const VectorXform& shaderXform,
152                        float precision = kTessellationPrecision) {
153         float n4 = wangs_formula::cubic_pow4(precision, p0, p1, p2, p3, shaderXform);
154         if (this->writesCurvesOnly()) {
155             if (n4 <= 1.f) {
156                 // This cubic only needs one segment (e.g. a line) but we're not filling space with
157                 // fans or stroking, so nothing actually needs to be drawn.
158                 return;
159             }
160         }
161         if (this->updateRequiredSegments(n4)) {
162             this->writeCubicPatch(p0, p1, p2, p3);
163         } else {
164             int numPatches = SkScalarCeilToInt(wangs_formula::root4(
165                     std::min(n4, pow4(kMaxTessellationSegmentsPerCurve)) / fMaxSegments_pow4));
166             this->chopAndWriteCubics(p0, p1, p2, p3, numPatches);
167         }
168     }
169     AI void writeCubic(const SkPoint pts[4],
170                        const VectorXform& shaderXform,
171                        float precision = kTessellationPrecision) {
172         float4 p0p1 = float4::Load(pts);
173         float4 p2p3 = float4::Load(pts + 2);
174         this->writeCubic(p0p1.lo, p0p1.hi, p2p3.lo, p2p3.hi, shaderXform, precision);
175     }
176 
177     // Write a conic curve with three control points and 'w', with the last coord of the last
178     // control point signaling a conic by being set to infinity.
179     AI void writeConic(float2 p0, float2 p1, float2 p2, float w,
180                        const VectorXform& shaderXform,
181                        float precision = kTessellationPrecision) {
182         float n2 = wangs_formula::conic_pow2(precision, p0, p1, p2, w, shaderXform);
183         if (this->writesCurvesOnly()) {
184             if (n2 <= 1.f) {
185                 // This conic only needs one segment (e.g. a line) but we're not filling space with
186                 // fans or stroking, so nothing actually needs to be drawn.
187                 return;
188             }
189         }
190         if (this->updateRequiredSegments(n2*n2)) {
191             this->writeConicPatch(p0, p1, p2, w);
192         } else {
193             int numPatches = SkScalarCeilToInt(sqrtf(
194                     std::min(n2, pow2(kMaxTessellationSegmentsPerCurve)) / fMaxSegments_pow2));
195             this->chopAndWriteConics(p0, p1, p2, w, numPatches);
196         }
197     }
198     AI void writeConic(const SkPoint pts[3], float w,
199                        const VectorXform& shaderXform,
200                        float precision = kTessellationPrecision) {
201         this->writeConic(skvx::bit_pun<float2>(pts[0]),
202                          skvx::bit_pun<float2>(pts[1]),
203                          skvx::bit_pun<float2>(pts[2]),
204                          w, shaderXform, precision);
205     }
206 
207     // Write a quadratic curve that automatically converts its three control points into an
208     // equivalent cubic.
209     AI void writeQuadratic(float2 p0, float2 p1, float2 p2,
210                            const VectorXform& shaderXform,
211                            float precision = kTessellationPrecision) {
212         float n4 = wangs_formula::quadratic_pow4(precision, p0, p1, p2, shaderXform);
213         if (this->writesCurvesOnly()) {
214             if (n4 <= 1.f) {
215                 // This quad only needs one segment (e.g. a line) but we're not filling space with
216                 // fans or stroking, so nothing actually needs to be drawn.
217                 return;
218             }
219         }
220         if (this->updateRequiredSegments(n4)) {
221             this->writeQuadPatch(p0, p1, p2);
222         } else {
223             int numPatches = SkScalarCeilToInt(wangs_formula::root4(
224                     std::min(n4, pow4(kMaxTessellationSegmentsPerCurve)) / fMaxSegments_pow4));
225             this->chopAndWriteQuads(p0, p1, p2, numPatches);
226         }
227     }
228     AI void writeQuadratic(const SkPoint pts[3],
229                            const VectorXform& shaderXform,
230                            float precision = kTessellationPrecision) {
231         this->writeQuadratic(skvx::bit_pun<float2>(pts[0]),
232                              skvx::bit_pun<float2>(pts[1]),
233                              skvx::bit_pun<float2>(pts[2]),
234                              shaderXform, precision);
235     }
236 
237     // Write a line that is automatically converted into an equivalent cubic.
writeLine(float4 p0p1)238     AI void writeLine(float4 p0p1) {
239         // No chopping needed, and should have been reset to 1 segment if using writeLine
240         SkASSERT(fCurrMinSegments_pow4 >= 1.f);
241         this->writeCubicPatch(p0p1.lo, (p0p1.zwxy() - p0p1) * (1/3.f) + p0p1, p0p1.hi);
242     }
writeLine(float2 p0,float2 p1)243     AI void writeLine(float2 p0, float2 p1) { this->writeLine({p0, p1}); }
writeLine(SkPoint p0,SkPoint p1)244     AI void writeLine(SkPoint p0, SkPoint p1) {
245         this->writeLine(skvx::bit_pun<float2>(p0), skvx::bit_pun<float2>(p1));
246     }
247 
248     // Write a triangle by setting it to a conic with w=Inf, and using a distinct
249     // explicit curve type for when inf isn't supported in shaders.
writeTriangle(float2 p0,float2 p1,float2 p2)250     AI void writeTriangle(float2 p0, float2 p1, float2 p2) {
251         // No chopping needed, and should have been reset to 2 segments if using writeTriangle.
252         SkASSERT(fCurrMinSegments_pow4 >= (2*2*2*2));
253         this->writePatch(p0, p1, p2, {SK_FloatInfinity, SK_FloatInfinity},
254                          kTriangularConicCurveType);
255     }
writeTriangle(SkPoint p0,SkPoint p1,SkPoint p2)256     AI void writeTriangle(SkPoint p0, SkPoint p1, SkPoint p2) {
257         this->writeTriangle(skvx::bit_pun<float2>(p0),
258                             skvx::bit_pun<float2>(p1),
259                             skvx::bit_pun<float2>(p2));
260     }
261 
262     // Writes a circle used for round caps and joins in stroking, encoded as a cubic with
263     // identical control points and an empty join.
writeCircle(SkPoint p)264     AI void writeCircle(SkPoint p) {
265         // This does not use writePatch() because it uses its own location as the join attribute
266         // value instead of fJoinControlPointAttrib and never defers.
267         SkASSERT(fAttribs & PatchAttribs::kJoinControlPoint);
268         if (VertexWriter vw = fChunker.appendVertex()) {
269             vw << VertexWriter::Repeat<4>(p); // p0,p1,p2,p3 = p -> 4 copies
270             this->emitPatchAttribs(std::move(vw), p, kCubicCurveType);
271         }
272     }
273 
274 private:
275     template <typename T>
If(bool c,const T & v)276     static VertexWriter::Conditional<T> If(bool c, const T& v) { return VertexWriter::If(c,v); }
277 
emitPatchAttribs(VertexWriter vertexWriter,SkPoint joinControlPoint,float explicitCurveType)278     AI void emitPatchAttribs(VertexWriter vertexWriter,
279                              SkPoint joinControlPoint,
280                              float explicitCurveType) {
281         vertexWriter << If((fAttribs & PatchAttribs::kJoinControlPoint), joinControlPoint)
282                      << If((fAttribs & PatchAttribs::kFanPoint), fFanPointAttrib)
283                      << If((fAttribs & PatchAttribs::kStrokeParams), fStrokeParamsAttrib)
284                      << If((fAttribs & PatchAttribs::kColor), fColorAttrib)
285                      << If((fAttribs & PatchAttribs::kExplicitCurveType), explicitCurveType);
286     }
287 
writePatch(float2 p0,float2 p1,float2 p2,float2 p3,float explicitCurveType)288     AI void writePatch(float2 p0, float2 p1, float2 p2, float2 p3, float explicitCurveType) {
289         const bool defer = (fAttribs & PatchAttribs::kJoinControlPoint) &&
290                            !fHasJoinControlPoint;
291 
292         SkASSERT(!defer || !fHasDeferredPatch);
293         SkASSERT(fChunker.stride() <= kMaxStride);
294         VertexWriter vw = defer ? VertexWriter{fDeferredPatchStorage, fChunker.stride()}
295                                 : fChunker.appendVertex();
296         fHasDeferredPatch |= defer;
297 
298         if (vw) {
299             vw << p0 << p1 << p2 << p3;
300             // NOTE: fJoinControlPointAttrib will contain NaN if we're writing to a deferred
301             // patch. If that's the case, correct data will overwrite it when the contour is
302             // closed (this is fine since a deferred patch writes to CPU memory instead of
303             // directly to the GPU buffer).
304             this->emitPatchAttribs(std::move(vw), fJoinControlPointAttrib, explicitCurveType);
305             // Automatically update join control point for next patch.
306             if (fAttribs & PatchAttribs::kJoinControlPoint) {
307                 fHasJoinControlPoint = true;
308                 if (explicitCurveType == kCubicCurveType && any(p3 != p2)) {
309                     // p2 is control point defining the tangent vector into the next patch.
310                     p2.store(&fJoinControlPointAttrib);
311                 } else if (any(p2 != p1)) {
312                     // p1 is the control point defining the tangent vector.
313                     p1.store(&fJoinControlPointAttrib);
314                 } else {
315                     // p0 is the control point defining the tangent vector.
316                     p0.store(&fJoinControlPointAttrib);
317                 }
318             }
319         }
320     }
321     // Helpers that normalize curves to a generic patch, but does no other work.
writeCubicPatch(float2 p0,float2 p1,float2 p2,float2 p3)322     AI void writeCubicPatch(float2 p0, float2 p1, float2 p2, float2 p3) {
323         this->writePatch(p0, p1, p2, p3, kCubicCurveType);
324     }
writeCubicPatch(float2 p0,float4 p1p2,float2 p3)325     AI void writeCubicPatch(float2 p0, float4 p1p2, float2 p3) {
326         this->writeCubicPatch(p0, p1p2.lo, p1p2.hi, p3);
327     }
writeQuadPatch(float2 p0,float2 p1,float2 p2)328     AI void writeQuadPatch(float2 p0, float2 p1, float2 p2) {
329         this->writeCubicPatch(p0, mix(float4(p0, p2), p1.xyxy(), 2/3.f), p2);
330     }
writeConicPatch(float2 p0,float2 p1,float2 p2,float w)331     AI void writeConicPatch(float2 p0, float2 p1, float2 p2, float w) {
332         this->writePatch(p0, p1, p2, {w, SK_FloatInfinity}, kConicCurveType);
333     }
334 
335     // Helpers that chop the curve type into 'numPatches' parametrically uniform curves. It is
336     // assumed that 'numPatches' is calculated such that the resulting curves require the maximum
337     // number of segments to draw appropriately (since the original presumably needed even more).
338     void chopAndWriteQuads(float2 p0, float2 p1, float2 p2, int numPatches);
339     void chopAndWriteConics(float2 p0, float2 p1, float2 p2, float w, int numPatches);
340     void chopAndWriteCubics(float2 p0, float2 p1, float2 p2, float2 p3, int numPatches);
341 
342     // Returns true if curve can be written w/o needing to chop
updateRequiredSegments(float n4)343     bool updateRequiredSegments(float n4) {
344         if (n4 <= fMaxSegments_pow4) {
345             fCurrMinSegments_pow4 = std::max(n4, fCurrMinSegments_pow4);
346             return true;
347         } else {
348             fCurrMinSegments_pow4 = fMaxSegments_pow4;
349             return false;
350         }
351     }
352 
353     // True if the patch writer only draws curves (presumably for filling), e.g. does not add a
354     // wedge fan point to help fill space, or a join control point for stroking.
writesCurvesOnly()355     bool writesCurvesOnly() const {
356         return !(fAttribs & (PatchAttribs::kJoinControlPoint | PatchAttribs::kFanPoint));
357     }
358 
359     const PatchAttribs fAttribs;
360 
361     const float fMaxSegments_pow2;
362     const float fMaxSegments_pow4;
363     float fCurrMinSegments_pow4;
364 
365     GrVertexChunkBuilder fChunker;
366 
367     SkPoint fJoinControlPointAttrib;
368     SkPoint fFanPointAttrib;
369     StrokeParams fStrokeParamsAttrib;
370     VertexColor fColorAttrib;
371 
372     static constexpr size_t kMaxStride =
373             4 * sizeof(SkPoint) + // control points
374                 sizeof(SkPoint) + // join control point or fan attrib point (not used at same time)
375                 sizeof(StrokeParams) + // stroke params
376             4 * sizeof(uint32_t); // wide vertex color
377 
378     // Only used if kJoinControlPointAttrib is set in fAttribs, in which case it holds data for
379     // a single patch waiting for the incoming join control point to be computed.
380     // Contents are valid (sans join control point) if fHasDeferredPatch is true.
381     char fDeferredPatchStorage[kMaxStride];
382     bool fHasDeferredPatch = false;
383 
384     bool fHasJoinControlPoint = false;
385 };
386 
387 }  // namespace skgpu
388 
389 #undef AI
390 
391 #endif  // tessellate_PatchWriter_DEFINED
392