xref: /aosp_15_r20/external/skia/src/gpu/graphite/render/CircularArcRenderStep.cpp (revision c8dee2aa9b3f27cf6c858bd81872bdeb2c07ed17)
1*c8dee2aaSAndroid Build Coastguard Worker /*
2*c8dee2aaSAndroid Build Coastguard Worker  * Copyright 2024 Google LLC
3*c8dee2aaSAndroid Build Coastguard Worker  *
4*c8dee2aaSAndroid Build Coastguard Worker  * Use of this source code is governed by a BSD-style license that can be
5*c8dee2aaSAndroid Build Coastguard Worker  * found in the LICENSE file.
6*c8dee2aaSAndroid Build Coastguard Worker  */
7*c8dee2aaSAndroid Build Coastguard Worker 
8*c8dee2aaSAndroid Build Coastguard Worker #include "src/gpu/graphite/render/CircularArcRenderStep.h"
9*c8dee2aaSAndroid Build Coastguard Worker 
10*c8dee2aaSAndroid Build Coastguard Worker #include "include/core/SkArc.h"
11*c8dee2aaSAndroid Build Coastguard Worker #include "include/core/SkM44.h"
12*c8dee2aaSAndroid Build Coastguard Worker #include "include/core/SkPaint.h"
13*c8dee2aaSAndroid Build Coastguard Worker #include "include/core/SkRect.h"
14*c8dee2aaSAndroid Build Coastguard Worker #include "include/core/SkScalar.h"
15*c8dee2aaSAndroid Build Coastguard Worker #include "include/private/base/SkAssert.h"
16*c8dee2aaSAndroid Build Coastguard Worker #include "include/private/base/SkPoint_impl.h"
17*c8dee2aaSAndroid Build Coastguard Worker #include "src/base/SkEnumBitMask.h"
18*c8dee2aaSAndroid Build Coastguard Worker #include "src/core/SkSLTypeShared.h"
19*c8dee2aaSAndroid Build Coastguard Worker #include "src/gpu/BufferWriter.h"
20*c8dee2aaSAndroid Build Coastguard Worker #include "src/gpu/graphite/Attribute.h"
21*c8dee2aaSAndroid Build Coastguard Worker #include "src/gpu/graphite/BufferManager.h"
22*c8dee2aaSAndroid Build Coastguard Worker #include "src/gpu/graphite/DrawOrder.h"
23*c8dee2aaSAndroid Build Coastguard Worker #include "src/gpu/graphite/DrawParams.h"
24*c8dee2aaSAndroid Build Coastguard Worker #include "src/gpu/graphite/DrawTypes.h"
25*c8dee2aaSAndroid Build Coastguard Worker #include "src/gpu/graphite/DrawWriter.h"
26*c8dee2aaSAndroid Build Coastguard Worker #include "src/gpu/graphite/geom/Geometry.h"
27*c8dee2aaSAndroid Build Coastguard Worker #include "src/gpu/graphite/geom/Shape.h"
28*c8dee2aaSAndroid Build Coastguard Worker #include "src/gpu/graphite/geom/Transform_graphite.h"
29*c8dee2aaSAndroid Build Coastguard Worker #include "src/gpu/graphite/render/CommonDepthStencilSettings.h"
30*c8dee2aaSAndroid Build Coastguard Worker 
31*c8dee2aaSAndroid Build Coastguard Worker #include <string_view>
32*c8dee2aaSAndroid Build Coastguard Worker 
33*c8dee2aaSAndroid Build Coastguard Worker // This RenderStep is used to render filled circular arcs and stroked circular arcs that
34*c8dee2aaSAndroid Build Coastguard Worker // don't include the center. Currently it only supports butt caps but will be extended
35*c8dee2aaSAndroid Build Coastguard Worker // to include round caps.
36*c8dee2aaSAndroid Build Coastguard Worker //
37*c8dee2aaSAndroid Build Coastguard Worker // Each arc is represented by a single instance. The instance attributes are enough to
38*c8dee2aaSAndroid Build Coastguard Worker // describe the given arc types without relying on uniforms to define its operation.
39*c8dee2aaSAndroid Build Coastguard Worker // The attributes encode shape as follows:
40*c8dee2aaSAndroid Build Coastguard Worker 
41*c8dee2aaSAndroid Build Coastguard Worker // float4 centerScales - used to transform the vertex data into local space.
42*c8dee2aaSAndroid Build Coastguard Worker //    The vertex data represents interleaved octagons that are respectively circumscribed
43*c8dee2aaSAndroid Build Coastguard Worker //    and inscribed on a unit circle, and have to be transformed into local space.
44*c8dee2aaSAndroid Build Coastguard Worker //    So the .xy values here are the center of the arc in local space, and .zw its outer and inner
45*c8dee2aaSAndroid Build Coastguard Worker //    radii, respectively. If the vertex is an outer vertex its local position will be computed as
46*c8dee2aaSAndroid Build Coastguard Worker //         centerScales.xy + position.xy * centerScales.z
47*c8dee2aaSAndroid Build Coastguard Worker //    Otherwise it will be computed as
48*c8dee2aaSAndroid Build Coastguard Worker //         centerScales.xy + position.xy * centerScales.w
49*c8dee2aaSAndroid Build Coastguard Worker //    We can tell whether a vertex is an outer or inner vertex by looking at the sign
50*c8dee2aaSAndroid Build Coastguard Worker //    of its z component. This z value is also used to compute half-pixel anti-aliasing offsets
51*c8dee2aaSAndroid Build Coastguard Worker //    once the vertex data is transformed into device space.
52*c8dee2aaSAndroid Build Coastguard Worker // float3 radiiAndFlags - in the fragment shader we will pass an offset in unit circle space to
53*c8dee2aaSAndroid Build Coastguard Worker //    determine the circle edge and for use for clipping. The .x value here is outerRadius+0.5 and
54*c8dee2aaSAndroid Build Coastguard Worker //    will be compared against the unit circle radius (i.e., 1.0) to compute the outer edge. The .y
55*c8dee2aaSAndroid Build Coastguard Worker //    value is innerRadius-0.5/outerRadius+0.5 and will be used as the comparison point for the
56*c8dee2aaSAndroid Build Coastguard Worker //    inner edge. The .z value is a flag which indicates whether fragClipPlane1 is for intersection
57*c8dee2aaSAndroid Build Coastguard Worker //    (+) or for union (-), and whether to set up rounded caps (-2/+2).
58*c8dee2aaSAndroid Build Coastguard Worker // float3 geoClipPlane - For very thin acute arcs, because of the 1/2 pixel boundary we can get
59*c8dee2aaSAndroid Build Coastguard Worker //    non-clipped artifacts beyond the center of the circle. To solve this, we clip the geometry
60*c8dee2aaSAndroid Build Coastguard Worker //    so any rendering doesn't cross that point.
61*c8dee2aaSAndroid Build Coastguard Worker 
62*c8dee2aaSAndroid Build Coastguard Worker // In addition, these values will be passed to the fragment shader:
63*c8dee2aaSAndroid Build Coastguard Worker //
64*c8dee2aaSAndroid Build Coastguard Worker // float3 fragClipPlane0 - the arc will always be clipped against this half plane, and passed as
65*c8dee2aaSAndroid Build Coastguard Worker //    the varying clipPlane.
66*c8dee2aaSAndroid Build Coastguard Worker // float3 fragClipPlane1 - for convex/acute arcs, we pass this via the varying isectPlane to clip
67*c8dee2aaSAndroid Build Coastguard Worker //    against this and multiply its value by the ClipPlane clip result. For concave/obtuse arcs,
68*c8dee2aaSAndroid Build Coastguard Worker //    we pass this via the varying unionPlane which will clip against this and add its value to the
69*c8dee2aaSAndroid Build Coastguard Worker //    ClipPlane clip result. This is controlled by the flag value in radiiAndFlags: if the
70*c8dee2aaSAndroid Build Coastguard Worker //    flag is > 0, it's passed as isectClip, if it's < 0 it's passed as unionClip. We set default
71*c8dee2aaSAndroid Build Coastguard Worker //    values for the alternative clip plane that end up being a null clip.
72*c8dee2aaSAndroid Build Coastguard Worker // float  roundCapRadius - this is computed in the vertex shader. If we're using round caps (i.e.,
73*c8dee2aaSAndroid Build Coastguard Worker //    if abs(flags) > 1), this will be half the distance between the outer and inner radii.
74*c8dee2aaSAndroid Build Coastguard Worker //    Otherwise it will be 0 which will end up zeroing out any round cap calculation.
75*c8dee2aaSAndroid Build Coastguard Worker // float4 inRoundCapPos - locations of the centers of the round caps in normalized space. This
76*c8dee2aaSAndroid Build Coastguard Worker //    will be all zeroes if not needed.
77*c8dee2aaSAndroid Build Coastguard Worker 
78*c8dee2aaSAndroid Build Coastguard Worker namespace skgpu::graphite {
79*c8dee2aaSAndroid Build Coastguard Worker 
80*c8dee2aaSAndroid Build Coastguard Worker // Represents the per-vertex attributes used in each instance.
81*c8dee2aaSAndroid Build Coastguard Worker struct Vertex {
82*c8dee2aaSAndroid Build Coastguard Worker     // Unit circle local space position (.xy) and AA offset (.z)
83*c8dee2aaSAndroid Build Coastguard Worker     SkV3 fPosition;
84*c8dee2aaSAndroid Build Coastguard Worker };
85*c8dee2aaSAndroid Build Coastguard Worker 
86*c8dee2aaSAndroid Build Coastguard Worker static constexpr int kVertexCount = 18;
87*c8dee2aaSAndroid Build Coastguard Worker 
write_vertex_buffer(VertexWriter writer)88*c8dee2aaSAndroid Build Coastguard Worker static void write_vertex_buffer(VertexWriter writer) {
89*c8dee2aaSAndroid Build Coastguard Worker     // Normalized geometry for octagons that circumscribe/inscribe a unit circle.
90*c8dee2aaSAndroid Build Coastguard Worker     // Outer ring offset
91*c8dee2aaSAndroid Build Coastguard Worker     static constexpr float kOctOffset = 0.41421356237f;  // sqrt(2) - 1
92*c8dee2aaSAndroid Build Coastguard Worker     // Inner ring points
93*c8dee2aaSAndroid Build Coastguard Worker     static constexpr SkScalar kCosPi8 = 0.923579533f;
94*c8dee2aaSAndroid Build Coastguard Worker     static constexpr SkScalar kSinPi8 = 0.382683432f;
95*c8dee2aaSAndroid Build Coastguard Worker 
96*c8dee2aaSAndroid Build Coastguard Worker     // Directional offset for anti-aliasing.
97*c8dee2aaSAndroid Build Coastguard Worker     // Also used as marker for whether this is an outer or inner vertex.
98*c8dee2aaSAndroid Build Coastguard Worker     static constexpr float kOuterAAOffset = 0.5f;
99*c8dee2aaSAndroid Build Coastguard Worker     static constexpr float kInnerAAOffset = -0.5f;
100*c8dee2aaSAndroid Build Coastguard Worker 
101*c8dee2aaSAndroid Build Coastguard Worker     static constexpr SkV3 kOctagonVertices[kVertexCount] = {
102*c8dee2aaSAndroid Build Coastguard Worker         {-kOctOffset, -1,          kOuterAAOffset},
103*c8dee2aaSAndroid Build Coastguard Worker         {-kSinPi8,    -kCosPi8,    kInnerAAOffset},
104*c8dee2aaSAndroid Build Coastguard Worker         { kOctOffset, -1,          kOuterAAOffset},
105*c8dee2aaSAndroid Build Coastguard Worker         {kSinPi8,     -kCosPi8,    kInnerAAOffset},
106*c8dee2aaSAndroid Build Coastguard Worker         { 1,          -kOctOffset, kOuterAAOffset},
107*c8dee2aaSAndroid Build Coastguard Worker         {kCosPi8,     -kSinPi8,    kInnerAAOffset},
108*c8dee2aaSAndroid Build Coastguard Worker         { 1,           kOctOffset, kOuterAAOffset},
109*c8dee2aaSAndroid Build Coastguard Worker         {kCosPi8,      kSinPi8,    kInnerAAOffset},
110*c8dee2aaSAndroid Build Coastguard Worker         { kOctOffset,  1,          kOuterAAOffset},
111*c8dee2aaSAndroid Build Coastguard Worker         {kSinPi8,      kCosPi8,    kInnerAAOffset},
112*c8dee2aaSAndroid Build Coastguard Worker         {-kOctOffset,  1,          kOuterAAOffset},
113*c8dee2aaSAndroid Build Coastguard Worker         {-kSinPi8,     kCosPi8,    kInnerAAOffset},
114*c8dee2aaSAndroid Build Coastguard Worker         {-1,           kOctOffset, kOuterAAOffset},
115*c8dee2aaSAndroid Build Coastguard Worker         {-kCosPi8,     kSinPi8,    kInnerAAOffset},
116*c8dee2aaSAndroid Build Coastguard Worker         {-1,          -kOctOffset, kOuterAAOffset},
117*c8dee2aaSAndroid Build Coastguard Worker         {-kCosPi8,    -kSinPi8,    kInnerAAOffset},
118*c8dee2aaSAndroid Build Coastguard Worker         {-kOctOffset, -1,          kOuterAAOffset},
119*c8dee2aaSAndroid Build Coastguard Worker         {-kSinPi8,    -kCosPi8,    kInnerAAOffset},
120*c8dee2aaSAndroid Build Coastguard Worker     };
121*c8dee2aaSAndroid Build Coastguard Worker 
122*c8dee2aaSAndroid Build Coastguard Worker     if (writer) {
123*c8dee2aaSAndroid Build Coastguard Worker         writer << kOctagonVertices;
124*c8dee2aaSAndroid Build Coastguard Worker     } // otherwise static buffer creation failed, so do nothing; Context initialization will fail.
125*c8dee2aaSAndroid Build Coastguard Worker }
126*c8dee2aaSAndroid Build Coastguard Worker 
CircularArcRenderStep(StaticBufferManager * bufferManager)127*c8dee2aaSAndroid Build Coastguard Worker CircularArcRenderStep::CircularArcRenderStep(StaticBufferManager* bufferManager)
128*c8dee2aaSAndroid Build Coastguard Worker         : RenderStep("CircularArcRenderStep",
129*c8dee2aaSAndroid Build Coastguard Worker                      "",
130*c8dee2aaSAndroid Build Coastguard Worker                      Flags::kPerformsShading | Flags::kEmitsCoverage | Flags::kOutsetBoundsForAA,
131*c8dee2aaSAndroid Build Coastguard Worker                      /*uniforms=*/{},
132*c8dee2aaSAndroid Build Coastguard Worker                      PrimitiveType::kTriangleStrip,
133*c8dee2aaSAndroid Build Coastguard Worker                      kDirectDepthGreaterPass,
134*c8dee2aaSAndroid Build Coastguard Worker                      /*vertexAttrs=*/{
135*c8dee2aaSAndroid Build Coastguard Worker                              {"position", VertexAttribType::kFloat3, SkSLType::kFloat3},
136*c8dee2aaSAndroid Build Coastguard Worker                      },
137*c8dee2aaSAndroid Build Coastguard Worker                      /*instanceAttrs=*/{
138*c8dee2aaSAndroid Build Coastguard Worker                              // Center plus radii, used to transform to local position
139*c8dee2aaSAndroid Build Coastguard Worker                              {"centerScales", VertexAttribType::kFloat4, SkSLType::kFloat4},
140*c8dee2aaSAndroid Build Coastguard Worker                              // Outer (device space) and inner (normalized) radii
141*c8dee2aaSAndroid Build Coastguard Worker                              // + flags for determining clipping and roundcaps
142*c8dee2aaSAndroid Build Coastguard Worker                              {"radiiAndFlags", VertexAttribType::kFloat3, SkSLType::kFloat3},
143*c8dee2aaSAndroid Build Coastguard Worker                              // Clips the geometry for acute arcs
144*c8dee2aaSAndroid Build Coastguard Worker                              {"geoClipPlane", VertexAttribType::kFloat3, SkSLType::kFloat3},
145*c8dee2aaSAndroid Build Coastguard Worker                              // Clip planes sent to the fragment shader for arc extents
146*c8dee2aaSAndroid Build Coastguard Worker                              {"fragClipPlane0", VertexAttribType::kFloat3, SkSLType::kFloat3},
147*c8dee2aaSAndroid Build Coastguard Worker                              {"fragClipPlane1", VertexAttribType::kFloat3, SkSLType::kFloat3},
148*c8dee2aaSAndroid Build Coastguard Worker                              // Roundcap positions, if needed
149*c8dee2aaSAndroid Build Coastguard Worker                              {"inRoundCapPos", VertexAttribType::kFloat4, SkSLType::kFloat4},
150*c8dee2aaSAndroid Build Coastguard Worker 
151*c8dee2aaSAndroid Build Coastguard Worker                              {"depth", VertexAttribType::kFloat, SkSLType::kFloat},
152*c8dee2aaSAndroid Build Coastguard Worker                              {"ssboIndices", VertexAttribType::kUInt2, SkSLType::kUInt2},
153*c8dee2aaSAndroid Build Coastguard Worker 
154*c8dee2aaSAndroid Build Coastguard Worker                              {"mat0", VertexAttribType::kFloat3, SkSLType::kFloat3},
155*c8dee2aaSAndroid Build Coastguard Worker                              {"mat1", VertexAttribType::kFloat3, SkSLType::kFloat3},
156*c8dee2aaSAndroid Build Coastguard Worker                              {"mat2", VertexAttribType::kFloat3, SkSLType::kFloat3},
157*c8dee2aaSAndroid Build Coastguard Worker                      },
158*c8dee2aaSAndroid Build Coastguard Worker                      /*varyings=*/{
159*c8dee2aaSAndroid Build Coastguard Worker                              // Normalized offset vector plus radii
160*c8dee2aaSAndroid Build Coastguard Worker                              {"circleEdge", SkSLType::kFloat4},
161*c8dee2aaSAndroid Build Coastguard Worker                              // Half-planes used to clip to arc shape.
162*c8dee2aaSAndroid Build Coastguard Worker                              {"clipPlane", SkSLType::kFloat3},
163*c8dee2aaSAndroid Build Coastguard Worker                              {"isectPlane", SkSLType::kFloat3},
164*c8dee2aaSAndroid Build Coastguard Worker                              {"unionPlane", SkSLType::kFloat3},
165*c8dee2aaSAndroid Build Coastguard Worker                              // Roundcap data
166*c8dee2aaSAndroid Build Coastguard Worker                              {"roundCapRadius", SkSLType::kFloat},
167*c8dee2aaSAndroid Build Coastguard Worker                              {"roundCapPos", SkSLType::kFloat4},
168*c8dee2aaSAndroid Build Coastguard Worker                      }) {
169*c8dee2aaSAndroid Build Coastguard Worker     // Initialize the static buffer we'll use when recording draw calls.
170*c8dee2aaSAndroid Build Coastguard Worker     // NOTE: Each instance of this RenderStep gets its own copy of the data. Since there should only
171*c8dee2aaSAndroid Build Coastguard Worker     // ever be one CircularArcRenderStep at a time, this shouldn't be an issue.
172*c8dee2aaSAndroid Build Coastguard Worker     write_vertex_buffer(bufferManager->getVertexWriter(sizeof(Vertex) * kVertexCount,
173*c8dee2aaSAndroid Build Coastguard Worker                                                        &fVertexBuffer));
174*c8dee2aaSAndroid Build Coastguard Worker }
175*c8dee2aaSAndroid Build Coastguard Worker 
~CircularArcRenderStep()176*c8dee2aaSAndroid Build Coastguard Worker CircularArcRenderStep::~CircularArcRenderStep() {}
177*c8dee2aaSAndroid Build Coastguard Worker 
vertexSkSL() const178*c8dee2aaSAndroid Build Coastguard Worker std::string CircularArcRenderStep::vertexSkSL() const {
179*c8dee2aaSAndroid Build Coastguard Worker     // Returns the body of a vertex function, which must define a float4 devPosition variable and
180*c8dee2aaSAndroid Build Coastguard Worker     // must write to an already-defined float2 stepLocalCoords variable.
181*c8dee2aaSAndroid Build Coastguard Worker     return "float4 devPosition = circular_arc_vertex_fn("
182*c8dee2aaSAndroid Build Coastguard Worker                    // Vertex Attributes
183*c8dee2aaSAndroid Build Coastguard Worker                    "position, "
184*c8dee2aaSAndroid Build Coastguard Worker                    // Instance Attributes
185*c8dee2aaSAndroid Build Coastguard Worker                    "centerScales, radiiAndFlags, geoClipPlane, fragClipPlane0, fragClipPlane1, "
186*c8dee2aaSAndroid Build Coastguard Worker                    "inRoundCapPos, depth, float3x3(mat0, mat1, mat2), "
187*c8dee2aaSAndroid Build Coastguard Worker                    // Varyings
188*c8dee2aaSAndroid Build Coastguard Worker                    "circleEdge, clipPlane, isectPlane, unionPlane, "
189*c8dee2aaSAndroid Build Coastguard Worker                    "roundCapRadius, roundCapPos, "
190*c8dee2aaSAndroid Build Coastguard Worker                    // Render Step
191*c8dee2aaSAndroid Build Coastguard Worker                    "stepLocalCoords);\n";
192*c8dee2aaSAndroid Build Coastguard Worker }
193*c8dee2aaSAndroid Build Coastguard Worker 
fragmentCoverageSkSL() const194*c8dee2aaSAndroid Build Coastguard Worker const char* CircularArcRenderStep::fragmentCoverageSkSL() const {
195*c8dee2aaSAndroid Build Coastguard Worker     // The returned SkSL must write its coverage into a 'half4 outputCoverage' variable (defined in
196*c8dee2aaSAndroid Build Coastguard Worker     // the calling code) with the actual coverage splatted out into all four channels.
197*c8dee2aaSAndroid Build Coastguard Worker     return "outputCoverage = circular_arc_coverage_fn(circleEdge, "
198*c8dee2aaSAndroid Build Coastguard Worker                                                      "clipPlane, "
199*c8dee2aaSAndroid Build Coastguard Worker                                                      "isectPlane, "
200*c8dee2aaSAndroid Build Coastguard Worker                                                      "unionPlane, "
201*c8dee2aaSAndroid Build Coastguard Worker                                                      "roundCapRadius, "
202*c8dee2aaSAndroid Build Coastguard Worker                                                      "roundCapPos);";
203*c8dee2aaSAndroid Build Coastguard Worker }
204*c8dee2aaSAndroid Build Coastguard Worker 
writeVertices(DrawWriter * writer,const DrawParams & params,skvx::uint2 ssboIndices) const205*c8dee2aaSAndroid Build Coastguard Worker void CircularArcRenderStep::writeVertices(DrawWriter* writer,
206*c8dee2aaSAndroid Build Coastguard Worker                                           const DrawParams& params,
207*c8dee2aaSAndroid Build Coastguard Worker                                           skvx::uint2 ssboIndices) const {
208*c8dee2aaSAndroid Build Coastguard Worker     SkASSERT(params.geometry().isShape() && params.geometry().shape().isArc());
209*c8dee2aaSAndroid Build Coastguard Worker 
210*c8dee2aaSAndroid Build Coastguard Worker     DrawWriter::Instances instance{*writer, fVertexBuffer, {}, kVertexCount};
211*c8dee2aaSAndroid Build Coastguard Worker     auto vw = instance.append(1);
212*c8dee2aaSAndroid Build Coastguard Worker 
213*c8dee2aaSAndroid Build Coastguard Worker     const Shape& shape = params.geometry().shape();
214*c8dee2aaSAndroid Build Coastguard Worker     const SkArc& arc = shape.arc();
215*c8dee2aaSAndroid Build Coastguard Worker 
216*c8dee2aaSAndroid Build Coastguard Worker     SkPoint localCenter = arc.oval().center();
217*c8dee2aaSAndroid Build Coastguard Worker     float localOuterRadius = arc.oval().width() / 2;
218*c8dee2aaSAndroid Build Coastguard Worker     float localInnerRadius = 0.0f;
219*c8dee2aaSAndroid Build Coastguard Worker 
220*c8dee2aaSAndroid Build Coastguard Worker     // We know that we have a similarity matrix so this will transform radius to device space
221*c8dee2aaSAndroid Build Coastguard Worker     const Transform& transform = params.transform();
222*c8dee2aaSAndroid Build Coastguard Worker     float radius = localOuterRadius * transform.maxScaleFactor();
223*c8dee2aaSAndroid Build Coastguard Worker     bool isStroke = params.isStroke();
224*c8dee2aaSAndroid Build Coastguard Worker 
225*c8dee2aaSAndroid Build Coastguard Worker     float innerRadius = -SK_ScalarHalf;
226*c8dee2aaSAndroid Build Coastguard Worker     float outerRadius = radius;
227*c8dee2aaSAndroid Build Coastguard Worker     float halfWidth = 0;
228*c8dee2aaSAndroid Build Coastguard Worker     if (isStroke) {
229*c8dee2aaSAndroid Build Coastguard Worker         float localHalfWidth = params.strokeStyle().halfWidth();
230*c8dee2aaSAndroid Build Coastguard Worker 
231*c8dee2aaSAndroid Build Coastguard Worker         halfWidth = localHalfWidth * transform.maxScaleFactor();
232*c8dee2aaSAndroid Build Coastguard Worker         if (SkScalarNearlyZero(halfWidth)) {
233*c8dee2aaSAndroid Build Coastguard Worker             halfWidth = SK_ScalarHalf;
234*c8dee2aaSAndroid Build Coastguard Worker             // Need to map this back to local space
235*c8dee2aaSAndroid Build Coastguard Worker             localHalfWidth = halfWidth / transform.maxScaleFactor();
236*c8dee2aaSAndroid Build Coastguard Worker         }
237*c8dee2aaSAndroid Build Coastguard Worker 
238*c8dee2aaSAndroid Build Coastguard Worker         outerRadius += halfWidth;
239*c8dee2aaSAndroid Build Coastguard Worker         innerRadius = radius - halfWidth;
240*c8dee2aaSAndroid Build Coastguard Worker         localInnerRadius = localOuterRadius - localHalfWidth;
241*c8dee2aaSAndroid Build Coastguard Worker         localOuterRadius += localHalfWidth;
242*c8dee2aaSAndroid Build Coastguard Worker     }
243*c8dee2aaSAndroid Build Coastguard Worker 
244*c8dee2aaSAndroid Build Coastguard Worker     // The radii are outset for two reasons. First, it allows the shader to simply perform
245*c8dee2aaSAndroid Build Coastguard Worker     // simpler computation because the computed alpha is zero, rather than 50%, at the radius.
246*c8dee2aaSAndroid Build Coastguard Worker     // Second, the outer radius is used to compute the verts of the bounding box that is
247*c8dee2aaSAndroid Build Coastguard Worker     // rendered and the outset ensures the box will cover all partially covered by the circle.
248*c8dee2aaSAndroid Build Coastguard Worker     outerRadius += SK_ScalarHalf;
249*c8dee2aaSAndroid Build Coastguard Worker     innerRadius -= SK_ScalarHalf;
250*c8dee2aaSAndroid Build Coastguard Worker 
251*c8dee2aaSAndroid Build Coastguard Worker     // The shader operates in a space where the circle is translated to be centered at the
252*c8dee2aaSAndroid Build Coastguard Worker     // origin. Here we compute points on the unit circle at the starting and ending angles.
253*c8dee2aaSAndroid Build Coastguard Worker     SkV2 localPoints[3];
254*c8dee2aaSAndroid Build Coastguard Worker     float startAngleRadians = SkDegreesToRadians(arc.startAngle());
255*c8dee2aaSAndroid Build Coastguard Worker     float sweepAngleRadians = SkDegreesToRadians(arc.sweepAngle());
256*c8dee2aaSAndroid Build Coastguard Worker     localPoints[0].y = SkScalarSin(startAngleRadians);
257*c8dee2aaSAndroid Build Coastguard Worker     localPoints[0].x = SkScalarCos(startAngleRadians);
258*c8dee2aaSAndroid Build Coastguard Worker     SkScalar endAngle = startAngleRadians + sweepAngleRadians;
259*c8dee2aaSAndroid Build Coastguard Worker     localPoints[1].y = SkScalarSin(endAngle);
260*c8dee2aaSAndroid Build Coastguard Worker     localPoints[1].x = SkScalarCos(endAngle);
261*c8dee2aaSAndroid Build Coastguard Worker     localPoints[2] = {0, 0};
262*c8dee2aaSAndroid Build Coastguard Worker 
263*c8dee2aaSAndroid Build Coastguard Worker     // Adjust the start and end points based on the view matrix (to handle rotated arcs)
264*c8dee2aaSAndroid Build Coastguard Worker     SkV4 devPoints[3];
265*c8dee2aaSAndroid Build Coastguard Worker     transform.mapPoints(localPoints, devPoints, 3);
266*c8dee2aaSAndroid Build Coastguard Worker     // Translate the point relative to the transformed origin
267*c8dee2aaSAndroid Build Coastguard Worker     SkV2 startPoint = {devPoints[0].x - devPoints[2].x, devPoints[0].y - devPoints[2].y};
268*c8dee2aaSAndroid Build Coastguard Worker     SkV2 stopPoint = {devPoints[1].x - devPoints[2].x, devPoints[1].y - devPoints[2].y};
269*c8dee2aaSAndroid Build Coastguard Worker     startPoint = startPoint.normalize();
270*c8dee2aaSAndroid Build Coastguard Worker     stopPoint = stopPoint.normalize();
271*c8dee2aaSAndroid Build Coastguard Worker 
272*c8dee2aaSAndroid Build Coastguard Worker     // We know the matrix is a similarity here. Detect mirroring which will affect how we
273*c8dee2aaSAndroid Build Coastguard Worker     // should orient the clip planes for arcs.
274*c8dee2aaSAndroid Build Coastguard Worker     const SkM44& m = transform.matrix();
275*c8dee2aaSAndroid Build Coastguard Worker     auto upperLeftDet = m.rc(0,0) * m.rc(1,1) -
276*c8dee2aaSAndroid Build Coastguard Worker                         m.rc(0,1) * m.rc(1,0);
277*c8dee2aaSAndroid Build Coastguard Worker     if (upperLeftDet < 0) {
278*c8dee2aaSAndroid Build Coastguard Worker         std::swap(startPoint, stopPoint);
279*c8dee2aaSAndroid Build Coastguard Worker     }
280*c8dee2aaSAndroid Build Coastguard Worker 
281*c8dee2aaSAndroid Build Coastguard Worker     // Like a fill without useCenter, butt-cap stroke can be implemented by clipping against
282*c8dee2aaSAndroid Build Coastguard Worker     // radial lines. We treat round caps the same way, but track coverage of circles at the
283*c8dee2aaSAndroid Build Coastguard Worker     // center of the butts.
284*c8dee2aaSAndroid Build Coastguard Worker     // However, in both cases we have to be careful about the half-circle.
285*c8dee2aaSAndroid Build Coastguard Worker     // case. In that case the two radial lines are equal and so that edge gets clipped
286*c8dee2aaSAndroid Build Coastguard Worker     // twice. Since the shared edge goes through the center we fall back on the !useCenter
287*c8dee2aaSAndroid Build Coastguard Worker     // case.
288*c8dee2aaSAndroid Build Coastguard Worker     auto absSweep = SkScalarAbs(sweepAngleRadians);
289*c8dee2aaSAndroid Build Coastguard Worker     bool useCenter = (arc.isWedge() || isStroke) &&
290*c8dee2aaSAndroid Build Coastguard Worker                      !SkScalarNearlyEqual(absSweep, SK_ScalarPI);
291*c8dee2aaSAndroid Build Coastguard Worker 
292*c8dee2aaSAndroid Build Coastguard Worker     // This makes every point fully inside the plane.
293*c8dee2aaSAndroid Build Coastguard Worker     SkV3 geoClipPlane = {0.f, 0.f, 1.f};
294*c8dee2aaSAndroid Build Coastguard Worker     SkV3 clipPlane0;
295*c8dee2aaSAndroid Build Coastguard Worker     SkV3 clipPlane1;
296*c8dee2aaSAndroid Build Coastguard Worker     SkV2 roundCapPos0 = {0, 0};
297*c8dee2aaSAndroid Build Coastguard Worker     SkV2 roundCapPos1 = {0, 0};
298*c8dee2aaSAndroid Build Coastguard Worker     static constexpr float kIntersection_NoRoundCaps = 1;
299*c8dee2aaSAndroid Build Coastguard Worker     static constexpr float kIntersection_RoundCaps = 2;
300*c8dee2aaSAndroid Build Coastguard Worker 
301*c8dee2aaSAndroid Build Coastguard Worker     // Default to intersection and no round caps.
302*c8dee2aaSAndroid Build Coastguard Worker     float flags = kIntersection_NoRoundCaps;
303*c8dee2aaSAndroid Build Coastguard Worker     // Determine if we need round caps.
304*c8dee2aaSAndroid Build Coastguard Worker     if (isStroke && innerRadius > -SK_ScalarHalf &&
305*c8dee2aaSAndroid Build Coastguard Worker         params.strokeStyle().halfWidth() > 0 &&
306*c8dee2aaSAndroid Build Coastguard Worker         params.strokeStyle().cap() == SkPaint::kRound_Cap) {
307*c8dee2aaSAndroid Build Coastguard Worker         // Compute the cap center points in the normalized space.
308*c8dee2aaSAndroid Build Coastguard Worker         float midRadius = (innerRadius + outerRadius) / (2 * outerRadius);
309*c8dee2aaSAndroid Build Coastguard Worker         roundCapPos0 = startPoint * midRadius;
310*c8dee2aaSAndroid Build Coastguard Worker         roundCapPos1 = stopPoint * midRadius;
311*c8dee2aaSAndroid Build Coastguard Worker         flags = kIntersection_RoundCaps;
312*c8dee2aaSAndroid Build Coastguard Worker     }
313*c8dee2aaSAndroid Build Coastguard Worker 
314*c8dee2aaSAndroid Build Coastguard Worker     // Determine clip planes.
315*c8dee2aaSAndroid Build Coastguard Worker     if (useCenter) {
316*c8dee2aaSAndroid Build Coastguard Worker         SkV2 norm0 = {startPoint.y, -startPoint.x};
317*c8dee2aaSAndroid Build Coastguard Worker         SkV2 norm1 = {stopPoint.y, -stopPoint.x};
318*c8dee2aaSAndroid Build Coastguard Worker         // This ensures that norm0 is always the clockwise plane, and norm1 is CCW.
319*c8dee2aaSAndroid Build Coastguard Worker         if (sweepAngleRadians < 0) {
320*c8dee2aaSAndroid Build Coastguard Worker             std::swap(norm0, norm1);
321*c8dee2aaSAndroid Build Coastguard Worker         }
322*c8dee2aaSAndroid Build Coastguard Worker         norm0 = -norm0;
323*c8dee2aaSAndroid Build Coastguard Worker         clipPlane0 = {norm0.x, norm0.y, 0.5f};
324*c8dee2aaSAndroid Build Coastguard Worker         clipPlane1 = {norm1.x, norm1.y, 0.5f};
325*c8dee2aaSAndroid Build Coastguard Worker         if (absSweep > SK_ScalarPI) {
326*c8dee2aaSAndroid Build Coastguard Worker             // Union
327*c8dee2aaSAndroid Build Coastguard Worker             flags = -flags;
328*c8dee2aaSAndroid Build Coastguard Worker         } else {
329*c8dee2aaSAndroid Build Coastguard Worker             // Intersection
330*c8dee2aaSAndroid Build Coastguard Worker             // Highly acute arc. We need to clip the vertices to the perpendicular half-plane.
331*c8dee2aaSAndroid Build Coastguard Worker             if (!isStroke && absSweep < 0.5f*SK_ScalarPI) {
332*c8dee2aaSAndroid Build Coastguard Worker                 // We do this clipping in normalized space so use our original local points.
333*c8dee2aaSAndroid Build Coastguard Worker                 // Should already be normalized since they're sin/cos.
334*c8dee2aaSAndroid Build Coastguard Worker                 SkV2 localNorm0 = {localPoints[0].y, -localPoints[0].x};
335*c8dee2aaSAndroid Build Coastguard Worker                 SkV2 localNorm1 = {localPoints[1].y, -localPoints[1].x};
336*c8dee2aaSAndroid Build Coastguard Worker                 // This ensures that norm0 is always the clockwise plane, and norm1 is CCW.
337*c8dee2aaSAndroid Build Coastguard Worker                 if (sweepAngleRadians < 0) {
338*c8dee2aaSAndroid Build Coastguard Worker                     std::swap(localNorm0, localNorm1);
339*c8dee2aaSAndroid Build Coastguard Worker                 }
340*c8dee2aaSAndroid Build Coastguard Worker                 // Negate norm0 and compute the perpendicular of the difference
341*c8dee2aaSAndroid Build Coastguard Worker                 SkV2 clipNorm = {-localNorm0.y - localNorm1.y, localNorm1.x + localNorm0.x};
342*c8dee2aaSAndroid Build Coastguard Worker                 clipNorm = clipNorm.normalize();
343*c8dee2aaSAndroid Build Coastguard Worker                 // This should give us 1/2 pixel spacing from the half-plane
344*c8dee2aaSAndroid Build Coastguard Worker                 // after transforming from normalized to local to device space.
345*c8dee2aaSAndroid Build Coastguard Worker                 float dist = 0.5f / radius / transform.maxScaleFactor();
346*c8dee2aaSAndroid Build Coastguard Worker                 geoClipPlane = {clipNorm.x, clipNorm.y, dist};
347*c8dee2aaSAndroid Build Coastguard Worker             }
348*c8dee2aaSAndroid Build Coastguard Worker         }
349*c8dee2aaSAndroid Build Coastguard Worker     } else {
350*c8dee2aaSAndroid Build Coastguard Worker         // We clip to a secant of the original circle, only one clip plane
351*c8dee2aaSAndroid Build Coastguard Worker         startPoint *= radius;
352*c8dee2aaSAndroid Build Coastguard Worker         stopPoint *= radius;
353*c8dee2aaSAndroid Build Coastguard Worker         SkV2 norm = {startPoint.y - stopPoint.y, stopPoint.x - startPoint.x};
354*c8dee2aaSAndroid Build Coastguard Worker         norm = norm.normalize();
355*c8dee2aaSAndroid Build Coastguard Worker         if (sweepAngleRadians > 0) {
356*c8dee2aaSAndroid Build Coastguard Worker             norm = -norm;
357*c8dee2aaSAndroid Build Coastguard Worker         }
358*c8dee2aaSAndroid Build Coastguard Worker         float d = -norm.dot(startPoint) + 0.5f;
359*c8dee2aaSAndroid Build Coastguard Worker         clipPlane0 = {norm.x, norm.y, d};
360*c8dee2aaSAndroid Build Coastguard Worker         clipPlane1 = {0.f, 0.f, 1.f}; // no clipping
361*c8dee2aaSAndroid Build Coastguard Worker     }
362*c8dee2aaSAndroid Build Coastguard Worker 
363*c8dee2aaSAndroid Build Coastguard Worker     // The inner radius in the vertex data must be specified in normalized space.
364*c8dee2aaSAndroid Build Coastguard Worker     innerRadius = innerRadius / outerRadius;
365*c8dee2aaSAndroid Build Coastguard Worker 
366*c8dee2aaSAndroid Build Coastguard Worker     vw << localCenter << localOuterRadius << localInnerRadius
367*c8dee2aaSAndroid Build Coastguard Worker        << outerRadius << innerRadius << flags
368*c8dee2aaSAndroid Build Coastguard Worker        << geoClipPlane << clipPlane0 << clipPlane1
369*c8dee2aaSAndroid Build Coastguard Worker        << roundCapPos0 << roundCapPos1
370*c8dee2aaSAndroid Build Coastguard Worker        << params.order().depthAsFloat()
371*c8dee2aaSAndroid Build Coastguard Worker        << ssboIndices
372*c8dee2aaSAndroid Build Coastguard Worker        << m.rc(0,0) << m.rc(1,0) << m.rc(3,0)  // mat0
373*c8dee2aaSAndroid Build Coastguard Worker        << m.rc(0,1) << m.rc(1,1) << m.rc(3,1)  // mat1
374*c8dee2aaSAndroid Build Coastguard Worker        << m.rc(0,3) << m.rc(1,3) << m.rc(3,3); // mat2
375*c8dee2aaSAndroid Build Coastguard Worker }
376*c8dee2aaSAndroid Build Coastguard Worker 
writeUniformsAndTextures(const DrawParams &,PipelineDataGatherer *) const377*c8dee2aaSAndroid Build Coastguard Worker void CircularArcRenderStep::writeUniformsAndTextures(const DrawParams&,
378*c8dee2aaSAndroid Build Coastguard Worker                                                      PipelineDataGatherer*) const {
379*c8dee2aaSAndroid Build Coastguard Worker     // All data is uploaded as instance attributes, so no uniforms are needed.
380*c8dee2aaSAndroid Build Coastguard Worker }
381*c8dee2aaSAndroid Build Coastguard Worker 
382*c8dee2aaSAndroid Build Coastguard Worker }  // namespace skgpu::graphite
383