• 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 skgpu_tessellate_PatchWriter_DEFINED
9 #define skgpu_tessellate_PatchWriter_DEFINED
10 
11 #include "include/private/SkColorData.h"
12 #include "src/gpu/BufferWriter.h"
13 #include "src/gpu/tessellate/LinearTolerances.h"
14 #include "src/gpu/tessellate/MiddleOutPolygonTriangulator.h"
15 #include "src/gpu/tessellate/Tessellation.h"
16 #include "src/gpu/tessellate/WangsFormula.h"
17 
18 #include <type_traits>
19 #include <variant>
20 
21 namespace skgpu::tess {
22 
23 /**
24  * PatchWriter writes out tessellation patches, formatted with their specific attribs, to a GPU
25  * buffer.
26  *
27  * PatchWriter is a template class that takes traits to configure both its compile-time and runtime
28  * behavior for the different tessellation rendering algorithms and GPU backends. The complexity of
29  * this system is worthwhile because the attribute writing operations and math already require
30  * heavy inlining for performance, and the algorithmic variations tend to only differ slightly, but
31  * do so in the inner most loops. Additionally, Graphite and Ganesh use the same fundamental
32  * algorithms, but Graphite's architecture and higher required hardware level mean that its
33  * attribute configurations can be determined entirely at compile time.
34  *
35  * Traits are specified in PatchWriter's single var-args template pack. Traits come in two main
36  * categories: PatchAttribs configuration and feature/processing configuration. A given PatchAttrib
37  * can be always enabled, enabled at runtime, or always disabled. A feature can be either enabled
38  * or disabled and are coupled more closely with the control points of the curve. Across the two
39  * GPU backends and different path rendering strategies, a "patch" has the following structure:
40  *
41  *   - 4 control points (8 floats total) defining the curve's geometry
42  *      - quadratic curves are converted to equivalent cubics on the CPU during writing
43  *      - conic curves store {w, inf} in their last control point
44  *      - triangles store {inf, inf} in their last control point
45  *      - everything else is presumed to be a cubic defined by all 4 control points
46  *   - Enabled PatchAttrib values, constant for the entire instance
47  *      - layout is identical to PatchAttrib's definition, skipping disabled attribs
48  *      - attribs can be enabled/disabled at runtime by building a mask of attrib values
49  *
50  * Currently PatchWriter supports the following traits:
51  *   - Required<PatchAttrib>
52  *   - Optional<PatchAttrib>
53  *   - TrackJoinControlPoints
54  *   - AddTrianglesWhenChopping
55  *   - DiscardFlatCurves
56  *
57  * In addition to variable traits, PatchWriter's first template argument defines the type used for
58  * allocating the GPU instance data. The templated "PatchAllocator" can be any type that provides:
59  *    // A GPU-backed vertex writer for a single instance worth of data. The provided
60  *    // LinearTolerances value represents the tolerances for the curve that will be written to the
61  *    // returned vertex space.
62  *    skgpu::VertexWriter append(const LinearTolerances&);
63  *
64  * Additionally, it must have a constructor that takes the stride as its first argument.
65  * PatchWriter forwards any additional constructor args from its ctor to the allocator after
66  * computing the necessary stride for its PatchAttribs configuration.
67  */
68 
69 // *** TRAITS ***
70 
71 // Marks a PatchAttrib is enabled at compile time, i.e. it must always be set and will always be
72 // written to each patch's instance data. If present, will assert if the runtime attribs do not fit.
73 template <PatchAttribs A> struct Required {};
74 // Marks a PatchAttrib as supported, i.e. it can be enabled or disabled at runtime. Optional<A> is
75 // overridden by Required<A>. If neither Required<A> nor Optional<A> are in a PatchWriter's trait
76 // list, then the attrib is disabled at compile time and it will assert if the runtime attribs
77 // attempt to enable it.
78 template <PatchAttribs A> struct Optional {};
79 
80 // Enables tracking of the kJoinControlPointAttrib based on control points of the previously
81 // written patch (automatically taking into account curve chopping). When a patch is first written
82 // (and there is no prior patch to define the join control point), the PatchWriter automatically
83 // records the patch to a temporary buffer--sans join--until writeDeferredStrokePatch() is called,
84 // filling in the now-defined join control point.
85 //
86 // This feature must be paired with Required<PatchAttribs::kJoinControlPoint>
87 struct TrackJoinControlPoints {};
88 
89 // Write additional triangular patches to fill the resulting empty area when a curve is chopped.
90 // Normally, the patch geometry covers the curve defined by its control points, up to the implicitly
91 // closing edge between its first and last control points. When a curve is chopped to fit within
92 // the maximum segment count, the resulting space between the original closing edge and new closing
93 // edges is not filled, unless some mechanism of the shader makes it so (e.g. a fan point or
94 // stroking).
95 //
96 // This feature enables automatically writing triangular patches to fill this empty space when a
97 // curve is chopped.
98 struct AddTrianglesWhenChopping {};
99 
100 // If a curve requires at most 1 segment to render accurately, it's effectively a straight line.
101 // This feature turns on automatically ignoring those curves, with the assumption that some other
102 // render pass will produce equivalent geometry (e.g. middle-out or inner triangulations).
103 struct DiscardFlatCurves {};
104 
105 // Upload lines as a cubic with {a, a, b, b} for control points, instead of the truly linear cubic
106 // of {a, 2/3a + 1/3b, 1/3a + 2/3b, b}. Wang's formula will not return an tight lower bound on the
107 // number of segments in this case, but it's convenient to detect in the vertex shader and assume
108 // only a single segment is required. This bypasses numerical stability issues in Wang's formula
109 // when evaluated on the ideal linear cubic for very large control point coordinates. Other curve
110 // types with large coordinates do not need this treatment since they would be pre-chopped and
111 // culled to lines.
112 struct ReplicateLineEndPoints {};
113 
114 // *** PatchWriter internals ***
115 
116 // AttribValue exposes a consistent store and write interface for a PatchAttrib's value while
117 // abstracting over compile-time enabled, conditionally-enabled, or compile-time disabled attribs.
118 template <PatchAttribs A, typename T, bool Required, bool Optional>
119 struct AttribValue {
120     using DataType = std::conditional_t<Required, T,
121                      std::conditional_t<Optional, std::pair<T, bool>,
122                                        /* else */ std::monostate>>;
123 
124     static constexpr bool kEnabled = Required || Optional;
125 
AttribValueAttribValue126     explicit AttribValue(PatchAttribs attribs) : AttribValue(attribs, {}) {}
AttribValueAttribValue127     AttribValue(PatchAttribs attribs, const T& t) {
128         (void) attribs; // may be unused on release builds
129         if constexpr (Required) {
130             SkASSERT(attribs & A);
131         } else if constexpr (Optional) {
132             std::get<1>(fV) = attribs & A;
133         } else {
134             SkASSERT(!(attribs & A));
135         }
136         *this = t;
137     }
138 
139     AttribValue& operator=(const T& v) {
140         if constexpr (Required) {
141             fV = v;
142         } else if constexpr (Optional) {
143             // for simplicity, store even if disabled and won't be written out to VertexWriter
144             std::get<0>(fV) = v;
145         } // else ignore for disabled values
146         return *this;
147     }
148 
149     DataType fV;
150 };
151 
152 template <PatchAttribs A, typename T, bool Required, bool Optional>
153 VertexWriter& operator<<(VertexWriter& w, const AttribValue<A, T, Required, Optional>& v) {
154     if constexpr (Required) {
155         w << v.fV; // always write
156     } else if constexpr (Optional) {
157         if (std::get<1>(v.fV)) {
158             w << std::get<0>(v.fV); // write if enabled
159         }
160     } // else never write
161     return w;
162 }
163 
164 // Stores state and deferred patch data when TrackJoinControlPoints is used for a PatchWriter.
165 template <size_t Stride>
166 struct PatchStorage {
167     float fN_p4    = -1.f; // The parametric segment value to restore on LinearTolerances
168     bool  fMustDefer = true;  // True means next patch must be deferred
169 
170     // Holds an entire patch, except with an undefined join control point.
171     char fData[Stride];
172 
hasPendingPatchStorage173     bool hasPending() const {
174         return fN_p4 >= 0.f;
175     }
resetPatchStorage176     void reset() {
177         fN_p4 = -1.f;
178         fMustDefer = true;
179     }
180 };
181 
182 // An empty object that has the same constructor signature as MiddleOutPolygonTriangulator, used
183 // as a stand-in when AddTrianglesWhenChopping is not a defined trait.
184 struct NullTriangulator {
NullTriangulatorNullTriangulator185     NullTriangulator(int, SkPoint) {}
186 };
187 
188 #define AI SK_ALWAYS_INLINE
189 #define ENABLE_IF(cond) template <typename Void=void> std::enable_if_t<cond, Void>
190 
191 // *** PatchWriter ***
192 template <typename PatchAllocator, typename... Traits>
193 class PatchWriter {
194     // Helpers to extract specifics from the template traits pack.
195     template <typename F>     struct has_trait  : std::disjunction<std::is_same<F, Traits>...> {};
196     template <PatchAttribs A> using  req_attrib = has_trait<Required<A>>;
197     template <PatchAttribs A> using  opt_attrib = has_trait<Optional<A>>;
198 
199     // Enabled features and attribute configuration
200     static constexpr bool kTrackJoinControlPoints   = has_trait<TrackJoinControlPoints>::value;
201     static constexpr bool kAddTrianglesWhenChopping = has_trait<AddTrianglesWhenChopping>::value;
202     static constexpr bool kDiscardFlatCurves        = has_trait<DiscardFlatCurves>::value;
203     static constexpr bool kReplicateLineEndPoints   = has_trait<ReplicateLineEndPoints>::value;
204 
205     // NOTE: MSVC 19.24 cannot compile constexpr fold expressions referenced in templates, so
206     // extract everything into constexpr bool's instead of using `req_attrib` directly, etc. :(
207     template <PatchAttribs A, typename T, bool Req/*=req_attrib<A>*/, bool Opt/*=opt_attrib<A>*/>
208     using attrib_t = AttribValue<A, T, Req, Opt>;
209 
210     // TODO: Remove when MSVC compiler is fixed, in favor of `using Name = attrib_t<>` directly.
211 #define DEF_ATTRIB_TYPE(name, A, T) \
212     static constexpr bool kRequire##name = req_attrib<A>::value; \
213     static constexpr bool kOptional##name = opt_attrib<A>::value; \
214     using name = attrib_t<A, T, kRequire##name, kOptional##name>
215 
216     DEF_ATTRIB_TYPE(JoinAttrib,      PatchAttribs::kJoinControlPoint,  SkPoint);
217     DEF_ATTRIB_TYPE(FanPointAttrib,  PatchAttribs::kFanPoint,          SkPoint);
218     DEF_ATTRIB_TYPE(StrokeAttrib,    PatchAttribs::kStrokeParams,      StrokeParams);
219 
220     // kWideColorIfEnabled does not define an attribute, but changes the type of the kColor attrib.
221     static constexpr bool kRequireWideColor  = req_attrib<PatchAttribs::kWideColorIfEnabled>::value;
222     static constexpr bool kOptionalWideColor = opt_attrib<PatchAttribs::kWideColorIfEnabled>::value;
223     using Color = std::conditional_t<kRequireWideColor,  SkPMColor4f,
224                   std::conditional_t<kOptionalWideColor, VertexColor,
225                                               /* else */ uint32_t>>;
226 
227     DEF_ATTRIB_TYPE(ColorAttrib,     PatchAttribs::kColor,             Color);
228     DEF_ATTRIB_TYPE(DepthAttrib,     PatchAttribs::kPaintDepth,        float);
229     DEF_ATTRIB_TYPE(CurveTypeAttrib, PatchAttribs::kExplicitCurveType, float);
230     DEF_ATTRIB_TYPE(SsboIndexAttrib, PatchAttribs::kSsboIndex, int);
231 #undef DEF_ATTRIB_TYPE
232 
233     static constexpr size_t kMaxStride = 4 * sizeof(SkPoint) + // control points
234             (JoinAttrib::kEnabled      ? sizeof(SkPoint)                              : 0) +
235             (FanPointAttrib::kEnabled  ? sizeof(SkPoint)                              : 0) +
236             (StrokeAttrib::kEnabled    ? sizeof(StrokeParams)                         : 0) +
237             (ColorAttrib::kEnabled     ? std::min(sizeof(Color), sizeof(SkPMColor4f)) : 0) +
238             (DepthAttrib::kEnabled     ? sizeof(float)                                : 0) +
239             (CurveTypeAttrib::kEnabled ? sizeof(float)                                : 0) +
240             (SsboIndexAttrib::kEnabled ? sizeof(int)                                  : 0);
241 
242     // Types that vary depending on the activated features, but do not define the patch data.
243     using DeferredPatch = std::conditional_t<kTrackJoinControlPoints,
244             PatchStorage<kMaxStride>, std::monostate>;
245     using InnerTriangulator = std::conditional_t<kAddTrianglesWhenChopping,
246             MiddleOutPolygonTriangulator, NullTriangulator>;
247 
248     using float2 = skvx::float2;
249     using float4 = skvx::float4;
250 
251     static_assert(!kTrackJoinControlPoints || req_attrib<PatchAttribs::kJoinControlPoint>::value,
252                   "Deferred patches and auto-updating joins requires kJoinControlPoint attrib");
253 public:
254     template <typename... Args> // forwarded to PatchAllocator
PatchWriter(PatchAttribs attribs,Args &&...allocArgs)255     PatchWriter(PatchAttribs attribs,
256                 Args&&... allocArgs)
257             : fAttribs(attribs)
258             , fPatchAllocator(PatchStride(attribs), std::forward<Args>(allocArgs)...)
259             , fJoin(attribs)
260             , fFanPoint(attribs)
261             , fStrokeParams(attribs)
262             , fColor(attribs)
263             , fDepth(attribs)
264             , fSsboIndex(attribs) {
265         // Explicit curve types are provided on the writePatch signature, and not a field of
266         // PatchWriter, so initialize one in the ctor to validate the provided runtime attribs.
267         SkDEBUGCODE((void) CurveTypeAttrib(attribs);)
268         // Validate the kWideColorIfEnabled attribute variant flag as well
269         if constexpr (req_attrib<PatchAttribs::kWideColorIfEnabled>::value) {
270             SkASSERT(attribs & PatchAttribs::kWideColorIfEnabled);    // required
271         } else if constexpr (!opt_attrib<PatchAttribs::kWideColorIfEnabled>::value) {
272             SkASSERT(!(attribs & PatchAttribs::kWideColorIfEnabled)); // disabled
273         }
274     }
275 
~PatchWriter()276     ~PatchWriter() {
277         if constexpr (kTrackJoinControlPoints) {
278             // flush any pending patch
279             this->writeDeferredStrokePatch();
280         }
281     }
282 
attribs()283     PatchAttribs attribs() const { return fAttribs; }
284 
285     // The max scale factor should be derived from the same matrix that 'xform' was. It's only used
286     // in stroking calculations, so can be ignored for path filling.
287     void setShaderTransform(const wangs_formula::VectorXform& xform,
288                             float maxScale = 1.f) {
289         fApproxTransform = xform;
290         fMaxScale = maxScale;
291     }
292 
293     // Completes a closed contour of a stroke by rewriting a deferred patch with now-available
294     // join control point information. Automatically resets the join control point attribute.
writeDeferredStrokePatch()295     ENABLE_IF(kTrackJoinControlPoints) writeDeferredStrokePatch() {
296         if (fDeferredPatch.hasPending()) {
297             SkASSERT(!fDeferredPatch.fMustDefer);
298             // Overwrite join control point with updated value, which is the first attribute
299             // after the 4 control points.
300             memcpy(SkTAddOffset<void>(fDeferredPatch.fData, 4 * sizeof(SkPoint)),
301                    &fJoin, sizeof(SkPoint));
302             // Assuming that the stroke parameters aren't changing within a contour, we only have
303             // to set the parametric segments in order to recover the LinearTolerances state at the
304             // time the deferred patch was recorded.
305             fTolerances.setParametricSegments(fDeferredPatch.fN_p4);
306             if (VertexWriter vw = fPatchAllocator.append(fTolerances)) {
307                 vw << VertexWriter::Array<char>(fDeferredPatch.fData, PatchStride(fAttribs));
308             }
309         }
310 
311         fDeferredPatch.reset();
312     }
313 
314     // Updates the stroke's join control point that will be written out with each patch. This is
315     // automatically adjusted when appending various geometries (e.g. Conic/Cubic), but sometimes
316     // must be set explicitly.
updateJoinControlPointAttrib(SkPoint lastControlPoint)317     ENABLE_IF(JoinAttrib::kEnabled) updateJoinControlPointAttrib(SkPoint lastControlPoint) {
318         SkASSERT(fAttribs & PatchAttribs::kJoinControlPoint); // must be runtime enabled as well
319         fJoin = lastControlPoint;
320         if constexpr (kTrackJoinControlPoints) {
321             fDeferredPatch.fMustDefer = false;
322         }
323     }
324 
325     // Updates the fan point that will be written out with each patch (i.e., the point that wedges
326     // fan around).
updateFanPointAttrib(SkPoint fanPoint)327     ENABLE_IF(FanPointAttrib::kEnabled) updateFanPointAttrib(SkPoint fanPoint) {
328         SkASSERT(fAttribs & PatchAttribs::kFanPoint);
329         fFanPoint = fanPoint;
330     }
331 
332     // Updates the stroke params that are written out with each patch.
updateStrokeParamsAttrib(StrokeParams strokeParams)333     ENABLE_IF(StrokeAttrib::kEnabled) updateStrokeParamsAttrib(StrokeParams strokeParams) {
334         SkASSERT(fAttribs & PatchAttribs::kStrokeParams);
335         fStrokeParams = strokeParams;
336         fTolerances.setStroke(strokeParams, fMaxScale);
337     }
338     // Updates tolerances to account for stroke params that are stored as uniforms instead of
339     // dynamic instance attributes.
updateUniformStrokeParams(StrokeParams strokeParams)340     ENABLE_IF(StrokeAttrib::kEnabled) updateUniformStrokeParams(StrokeParams strokeParams) {
341         SkASSERT(!(fAttribs & PatchAttribs::kStrokeParams));
342         fTolerances.setStroke(strokeParams, fMaxScale);
343     }
344 
345     // Updates the color that will be written out with each patch.
updateColorAttrib(const SkPMColor4f & color)346     ENABLE_IF(ColorAttrib::kEnabled) updateColorAttrib(const SkPMColor4f& color) {
347         SkASSERT(fAttribs & PatchAttribs::kColor);
348         // Converts SkPMColor4f to the selected 'Color' attrib type. The always-wide and never-wide
349         // branches match what VertexColor does based on the runtime check.
350         if constexpr (req_attrib<PatchAttribs::kWideColorIfEnabled>::value) {
351             fColor = color;
352         } else if constexpr (opt_attrib<PatchAttribs::kWideColorIfEnabled>::value) {
353             fColor = VertexColor(color, fAttribs & PatchAttribs::kWideColorIfEnabled);
354         } else {
355             fColor = color.toBytes_RGBA();
356         }
357     }
358 
359     // Updates the paint depth written out with each patch.
updatePaintDepthAttrib(float depth)360     ENABLE_IF(DepthAttrib::kEnabled) updatePaintDepthAttrib(float depth) {
361         SkASSERT(fAttribs & PatchAttribs::kPaintDepth);
362         fDepth = depth;
363     }
364 
365     // Updates the storage buffer index used to access uniforms.
366     ENABLE_IF(SsboIndexAttrib::kEnabled)
updateSsboIndexAttrib(int ssboIndex)367     updateSsboIndexAttrib(int ssboIndex) {
368         SkASSERT(fAttribs & PatchAttribs::kSsboIndex);
369         fSsboIndex = ssboIndex;
370     }
371 
372     /**
373      * writeX functions for supported patch geometry types. Every geometric type is converted to an
374      * equivalent cubic or conic, so this will always write at minimum 8 floats for the four control
375      * points (cubic) or three control points and {w, inf} (conics). The PatchWriter additionally
376      * writes the current values of all attributes enabled in its PatchAttribs flags.
377      */
378 
379     // Write a cubic curve with its four control points.
writeCubic(float2 p0,float2 p1,float2 p2,float2 p3)380     AI void writeCubic(float2 p0, float2 p1, float2 p2, float2 p3) {
381         float n4 = wangs_formula::cubic_p4(kPrecision, p0, p1, p2, p3, fApproxTransform);
382         if constexpr (kDiscardFlatCurves) {
383             if (n4 <= 1.f) {
384                 // This cubic only needs one segment (e.g. a line) but we're not filling space with
385                 // fans or stroking, so nothing actually needs to be drawn.
386                 return;
387             }
388         }
389         if (int numPatches = this->accountForCurve(n4)) {
390             this->chopAndWriteCubics(p0, p1, p2, p3, numPatches);
391         } else {
392             this->writeCubicPatch(p0, p1, p2, p3);
393         }
394     }
writeCubic(const SkPoint pts[4])395     AI void writeCubic(const SkPoint pts[4]) {
396         float4 p0p1 = float4::Load(pts);
397         float4 p2p3 = float4::Load(pts + 2);
398         this->writeCubic(p0p1.lo, p0p1.hi, p2p3.lo, p2p3.hi);
399     }
400 
401     // Write a conic curve with three control points and 'w', with the last coord of the last
402     // control point signaling a conic by being set to infinity.
writeConic(float2 p0,float2 p1,float2 p2,float w)403     AI void writeConic(float2 p0, float2 p1, float2 p2, float w) {
404         float n2 = wangs_formula::conic_p2(kPrecision, p0, p1, p2, w, fApproxTransform);
405         if constexpr (kDiscardFlatCurves) {
406             if (n2 <= 1.f) {
407                 // This conic only needs one segment (e.g. a line) but we're not filling space with
408                 // fans or stroking, so nothing actually needs to be drawn.
409                 return;
410             }
411         }
412         if (int numPatches = this->accountForCurve(n2 * n2)) {
413             this->chopAndWriteConics(p0, p1, p2, w, numPatches);
414         } else {
415             this->writeConicPatch(p0, p1, p2, w);
416         }
417     }
writeConic(const SkPoint pts[3],float w)418     AI void writeConic(const SkPoint pts[3], float w) {
419         this->writeConic(skvx::bit_pun<float2>(pts[0]),
420                          skvx::bit_pun<float2>(pts[1]),
421                          skvx::bit_pun<float2>(pts[2]),
422                          w);
423     }
424 
425     // Write a quadratic curve that automatically converts its three control points into an
426     // equivalent cubic.
writeQuadratic(float2 p0,float2 p1,float2 p2)427     AI void writeQuadratic(float2 p0, float2 p1, float2 p2) {
428         float n4 = wangs_formula::quadratic_p4(kPrecision, p0, p1, p2, fApproxTransform);
429         if constexpr (kDiscardFlatCurves) {
430             if (n4 <= 1.f) {
431                 // This quad only needs one segment (e.g. a line) but we're not filling space with
432                 // fans or stroking, so nothing actually needs to be drawn.
433                 return;
434             }
435         }
436         if (int numPatches = this->accountForCurve(n4)) {
437             this->chopAndWriteQuads(p0, p1, p2, numPatches);
438         } else {
439             this->writeQuadPatch(p0, p1, p2);
440         }
441     }
writeQuadratic(const SkPoint pts[3])442     AI void writeQuadratic(const SkPoint pts[3]) {
443         this->writeQuadratic(skvx::bit_pun<float2>(pts[0]),
444                              skvx::bit_pun<float2>(pts[1]),
445                              skvx::bit_pun<float2>(pts[2]));
446     }
447 
448     // Write a line that is automatically converted into an equivalent cubic.
writeLine(float4 p0p1)449     AI void writeLine(float4 p0p1) {
450         // No chopping needed, a line only ever requires one segment (the minimum required already).
451         fTolerances.setParametricSegments(1.f);
452         if constexpr (kReplicateLineEndPoints) {
453             // Visually this cubic is still a line, but 't' does not move linearly over the line,
454             // so Wang's formula is more pessimistic. Shaders should avoid evaluating Wang's
455             // formula when a patch has control points in this arrangement.
456             this->writeCubicPatch(p0p1.lo, p0p1.lo, p0p1.hi, p0p1.hi);
457         } else {
458             // In exact math, this cubic structure should have Wang's formula return 0. Due to
459             // floating point math, this isn't always the case, so shaders need some way to restrict
460             // the number of parametric segments if Wang's formula numerically blows up.
461             this->writeCubicPatch(p0p1.lo, (p0p1.zwxy() - p0p1) * (1/3.f) + p0p1, p0p1.hi);
462         }
463     }
writeLine(float2 p0,float2 p1)464     AI void writeLine(float2 p0, float2 p1) { this->writeLine({p0, p1}); }
writeLine(SkPoint p0,SkPoint p1)465     AI void writeLine(SkPoint p0, SkPoint p1) {
466         this->writeLine(skvx::bit_pun<float2>(p0), skvx::bit_pun<float2>(p1));
467     }
468 
469     // Write a triangle by setting it to a conic with w=Inf, and using a distinct
470     // explicit curve type for when inf isn't supported in shaders.
writeTriangle(float2 p0,float2 p1,float2 p2)471     AI void writeTriangle(float2 p0, float2 p1, float2 p2) {
472         // No chopping needed, the max supported segment count should always support 2 lines
473         // (which form a triangle when implicitly closed).
474         static constexpr float kTriangleSegments_p4 = 2.f * 2.f * 2.f * 2.f;
475         fTolerances.setParametricSegments(kTriangleSegments_p4);
476         this->writePatch(p0, p1, p2, {SK_FloatInfinity, SK_FloatInfinity},
477                          kTriangularConicCurveType);
478     }
writeTriangle(SkPoint p0,SkPoint p1,SkPoint p2)479     AI void writeTriangle(SkPoint p0, SkPoint p1, SkPoint p2) {
480         this->writeTriangle(skvx::bit_pun<float2>(p0),
481                             skvx::bit_pun<float2>(p1),
482                             skvx::bit_pun<float2>(p2));
483     }
484 
485     // Writes a circle used for round caps and joins in stroking, encoded as a cubic with
486     // identical control points and an empty join.
writeCircle(SkPoint p)487     AI void writeCircle(SkPoint p) {
488         // This does not use writePatch() because it uses its own location as the join attribute
489         // value instead of fJoin and never defers.
490         fTolerances.setParametricSegments(0.f);
491         if (VertexWriter vw = fPatchAllocator.append(fTolerances)) {
492             vw << VertexWriter::Repeat<4>(p); // p0,p1,p2,p3 = p -> 4 copies
493             this->emitPatchAttribs(std::move(vw), {fAttribs, p}, kCubicCurveType);
494         }
495     }
496 
497 private:
emitPatchAttribs(VertexWriter vertexWriter,const JoinAttrib & join,float explicitCurveType)498     AI void emitPatchAttribs(VertexWriter vertexWriter,
499                              const JoinAttrib& join,
500                              float explicitCurveType) {
501         // NOTE: operator<< overrides automatically handle optional and disabled attribs.
502         vertexWriter << join << fFanPoint << fStrokeParams << fColor << fDepth
503                      << CurveTypeAttrib{fAttribs, explicitCurveType} << fSsboIndex;
504     }
505 
appendPatch()506     AI VertexWriter appendPatch() {
507         if constexpr (kTrackJoinControlPoints) {
508             if (fDeferredPatch.fMustDefer) {
509                 SkASSERT(!fDeferredPatch.hasPending());
510                 SkASSERT(PatchStride(fAttribs) <= kMaxStride);
511                 // Save the computed parametric segment tolerance value so that we can pass that to
512                 // the PatchAllocator when flushing the deferred patch.
513                 fDeferredPatch.fN_p4 = fTolerances.numParametricSegments_p4();
514                 return {fDeferredPatch.fData, PatchStride(fAttribs)};
515             }
516         }
517         return fPatchAllocator.append(fTolerances);
518     }
519 
writePatch(float2 p0,float2 p1,float2 p2,float2 p3,float explicitCurveType)520     AI void writePatch(float2 p0, float2 p1, float2 p2, float2 p3, float explicitCurveType) {
521         if (VertexWriter vw = this->appendPatch()) {
522             // NOTE: fJoin will be undefined if we're writing to a deferred patch. If that's the
523             // case, correct data will overwrite it when the contour is closed (this is fine since a
524             // deferred patch writes to CPU memory instead of directly to the GPU buffer).
525             vw << p0 << p1 << p2 << p3;
526             this->emitPatchAttribs(std::move(vw), fJoin, explicitCurveType);
527 
528             // Automatically update join control point for next patch.
529             if constexpr (kTrackJoinControlPoints) {
530                 if (explicitCurveType == kCubicCurveType && any(p3 != p2)) {
531                     // p2 is control point defining the tangent vector into the next patch.
532                     p2.store(&fJoin);
533                 } else if (any(p2 != p1)) {
534                     // p1 is the control point defining the tangent vector.
535                     p1.store(&fJoin);
536                 } else {
537                     // p0 is the control point defining the tangent vector.
538                     p0.store(&fJoin);
539                 }
540                 fDeferredPatch.fMustDefer = false;
541             }
542         }
543     }
544 
545     // Helpers that normalize curves to a generic patch, but do no other work.
writeCubicPatch(float2 p0,float2 p1,float2 p2,float2 p3)546     AI void writeCubicPatch(float2 p0, float2 p1, float2 p2, float2 p3) {
547         this->writePatch(p0, p1, p2, p3, kCubicCurveType);
548     }
writeCubicPatch(float2 p0,float4 p1p2,float2 p3)549     AI void writeCubicPatch(float2 p0, float4 p1p2, float2 p3) {
550         this->writeCubicPatch(p0, p1p2.lo, p1p2.hi, p3);
551     }
writeQuadPatch(float2 p0,float2 p1,float2 p2)552     AI void writeQuadPatch(float2 p0, float2 p1, float2 p2) {
553         this->writeCubicPatch(p0, mix(float4(p0, p2), p1.xyxy(), 2/3.f), p2);
554     }
writeConicPatch(float2 p0,float2 p1,float2 p2,float w)555     AI void writeConicPatch(float2 p0, float2 p1, float2 p2, float w) {
556         this->writePatch(p0, p1, p2, {w, SK_FloatInfinity}, kConicCurveType);
557     }
558 
accountForCurve(float n4)559     int accountForCurve(float n4) {
560         if (n4 <= kMaxParametricSegments_p4) {
561             // Record n^4 and return 0 to signal no chopping
562             fTolerances.setParametricSegments(n4);
563             return 0;
564         } else {
565             // Clamp to max allowed segmentation for a patch and return required number of chops
566             // to achieve visual correctness.
567             fTolerances.setParametricSegments(kMaxParametricSegments_p4);
568             return SkScalarCeilToInt(wangs_formula::root4(std::min(n4, kMaxSegmentsPerCurve_p4) /
569                                                           kMaxParametricSegments_p4));
570         }
571     }
572 
573     // This does not return b when t==1, but it otherwise seems to get better precision than
574     // "a*(1 - t) + b*t" for things like chopping cubics on exact cusp points.
575     // The responsibility falls on the caller to check that t != 1 before calling.
mix(float4 a,float4 b,float4 T)576     static AI float4 mix(float4 a, float4 b, float4 T) {
577         SkASSERT(all((0 <= T) & (T < 1)));
578         return (b - a)*T + a;
579     }
580 
581     // Helpers that chop the curve type into 'numPatches' parametrically uniform curves. It is
582     // assumed that 'numPatches' is calculated such that the resulting curves require the maximum
583     // number of segments to draw appropriately (since the original presumably needed even more).
chopAndWriteQuads(float2 p0,float2 p1,float2 p2,int numPatches)584     void chopAndWriteQuads(float2 p0, float2 p1, float2 p2, int numPatches) {
585         InnerTriangulator triangulator(numPatches, skvx::bit_pun<SkPoint>(p0));
586         for (; numPatches >= 3; numPatches -= 2) {
587             // Chop into 3 quads.
588             float4 T = float4(1,1,2,2) / numPatches;
589             float4 ab = mix(p0.xyxy(), p1.xyxy(), T);
590             float4 bc = mix(p1.xyxy(), p2.xyxy(), T);
591             float4 abc = mix(ab, bc, T);
592             // p1 & p2 of the cubic representation of the middle quad.
593             float4 middle = mix(ab, bc, mix(T, T.zwxy(), 2/3.f));
594 
595             this->writeQuadPatch(p0, ab.lo, abc.lo);  // Write the 1st quad.
596             if constexpr (kAddTrianglesWhenChopping) {
597                 this->writeTriangle(p0, abc.lo, abc.hi);
598             }
599             this->writeCubicPatch(abc.lo, middle, abc.hi);  // Write the 2nd quad (already a cubic)
600             if constexpr (kAddTrianglesWhenChopping) {
601                 this->writeTriangleStack(triangulator.pushVertex(skvx::bit_pun<SkPoint>(abc.hi)));
602             }
603             std::tie(p0, p1) = {abc.hi, bc.hi};  // Save the 3rd quad.
604         }
605         if (numPatches == 2) {
606             // Chop into 2 quads.
607             float2 ab = (p0 + p1) * .5f;
608             float2 bc = (p1 + p2) * .5f;
609             float2 abc = (ab + bc) * .5f;
610 
611             this->writeQuadPatch(p0, ab, abc);  // Write the 1st quad.
612             if constexpr (kAddTrianglesWhenChopping) {
613                 this->writeTriangle(p0, abc, p2);
614             }
615             this->writeQuadPatch(abc, bc, p2);  // Write the 2nd quad.
616         } else {
617             SkASSERT(numPatches == 1);
618             this->writeQuadPatch(p0, p1, p2);  // Write the single remaining quad.
619         }
620         if constexpr (kAddTrianglesWhenChopping) {
621             this->writeTriangleStack(triangulator.pushVertex(skvx::bit_pun<SkPoint>(p2)));
622             this->writeTriangleStack(triangulator.close());
623         }
624     }
625 
chopAndWriteConics(float2 p0,float2 p1,float2 p2,float w,int numPatches)626     void chopAndWriteConics(float2 p0, float2 p1, float2 p2, float w, int numPatches) {
627         InnerTriangulator triangulator(numPatches, skvx::bit_pun<SkPoint>(p0));
628         // Load the conic in 3d homogeneous (unprojected) space.
629         float4 h0 = float4(p0,1,1);
630         float4 h1 = float4(p1,1,1) * w;
631         float4 h2 = float4(p2,1,1);
632         for (; numPatches >= 2; --numPatches) {
633             // Chop in homogeneous space.
634             float T = 1.f/numPatches;
635             float4 ab = mix(h0, h1, T);
636             float4 bc = mix(h1, h2, T);
637             float4 abc = mix(ab, bc, T);
638 
639             // Project and write the 1st conic.
640             float2 midpoint = abc.xy() / abc.w();
641             this->writeConicPatch(h0.xy() / h0.w(),
642                                   ab.xy() / ab.w(),
643                                   midpoint,
644                                   ab.w() / sqrtf(h0.w() * abc.w()));
645             if constexpr (kAddTrianglesWhenChopping) {
646                 this->writeTriangleStack(triangulator.pushVertex(skvx::bit_pun<SkPoint>(midpoint)));
647             }
648             std::tie(h0, h1) = {abc, bc};  // Save the 2nd conic (in homogeneous space).
649         }
650         // Project and write the remaining conic.
651         SkASSERT(numPatches == 1);
652         this->writeConicPatch(h0.xy() / h0.w(),
653                               h1.xy() / h1.w(),
654                               h2.xy(), // h2.w == 1
655                               h1.w() / sqrtf(h0.w()));
656         if constexpr (kAddTrianglesWhenChopping) {
657             this->writeTriangleStack(triangulator.pushVertex(skvx::bit_pun<SkPoint>(h2.xy())));
658             this->writeTriangleStack(triangulator.close());
659         }
660     }
661 
chopAndWriteCubics(float2 p0,float2 p1,float2 p2,float2 p3,int numPatches)662     void chopAndWriteCubics(float2 p0, float2 p1, float2 p2, float2 p3, int numPatches) {
663         InnerTriangulator triangulator(numPatches, skvx::bit_pun<SkPoint>(p0));
664         for (; numPatches >= 3; numPatches -= 2) {
665             // Chop into 3 cubics.
666             float4 T = float4(1,1,2,2) / numPatches;
667             float4 ab = mix(p0.xyxy(), p1.xyxy(), T);
668             float4 bc = mix(p1.xyxy(), p2.xyxy(), T);
669             float4 cd = mix(p2.xyxy(), p3.xyxy(), T);
670             float4 abc = mix(ab, bc, T);
671             float4 bcd = mix(bc, cd, T);
672             float4 abcd = mix(abc, bcd, T);
673             float4 middle = mix(abc, bcd, T.zwxy());  // p1 & p2 of the middle cubic.
674 
675             this->writeCubicPatch(p0, ab.lo, abc.lo, abcd.lo);  // Write the 1st cubic.
676             if constexpr (kAddTrianglesWhenChopping) {
677                 this->writeTriangle(p0, abcd.lo, abcd.hi);
678             }
679             this->writeCubicPatch(abcd.lo, middle, abcd.hi);  // Write the 2nd cubic.
680             if constexpr (kAddTrianglesWhenChopping) {
681                 this->writeTriangleStack(triangulator.pushVertex(skvx::bit_pun<SkPoint>(abcd.hi)));
682             }
683             std::tie(p0, p1, p2) = {abcd.hi, bcd.hi, cd.hi};  // Save the 3rd cubic.
684         }
685         if (numPatches == 2) {
686             // Chop into 2 cubics.
687             float2 ab = (p0 + p1) * .5f;
688             float2 bc = (p1 + p2) * .5f;
689             float2 cd = (p2 + p3) * .5f;
690             float2 abc = (ab + bc) * .5f;
691             float2 bcd = (bc + cd) * .5f;
692             float2 abcd = (abc + bcd) * .5f;
693 
694             this->writeCubicPatch(p0, ab, abc, abcd);  // Write the 1st cubic.
695             if constexpr (kAddTrianglesWhenChopping) {
696                 this->writeTriangle(p0, abcd, p3);
697             }
698             this->writeCubicPatch(abcd, bcd, cd, p3);  // Write the 2nd cubic.
699         } else {
700             SkASSERT(numPatches == 1);
701             this->writeCubicPatch(p0, p1, p2, p3);  // Write the single remaining cubic.
702         }
703         if constexpr (kAddTrianglesWhenChopping) {
704             this->writeTriangleStack(triangulator.pushVertex(skvx::bit_pun<SkPoint>(p3)));
705             this->writeTriangleStack(triangulator.close());
706         }
707     }
708 
709     ENABLE_IF(kAddTrianglesWhenChopping)
writeTriangleStack(MiddleOutPolygonTriangulator::PoppedTriangleStack && stack)710     writeTriangleStack(MiddleOutPolygonTriangulator::PoppedTriangleStack&& stack) {
711         for (auto [p0, p1, p2] : stack) {
712             this->writeTriangle(p0, p1, p2);
713         }
714     }
715 
716     // Runtime configuration, will always contain required attribs but may not have all optional
717     // attribs enabled (e.g. depending on caps or batching).
718     const PatchAttribs fAttribs;
719 
720     // The 2x2 approximation of the local-to-device transform that will affect subsequently
721     // recorded curves (when fully transformed in the vertex shader).
722     wangs_formula::VectorXform fApproxTransform = {};
723     // A maximum scale factor extracted from the current approximate transform.
724     float fMaxScale = 1.0f;
725     // Tracks the linear tolerances for the most recently written patches.
726     LinearTolerances fTolerances;
727 
728     PatchAllocator fPatchAllocator;
729     DeferredPatch  fDeferredPatch; // only usable if kTrackJoinControlPoints is true
730 
731     // Instance attribute state written after the 4 control points of a patch
732     JoinAttrib     fJoin;
733     FanPointAttrib fFanPoint;
734     StrokeAttrib   fStrokeParams;
735     ColorAttrib    fColor;
736     DepthAttrib    fDepth;
737 
738     // Index into a shared storage buffer containing this PatchWriter's patches' corresponding
739     // uniforms. Written out as an attribute with every patch, to read the appropriate uniform
740     // values from the storage buffer on draw.
741     SsboIndexAttrib fSsboIndex;
742 };
743 
744 }  // namespace skgpu::tess
745 
746 #undef ENABLE_IF
747 #undef AI
748 
749 #endif  // skgpu_tessellate_PatchWriter_DEFINED
750