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