Flutter Engine
The Flutter Engine
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
144}
150}
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
160 // Vertical insets would intersect opposite corner's curve
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.
237struct Vertex {
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.
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
sk_sp< const GrBuffer > fIndexBuffer
sk_sp< const GrBuffer > fVertexBuffer
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
const char * fragmentCoverageSkSL() 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
Optional< SkRect > bounds
Definition: SkRecords.h:189
SkRRect rrect
Definition: SkRecords.h:232
sk_sp< SkBlender > blender SkRect rect
Definition: SkRecords.h:350
it will be possible to load the file into Perfetto s trace viewer disable asset Prevents usage of any non test fonts unless they were explicitly Loaded via prefetched default font Indicates whether the embedding started a prefetch of the default font manager before creating the engine run In non interactive keep the shell running after the Dart script has completed enable serial On low power devices with low core running concurrent GC tasks on threads can cause them to contend with the UI thread which could potentially lead to jank This option turns off all concurrent GC activities domain network JSON encoded network policy per domain This overrides the DisallowInsecureConnections switch Embedder can specify whether to allow or disallow insecure connections at a domain level old gen heap size
Definition: switches.h:259
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)
SINT T dot(const Vec< N, T > &a, const Vec< N, T > &b)
Definition: SkVx.h:964
Vec< 4, float > float4
Definition: SkVx.h:1146
SIT bool all(const Vec< 1, T > &x)
Definition: SkVx.h:582
Vec< 2, float > float2
Definition: SkVx.h:1145
SIT bool any(const Vec< 1, T > &x)
Definition: SkVx.h:530
float fX
x-axis value
Definition: SkPoint_impl.h:164
float fY
y-axis value
Definition: SkPoint_impl.h:165
Definition: SkM44.h:19
Definition: SkVx.h:83