/src/skia/src/gpu/graphite/render/AnalyticRRectRenderStep.cpp
Line | Count | Source (jump to first uncovered line) |
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 | | |
8 | | #include "src/gpu/graphite/render/AnalyticRRectRenderStep.h" |
9 | | |
10 | | #include "src/base/SkVx.h" |
11 | | #include "src/core/SkRRectPriv.h" |
12 | | #include "src/gpu/graphite/DrawParams.h" |
13 | | #include "src/gpu/graphite/DrawWriter.h" |
14 | | #include "src/gpu/graphite/render/CommonDepthStencilSettings.h" |
15 | | |
16 | | // This RenderStep is flexible and can draw filled rectangles, filled quadrilaterals with per-edge |
17 | | // AA, filled rounded rectangles with arbitrary corner radii, stroked rectangles with any join, |
18 | | // stroked lines with any cap, stroked rounded rectangles with circular corners (each corner can be |
19 | | // different or square), hairline rectangles, hairline lines, and hairline rounded rectangles with |
20 | | // arbitrary corners. |
21 | | // |
22 | | // We combine all of these together to maximize batching across simple geometric draws and reduce |
23 | | // the number pipeline specializations. Additionally, these primitives are the most common |
24 | | // operations and help us avoid triggering MSAA. |
25 | | // |
26 | | // Each of these "primitives" is represented by a single instance. The instance attributes are |
27 | | // flexible enough to describe any of the above shapes without relying on uniforms to define its |
28 | | // operation. The attributes encode shape as follows: |
29 | | // |
30 | | // float4 xRadiiOrFlags - if any components is > 0, the instance represents a filled round rect |
31 | | // with elliptical corners and these values specify the X radii in top-left CW order. |
32 | | // Otherwise, if .x < -1, the instance represents a stroked or hairline [round] rect or line, |
33 | | // where .y differentiates hairline vs. stroke. If .y is negative, then it is a hairline [round] |
34 | | // rect and xRadiiOrFlags stores (-2 - X radii); if .y is zero, it is a regular stroked [round] |
35 | | // rect; if .y is positive, then it is a stroked *or* hairline line. For .y >= 0, .z holds the |
36 | | // stroke radius and .w stores the join limit (matching StrokeStyle's conventions). |
37 | | // Lastly, if -1 <= .x <= 0, it's a filled quadrilateral with per-edge AA defined by each by the |
38 | | // component: aa != 0. |
39 | | // float4 radiiOrQuadXs - if in filled round rect or hairline [round] rect mode, these values |
40 | | // provide the Y radii in top-left CW order. If in stroked [round] rect mode, these values |
41 | | // provide the circular corner radii (same order). Otherwise, when in per-edge quad mode, these |
42 | | // values provide the X coordinates of the quadrilateral (same order). |
43 | | // float4 ltrbOrQuadYs - if in filled round rect mode or stroked [round] rect mode, these values |
44 | | // define the LTRB edge coordinates of the rectangle surrounding the round rect (or the |
45 | | // rect itself when the radii are 0s). In stroked line mode, LTRB is treated as (x0,y0) and |
46 | | // (x1,y1) that defines the line. Otherwise, in per-edge quad mode, these values provide |
47 | | // the Y coordinates of the quadrilateral. |
48 | | // |
49 | | // From the other direction, shapes produce instance values like: |
50 | | // - filled rect: [-1 -1 -1 -1] [L R R L] [T T B B] |
51 | | // - stroked rect: [-2 0 stroke join] [0 0 0 0] [L T R B] |
52 | | // - hairline rect: [-2 -2 -2 -2] [0 0 0 0] [L T R B] |
53 | | // - filled rrect: [xRadii(tl,tr,br,bl)] [yRadii(tl,tr,br,bl)] [L T R B] |
54 | | // - stroked rrect: [-2 0 stroke join] [radii(tl,tr,br,bl)] [L T R B] |
55 | | // - hairline rrect: [-2-xRadii(tl,tr,br,bl)] [radii(tl,tr,br,bl)] [L T R B] |
56 | | // - filled line: N/A, discarded higher in the stack |
57 | | // - stroked line: [-2 1 stroke cap] [0 0 0 0] [x0,y0,x1,y1] |
58 | | // - hairline line: [-2 1 0 1] [0 0 0 0] [x0,y0,x1,y1] |
59 | | // - per-edge quad: [aa(t,r,b,l) ? -1 : 0] [xs(tl,tr,br,bl)] [ys(tl,tr,br,bl)] |
60 | | // |
61 | | // This encoding relies on the fact that a valid SkRRect with all x radii equal to 0 must have |
62 | | // y radii equal to 0 (so it's a rectangle and we can treat it as a quadrilateral with |
63 | | // all edges AA'ed). This avoids other encodings' inability to represent a quad with all edges |
64 | | // anti-aliased (e.g. checking for negatives in xRadiiOrFlags to turn on per-edge mode). |
65 | | // |
66 | | // From this encoding, data can be unpacked for each corner, which are equivalent under |
67 | | // rotational symmetry. A corner can have an outer curve, be mitered, or be beveled. It can |
68 | | // have an inner curve, an inner miter, or fill the interior. Per-edge quads are always mitered |
69 | | // and fill the interior, but the vertices are placed such that the edge coverage ramps can |
70 | | // collapse to 0 area on non-AA edges. |
71 | | // |
72 | | // The vertices that describe each corner are placed so that edges, miters, and bevels calculate |
73 | | // coverage by interpolating a varying and then clamping in the fragment shader. Triangles that |
74 | | // cover the inner and outer curves calculate distance to the curve within the fragment shader. |
75 | | // |
76 | | // See https://docs.google.com/presentation/d/1MCPstNsSlDBhR8CrsJo0r-cZNbu-sEJEvU9W94GOJoY/edit?usp=sharing |
77 | | // for diagrams and explanation of how the geometry is defined. |
78 | | // |
79 | | // AnalyticRRectRenderStep uses the common technique of approximating distance to the level set by |
80 | | // one expansion of the Taylor's series for the level set's equation. Given a level set function |
81 | | // C(x,y), this amounts to calculating C(px,py)/|∇C(px,py)|. For the straight edges the level set |
82 | | // is linear and calculated in the vertex shader and then interpolated exactly over the rectangle. |
83 | | // This provides distances to all four exterior edges within the fragment shader and allows it to |
84 | | // reconstruct a relative position per elliptical corner. Unfortunately this requires the fragment |
85 | | // shader to calculate the length of the gradient for straight edges instead of interpolating |
86 | | // exact device-space distance. |
87 | | // |
88 | | // All four corner radii are potentially evaluated by the fragment shader although each corner's |
89 | | // coverage is only calculated when the pixel is within the bounding box of its quadrant. For fills |
90 | | // and simple strokes it's theoretically valid to have each pixel calculate a single corner's |
91 | | // coverage that was controlled via the vertex shader. However, testing all four corners is |
92 | | // necessary in order to correctly handle self-intersecting stroke interiors. Similarly, all four |
93 | | // edges must be evaluated in order to handle extremely thin shapes; whereas often you could get |
94 | | // away with tracking a single edge distance per pixel. |
95 | | // |
96 | | // Analytic derivatives are used so that a single pipeline can be used regardless of HW derivative |
97 | | // support or for geometry that would prove difficult for forward differencing. The device-space |
98 | | // gradient for ellipses is calculated per-pixel by transforming a per-pixel local gradient vector |
99 | | // with the Jacobian of the inverse local-to-device transform: |
100 | | // |
101 | | // (px,py) is the projected point of (u,v) transformed by a 3x3 matrix, M: |
102 | | // [x(u,v) / w(u,v)] [x] [m00 m01 m02] [u] |
103 | | // (px,py) = [y(u,v) / w(u,v)] where [y] = [m10 m11 m12]X[v] = M*(u,v,1) |
104 | | // [w] [m20 m21 m22] [1] |
105 | | // |
106 | | // C(px,py) can be defined in terms of a local Cl(u,v) as C(px,py) = Cl(p^-1(px,py)), where p^-1 = |
107 | | // |
108 | | // [x'(px,py) / w'(px,py)] [x'] [m00' m01' * m02'] [px] |
109 | | // (u,v) = [y'(px,py) / w'(px,py)] where [y'] = [m10' m11' * m12']X[py] = M^-1*(px,py,0,1) |
110 | | // [w'] [m20' m21' * m22'] [ 1] |
111 | | // |
112 | | // Note that if the 3x3 M was arrived by dropping the 3rd row and column from a 4x4 since we assume |
113 | | // a local 3rd coordinate of 0, M^-1 is not equal to the 4x4 inverse with dropped rows and columns. |
114 | | // |
115 | | // Using the chain rule, then ∇C(px,py) |
116 | | // = ∇Cl(u,v)X[1/w'(px,py) 0 -x'(px,py)/w'(px,py)^2] [m00' m01'] |
117 | | // [ 0 1/w'(px,py) -y'(px,py)/w'(px,py)^2]X[m10' m11'] |
118 | | // [m20' m21'] |
119 | | // |
120 | | // = 1/w'(px,py)*∇Cl(u,v)X[1 0 -x'(px,py)/w'(px,py)] [m00' m01'] |
121 | | // [0 1 -y'(px,py)/w'(px,py)]X[m10' m11'] |
122 | | // [m20' m21'] |
123 | | // |
124 | | // = w(u,v)*∇Cl(u,v)X[1 0 0 -u] [m00' m01'] |
125 | | // [0 1 0 -v]X[m10' m11'] |
126 | | // [m20' m21'] |
127 | | // |
128 | | // = w(u,v)*∇Cl(u,v)X[m00'-m20'u m01'-m21'u] |
129 | | // [m10'-m20'v m11'-m21'v] |
130 | | // |
131 | | // The vertex shader calculates the rightmost 2x2 matrix and interpolates it across the shape since |
132 | | // each component is linear in (u,v). ∇Cl(u,v) is evaluated per pixel in the fragment shader and |
133 | | // depends on which corner and edge being evaluated. w(u,v) is the device-space W coordinate, so |
134 | | // its reciprocal is provided in sk_FragCoord.w. |
135 | | namespace skgpu::graphite { |
136 | | |
137 | | using AAFlags = EdgeAAQuad::Flags; |
138 | | |
139 | 0 | static skvx::float4 load_x_radii(const SkRRect& rrect) { |
140 | 0 | return skvx::float4{rrect.radii(SkRRect::kUpperLeft_Corner).fX, |
141 | 0 | rrect.radii(SkRRect::kUpperRight_Corner).fX, |
142 | 0 | rrect.radii(SkRRect::kLowerRight_Corner).fX, |
143 | 0 | rrect.radii(SkRRect::kLowerLeft_Corner).fX}; |
144 | 0 | } |
145 | 0 | static skvx::float4 load_y_radii(const SkRRect& rrect) { |
146 | 0 | return skvx::float4{rrect.radii(SkRRect::kUpperLeft_Corner).fY, |
147 | 0 | rrect.radii(SkRRect::kUpperRight_Corner).fY, |
148 | 0 | rrect.radii(SkRRect::kLowerRight_Corner).fY, |
149 | 0 | rrect.radii(SkRRect::kLowerLeft_Corner).fY}; |
150 | 0 | } |
151 | | |
152 | 0 | static bool opposite_insets_intersect(const SkRRect& rrect, float strokeRadius, float aaRadius) { |
153 | | // One AA inset per side |
154 | 0 | const float maxInset = strokeRadius + 2.f * aaRadius; |
155 | 0 | return // Horizontal insets would intersect opposite corner's curve |
156 | 0 | maxInset >= rrect.width() - rrect.radii(SkRRect::kLowerLeft_Corner).fX || |
157 | 0 | maxInset >= rrect.width() - rrect.radii(SkRRect::kLowerRight_Corner).fX || |
158 | 0 | maxInset >= rrect.width() - rrect.radii(SkRRect::kUpperLeft_Corner).fX || |
159 | 0 | maxInset >= rrect.width() - rrect.radii(SkRRect::kUpperRight_Corner).fX || |
160 | | // Vertical insets would intersect opposite corner's curve |
161 | 0 | maxInset >= rrect.height() - rrect.radii(SkRRect::kLowerLeft_Corner).fY || |
162 | 0 | maxInset >= rrect.height() - rrect.radii(SkRRect::kLowerRight_Corner).fY || |
163 | 0 | maxInset >= rrect.height() - rrect.radii(SkRRect::kUpperLeft_Corner).fY || |
164 | 0 | maxInset >= rrect.height() - rrect.radii(SkRRect::kUpperRight_Corner).fY; |
165 | 0 | } |
166 | | |
167 | 0 | static bool opposite_insets_intersect(const Rect& rect, float strokeRadius, float aaRadius) { |
168 | 0 | return any(rect.size() <= 2.f * (strokeRadius + aaRadius)); |
169 | 0 | } |
170 | | |
171 | | static bool opposite_insets_intersect(const Geometry& geometry, |
172 | | float strokeRadius, |
173 | 0 | float aaRadius) { |
174 | 0 | if (geometry.isEdgeAAQuad()) { |
175 | 0 | SkASSERT(strokeRadius == 0.f); |
176 | 0 | const EdgeAAQuad& quad = geometry.edgeAAQuad(); |
177 | 0 | if (quad.edgeFlags() == AAFlags::kNone) { |
178 | | // If all edges are non-AA, there won't be any insetting. This allows completely non-AA |
179 | | // quads to use the fill triangles for simpler fragment shader work. |
180 | 0 | return false; |
181 | 0 | } else if (quad.isRect() && quad.edgeFlags() == AAFlags::kAll) { |
182 | 0 | return opposite_insets_intersect(quad.bounds(), 0.f, aaRadius); |
183 | 0 | } else { |
184 | | // Quads with mixed AA edges are tiles where non-AA edges must seam perfectly together. |
185 | | // If we were to inset along just the axis with AA at a corner, two adjacent quads could |
186 | | // arrive at slightly different inset coordinates and then we wouldn't have a perfect |
187 | | // mesh. Forcing insets to snap to the center means all non-AA edges are formed solely |
188 | | // by the original quad coordinates and should seam perfectly assuming perfect input. |
189 | | // The only downside to this is the fill triangles cannot be used since they would |
190 | | // partially extend into the coverage ramp from adjacent AA edges. |
191 | 0 | return true; |
192 | 0 | } |
193 | 0 | } else { |
194 | 0 | const Shape& shape = geometry.shape(); |
195 | 0 | if (shape.isLine()) { |
196 | 0 | return strokeRadius <= aaRadius; |
197 | 0 | } else if (shape.isRect()) { |
198 | 0 | return opposite_insets_intersect(shape.rect(), strokeRadius, aaRadius); |
199 | 0 | } else { |
200 | 0 | SkASSERT(shape.isRRect()); |
201 | 0 | return opposite_insets_intersect(shape.rrect(), strokeRadius, aaRadius); |
202 | 0 | } |
203 | 0 | } |
204 | 0 | } Unexecuted instantiation: AnalyticRRectRenderStep.cpp:skgpu::graphite::opposite_insets_intersect(skgpu::graphite::Geometry const&, float, float) Unexecuted instantiation: AnalyticRRectRenderStep.cpp:skgpu::graphite::opposite_insets_intersect(skgpu::graphite::Geometry const&, float, float) |
205 | | |
206 | 0 | static bool is_clockwise(const EdgeAAQuad& quad) { |
207 | 0 | if (quad.isRect()) { |
208 | 0 | return true; // by construction, these are always locally clockwise |
209 | 0 | } |
210 | | |
211 | | // This assumes that each corner has a consistent winding, which is the case for convex inputs, |
212 | | // which is an assumption of the per-edge AA API. Check the sign of cross product between the |
213 | | // first two edges. |
214 | 0 | const skvx::float4& xs = quad.xs(); |
215 | 0 | const skvx::float4& ys = quad.ys(); |
216 | |
|
217 | 0 | float winding = (xs[0] - xs[3])*(ys[1] - ys[0]) - (ys[0] - ys[3])*(xs[1] - xs[0]); |
218 | 0 | if (winding == 0.f) { |
219 | | // The input possibly forms a triangle with duplicate vertices, so check the opposite corner |
220 | 0 | winding = (xs[2] - xs[1])*(ys[3] - ys[2]) - (ys[2] - ys[1])*(xs[3] - xs[2]); |
221 | 0 | } |
222 | | |
223 | | // At this point if winding is < 0, the quad's vertices are CCW. If it's still 0, the vertices |
224 | | // form a line, in which case the vertex shader constructs a correct CW winding. Otherwise, |
225 | | // the quad or triangle vertices produce a positive winding and are CW. |
226 | 0 | return winding >= 0.f; |
227 | 0 | } |
228 | | |
229 | 0 | static skvx::float2 quad_center(const EdgeAAQuad& quad) { |
230 | | // The center of the bounding box is *not* a good center to use. Take the average of the |
231 | | // four points instead (which is slightly biased if they form a triangle, but still okay). |
232 | 0 | return skvx::float2(dot(quad.xs(), skvx::float4(0.25f)), |
233 | 0 | dot(quad.ys(), skvx::float4(0.25f))); |
234 | 0 | } |
235 | | |
236 | | // Represents the per-vertex attributes used in each instance. |
237 | | struct Vertex { |
238 | | SkV2 fPosition; |
239 | | SkV2 fNormal; |
240 | | float fNormalScale; |
241 | | float fCenterWeight; |
242 | | }; |
243 | | |
244 | | // Allowed values for the center weight instance value (selected at record time based on style |
245 | | // and transform), and are defined such that when (insance-weight > vertex-weight) is true, the |
246 | | // vertex should be snapped to the center instead of its regular calculation. |
247 | | static constexpr float kSolidInterior = 1.f; |
248 | | static constexpr float kStrokeInterior = 0.f; |
249 | | static constexpr float kFilledStrokeInterior = -1.f; |
250 | | |
251 | | // Special value for local AA radius to signal when the self-intersections of a stroke interior |
252 | | // need extra calculations in the vertex shader. |
253 | | static constexpr float kComplexAAInsets = -1.f; |
254 | | |
255 | | static constexpr int kCornerVertexCount = 9; // sk_VertexID is divided by this in SkSL |
256 | | static constexpr int kVertexCount = 4 * kCornerVertexCount; |
257 | | static constexpr int kIndexCount = 69; |
258 | | |
259 | 0 | static void write_index_buffer(VertexWriter writer) { |
260 | 0 | static constexpr uint16_t kTL = 0 * kCornerVertexCount; |
261 | 0 | static constexpr uint16_t kTR = 1 * kCornerVertexCount; |
262 | 0 | static constexpr uint16_t kBR = 2 * kCornerVertexCount; |
263 | 0 | static constexpr uint16_t kBL = 3 * kCornerVertexCount; |
264 | |
|
265 | 0 | static const uint16_t kIndices[kIndexCount] = { |
266 | | // Exterior AA ramp outset |
267 | 0 | kTL+0,kTL+4,kTL+1,kTL+5,kTL+2,kTL+3,kTL+5, |
268 | 0 | kTR+0,kTR+4,kTR+1,kTR+5,kTR+2,kTR+3,kTR+5, |
269 | 0 | kBR+0,kBR+4,kBR+1,kBR+5,kBR+2,kBR+3,kBR+5, |
270 | 0 | kBL+0,kBL+4,kBL+1,kBL+5,kBL+2,kBL+3,kBL+5, |
271 | 0 | kTL+0,kTL+4, // close and jump to next strip |
272 | | // Outer to inner edges |
273 | 0 | kTL+4,kTL+6,kTL+5,kTL+7, |
274 | 0 | kTR+4,kTR+6,kTR+5,kTR+7, |
275 | 0 | kBR+4,kBR+6,kBR+5,kBR+7, |
276 | 0 | kBL+4,kBL+6,kBL+5,kBL+7, |
277 | 0 | kTL+4,kTL+6, // close and jump to next strip |
278 | | // Fill triangles |
279 | 0 | kTL+6,kTL+8,kTL+7, kTL+7,kTR+8, |
280 | 0 | kTR+6,kTR+8,kTR+7, kTR+7,kBR+8, |
281 | 0 | kBR+6,kBR+8,kBR+7, kBR+7,kBL+8, |
282 | 0 | kBL+6,kBL+8,kBL+7, kBL+7,kTL+8, |
283 | 0 | kTL+6 // close |
284 | 0 | }; |
285 | |
|
286 | 0 | if (writer) { |
287 | 0 | writer << kIndices; |
288 | 0 | } // otherwise static buffer creation failed, so do nothing; Context initialization will fail. |
289 | 0 | } |
290 | | |
291 | 0 | static void write_vertex_buffer(VertexWriter writer) { |
292 | | // Allowed values for the normal scale attribute. +1 signals a device-space outset along the |
293 | | // normal away from the outer edge of the stroke. 0 signals no outset, but placed on the outer |
294 | | // edge of the stroke. -1 signals a local inset along the normal from the inner edge. |
295 | 0 | static constexpr float kOutset = 1.0; |
296 | 0 | static constexpr float kInset = -1.0; |
297 | |
|
298 | 0 | static constexpr float kCenter = 1.f; // "true" as a float |
299 | | |
300 | | // Zero, but named this way to help call out non-zero parameters. |
301 | 0 | static constexpr float _______ = 0.f; |
302 | |
|
303 | 0 | static constexpr float kHR2 = 0.5f * SK_FloatSqrt2; // "half root 2" |
304 | | |
305 | | // This template is repeated 4 times in the vertex buffer, for each of the four corners. |
306 | | // The vertex ID is used to lookup per-corner instance properties such as corner radii or |
307 | | // positions, but otherwise this vertex data produces a consistent clockwise mesh from |
308 | | // TL -> TR -> BR -> BL. |
309 | 0 | static constexpr Vertex kCornerTemplate[kCornerVertexCount] = { |
310 | | // Device-space AA outsets from outer curve |
311 | 0 | { {1.0f, 0.0f}, {1.0f, 0.0f}, kOutset, _______ }, |
312 | 0 | { {1.0f, 0.0f}, {kHR2, kHR2}, kOutset, _______ }, |
313 | 0 | { {0.0f, 1.0f}, {kHR2, kHR2}, kOutset, _______ }, |
314 | 0 | { {0.0f, 1.0f}, {0.0f, 1.0f}, kOutset, _______ }, |
315 | | |
316 | | // Outer anchors (no local or device-space normal outset) |
317 | 0 | { {1.0f, 0.0f}, {kHR2, kHR2}, _______, _______ }, |
318 | 0 | { {0.0f, 1.0f}, {kHR2, kHR2}, _______, _______ }, |
319 | | |
320 | | // Inner curve (with additional AA inset in the common case) |
321 | 0 | { {1.0f, 0.0f}, {1.0f, 0.0f}, kInset, _______ }, |
322 | 0 | { {0.0f, 1.0f}, {0.0f, 1.0f}, kInset, _______ }, |
323 | | |
324 | | // Center filling vertices (equal to inner AA insets unless 'center' triggers a fill). |
325 | | // TODO: On backends that support "cull" distances (and with SkSL support), these vertices |
326 | | // and their corresponding triangles can be completely removed. The inset vertices can |
327 | | // set their cull distance value to cause all filling triangles to be discarded or not |
328 | | // depending on the instance's style. |
329 | 0 | { {1.0f, 0.0f}, {1.0f, 0.0f}, kInset, kCenter }, |
330 | 0 | }; |
331 | |
|
332 | 0 | if (writer) { |
333 | 0 | writer << kCornerTemplate // TL |
334 | 0 | << kCornerTemplate // TR |
335 | 0 | << kCornerTemplate // BR |
336 | 0 | << kCornerTemplate; // BL |
337 | 0 | } // otherwise static buffer creation failed, so do nothing; Context initialization will fail. |
338 | 0 | } |
339 | | |
340 | | AnalyticRRectRenderStep::AnalyticRRectRenderStep(StaticBufferManager* bufferManager) |
341 | | : RenderStep("AnalyticRRectRenderStep", |
342 | | "", |
343 | | Flags::kPerformsShading | Flags::kEmitsCoverage | Flags::kOutsetBoundsForAA, |
344 | | /*uniforms=*/{}, |
345 | | PrimitiveType::kTriangleStrip, |
346 | | kDirectDepthGreaterPass, |
347 | | /*vertexAttrs=*/{ |
348 | | {"position", VertexAttribType::kFloat2, SkSLType::kFloat2}, |
349 | | {"normal", VertexAttribType::kFloat2, SkSLType::kFloat2}, |
350 | | // TODO: These values are all +1/0/-1, or +1/0, so could be packed |
351 | | // much more densely than as three floats. |
352 | | {"normalScale", VertexAttribType::kFloat, SkSLType::kFloat}, |
353 | | {"centerWeight", VertexAttribType::kFloat, SkSLType::kFloat} |
354 | | }, |
355 | | /*instanceAttrs=*/ |
356 | | {{"xRadiiOrFlags", VertexAttribType::kFloat4, SkSLType::kFloat4}, |
357 | | {"radiiOrQuadXs", VertexAttribType::kFloat4, SkSLType::kFloat4}, |
358 | | {"ltrbOrQuadYs", VertexAttribType::kFloat4, SkSLType::kFloat4}, |
359 | | // XY stores center of rrect in local coords. Z and W store values to |
360 | | // control interior fill behavior. Z can be -1, 0, or 1: |
361 | | // -1: A stroked interior where AA insets overlap, but isn't solid. |
362 | | // 0: A stroked interior with no complications. |
363 | | // 1: A solid interior (fill or sufficiently large stroke width). |
364 | | // W specifies the size of the AA inset if it's >= 0, or signals that |
365 | | // the inner curves intersect in a complex manner (rare). |
366 | | {"center", VertexAttribType::kFloat4, SkSLType::kFloat4}, |
367 | | |
368 | | // TODO: pack depth and ssbo index into one 32-bit attribute, if we can |
369 | | // go without needing both render step and paint ssbo index attributes. |
370 | | {"depth", VertexAttribType::kFloat, SkSLType::kFloat}, |
371 | | {"ssboIndices", VertexAttribType::kUShort2, SkSLType::kUShort2}, |
372 | | |
373 | | {"mat0", VertexAttribType::kFloat3, SkSLType::kFloat3}, |
374 | | {"mat1", VertexAttribType::kFloat3, SkSLType::kFloat3}, |
375 | | {"mat2", VertexAttribType::kFloat3, SkSLType::kFloat3}}, |
376 | | /*varyings=*/{ |
377 | | // TODO: If the inverse transform is part of the draw's SSBO, we can |
378 | | // reconstruct the Jacobian in the fragment shader using the existing |
379 | | // local coordinates varying |
380 | | {"jacobian", SkSLType::kFloat4}, // float2x2 |
381 | | // Distance to LTRB edges of unstroked shape. Depending on |
382 | | // 'perPixelControl' these will either be local or device-space values. |
383 | | {"edgeDistances", SkSLType::kFloat4}, // distance to LTRB edges |
384 | | // TODO: These are constant for all fragments for a given instance, |
385 | | // could we store them in the draw's SSBO? |
386 | | {"xRadii", SkSLType::kFloat4}, |
387 | | {"yRadii", SkSLType::kFloat4}, |
388 | | // Matches the StrokeStyle struct (X is radius, Y < 0 is round join, |
389 | | // Y = 0 is bevel, Y > 0 is miter join). |
390 | | // TODO: These could easily be considered part of the draw's uniforms. |
391 | | {"strokeParams", SkSLType::kFloat2}, |
392 | | // 'perPixelControl' is a tightly packed description of how to |
393 | | // evaluate the possible edges that influence coverage in a pixel. |
394 | | // The decision points and encoded values are spread across X and Y |
395 | | // so that they are consistent regardless of whether or not MSAA is |
396 | | // used and does not require centroid sampling. |
397 | | // |
398 | | // The signs of values are used to determine the type of coverage to |
399 | | // calculate in the fragment shader and depending on the state, extra |
400 | | // varying state is encoded in the fields: |
401 | | // - A positive X value overrides all per-pixel coverage calculations |
402 | | // and sets the pixel to full coverage. Y is ignored in this case. |
403 | | // - A zero X value represents a solid interior shape. |
404 | | // - X much less than 0 represents bidirectional coverage for a |
405 | | // stroke, using a sufficiently negative value to avoid |
406 | | // extrapolation from fill triangles. For actual shapes with |
407 | | // bidirectional coverage, the fill triangles are zero area. |
408 | | // |
409 | | // - Y much greater than 0 takes precedence over the latter two X |
410 | | // rules and signals that 'edgeDistances' holds device-space values |
411 | | // and does not require additional per-pixel calculations. The |
412 | | // coverage scale is encoded as (1+scale*w) and the bias is |
413 | | // reconstructed from that. X is always 0 for non-fill triangles |
414 | | // since device-space edge distance is only used for solid interiors |
415 | | // - Otherwise, any negative Y value represents an additional |
416 | | // reduction in coverage due to a device-space outset. It is clamped |
417 | | // below 0 to avoid adding coverage from extrapolation. |
418 | | {"perPixelControl", SkSLType::kFloat2}, |
419 | 0 | }) { |
420 | | // Initialize the static buffers we'll use when recording draw calls. |
421 | | // NOTE: Each instance of this RenderStep gets its own copy of the data. Since there should only |
422 | | // ever be one AnalyticRRectRenderStep at a time, this shouldn't be an issue. |
423 | 0 | write_vertex_buffer(bufferManager->getVertexWriter(sizeof(Vertex) * kVertexCount, |
424 | 0 | &fVertexBuffer)); |
425 | 0 | write_index_buffer(bufferManager->getIndexWriter(sizeof(uint16_t) * kIndexCount, |
426 | 0 | &fIndexBuffer)); |
427 | 0 | } |
428 | | |
429 | 0 | AnalyticRRectRenderStep::~AnalyticRRectRenderStep() {} |
430 | | |
431 | 0 | std::string AnalyticRRectRenderStep::vertexSkSL() const { |
432 | | // Returns the body of a vertex function, which must define a float4 devPosition variable and |
433 | | // must write to an already-defined float2 stepLocalCoords variable. |
434 | 0 | return "float4 devPosition = analytic_rrect_vertex_fn(" |
435 | | // Vertex Attributes |
436 | 0 | "position, normal, normalScale, centerWeight, " |
437 | | // Instance Attributes |
438 | 0 | "xRadiiOrFlags, radiiOrQuadXs, ltrbOrQuadYs, center, depth, " |
439 | 0 | "float3x3(mat0, mat1, mat2), " |
440 | | // Varyings |
441 | 0 | "jacobian, edgeDistances, xRadii, yRadii, strokeParams, perPixelControl, " |
442 | | // Render Step |
443 | 0 | "stepLocalCoords);\n"; |
444 | 0 | } |
445 | | |
446 | 0 | const char* AnalyticRRectRenderStep::fragmentCoverageSkSL() const { |
447 | | // The returned SkSL must write its coverage into a 'half4 outputCoverage' variable (defined in |
448 | | // the calling code) with the actual coverage splatted out into all four channels. |
449 | 0 | return "outputCoverage = analytic_rrect_coverage_fn(sk_FragCoord, " |
450 | 0 | "jacobian, " |
451 | 0 | "edgeDistances, " |
452 | 0 | "xRadii, " |
453 | 0 | "yRadii, " |
454 | 0 | "strokeParams, " |
455 | 0 | "perPixelControl);"; |
456 | 0 | } |
457 | | |
458 | | void AnalyticRRectRenderStep::writeVertices(DrawWriter* writer, |
459 | | const DrawParams& params, |
460 | 0 | skvx::ushort2 ssboIndices) const { |
461 | 0 | SkASSERT(params.geometry().isShape() || params.geometry().isEdgeAAQuad()); |
462 | |
|
463 | 0 | DrawWriter::Instances instance{*writer, fVertexBuffer, fIndexBuffer, kIndexCount}; |
464 | 0 | auto vw = instance.append(1); |
465 | | |
466 | | // The bounds of a rect is the rect, and the bounds of a rrect is tight (== SkRRect::getRect()). |
467 | 0 | Rect bounds = params.geometry().bounds(); |
468 | | |
469 | | // aaRadius will be set to a negative value to signal a complex self-intersection that has to |
470 | | // be calculated in the vertex shader. |
471 | 0 | float aaRadius = params.transform().localAARadius(bounds); |
472 | 0 | float strokeInset = 0.f; |
473 | 0 | float centerWeight = kSolidInterior; |
474 | |
|
475 | 0 | if (params.isStroke()) { |
476 | | // EdgeAAQuads are not stroked so we know it's a Shape, but we support rects, rrects, and |
477 | | // lines that all need to be converted to the same form. |
478 | 0 | const Shape& shape = params.geometry().shape(); |
479 | |
|
480 | 0 | SkASSERT(params.strokeStyle().halfWidth() >= 0.f); |
481 | 0 | SkASSERT(shape.isRect() || shape.isLine() || params.strokeStyle().halfWidth() == 0.f || |
482 | 0 | (shape.isRRect() && SkRRectPriv::AllCornersCircular(shape.rrect()))); |
483 | |
|
484 | 0 | float strokeRadius = params.strokeStyle().halfWidth(); |
485 | |
|
486 | 0 | skvx::float2 size = shape.isLine() ? skvx::float2(length(shape.p1() - shape.p0()), 0.f) |
487 | 0 | : bounds.size(); // rect or [r]rect |
488 | |
|
489 | 0 | skvx::float2 innerGap = size - 2.f * params.strokeStyle().halfWidth(); |
490 | 0 | if (any(innerGap <= 0.f) && strokeRadius > 0.f) { |
491 | | // AA inset intersections are measured from the *outset* and remain marked as "solid" |
492 | 0 | strokeInset = -strokeRadius; |
493 | 0 | } else { |
494 | | // This will be upgraded to kFilledStrokeInterior if insets intersect |
495 | 0 | centerWeight = kStrokeInterior; |
496 | 0 | strokeInset = strokeRadius; |
497 | 0 | } |
498 | |
|
499 | 0 | skvx::float4 xRadii = shape.isRRect() ? load_x_radii(shape.rrect()) : skvx::float4(0.f); |
500 | 0 | if (strokeRadius > 0.f || shape.isLine()) { |
501 | | // Regular strokes only need to upload 4 corner radii; hairline lines can be uploaded in |
502 | | // the same manner since it has no real corner radii. |
503 | 0 | float joinStyle = params.strokeStyle().joinLimit(); |
504 | 0 | float lineFlag = shape.isLine() ? 1.f : 0.f; |
505 | 0 | auto empty = size == 0.f; |
506 | | |
507 | | // Points and lines produce caps instead of joins. However, the capped geometry is |
508 | | // visually equivalent to a joined, stroked [r]rect of the paired join style. |
509 | 0 | if (shape.isLine() || all(empty)) { |
510 | | // However, butt-cap points are defined not to produce any geometry, so that combo |
511 | | // should have been rejected earlier. |
512 | 0 | SkASSERT(shape.isLine() || params.strokeStyle().cap() != SkPaint::kButt_Cap); |
513 | 0 | switch(params.strokeStyle().cap()) { |
514 | 0 | case SkPaint::kRound_Cap: joinStyle = -1.f; break; // round cap == round join |
515 | 0 | case SkPaint::kButt_Cap: joinStyle = 0.f; break; // butt cap == bevel join |
516 | 0 | case SkPaint::kSquare_Cap: joinStyle = 1.f; break; // square cap == miter join |
517 | 0 | } |
518 | 0 | } else if (params.strokeStyle().isMiterJoin()) { |
519 | | // Normal corners are 90-degrees so become beveled if the miter limit is < sqrt(2). |
520 | | // If the [r]rect has a width or height of 0, the corners are actually 180-degrees, |
521 | | // so the must always be beveled (or, equivalently, butt-capped). |
522 | 0 | if (params.strokeStyle().miterLimit() < SK_ScalarSqrt2 || any(empty)) { |
523 | 0 | joinStyle = 0.f; // == bevel (or butt if width or height are zero) |
524 | 0 | } else { |
525 | | // Discard actual miter limit because a 90-degree corner never exceeds it. |
526 | 0 | joinStyle = 1.f; |
527 | 0 | } |
528 | 0 | } // else no join style correction needed for non-empty geometry or round joins |
529 | | |
530 | | // Write a negative value outside [-1, 0] to signal a stroked shape, the line flag, then |
531 | | // the style params, followed by corner radii and coords. |
532 | 0 | vw << -2.f << lineFlag << strokeRadius << joinStyle << xRadii |
533 | 0 | << (shape.isLine() ? shape.line() : bounds.ltrb()); |
534 | 0 | } else { |
535 | | // Write -2 - cornerRadii to encode the X radii in such a way to trigger stroking but |
536 | | // guarantee the 2nd field is non-zero to signal hairline. Then we upload Y radii as |
537 | | // well to allow for elliptical hairlines. |
538 | 0 | skvx::float4 yRadii = shape.isRRect() ? load_y_radii(shape.rrect()) : skvx::float4(0.f); |
539 | 0 | vw << (-2.f - xRadii) << yRadii << bounds.ltrb(); |
540 | 0 | } |
541 | 0 | } else { |
542 | | // Empty fills should not have been recorded at all. |
543 | 0 | SkASSERT(!bounds.isEmptyNegativeOrNaN()); |
544 | |
|
545 | 0 | if (params.geometry().isEdgeAAQuad()) { |
546 | | // NOTE: If quad.isRect() && quad.edgeFlags() == kAll, the written data is identical to |
547 | | // Shape.isRect() case below. |
548 | 0 | const EdgeAAQuad& quad = params.geometry().edgeAAQuad(); |
549 | | |
550 | | // If all edges are non-AA, set localAARadius to 0 so that the fill triangles cover the |
551 | | // entire shape. Otherwise leave it as-is for the full AA rect case; in the event it's |
552 | | // mixed-AA or a quad, it'll be converted to complex insets down below. |
553 | 0 | if (quad.edgeFlags() == EdgeAAQuad::Flags::kNone) { |
554 | 0 | aaRadius = 0.f; |
555 | 0 | } |
556 | | |
557 | | // -1 for AA on, 0 for AA off |
558 | 0 | auto edgeSigns = skvx::float4{quad.edgeFlags() & AAFlags::kLeft ? -1.f : 0.f, |
559 | 0 | quad.edgeFlags() & AAFlags::kTop ? -1.f : 0.f, |
560 | 0 | quad.edgeFlags() & AAFlags::kRight ? -1.f : 0.f, |
561 | 0 | quad.edgeFlags() & AAFlags::kBottom ? -1.f : 0.f}; |
562 | | |
563 | | // The vertex shader expects points to be in clockwise order. EdgeAAQuad is the only |
564 | | // shape that *might* have counter-clockwise input. |
565 | 0 | if (is_clockwise(quad)) { |
566 | 0 | vw << edgeSigns << quad.xs() << quad.ys(); |
567 | 0 | } else { |
568 | 0 | vw << skvx::shuffle<2,1,0,3>(edgeSigns) // swap left and right AA bits |
569 | 0 | << skvx::shuffle<1,0,3,2>(quad.xs()) // swap TL with TR, and BL with BR |
570 | 0 | << skvx::shuffle<1,0,3,2>(quad.ys()); // "" |
571 | 0 | } |
572 | 0 | } else { |
573 | 0 | const Shape& shape = params.geometry().shape(); |
574 | | // Filled lines are empty by definition, so they shouldn't have been recorded |
575 | 0 | SkASSERT(!shape.isLine()); |
576 | |
|
577 | 0 | if (shape.isRect() || (shape.isRRect() && shape.rrect().isRect())) { |
578 | | // Rectangles (or rectangles embedded in an SkRRect) are converted to the |
579 | | // quadrilateral case, but with all edges anti-aliased (== -1). |
580 | 0 | skvx::float4 ltrb = bounds.ltrb(); |
581 | 0 | vw << /*edge flags*/ skvx::float4(-1.f) |
582 | 0 | << /*xs*/ skvx::shuffle<0,2,2,0>(ltrb) |
583 | 0 | << /*ys*/ skvx::shuffle<1,1,3,3>(ltrb); |
584 | 0 | } else { |
585 | | // A filled rounded rectangle, so make sure at least one corner radii > 0 or the |
586 | | // shader won't detect it as a rounded rect. |
587 | 0 | SkASSERT(any(load_x_radii(shape.rrect()) > 0.f)); |
588 | |
|
589 | 0 | vw << load_x_radii(shape.rrect()) << load_y_radii(shape.rrect()) << bounds.ltrb(); |
590 | 0 | } |
591 | 0 | } |
592 | 0 | } |
593 | | |
594 | 0 | if (opposite_insets_intersect(params.geometry(), strokeInset, aaRadius)) { |
595 | 0 | aaRadius = kComplexAAInsets; |
596 | 0 | if (centerWeight == kStrokeInterior) { |
597 | 0 | centerWeight = kFilledStrokeInterior; |
598 | 0 | } |
599 | 0 | } |
600 | | |
601 | | // All instance types share the remaining instance attribute definitions |
602 | 0 | const SkM44& m = params.transform().matrix(); |
603 | 0 | auto center = params.geometry().isEdgeAAQuad() ? quad_center(params.geometry().edgeAAQuad()) |
604 | 0 | : bounds.center(); |
605 | 0 | vw << center << centerWeight << aaRadius |
606 | 0 | << params.order().depthAsFloat() |
607 | 0 | << ssboIndices |
608 | 0 | << m.rc(0,0) << m.rc(1,0) << m.rc(3,0) // mat0 |
609 | 0 | << m.rc(0,1) << m.rc(1,1) << m.rc(3,1) // mat1 |
610 | 0 | << m.rc(0,3) << m.rc(1,3) << m.rc(3,3); // mat2 |
611 | 0 | } Unexecuted instantiation: skgpu::graphite::AnalyticRRectRenderStep::writeVertices(skgpu::graphite::DrawWriter*, skgpu::graphite::DrawParams const&, skvx::Vec<2, unsigned short>) const Unexecuted instantiation: skgpu::graphite::AnalyticRRectRenderStep::writeVertices(skgpu::graphite::DrawWriter*, skgpu::graphite::DrawParams const&, skvx::Vec<2, unsigned short>) const |
612 | | |
613 | | void AnalyticRRectRenderStep::writeUniformsAndTextures(const DrawParams&, |
614 | 0 | PipelineDataGatherer*) const { |
615 | | // All data is uploaded as instance attributes, so no uniforms are needed. |
616 | 0 | } |
617 | | |
618 | | } // namespace skgpu::graphite |