Coverage Report

Created: 2024-05-20 07:14

/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