Flutter Engine
The Flutter Engine
Loading...
Searching...
No Matches
AnalyticRRectRenderStep.cpp
Go to the documentation of this file.
1/*
2 * Copyright 2022 Google LLC
3 *
4 * Use of this source code is governed by a BSD-style license that can be
5 * found in the LICENSE file.
6 */
7
9
10#include "src/base/SkVx.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.
135namespace skgpu::graphite {
136
138
151
152static bool opposite_insets_intersect(const SkRRect& rrect, float strokeRadius, float aaRadius) {
153 // One AA inset per side
154 const float maxInset = strokeRadius + 2.f * aaRadius;
155 return // Horizontal insets would intersect opposite corner's curve
156 maxInset >= rrect.width() - rrect.radii(SkRRect::kLowerLeft_Corner).fX ||
157 maxInset >= rrect.width() - rrect.radii(SkRRect::kLowerRight_Corner).fX ||
158 maxInset >= rrect.width() - rrect.radii(SkRRect::kUpperLeft_Corner).fX ||
159 maxInset >= rrect.width() - rrect.radii(SkRRect::kUpperRight_Corner).fX ||
160 // Vertical insets would intersect opposite corner's curve
161 maxInset >= rrect.height() - rrect.radii(SkRRect::kLowerLeft_Corner).fY ||
162 maxInset >= rrect.height() - rrect.radii(SkRRect::kLowerRight_Corner).fY ||
163 maxInset >= rrect.height() - rrect.radii(SkRRect::kUpperLeft_Corner).fY ||
164 maxInset >= rrect.height() - rrect.radii(SkRRect::kUpperRight_Corner).fY;
165}
166
167static bool opposite_insets_intersect(const Rect& rect, float strokeRadius, float aaRadius) {
168 return any(rect.size() <= 2.f * (strokeRadius + aaRadius));
169}
170
171static bool opposite_insets_intersect(const Geometry& geometry,
172 float strokeRadius,
173 float aaRadius) {
174 if (geometry.isEdgeAAQuad()) {
175 SkASSERT(strokeRadius == 0.f);
176 const EdgeAAQuad& quad = geometry.edgeAAQuad();
177 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 return false;
181 } else if (quad.isRect() && quad.edgeFlags() == AAFlags::kAll) {
182 return opposite_insets_intersect(quad.bounds(), 0.f, aaRadius);
183 } 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 return true;
192 }
193 } else {
194 const Shape& shape = geometry.shape();
195 if (shape.isLine()) {
196 return strokeRadius <= aaRadius;
197 } else if (shape.isRect()) {
198 return opposite_insets_intersect(shape.rect(), strokeRadius, aaRadius);
199 } else {
200 SkASSERT(shape.isRRect());
201 return opposite_insets_intersect(shape.rrect(), strokeRadius, aaRadius);
202 }
203 }
204}
205
206static bool is_clockwise(const EdgeAAQuad& quad) {
207 if (quad.isRect()) {
208 return true; // by construction, these are always locally clockwise
209 }
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 const skvx::float4& xs = quad.xs();
215 const skvx::float4& ys = quad.ys();
216
217 float winding = (xs[0] - xs[3])*(ys[1] - ys[0]) - (ys[0] - ys[3])*(xs[1] - xs[0]);
218 if (winding == 0.f) {
219 // The input possibly forms a triangle with duplicate vertices, so check the opposite corner
220 winding = (xs[2] - xs[1])*(ys[3] - ys[2]) - (ys[2] - ys[1])*(xs[3] - xs[2]);
221 }
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 return winding >= 0.f;
227}
228
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 return skvx::float2(dot(quad.xs(), skvx::float4(0.25f)),
233 dot(quad.ys(), skvx::float4(0.25f)));
234}
235
236// Represents the per-vertex attributes used in each instance.
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.
247static constexpr float kSolidInterior = 1.f;
248static constexpr float kStrokeInterior = 0.f;
249static 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.
253static constexpr float kComplexAAInsets = -1.f;
254
255static constexpr int kCornerVertexCount = 9; // sk_VertexID is divided by this in SkSL
256static constexpr int kVertexCount = 4 * kCornerVertexCount;
257static constexpr int kIndexCount = 69;
258
259static void write_index_buffer(VertexWriter writer) {
260 static constexpr uint16_t kTL = 0 * kCornerVertexCount;
261 static constexpr uint16_t kTR = 1 * kCornerVertexCount;
262 static constexpr uint16_t kBR = 2 * kCornerVertexCount;
263 static constexpr uint16_t kBL = 3 * kCornerVertexCount;
264
265 static const uint16_t kIndices[kIndexCount] = {
266 // Exterior AA ramp outset
267 kTL+0,kTL+4,kTL+1,kTL+5,kTL+2,kTL+3,kTL+5,
268 kTR+0,kTR+4,kTR+1,kTR+5,kTR+2,kTR+3,kTR+5,
269 kBR+0,kBR+4,kBR+1,kBR+5,kBR+2,kBR+3,kBR+5,
270 kBL+0,kBL+4,kBL+1,kBL+5,kBL+2,kBL+3,kBL+5,
271 kTL+0,kTL+4, // close and jump to next strip
272 // Outer to inner edges
273 kTL+4,kTL+6,kTL+5,kTL+7,
274 kTR+4,kTR+6,kTR+5,kTR+7,
275 kBR+4,kBR+6,kBR+5,kBR+7,
276 kBL+4,kBL+6,kBL+5,kBL+7,
277 kTL+4,kTL+6, // close and jump to next strip
278 // Fill triangles
279 kTL+6,kTL+8,kTL+7, kTL+7,kTR+8,
280 kTR+6,kTR+8,kTR+7, kTR+7,kBR+8,
281 kBR+6,kBR+8,kBR+7, kBR+7,kBL+8,
282 kBL+6,kBL+8,kBL+7, kBL+7,kTL+8,
283 kTL+6 // close
284 };
285
286 if (writer) {
287 writer << kIndices;
288 } // otherwise static buffer creation failed, so do nothing; Context initialization will fail.
289}
290
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 static constexpr float kOutset = 1.0;
296 static constexpr float kInset = -1.0;
297
298 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 static constexpr float _______ = 0.f;
302
303 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 static constexpr Vertex kCornerTemplate[kCornerVertexCount] = {
310 // Device-space AA outsets from outer curve
311 { {1.0f, 0.0f}, {1.0f, 0.0f}, kOutset, _______ },
312 { {1.0f, 0.0f}, {kHR2, kHR2}, kOutset, _______ },
313 { {0.0f, 1.0f}, {kHR2, kHR2}, kOutset, _______ },
314 { {0.0f, 1.0f}, {0.0f, 1.0f}, kOutset, _______ },
315
316 // Outer anchors (no local or device-space normal outset)
317 { {1.0f, 0.0f}, {kHR2, kHR2}, _______, _______ },
318 { {0.0f, 1.0f}, {kHR2, kHR2}, _______, _______ },
319
320 // Inner curve (with additional AA inset in the common case)
321 { {1.0f, 0.0f}, {1.0f, 0.0f}, kInset, _______ },
322 { {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 { {1.0f, 0.0f}, {1.0f, 0.0f}, kInset, kCenter },
330 };
331
332 if (writer) {
333 writer << kCornerTemplate // TL
334 << kCornerTemplate // TR
335 << kCornerTemplate // BR
336 << kCornerTemplate; // BL
337 } // otherwise static buffer creation failed, so do nothing; Context initialization will fail.
338}
339
341 : RenderStep("AnalyticRRectRenderStep",
342 "",
343 Flags::kPerformsShading | Flags::kEmitsCoverage | Flags::kOutsetBoundsForAA,
344 /*uniforms=*/{},
347 /*vertexAttrs=*/{
350 // TODO: These values are all +1/0/-1, or +1/0, so could be packed
351 // much more densely than as three floats.
354 },
355 /*instanceAttrs=*/
356 {{"xRadiiOrFlags", 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).
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.
372
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 }) {
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 write_vertex_buffer(bufferManager->getVertexWriter(sizeof(Vertex) * kVertexCount,
424 &fVertexBuffer));
425 write_index_buffer(bufferManager->getIndexWriter(sizeof(uint16_t) * kIndexCount,
426 &fIndexBuffer));
427}
428
430
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 return "float4 devPosition = analytic_rrect_vertex_fn("
435 // Vertex Attributes
436 "position, normal, normalScale, centerWeight, "
437 // Instance Attributes
438 "xRadiiOrFlags, radiiOrQuadXs, ltrbOrQuadYs, center, depth, "
439 "float3x3(mat0, mat1, mat2), "
440 // Varyings
441 "jacobian, edgeDistances, xRadii, yRadii, strokeParams, perPixelControl, "
442 // Render Step
443 "stepLocalCoords);\n";
444}
445
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 return "outputCoverage = analytic_rrect_coverage_fn(sk_FragCoord, "
450 "jacobian, "
451 "edgeDistances, "
452 "xRadii, "
453 "yRadii, "
454 "strokeParams, "
455 "perPixelControl);";
456}
457
459 const DrawParams& params,
460 skvx::ushort2 ssboIndices) const {
461 SkASSERT(params.geometry().isShape() || params.geometry().isEdgeAAQuad());
462
463 DrawWriter::Instances instance{*writer, fVertexBuffer, fIndexBuffer, kIndexCount};
464 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 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 float aaRadius = params.transform().localAARadius(bounds);
472 float strokeInset = 0.f;
473 float centerWeight = kSolidInterior;
474
475 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 const Shape& shape = params.geometry().shape();
479
480 SkASSERT(params.strokeStyle().halfWidth() >= 0.f);
481 SkASSERT(shape.isRect() || shape.isLine() || params.strokeStyle().halfWidth() == 0.f ||
482 (shape.isRRect() && SkRRectPriv::AllCornersCircular(shape.rrect())));
483
484 float strokeRadius = params.strokeStyle().halfWidth();
485
486 skvx::float2 size = shape.isLine() ? skvx::float2(length(shape.p1() - shape.p0()), 0.f)
487 : bounds.size(); // rect or [r]rect
488
489 skvx::float2 innerGap = size - 2.f * params.strokeStyle().halfWidth();
490 if (any(innerGap <= 0.f) && strokeRadius > 0.f) {
491 // AA inset intersections are measured from the *outset* and remain marked as "solid"
492 strokeInset = -strokeRadius;
493 } else {
494 // This will be upgraded to kFilledStrokeInterior if insets intersect
495 centerWeight = kStrokeInterior;
496 strokeInset = strokeRadius;
497 }
498
499 skvx::float4 xRadii = shape.isRRect() ? load_x_radii(shape.rrect()) : skvx::float4(0.f);
500 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 float joinStyle = params.strokeStyle().joinLimit();
504 float lineFlag = shape.isLine() ? 1.f : 0.f;
505 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 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 SkASSERT(shape.isLine() || params.strokeStyle().cap() != SkPaint::kButt_Cap);
513 switch(params.strokeStyle().cap()) {
514 case SkPaint::kRound_Cap: joinStyle = -1.f; break; // round cap == round join
515 case SkPaint::kButt_Cap: joinStyle = 0.f; break; // butt cap == bevel join
516 case SkPaint::kSquare_Cap: joinStyle = 1.f; break; // square cap == miter join
517 }
518 } 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 if (params.strokeStyle().miterLimit() < SK_ScalarSqrt2 || any(empty)) {
523 joinStyle = 0.f; // == bevel (or butt if width or height are zero)
524 } else {
525 // Discard actual miter limit because a 90-degree corner never exceeds it.
526 joinStyle = 1.f;
527 }
528 } // 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 vw << -2.f << lineFlag << strokeRadius << joinStyle << xRadii
533 << (shape.isLine() ? shape.line() : bounds.ltrb());
534 } 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 skvx::float4 yRadii = shape.isRRect() ? load_y_radii(shape.rrect()) : skvx::float4(0.f);
539 vw << (-2.f - xRadii) << yRadii << bounds.ltrb();
540 }
541 } else {
542 // Empty fills should not have been recorded at all.
543 SkASSERT(!bounds.isEmptyNegativeOrNaN());
544
545 if (params.geometry().isEdgeAAQuad()) {
546 // NOTE: If quad.isRect() && quad.edgeFlags() == kAll, the written data is identical to
547 // Shape.isRect() case below.
548 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 if (quad.edgeFlags() == EdgeAAQuad::Flags::kNone) {
554 aaRadius = 0.f;
555 }
556
557 // -1 for AA on, 0 for AA off
558 auto edgeSigns = skvx::float4{quad.edgeFlags() & AAFlags::kLeft ? -1.f : 0.f,
559 quad.edgeFlags() & AAFlags::kTop ? -1.f : 0.f,
560 quad.edgeFlags() & AAFlags::kRight ? -1.f : 0.f,
561 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 if (is_clockwise(quad)) {
566 vw << edgeSigns << quad.xs() << quad.ys();
567 } else {
568 vw << skvx::shuffle<2,1,0,3>(edgeSigns) // swap left and right AA bits
569 << skvx::shuffle<1,0,3,2>(quad.xs()) // swap TL with TR, and BL with BR
570 << skvx::shuffle<1,0,3,2>(quad.ys()); // ""
571 }
572 } else {
573 const Shape& shape = params.geometry().shape();
574 // Filled lines are empty by definition, so they shouldn't have been recorded
575 SkASSERT(!shape.isLine());
576
577 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 skvx::float4 ltrb = bounds.ltrb();
581 vw << /*edge flags*/ skvx::float4(-1.f)
582 << /*xs*/ skvx::shuffle<0,2,2,0>(ltrb)
583 << /*ys*/ skvx::shuffle<1,1,3,3>(ltrb);
584 } 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 SkASSERT(any(load_x_radii(shape.rrect()) > 0.f));
588
589 vw << load_x_radii(shape.rrect()) << load_y_radii(shape.rrect()) << bounds.ltrb();
590 }
591 }
592 }
593
594 if (opposite_insets_intersect(params.geometry(), strokeInset, aaRadius)) {
595 aaRadius = kComplexAAInsets;
596 if (centerWeight == kStrokeInterior) {
597 centerWeight = kFilledStrokeInterior;
598 }
599 }
600
601 // All instance types share the remaining instance attribute definitions
602 const SkM44& m = params.transform().matrix();
603 auto center = params.geometry().isEdgeAAQuad() ? quad_center(params.geometry().edgeAAQuad())
604 : bounds.center();
605 vw << center << centerWeight << aaRadius
606 << params.order().depthAsFloat()
607 << ssboIndices
608 << m.rc(0,0) << m.rc(1,0) << m.rc(3,0) // mat0
609 << m.rc(0,1) << m.rc(1,1) << m.rc(3,1) // mat1
610 << m.rc(0,3) << m.rc(1,3) << m.rc(3,3); // mat2
611}
612
614 PipelineDataGatherer*) const {
615 // All data is uploaded as instance attributes, so no uniforms are needed.
616}
617
618} // namespace skgpu::graphite
static const uint16_t kTL
static const uint16_t kBL
static const uint16_t kIndices[]
static const uint16_t kBR
static constexpr LocalCornerVert kCornerTemplate[19]
static const uint16_t kTR
static constexpr float kHR2
#define SkASSERT(cond)
Definition SkAssert.h:116
constexpr float SK_FloatSqrt2
#define SK_ScalarSqrt2
Definition SkScalar.h:20
static SkScalar center(float pos0, float pos1)
Definition SkM44.h:150
@ kRound_Cap
adds circle
Definition SkPaint.h:335
@ kButt_Cap
no stroke extension
Definition SkPaint.h:334
@ kSquare_Cap
adds square
Definition SkPaint.h:336
static bool AllCornersCircular(const SkRRect &rr, SkScalar tolerance=SK_ScalarNearlyZero)
Definition SkRRect.cpp:353
SkVector radii(Corner corner) const
Definition SkRRect.h:271
@ kUpperLeft_Corner
index of top-left corner radii
Definition SkRRect.h:252
@ kLowerRight_Corner
index of bottom-right corner radii
Definition SkRRect.h:254
@ kUpperRight_Corner
index of top-right corner radii
Definition SkRRect.h:253
@ kLowerLeft_Corner
index of bottom-left corner radii
Definition SkRRect.h:255
SkScalar width() const
Definition SkRRect.h:95
bool isRect() const
Definition SkRRect.h:84
SkScalar height() const
Definition SkRRect.h:102
void writeVertices(DrawWriter *, const DrawParams &, skvx::ushort2 ssboIndices) const override
void writeUniformsAndTextures(const DrawParams &, PipelineDataGatherer *) const override
AnalyticRRectRenderStep(StaticBufferManager *bufferManager)
const skvx::float4 & ys() const
Definition EdgeAAQuad.h:73
const skvx::float4 & xs() const
Definition EdgeAAQuad.h:72
SkEnumBitMask< Flags > edgeFlags() const
Definition EdgeAAQuad.h:74
const EdgeAAQuad & edgeAAQuad() const
Definition Geometry.h:108
const Shape & shape() const
Definition Geometry.h:106
bool isEdgeAAQuad() const
Definition Geometry.h:99
skvx::float2 p0() const
Definition Shape.h:102
bool isRect() const
Definition Shape.h:61
bool isLine() const
Definition Shape.h:60
skvx::float2 p1() const
Definition Shape.h:103
skvx::float4 line() const
Definition Shape.h:104
const SkRRect & rrect() const
Definition Shape.h:106
bool isRRect() const
Definition Shape.h:62
const Rect & rect() const
Definition Shape.h:105
const EmbeddedViewParams * params
VkInstance instance
Definition main.cc:48
EMSCRIPTEN_KEEPALIVE void empty()
static const int kInset
Definition flippity.cpp:47
size_t length
static bool opposite_insets_intersect(const SkRRect &rrect, float strokeRadius, float aaRadius)
static constexpr float kComplexAAInsets
static constexpr float kFilledStrokeInterior
static void write_index_buffer(VertexWriter writer)
static bool is_clockwise(const EdgeAAQuad &quad)
static constexpr DepthStencilSettings kDirectDepthGreaterPass
static constexpr int kIndexCount
static skvx::float4 load_x_radii(const SkRRect &rrect)
static constexpr int kCornerVertexCount
static constexpr int kVertexCount
static constexpr float kSolidInterior
static constexpr float kStrokeInterior
static void write_vertex_buffer(VertexWriter writer)
static skvx::float4 load_y_radii(const SkRRect &rrect)
static skvx::float2 quad_center(const EdgeAAQuad &quad)
Vec< 4, float > float4
Definition SkVx.h:1146
Vec< 2, float > float2
Definition SkVx.h:1145
float fX
x-axis value
float fY
y-axis value
Definition SkM44.h:19