xref: /aosp_15_r20/external/skia/src/gpu/graphite/ClipStack_graphite.cpp (revision c8dee2aa9b3f27cf6c858bd81872bdeb2c07ed17)
1*c8dee2aaSAndroid Build Coastguard Worker /*
2*c8dee2aaSAndroid Build Coastguard Worker  * Copyright 2022 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/ClipStack_graphite.h"
9*c8dee2aaSAndroid Build Coastguard Worker 
10*c8dee2aaSAndroid Build Coastguard Worker #include "include/core/SkMatrix.h"
11*c8dee2aaSAndroid Build Coastguard Worker #include "include/core/SkShader.h"
12*c8dee2aaSAndroid Build Coastguard Worker #include "include/core/SkStrokeRec.h"
13*c8dee2aaSAndroid Build Coastguard Worker #include "src/base/SkTLazy.h"
14*c8dee2aaSAndroid Build Coastguard Worker #include "src/core/SkPathPriv.h"
15*c8dee2aaSAndroid Build Coastguard Worker #include "src/core/SkRRectPriv.h"
16*c8dee2aaSAndroid Build Coastguard Worker #include "src/core/SkRectPriv.h"
17*c8dee2aaSAndroid Build Coastguard Worker #include "src/gpu/graphite/Device.h"
18*c8dee2aaSAndroid Build Coastguard Worker #include "src/gpu/graphite/DrawParams.h"
19*c8dee2aaSAndroid Build Coastguard Worker #include "src/gpu/graphite/geom/BoundsManager.h"
20*c8dee2aaSAndroid Build Coastguard Worker #include "src/gpu/graphite/geom/Geometry.h"
21*c8dee2aaSAndroid Build Coastguard Worker 
22*c8dee2aaSAndroid Build Coastguard Worker namespace skgpu::graphite {
23*c8dee2aaSAndroid Build Coastguard Worker 
24*c8dee2aaSAndroid Build Coastguard Worker namespace {
25*c8dee2aaSAndroid Build Coastguard Worker 
subtract(const Rect & a,const Rect & b,bool exact)26*c8dee2aaSAndroid Build Coastguard Worker Rect subtract(const Rect& a, const Rect& b, bool exact) {
27*c8dee2aaSAndroid Build Coastguard Worker     SkRect diff;
28*c8dee2aaSAndroid Build Coastguard Worker     if (SkRectPriv::Subtract(a.asSkRect(), b.asSkRect(), &diff) || !exact) {
29*c8dee2aaSAndroid Build Coastguard Worker         // Either A-B is exactly the rectangle stored in diff, or we don't need an exact answer
30*c8dee2aaSAndroid Build Coastguard Worker         // and can settle for the subrect of A excluded from B (which is also 'diff')
31*c8dee2aaSAndroid Build Coastguard Worker         return Rect{diff};
32*c8dee2aaSAndroid Build Coastguard Worker     } else {
33*c8dee2aaSAndroid Build Coastguard Worker         // For our purposes, we want the original A when A-B cannot be exactly represented
34*c8dee2aaSAndroid Build Coastguard Worker         return a;
35*c8dee2aaSAndroid Build Coastguard Worker     }
36*c8dee2aaSAndroid Build Coastguard Worker }
37*c8dee2aaSAndroid Build Coastguard Worker 
oriented_bbox_intersection(const Rect & a,const Transform & aXform,const Rect & b,const Transform & bXform)38*c8dee2aaSAndroid Build Coastguard Worker bool oriented_bbox_intersection(const Rect& a, const Transform& aXform,
39*c8dee2aaSAndroid Build Coastguard Worker                                 const Rect& b, const Transform& bXform) {
40*c8dee2aaSAndroid Build Coastguard Worker     // NOTE: We intentionally exclude projected bounds for two reasons:
41*c8dee2aaSAndroid Build Coastguard Worker     //   1. We can skip the division by w and worring about clipping to w = 0.
42*c8dee2aaSAndroid Build Coastguard Worker     //   2. W/o the projective case, the separating axes are simpler to compute (see below).
43*c8dee2aaSAndroid Build Coastguard Worker     SkASSERT(aXform.type() != Transform::Type::kPerspective &&
44*c8dee2aaSAndroid Build Coastguard Worker              bXform.type() != Transform::Type::kPerspective);
45*c8dee2aaSAndroid Build Coastguard Worker     SkV4 quadA[4], quadB[4];
46*c8dee2aaSAndroid Build Coastguard Worker 
47*c8dee2aaSAndroid Build Coastguard Worker     aXform.mapPoints(a, quadA);
48*c8dee2aaSAndroid Build Coastguard Worker     bXform.mapPoints(b, quadB);
49*c8dee2aaSAndroid Build Coastguard Worker 
50*c8dee2aaSAndroid Build Coastguard Worker     // There are 4 separating axes, defined by the two normals from quadA and from quadB, but
51*c8dee2aaSAndroid Build Coastguard Worker     // since they were produced by transforming a rectangle by an affine transform, we know the
52*c8dee2aaSAndroid Build Coastguard Worker     // normals are orthoganal to the basis vectors of upper 2x2 of their two transforms.
53*c8dee2aaSAndroid Build Coastguard Worker     auto axesX = skvx::float4(-aXform.matrix().rc(1,0), -aXform.matrix().rc(1,1),
54*c8dee2aaSAndroid Build Coastguard Worker                               -bXform.matrix().rc(1,0), -bXform.matrix().rc(1,1));
55*c8dee2aaSAndroid Build Coastguard Worker     auto axesY = skvx::float4(aXform.matrix().rc(0,0), aXform.matrix().rc(0,1),
56*c8dee2aaSAndroid Build Coastguard Worker                               bXform.matrix().rc(0,0), bXform.matrix().rc(0,1));
57*c8dee2aaSAndroid Build Coastguard Worker 
58*c8dee2aaSAndroid Build Coastguard Worker     // Projections of the 4 corners of each quadrilateral vs. the 4 axes. For orthonormal
59*c8dee2aaSAndroid Build Coastguard Worker     // transforms, the projections of a quad's corners to its own normal axes should work out
60*c8dee2aaSAndroid Build Coastguard Worker     // to the original dimensions of the rectangle, but this code handles skew and scale factors
61*c8dee2aaSAndroid Build Coastguard Worker     // without branching.
62*c8dee2aaSAndroid Build Coastguard Worker     auto aProj0 = quadA[0].x * axesX + quadA[0].y * axesY;
63*c8dee2aaSAndroid Build Coastguard Worker     auto aProj1 = quadA[1].x * axesX + quadA[1].y * axesY;
64*c8dee2aaSAndroid Build Coastguard Worker     auto aProj2 = quadA[2].x * axesX + quadA[2].y * axesY;
65*c8dee2aaSAndroid Build Coastguard Worker     auto aProj3 = quadA[3].x * axesX + quadA[3].y * axesY;
66*c8dee2aaSAndroid Build Coastguard Worker 
67*c8dee2aaSAndroid Build Coastguard Worker     auto bProj0 = quadB[0].x * axesX + quadB[0].y * axesY;
68*c8dee2aaSAndroid Build Coastguard Worker     auto bProj1 = quadB[1].x * axesX + quadB[1].y * axesY;
69*c8dee2aaSAndroid Build Coastguard Worker     auto bProj2 = quadB[2].x * axesX + quadB[2].y * axesY;
70*c8dee2aaSAndroid Build Coastguard Worker     auto bProj3 = quadB[3].x * axesX + quadB[3].y * axesY;
71*c8dee2aaSAndroid Build Coastguard Worker 
72*c8dee2aaSAndroid Build Coastguard Worker     // Minimum and maximum projected values against the 4 axes, for both quadA and quadB, which
73*c8dee2aaSAndroid Build Coastguard Worker     // gives us four pairs of intervals to test for separation.
74*c8dee2aaSAndroid Build Coastguard Worker     auto minA = min(min(aProj0, aProj1), min(aProj2, aProj3));
75*c8dee2aaSAndroid Build Coastguard Worker     auto maxA = max(max(aProj0, aProj1), max(aProj2, aProj3));
76*c8dee2aaSAndroid Build Coastguard Worker     auto minB = min(min(bProj0, bProj1), min(bProj2, bProj3));
77*c8dee2aaSAndroid Build Coastguard Worker     auto maxB = max(max(bProj0, bProj1), max(bProj2, bProj3));
78*c8dee2aaSAndroid Build Coastguard Worker 
79*c8dee2aaSAndroid Build Coastguard Worker     auto overlaps = (minB <= maxA) & (minA <= maxB);
80*c8dee2aaSAndroid Build Coastguard Worker     return all(overlaps); // any non-overlapping interval would imply no intersection
81*c8dee2aaSAndroid Build Coastguard Worker }
82*c8dee2aaSAndroid Build Coastguard Worker 
83*c8dee2aaSAndroid Build Coastguard Worker static constexpr Transform kIdentity = Transform::Identity();
84*c8dee2aaSAndroid Build Coastguard Worker 
85*c8dee2aaSAndroid Build Coastguard Worker } // anonymous namespace
86*c8dee2aaSAndroid Build Coastguard Worker 
87*c8dee2aaSAndroid Build Coastguard Worker ///////////////////////////////////////////////////////////////////////////////
88*c8dee2aaSAndroid Build Coastguard Worker // ClipStack::TransformedShape
89*c8dee2aaSAndroid Build Coastguard Worker 
90*c8dee2aaSAndroid Build Coastguard Worker // A flyweight object describing geometry, subject to a local-to-device transform.
91*c8dee2aaSAndroid Build Coastguard Worker // This can be used by SaveRecords, Elements, and draws to determine how two shape operations
92*c8dee2aaSAndroid Build Coastguard Worker // interact with each other, without needing to share a base class, friend each other, or have a
93*c8dee2aaSAndroid Build Coastguard Worker // template for each combination of two types.
94*c8dee2aaSAndroid Build Coastguard Worker struct ClipStack::TransformedShape {
95*c8dee2aaSAndroid Build Coastguard Worker     const Transform& fLocalToDevice;
96*c8dee2aaSAndroid Build Coastguard Worker     const Shape&     fShape;
97*c8dee2aaSAndroid Build Coastguard Worker     const Rect&      fOuterBounds;
98*c8dee2aaSAndroid Build Coastguard Worker     const Rect&      fInnerBounds;
99*c8dee2aaSAndroid Build Coastguard Worker 
100*c8dee2aaSAndroid Build Coastguard Worker     SkClipOp         fOp;
101*c8dee2aaSAndroid Build Coastguard Worker 
102*c8dee2aaSAndroid Build Coastguard Worker     // contains() performs a fair amount of work to be as accurate as possible since it can mean
103*c8dee2aaSAndroid Build Coastguard Worker     // greatly simplifying the clip stack. However, in some contexts this isn't worth doing because
104*c8dee2aaSAndroid Build Coastguard Worker     // the actual shape is only an approximation (save records), or there's no current way to take
105*c8dee2aaSAndroid Build Coastguard Worker     // advantage of knowing this shape contains another (draws containing a clip hypothetically
106*c8dee2aaSAndroid Build Coastguard Worker     // could replace their geometry to draw the clip directly, but that isn't implemented now).
107*c8dee2aaSAndroid Build Coastguard Worker     bool fContainsChecksOnlyBounds = false;
108*c8dee2aaSAndroid Build Coastguard Worker 
109*c8dee2aaSAndroid Build Coastguard Worker     bool intersects(const TransformedShape&) const;
110*c8dee2aaSAndroid Build Coastguard Worker     bool contains(const TransformedShape&) const;
111*c8dee2aaSAndroid Build Coastguard Worker };
112*c8dee2aaSAndroid Build Coastguard Worker 
intersects(const TransformedShape & o) const113*c8dee2aaSAndroid Build Coastguard Worker bool ClipStack::TransformedShape::intersects(const TransformedShape& o) const {
114*c8dee2aaSAndroid Build Coastguard Worker     if (!fOuterBounds.intersects(o.fOuterBounds)) {
115*c8dee2aaSAndroid Build Coastguard Worker         return false;
116*c8dee2aaSAndroid Build Coastguard Worker     }
117*c8dee2aaSAndroid Build Coastguard Worker 
118*c8dee2aaSAndroid Build Coastguard Worker     if (fLocalToDevice.type() <= Transform::Type::kRectStaysRect &&
119*c8dee2aaSAndroid Build Coastguard Worker         o.fLocalToDevice.type() <= Transform::Type::kRectStaysRect) {
120*c8dee2aaSAndroid Build Coastguard Worker         // The two shape's coordinate spaces are different but both rect-stays-rect or simpler.
121*c8dee2aaSAndroid Build Coastguard Worker         // This means, though, that their outer bounds approximations are tight to their transormed
122*c8dee2aaSAndroid Build Coastguard Worker         // shape bounds. There's no point to do further tests given that and that we already found
123*c8dee2aaSAndroid Build Coastguard Worker         // that these outer bounds *do* intersect.
124*c8dee2aaSAndroid Build Coastguard Worker         return true;
125*c8dee2aaSAndroid Build Coastguard Worker     } else if (fLocalToDevice == o.fLocalToDevice) {
126*c8dee2aaSAndroid Build Coastguard Worker         // Since the two shape's local coordinate spaces are the same, we can compare shape
127*c8dee2aaSAndroid Build Coastguard Worker         // bounds directly for a more accurate intersection test. We intentionally do not go
128*c8dee2aaSAndroid Build Coastguard Worker         // further and do shape-specific intersection tests since these could have unknown
129*c8dee2aaSAndroid Build Coastguard Worker         // complexity (for paths) and limited utility (e.g. two round rects that are disjoint
130*c8dee2aaSAndroid Build Coastguard Worker         // solely from their corner curves).
131*c8dee2aaSAndroid Build Coastguard Worker         return fShape.bounds().intersects(o.fShape.bounds());
132*c8dee2aaSAndroid Build Coastguard Worker     } else if (fLocalToDevice.type() != Transform::Type::kPerspective &&
133*c8dee2aaSAndroid Build Coastguard Worker                o.fLocalToDevice.type() != Transform::Type::kPerspective) {
134*c8dee2aaSAndroid Build Coastguard Worker         // The shapes don't share the same coordinate system, and their approximate 'outer'
135*c8dee2aaSAndroid Build Coastguard Worker         // bounds in device space could have substantial outsetting to contain the transformed
136*c8dee2aaSAndroid Build Coastguard Worker         // shape (e.g. 45 degree rotation). Perform a more detailed check on their oriented
137*c8dee2aaSAndroid Build Coastguard Worker         // bounding boxes.
138*c8dee2aaSAndroid Build Coastguard Worker         return oriented_bbox_intersection(fShape.bounds(), fLocalToDevice,
139*c8dee2aaSAndroid Build Coastguard Worker                                           o.fShape.bounds(), o.fLocalToDevice);
140*c8dee2aaSAndroid Build Coastguard Worker     }
141*c8dee2aaSAndroid Build Coastguard Worker     // Else multiple perspective transforms are involved, so assume intersection and allow the
142*c8dee2aaSAndroid Build Coastguard Worker     // rasterizer to handle perspective clipping.
143*c8dee2aaSAndroid Build Coastguard Worker     return true;
144*c8dee2aaSAndroid Build Coastguard Worker }
145*c8dee2aaSAndroid Build Coastguard Worker 
contains(const TransformedShape & o) const146*c8dee2aaSAndroid Build Coastguard Worker bool ClipStack::TransformedShape::contains(const TransformedShape& o) const {
147*c8dee2aaSAndroid Build Coastguard Worker     if (fInnerBounds.contains(o.fOuterBounds)) {
148*c8dee2aaSAndroid Build Coastguard Worker         return true;
149*c8dee2aaSAndroid Build Coastguard Worker     }
150*c8dee2aaSAndroid Build Coastguard Worker     // Skip more expensive contains() checks if configured not to, or if the extent of 'o' exceeds
151*c8dee2aaSAndroid Build Coastguard Worker     // this shape's outer bounds. When that happens there must be some part of 'o' that cannot be
152*c8dee2aaSAndroid Build Coastguard Worker     // contained in this shape.
153*c8dee2aaSAndroid Build Coastguard Worker     if (fContainsChecksOnlyBounds || !fOuterBounds.contains(o.fOuterBounds)) {
154*c8dee2aaSAndroid Build Coastguard Worker         return false;
155*c8dee2aaSAndroid Build Coastguard Worker     }
156*c8dee2aaSAndroid Build Coastguard Worker 
157*c8dee2aaSAndroid Build Coastguard Worker     if (fContainsChecksOnlyBounds) {
158*c8dee2aaSAndroid Build Coastguard Worker         return false; // don't do any more work
159*c8dee2aaSAndroid Build Coastguard Worker     }
160*c8dee2aaSAndroid Build Coastguard Worker 
161*c8dee2aaSAndroid Build Coastguard Worker     if (fLocalToDevice == o.fLocalToDevice) {
162*c8dee2aaSAndroid Build Coastguard Worker         // Test the shapes directly against each other, with a special check for a rrect+rrect
163*c8dee2aaSAndroid Build Coastguard Worker         // containment (a intersect b == a implies b contains a) and paths (same gen ID, or same
164*c8dee2aaSAndroid Build Coastguard Worker         // path for small paths means they contain each other).
165*c8dee2aaSAndroid Build Coastguard Worker         static constexpr int kMaxPathComparePoints = 16;
166*c8dee2aaSAndroid Build Coastguard Worker         if (fShape.isRRect() && o.fShape.isRRect()) {
167*c8dee2aaSAndroid Build Coastguard Worker             return SkRRectPriv::ConservativeIntersect(fShape.rrect(), o.fShape.rrect())
168*c8dee2aaSAndroid Build Coastguard Worker                     == o.fShape.rrect();
169*c8dee2aaSAndroid Build Coastguard Worker         } else if (fShape.isPath() && o.fShape.isPath()) {
170*c8dee2aaSAndroid Build Coastguard Worker             // TODO: Is this worth doing still if clips only cost as much as a single draw?
171*c8dee2aaSAndroid Build Coastguard Worker             return (fShape.path().getGenerationID() == o.fShape.path().getGenerationID()) ||
172*c8dee2aaSAndroid Build Coastguard Worker                     (fShape.path().countPoints() <= kMaxPathComparePoints &&
173*c8dee2aaSAndroid Build Coastguard Worker                     fShape.path() == o.fShape.path());
174*c8dee2aaSAndroid Build Coastguard Worker         } else {
175*c8dee2aaSAndroid Build Coastguard Worker             return fShape.conservativeContains(o.fShape.bounds());
176*c8dee2aaSAndroid Build Coastguard Worker         }
177*c8dee2aaSAndroid Build Coastguard Worker     } else if (fLocalToDevice.type() <= Transform::Type::kRectStaysRect &&
178*c8dee2aaSAndroid Build Coastguard Worker                o.fLocalToDevice.type() <= Transform::Type::kRectStaysRect) {
179*c8dee2aaSAndroid Build Coastguard Worker         // Optimize the common case where o's bounds can be mapped tightly into this coordinate
180*c8dee2aaSAndroid Build Coastguard Worker         // space and then tested against our shape.
181*c8dee2aaSAndroid Build Coastguard Worker         Rect localBounds = fLocalToDevice.inverseMapRect(
182*c8dee2aaSAndroid Build Coastguard Worker                 o.fLocalToDevice.mapRect(o.fShape.bounds()));
183*c8dee2aaSAndroid Build Coastguard Worker         return fShape.conservativeContains(localBounds);
184*c8dee2aaSAndroid Build Coastguard Worker     } else if (fShape.convex()) {
185*c8dee2aaSAndroid Build Coastguard Worker         // Since this shape is convex, if all four corners of o's bounding box are inside it
186*c8dee2aaSAndroid Build Coastguard Worker         // then the entirety of o is also guaranteed to be inside it.
187*c8dee2aaSAndroid Build Coastguard Worker         SkV4 deviceQuad[4];
188*c8dee2aaSAndroid Build Coastguard Worker         o.fLocalToDevice.mapPoints(o.fShape.bounds(), deviceQuad);
189*c8dee2aaSAndroid Build Coastguard Worker         SkV4 localQuad[4];
190*c8dee2aaSAndroid Build Coastguard Worker         fLocalToDevice.inverseMapPoints(deviceQuad, localQuad, 4);
191*c8dee2aaSAndroid Build Coastguard Worker         for (int i = 0; i < 4; ++i) {
192*c8dee2aaSAndroid Build Coastguard Worker             // TODO: Would be nice to make this consistent with how the GPU clips NDC w.
193*c8dee2aaSAndroid Build Coastguard Worker             if (deviceQuad[i].w < SkPathPriv::kW0PlaneDistance ||
194*c8dee2aaSAndroid Build Coastguard Worker                 localQuad[i].w < SkPathPriv::kW0PlaneDistance) {
195*c8dee2aaSAndroid Build Coastguard Worker                 // Something in O actually projects behind the W = 0 plane and would be clipped
196*c8dee2aaSAndroid Build Coastguard Worker                 // to infinity, so it's extremely unlikely that this contains O.
197*c8dee2aaSAndroid Build Coastguard Worker                 return false;
198*c8dee2aaSAndroid Build Coastguard Worker             }
199*c8dee2aaSAndroid Build Coastguard Worker             if (!fShape.conservativeContains(skvx::float2::Load(localQuad + i) / localQuad[i].w)) {
200*c8dee2aaSAndroid Build Coastguard Worker                 return false;
201*c8dee2aaSAndroid Build Coastguard Worker             }
202*c8dee2aaSAndroid Build Coastguard Worker         }
203*c8dee2aaSAndroid Build Coastguard Worker         return true;
204*c8dee2aaSAndroid Build Coastguard Worker     }
205*c8dee2aaSAndroid Build Coastguard Worker 
206*c8dee2aaSAndroid Build Coastguard Worker     // Else not an easily comparable pair of shapes so assume this doesn't contain O
207*c8dee2aaSAndroid Build Coastguard Worker     return false;
208*c8dee2aaSAndroid Build Coastguard Worker }
209*c8dee2aaSAndroid Build Coastguard Worker 
Simplify(const TransformedShape & a,const TransformedShape & b)210*c8dee2aaSAndroid Build Coastguard Worker ClipStack::SimplifyResult ClipStack::Simplify(const TransformedShape& a,
211*c8dee2aaSAndroid Build Coastguard Worker                                               const TransformedShape& b) {
212*c8dee2aaSAndroid Build Coastguard Worker     enum class ClipCombo {
213*c8dee2aaSAndroid Build Coastguard Worker         kDD = 0b00,
214*c8dee2aaSAndroid Build Coastguard Worker         kDI = 0b01,
215*c8dee2aaSAndroid Build Coastguard Worker         kID = 0b10,
216*c8dee2aaSAndroid Build Coastguard Worker         kII = 0b11
217*c8dee2aaSAndroid Build Coastguard Worker     };
218*c8dee2aaSAndroid Build Coastguard Worker 
219*c8dee2aaSAndroid Build Coastguard Worker     switch(static_cast<ClipCombo>(((int) a.fOp << 1) | (int) b.fOp)) {
220*c8dee2aaSAndroid Build Coastguard Worker         case ClipCombo::kII:
221*c8dee2aaSAndroid Build Coastguard Worker             // Intersect (A) + Intersect (B)
222*c8dee2aaSAndroid Build Coastguard Worker             if (!a.intersects(b)) {
223*c8dee2aaSAndroid Build Coastguard Worker                 // Regions with non-zero coverage are disjoint, so intersection = empty
224*c8dee2aaSAndroid Build Coastguard Worker                 return SimplifyResult::kEmpty;
225*c8dee2aaSAndroid Build Coastguard Worker             } else if (b.contains(a)) {
226*c8dee2aaSAndroid Build Coastguard Worker                 // B's full coverage region contains entirety of A, so intersection = A
227*c8dee2aaSAndroid Build Coastguard Worker                 return SimplifyResult::kAOnly;
228*c8dee2aaSAndroid Build Coastguard Worker             } else if (a.contains(b)) {
229*c8dee2aaSAndroid Build Coastguard Worker                 // A's full coverage region contains entirety of B, so intersection = B
230*c8dee2aaSAndroid Build Coastguard Worker                 return SimplifyResult::kBOnly;
231*c8dee2aaSAndroid Build Coastguard Worker             } else {
232*c8dee2aaSAndroid Build Coastguard Worker                 // The shapes intersect in some non-trivial manner
233*c8dee2aaSAndroid Build Coastguard Worker                 return SimplifyResult::kBoth;
234*c8dee2aaSAndroid Build Coastguard Worker             }
235*c8dee2aaSAndroid Build Coastguard Worker         case ClipCombo::kID:
236*c8dee2aaSAndroid Build Coastguard Worker             // Intersect (A) + Difference (B)
237*c8dee2aaSAndroid Build Coastguard Worker             if (!a.intersects(b)) {
238*c8dee2aaSAndroid Build Coastguard Worker                 // A only intersects B's full coverage region, so intersection = A
239*c8dee2aaSAndroid Build Coastguard Worker                 return SimplifyResult::kAOnly;
240*c8dee2aaSAndroid Build Coastguard Worker             } else if (b.contains(a)) {
241*c8dee2aaSAndroid Build Coastguard Worker                 // B's zero coverage region completely contains A, so intersection = empty
242*c8dee2aaSAndroid Build Coastguard Worker                 return SimplifyResult::kEmpty;
243*c8dee2aaSAndroid Build Coastguard Worker             } else {
244*c8dee2aaSAndroid Build Coastguard Worker                 // Intersection cannot be simplified. Note that the combination of a intersect
245*c8dee2aaSAndroid Build Coastguard Worker                 // and difference op in this order cannot produce kBOnly
246*c8dee2aaSAndroid Build Coastguard Worker                 return SimplifyResult::kBoth;
247*c8dee2aaSAndroid Build Coastguard Worker             }
248*c8dee2aaSAndroid Build Coastguard Worker         case ClipCombo::kDI:
249*c8dee2aaSAndroid Build Coastguard Worker             // Difference (A) + Intersect (B) - the mirror of Intersect(A) + Difference(B),
250*c8dee2aaSAndroid Build Coastguard Worker             // but combining is commutative so this is equivalent barring naming.
251*c8dee2aaSAndroid Build Coastguard Worker             if (!b.intersects(a)) {
252*c8dee2aaSAndroid Build Coastguard Worker                 // B only intersects A's full coverage region, so intersection = B
253*c8dee2aaSAndroid Build Coastguard Worker                 return SimplifyResult::kBOnly;
254*c8dee2aaSAndroid Build Coastguard Worker             } else if (a.contains(b)) {
255*c8dee2aaSAndroid Build Coastguard Worker                 // A's zero coverage region completely contains B, so intersection = empty
256*c8dee2aaSAndroid Build Coastguard Worker                 return SimplifyResult::kEmpty;
257*c8dee2aaSAndroid Build Coastguard Worker             } else {
258*c8dee2aaSAndroid Build Coastguard Worker                 // Cannot be simplified
259*c8dee2aaSAndroid Build Coastguard Worker                 return SimplifyResult::kBoth;
260*c8dee2aaSAndroid Build Coastguard Worker             }
261*c8dee2aaSAndroid Build Coastguard Worker         case ClipCombo::kDD:
262*c8dee2aaSAndroid Build Coastguard Worker             // Difference (A) + Difference (B)
263*c8dee2aaSAndroid Build Coastguard Worker             if (a.contains(b)) {
264*c8dee2aaSAndroid Build Coastguard Worker                 // A's zero coverage region contains B, so B doesn't remove any extra
265*c8dee2aaSAndroid Build Coastguard Worker                 // coverage from their intersection.
266*c8dee2aaSAndroid Build Coastguard Worker                 return SimplifyResult::kAOnly;
267*c8dee2aaSAndroid Build Coastguard Worker             } else if (b.contains(a)) {
268*c8dee2aaSAndroid Build Coastguard Worker                 // Mirror of the above case, intersection = B instead
269*c8dee2aaSAndroid Build Coastguard Worker                 return SimplifyResult::kBOnly;
270*c8dee2aaSAndroid Build Coastguard Worker             } else {
271*c8dee2aaSAndroid Build Coastguard Worker                 // Intersection of the two differences cannot be simplified. Note that for
272*c8dee2aaSAndroid Build Coastguard Worker                 // this op combination it is not possible to produce kEmpty.
273*c8dee2aaSAndroid Build Coastguard Worker                 return SimplifyResult::kBoth;
274*c8dee2aaSAndroid Build Coastguard Worker             }
275*c8dee2aaSAndroid Build Coastguard Worker     }
276*c8dee2aaSAndroid Build Coastguard Worker     SkUNREACHABLE;
277*c8dee2aaSAndroid Build Coastguard Worker }
278*c8dee2aaSAndroid Build Coastguard Worker 
279*c8dee2aaSAndroid Build Coastguard Worker ///////////////////////////////////////////////////////////////////////////////
280*c8dee2aaSAndroid Build Coastguard Worker // ClipStack::Element
281*c8dee2aaSAndroid Build Coastguard Worker 
RawElement(const Rect & deviceBounds,const Transform & localToDevice,const Shape & shape,SkClipOp op,PixelSnapping snapping)282*c8dee2aaSAndroid Build Coastguard Worker ClipStack::RawElement::RawElement(const Rect& deviceBounds,
283*c8dee2aaSAndroid Build Coastguard Worker                                   const Transform& localToDevice,
284*c8dee2aaSAndroid Build Coastguard Worker                                   const Shape& shape,
285*c8dee2aaSAndroid Build Coastguard Worker                                   SkClipOp op,
286*c8dee2aaSAndroid Build Coastguard Worker                                   PixelSnapping snapping)
287*c8dee2aaSAndroid Build Coastguard Worker         : Element{shape, localToDevice, op}
288*c8dee2aaSAndroid Build Coastguard Worker         , fUsageBounds{Rect::InfiniteInverted()}
289*c8dee2aaSAndroid Build Coastguard Worker         , fOrder(DrawOrder::kNoIntersection)
290*c8dee2aaSAndroid Build Coastguard Worker         , fMaxZ(DrawOrder::kClearDepth)
291*c8dee2aaSAndroid Build Coastguard Worker         , fInvalidatedByIndex(-1) {
292*c8dee2aaSAndroid Build Coastguard Worker     // Discard shapes that don't have any area (including when a transform can't be inverted, since
293*c8dee2aaSAndroid Build Coastguard Worker     // it means the two dimensions are collapsed to 0 or 1 dimension in device space).
294*c8dee2aaSAndroid Build Coastguard Worker     if (fShape.isLine() || !localToDevice.valid()) {
295*c8dee2aaSAndroid Build Coastguard Worker         fShape.reset();
296*c8dee2aaSAndroid Build Coastguard Worker     }
297*c8dee2aaSAndroid Build Coastguard Worker     // Make sure the shape is not inverted. An inverted shape is equivalent to a non-inverted shape
298*c8dee2aaSAndroid Build Coastguard Worker     // with the clip op toggled.
299*c8dee2aaSAndroid Build Coastguard Worker     if (fShape.inverted()) {
300*c8dee2aaSAndroid Build Coastguard Worker         fOp = (fOp == SkClipOp::kIntersect) ? SkClipOp::kDifference : SkClipOp::kIntersect;
301*c8dee2aaSAndroid Build Coastguard Worker     }
302*c8dee2aaSAndroid Build Coastguard Worker 
303*c8dee2aaSAndroid Build Coastguard Worker     fOuterBounds = fLocalToDevice.mapRect(fShape.bounds()).makeIntersect(deviceBounds);
304*c8dee2aaSAndroid Build Coastguard Worker     fInnerBounds = Rect::InfiniteInverted();
305*c8dee2aaSAndroid Build Coastguard Worker 
306*c8dee2aaSAndroid Build Coastguard Worker     // Apply rect-stays-rect transforms to rects and round rects to reduce the number of unique
307*c8dee2aaSAndroid Build Coastguard Worker     // local coordinate systems that are in play.
308*c8dee2aaSAndroid Build Coastguard Worker     if (!fOuterBounds.isEmptyNegativeOrNaN() &&
309*c8dee2aaSAndroid Build Coastguard Worker         fLocalToDevice.type() <= Transform::Type::kRectStaysRect) {
310*c8dee2aaSAndroid Build Coastguard Worker         if (fShape.isRect()) {
311*c8dee2aaSAndroid Build Coastguard Worker             // The actual geometry can be updated to the device-intersected bounds and we know the
312*c8dee2aaSAndroid Build Coastguard Worker             // inner bounds are equal to the outer.
313*c8dee2aaSAndroid Build Coastguard Worker             if (snapping == PixelSnapping::kYes) {
314*c8dee2aaSAndroid Build Coastguard Worker                 fOuterBounds.round();
315*c8dee2aaSAndroid Build Coastguard Worker             }
316*c8dee2aaSAndroid Build Coastguard Worker             fShape.setRect(fOuterBounds);
317*c8dee2aaSAndroid Build Coastguard Worker             fLocalToDevice = kIdentity;
318*c8dee2aaSAndroid Build Coastguard Worker             fInnerBounds = fOuterBounds;
319*c8dee2aaSAndroid Build Coastguard Worker         } else if (fShape.isRRect()) {
320*c8dee2aaSAndroid Build Coastguard Worker             // Can't transform in place and must still check transform result since some very
321*c8dee2aaSAndroid Build Coastguard Worker             // ill-formed scale+translate matrices can cause invalid rrect radii.
322*c8dee2aaSAndroid Build Coastguard Worker             SkRRect xformed;
323*c8dee2aaSAndroid Build Coastguard Worker             if (fShape.rrect().transform(fLocalToDevice, &xformed)) {
324*c8dee2aaSAndroid Build Coastguard Worker                 if (snapping == PixelSnapping::kYes) {
325*c8dee2aaSAndroid Build Coastguard Worker                     // The rounded corners will still be anti-aliased, but snap the horizontal and
326*c8dee2aaSAndroid Build Coastguard Worker                     // vertical edges to pixel values.
327*c8dee2aaSAndroid Build Coastguard Worker                     xformed.setRectRadii(SkRect::Make(xformed.rect().round()),
328*c8dee2aaSAndroid Build Coastguard Worker                                          xformed.radii().data());
329*c8dee2aaSAndroid Build Coastguard Worker                 }
330*c8dee2aaSAndroid Build Coastguard Worker                 fShape.setRRect(xformed);
331*c8dee2aaSAndroid Build Coastguard Worker                 fLocalToDevice = kIdentity;
332*c8dee2aaSAndroid Build Coastguard Worker                 // Refresh outer bounds to match the transformed round rect in case
333*c8dee2aaSAndroid Build Coastguard Worker                 // SkRRect::transform produces slightly different results from Transform::mapRect.
334*c8dee2aaSAndroid Build Coastguard Worker                 fOuterBounds = fShape.bounds().makeIntersect(deviceBounds);
335*c8dee2aaSAndroid Build Coastguard Worker                 fInnerBounds = Rect{SkRRectPriv::InnerBounds(xformed)}.makeIntersect(fOuterBounds);
336*c8dee2aaSAndroid Build Coastguard Worker             }
337*c8dee2aaSAndroid Build Coastguard Worker         }
338*c8dee2aaSAndroid Build Coastguard Worker     }
339*c8dee2aaSAndroid Build Coastguard Worker 
340*c8dee2aaSAndroid Build Coastguard Worker     if (fOuterBounds.isEmptyNegativeOrNaN()) {
341*c8dee2aaSAndroid Build Coastguard Worker         // Either was already an empty shape or a non-empty shape is offscreen, so treat it as such.
342*c8dee2aaSAndroid Build Coastguard Worker         fShape.reset();
343*c8dee2aaSAndroid Build Coastguard Worker         fInnerBounds = Rect::InfiniteInverted();
344*c8dee2aaSAndroid Build Coastguard Worker     }
345*c8dee2aaSAndroid Build Coastguard Worker 
346*c8dee2aaSAndroid Build Coastguard Worker     // Now that fOp and fShape are canonical, set the shape's fill type to match how it needs to be
347*c8dee2aaSAndroid Build Coastguard Worker     // drawn as a depth-only shape everywhere that is clipped out (intersect is thus inverse-filled)
348*c8dee2aaSAndroid Build Coastguard Worker     fShape.setInverted(fOp == SkClipOp::kIntersect);
349*c8dee2aaSAndroid Build Coastguard Worker 
350*c8dee2aaSAndroid Build Coastguard Worker     // Post-conditions on inner and outer bounds
351*c8dee2aaSAndroid Build Coastguard Worker     SkASSERT(fShape.isEmpty() || deviceBounds.contains(fOuterBounds));
352*c8dee2aaSAndroid Build Coastguard Worker     this->validate();
353*c8dee2aaSAndroid Build Coastguard Worker }
354*c8dee2aaSAndroid Build Coastguard Worker 
operator ClipStack::TransformedShape() const355*c8dee2aaSAndroid Build Coastguard Worker ClipStack::RawElement::operator ClipStack::TransformedShape() const {
356*c8dee2aaSAndroid Build Coastguard Worker     return {fLocalToDevice, fShape, fOuterBounds, fInnerBounds, fOp};
357*c8dee2aaSAndroid Build Coastguard Worker }
358*c8dee2aaSAndroid Build Coastguard Worker 
drawClip(Device * device)359*c8dee2aaSAndroid Build Coastguard Worker void ClipStack::RawElement::drawClip(Device* device) {
360*c8dee2aaSAndroid Build Coastguard Worker     this->validate();
361*c8dee2aaSAndroid Build Coastguard Worker 
362*c8dee2aaSAndroid Build Coastguard Worker     // Skip elements that have not affected any draws
363*c8dee2aaSAndroid Build Coastguard Worker     if (!this->hasPendingDraw()) {
364*c8dee2aaSAndroid Build Coastguard Worker         SkASSERT(fUsageBounds.isEmptyNegativeOrNaN());
365*c8dee2aaSAndroid Build Coastguard Worker         return;
366*c8dee2aaSAndroid Build Coastguard Worker     }
367*c8dee2aaSAndroid Build Coastguard Worker 
368*c8dee2aaSAndroid Build Coastguard Worker     SkASSERT(!fUsageBounds.isEmptyNegativeOrNaN());
369*c8dee2aaSAndroid Build Coastguard Worker     // For clip draws, the usage bounds is the scissor.
370*c8dee2aaSAndroid Build Coastguard Worker     Rect scissor = fUsageBounds.makeRoundOut();
371*c8dee2aaSAndroid Build Coastguard Worker     Rect drawBounds = fOuterBounds.makeIntersect(scissor);
372*c8dee2aaSAndroid Build Coastguard Worker     if (!drawBounds.isEmptyNegativeOrNaN()) {
373*c8dee2aaSAndroid Build Coastguard Worker         // Although we are recording this clip draw after all the draws it affects, 'fOrder' was
374*c8dee2aaSAndroid Build Coastguard Worker         // determined at the first usage, so after sorting by DrawOrder the clip draw will be in the
375*c8dee2aaSAndroid Build Coastguard Worker         // right place. Unlike regular draws that use their own "Z", by writing (1 + max Z this clip
376*c8dee2aaSAndroid Build Coastguard Worker         // affects), it will cause those draws to fail either GREATER and GEQUAL depth tests where
377*c8dee2aaSAndroid Build Coastguard Worker         // they need to be clipped.
378*c8dee2aaSAndroid Build Coastguard Worker         DrawOrder order{fMaxZ.next(), fOrder};
379*c8dee2aaSAndroid Build Coastguard Worker         // An element's clip op is encoded in the shape's fill type. Inverse fills are intersect ops
380*c8dee2aaSAndroid Build Coastguard Worker         // and regular fills are difference ops. This means fShape is already in the right state to
381*c8dee2aaSAndroid Build Coastguard Worker         // draw directly.
382*c8dee2aaSAndroid Build Coastguard Worker         SkASSERT((fOp == SkClipOp::kDifference && !fShape.inverted()) ||
383*c8dee2aaSAndroid Build Coastguard Worker                  (fOp == SkClipOp::kIntersect && fShape.inverted()));
384*c8dee2aaSAndroid Build Coastguard Worker         device->drawClipShape(fLocalToDevice,
385*c8dee2aaSAndroid Build Coastguard Worker                               fShape,
386*c8dee2aaSAndroid Build Coastguard Worker                               Clip{drawBounds, drawBounds, scissor.asSkIRect(),
387*c8dee2aaSAndroid Build Coastguard Worker                                    /* analyticClip= */ {}, /* shader= */ nullptr},
388*c8dee2aaSAndroid Build Coastguard Worker                               order);
389*c8dee2aaSAndroid Build Coastguard Worker     }
390*c8dee2aaSAndroid Build Coastguard Worker 
391*c8dee2aaSAndroid Build Coastguard Worker     // After the clip shape is drawn, reset its state. If the clip element is being popped off the
392*c8dee2aaSAndroid Build Coastguard Worker     // stack or overwritten because a new clip invalidated it, this won't matter. But if the clips
393*c8dee2aaSAndroid Build Coastguard Worker     // were drawn because the Device had to flush pending work while the clip stack was not empty,
394*c8dee2aaSAndroid Build Coastguard Worker     // subsequent draws will still need to be clipped to the elements. In this case, the usage
395*c8dee2aaSAndroid Build Coastguard Worker     // accumulation process will begin again and automatically use the Device's post-flush Z values
396*c8dee2aaSAndroid Build Coastguard Worker     // and BoundsManager state.
397*c8dee2aaSAndroid Build Coastguard Worker     fUsageBounds = Rect::InfiniteInverted();
398*c8dee2aaSAndroid Build Coastguard Worker     fOrder = DrawOrder::kNoIntersection;
399*c8dee2aaSAndroid Build Coastguard Worker     fMaxZ = DrawOrder::kClearDepth;
400*c8dee2aaSAndroid Build Coastguard Worker }
401*c8dee2aaSAndroid Build Coastguard Worker 
validate() const402*c8dee2aaSAndroid Build Coastguard Worker void ClipStack::RawElement::validate() const {
403*c8dee2aaSAndroid Build Coastguard Worker     // If the shape type isn't empty, the outer bounds shouldn't be empty; if the inner bounds are
404*c8dee2aaSAndroid Build Coastguard Worker     // not empty, they must be contained in outer.
405*c8dee2aaSAndroid Build Coastguard Worker     SkASSERT((fShape.isEmpty() || !fOuterBounds.isEmptyNegativeOrNaN()) &&
406*c8dee2aaSAndroid Build Coastguard Worker              (fInnerBounds.isEmptyNegativeOrNaN() || fOuterBounds.contains(fInnerBounds)));
407*c8dee2aaSAndroid Build Coastguard Worker     SkASSERT((fOp == SkClipOp::kDifference && !fShape.inverted()) ||
408*c8dee2aaSAndroid Build Coastguard Worker              (fOp == SkClipOp::kIntersect && fShape.inverted()));
409*c8dee2aaSAndroid Build Coastguard Worker     SkASSERT(!this->hasPendingDraw() || !fUsageBounds.isEmptyNegativeOrNaN());
410*c8dee2aaSAndroid Build Coastguard Worker }
411*c8dee2aaSAndroid Build Coastguard Worker 
markInvalid(const SaveRecord & current)412*c8dee2aaSAndroid Build Coastguard Worker void ClipStack::RawElement::markInvalid(const SaveRecord& current) {
413*c8dee2aaSAndroid Build Coastguard Worker     SkASSERT(!this->isInvalid());
414*c8dee2aaSAndroid Build Coastguard Worker     fInvalidatedByIndex = current.firstActiveElementIndex();
415*c8dee2aaSAndroid Build Coastguard Worker     // NOTE: We don't draw the accumulated clip usage when the element is marked invalid. Some
416*c8dee2aaSAndroid Build Coastguard Worker     // invalidated elements are part of earlier save records so can become re-active after a restore
417*c8dee2aaSAndroid Build Coastguard Worker     // in which case they should continue to accumulate. Invalidated elements that are part of the
418*c8dee2aaSAndroid Build Coastguard Worker     // active save record are removed at the end of the stack modification, which is when they are
419*c8dee2aaSAndroid Build Coastguard Worker     // explicitly drawn.
420*c8dee2aaSAndroid Build Coastguard Worker }
421*c8dee2aaSAndroid Build Coastguard Worker 
restoreValid(const SaveRecord & current)422*c8dee2aaSAndroid Build Coastguard Worker void ClipStack::RawElement::restoreValid(const SaveRecord& current) {
423*c8dee2aaSAndroid Build Coastguard Worker     if (current.firstActiveElementIndex() < fInvalidatedByIndex) {
424*c8dee2aaSAndroid Build Coastguard Worker         fInvalidatedByIndex = -1;
425*c8dee2aaSAndroid Build Coastguard Worker     }
426*c8dee2aaSAndroid Build Coastguard Worker }
427*c8dee2aaSAndroid Build Coastguard Worker 
combine(const RawElement & other,const SaveRecord & current)428*c8dee2aaSAndroid Build Coastguard Worker bool ClipStack::RawElement::combine(const RawElement& other, const SaveRecord& current) {
429*c8dee2aaSAndroid Build Coastguard Worker     // Don't combine elements that have collected draw usage, since that changes their geometry.
430*c8dee2aaSAndroid Build Coastguard Worker     if (this->hasPendingDraw() || other.hasPendingDraw()) {
431*c8dee2aaSAndroid Build Coastguard Worker         return false;
432*c8dee2aaSAndroid Build Coastguard Worker     }
433*c8dee2aaSAndroid Build Coastguard Worker     // To reduce the number of possibilities, only consider intersect+intersect. Difference and
434*c8dee2aaSAndroid Build Coastguard Worker     // mixed op cases could be analyzed to simplify one of the shapes, but that is a rare
435*c8dee2aaSAndroid Build Coastguard Worker     // occurrence and the math is much more complicated.
436*c8dee2aaSAndroid Build Coastguard Worker     if (other.fOp != SkClipOp::kIntersect || fOp != SkClipOp::kIntersect) {
437*c8dee2aaSAndroid Build Coastguard Worker         return false;
438*c8dee2aaSAndroid Build Coastguard Worker     }
439*c8dee2aaSAndroid Build Coastguard Worker 
440*c8dee2aaSAndroid Build Coastguard Worker     // At the moment, only rect+rect or rrect+rrect are supported (although rect+rrect is
441*c8dee2aaSAndroid Build Coastguard Worker     // treated as a degenerate case of rrect+rrect).
442*c8dee2aaSAndroid Build Coastguard Worker     bool shapeUpdated = false;
443*c8dee2aaSAndroid Build Coastguard Worker     if (fShape.isRect() && other.fShape.isRect()) {
444*c8dee2aaSAndroid Build Coastguard Worker         if (fLocalToDevice == other.fLocalToDevice) {
445*c8dee2aaSAndroid Build Coastguard Worker             Rect intersection = fShape.rect().makeIntersect(other.fShape.rect());
446*c8dee2aaSAndroid Build Coastguard Worker             // Simplify() should have caught this case
447*c8dee2aaSAndroid Build Coastguard Worker             SkASSERT(!intersection.isEmptyNegativeOrNaN());
448*c8dee2aaSAndroid Build Coastguard Worker             fShape.setRect(intersection);
449*c8dee2aaSAndroid Build Coastguard Worker             shapeUpdated = true;
450*c8dee2aaSAndroid Build Coastguard Worker         }
451*c8dee2aaSAndroid Build Coastguard Worker     } else if ((fShape.isRect() || fShape.isRRect()) &&
452*c8dee2aaSAndroid Build Coastguard Worker                (other.fShape.isRect() || other.fShape.isRRect())) {
453*c8dee2aaSAndroid Build Coastguard Worker         if (fLocalToDevice == other.fLocalToDevice) {
454*c8dee2aaSAndroid Build Coastguard Worker             // Treat rrect+rect intersections as rrect+rrect
455*c8dee2aaSAndroid Build Coastguard Worker             SkRRect a = fShape.isRect() ? SkRRect::MakeRect(fShape.rect().asSkRect())
456*c8dee2aaSAndroid Build Coastguard Worker                                         : fShape.rrect();
457*c8dee2aaSAndroid Build Coastguard Worker             SkRRect b = other.fShape.isRect() ? SkRRect::MakeRect(other.fShape.rect().asSkRect())
458*c8dee2aaSAndroid Build Coastguard Worker                                               : other.fShape.rrect();
459*c8dee2aaSAndroid Build Coastguard Worker 
460*c8dee2aaSAndroid Build Coastguard Worker             SkRRect joined = SkRRectPriv::ConservativeIntersect(a, b);
461*c8dee2aaSAndroid Build Coastguard Worker             if (!joined.isEmpty()) {
462*c8dee2aaSAndroid Build Coastguard Worker                 // Can reduce to a single element
463*c8dee2aaSAndroid Build Coastguard Worker                 if (joined.isRect()) {
464*c8dee2aaSAndroid Build Coastguard Worker                     // And with a simplified type
465*c8dee2aaSAndroid Build Coastguard Worker                     fShape.setRect(joined.rect());
466*c8dee2aaSAndroid Build Coastguard Worker                 } else {
467*c8dee2aaSAndroid Build Coastguard Worker                     fShape.setRRect(joined);
468*c8dee2aaSAndroid Build Coastguard Worker                 }
469*c8dee2aaSAndroid Build Coastguard Worker                 shapeUpdated = true;
470*c8dee2aaSAndroid Build Coastguard Worker             }
471*c8dee2aaSAndroid Build Coastguard Worker             // else the intersection isn't representable as a rrect, or doesn't actually intersect.
472*c8dee2aaSAndroid Build Coastguard Worker             // ConservativeIntersect doesn't disambiguate those two cases, and just testing bounding
473*c8dee2aaSAndroid Build Coastguard Worker             // boxes for non-intersection would have already been caught by Simplify(), so
474*c8dee2aaSAndroid Build Coastguard Worker             // just don't combine the two elements and let rasterization resolve the combination.
475*c8dee2aaSAndroid Build Coastguard Worker         }
476*c8dee2aaSAndroid Build Coastguard Worker     }
477*c8dee2aaSAndroid Build Coastguard Worker 
478*c8dee2aaSAndroid Build Coastguard Worker     if (shapeUpdated) {
479*c8dee2aaSAndroid Build Coastguard Worker         // This logic works under the assumption that both combined elements were intersect.
480*c8dee2aaSAndroid Build Coastguard Worker         SkASSERT(fOp == SkClipOp::kIntersect && other.fOp == SkClipOp::kIntersect);
481*c8dee2aaSAndroid Build Coastguard Worker         fOuterBounds.intersect(other.fOuterBounds);
482*c8dee2aaSAndroid Build Coastguard Worker         fInnerBounds.intersect(other.fInnerBounds);
483*c8dee2aaSAndroid Build Coastguard Worker         // Inner bounds can become empty, but outer bounds should not be able to.
484*c8dee2aaSAndroid Build Coastguard Worker         SkASSERT(!fOuterBounds.isEmptyNegativeOrNaN());
485*c8dee2aaSAndroid Build Coastguard Worker         fShape.setInverted(true); // the setR[R]ect operations reset to non-inverse
486*c8dee2aaSAndroid Build Coastguard Worker         this->validate();
487*c8dee2aaSAndroid Build Coastguard Worker         return true;
488*c8dee2aaSAndroid Build Coastguard Worker     } else {
489*c8dee2aaSAndroid Build Coastguard Worker         return false;
490*c8dee2aaSAndroid Build Coastguard Worker     }
491*c8dee2aaSAndroid Build Coastguard Worker }
492*c8dee2aaSAndroid Build Coastguard Worker 
updateForElement(RawElement * added,const SaveRecord & current)493*c8dee2aaSAndroid Build Coastguard Worker void ClipStack::RawElement::updateForElement(RawElement* added, const SaveRecord& current) {
494*c8dee2aaSAndroid Build Coastguard Worker     if (this->isInvalid()) {
495*c8dee2aaSAndroid Build Coastguard Worker         // Already doesn't do anything, so skip this element
496*c8dee2aaSAndroid Build Coastguard Worker         return;
497*c8dee2aaSAndroid Build Coastguard Worker     }
498*c8dee2aaSAndroid Build Coastguard Worker 
499*c8dee2aaSAndroid Build Coastguard Worker     // 'A' refers to this element, 'B' refers to 'added'.
500*c8dee2aaSAndroid Build Coastguard Worker     switch (Simplify(*this, *added)) {
501*c8dee2aaSAndroid Build Coastguard Worker         case SimplifyResult::kEmpty:
502*c8dee2aaSAndroid Build Coastguard Worker             // Mark both elements as invalid to signal that the clip is fully empty
503*c8dee2aaSAndroid Build Coastguard Worker             this->markInvalid(current);
504*c8dee2aaSAndroid Build Coastguard Worker             added->markInvalid(current);
505*c8dee2aaSAndroid Build Coastguard Worker             break;
506*c8dee2aaSAndroid Build Coastguard Worker 
507*c8dee2aaSAndroid Build Coastguard Worker         case SimplifyResult::kAOnly:
508*c8dee2aaSAndroid Build Coastguard Worker             // This element already clips more than 'added', so mark 'added' is invalid to skip it
509*c8dee2aaSAndroid Build Coastguard Worker             added->markInvalid(current);
510*c8dee2aaSAndroid Build Coastguard Worker             break;
511*c8dee2aaSAndroid Build Coastguard Worker 
512*c8dee2aaSAndroid Build Coastguard Worker         case SimplifyResult::kBOnly:
513*c8dee2aaSAndroid Build Coastguard Worker             // 'added' clips more than this element, so mark this as invalid
514*c8dee2aaSAndroid Build Coastguard Worker             this->markInvalid(current);
515*c8dee2aaSAndroid Build Coastguard Worker             break;
516*c8dee2aaSAndroid Build Coastguard Worker 
517*c8dee2aaSAndroid Build Coastguard Worker         case SimplifyResult::kBoth:
518*c8dee2aaSAndroid Build Coastguard Worker             // Else the bounds checks think we need to keep both, but depending on the combination
519*c8dee2aaSAndroid Build Coastguard Worker             // of the ops and shape kinds, we may be able to do better.
520*c8dee2aaSAndroid Build Coastguard Worker             if (added->combine(*this, current)) {
521*c8dee2aaSAndroid Build Coastguard Worker                 // 'added' now fully represents the combination of the two elements
522*c8dee2aaSAndroid Build Coastguard Worker                 this->markInvalid(current);
523*c8dee2aaSAndroid Build Coastguard Worker             }
524*c8dee2aaSAndroid Build Coastguard Worker             break;
525*c8dee2aaSAndroid Build Coastguard Worker     }
526*c8dee2aaSAndroid Build Coastguard Worker }
527*c8dee2aaSAndroid Build Coastguard Worker 
528*c8dee2aaSAndroid Build Coastguard Worker ClipStack::RawElement::DrawInfluence
testForDraw(const TransformedShape & draw) const529*c8dee2aaSAndroid Build Coastguard Worker ClipStack::RawElement::testForDraw(const TransformedShape& draw) const {
530*c8dee2aaSAndroid Build Coastguard Worker     if (this->isInvalid()) {
531*c8dee2aaSAndroid Build Coastguard Worker         // Cannot affect the draw
532*c8dee2aaSAndroid Build Coastguard Worker         return DrawInfluence::kNone;
533*c8dee2aaSAndroid Build Coastguard Worker     }
534*c8dee2aaSAndroid Build Coastguard Worker 
535*c8dee2aaSAndroid Build Coastguard Worker     // For this analysis, A refers to the Element and B refers to the draw
536*c8dee2aaSAndroid Build Coastguard Worker     switch(Simplify(*this, draw)) {
537*c8dee2aaSAndroid Build Coastguard Worker         case SimplifyResult::kEmpty:
538*c8dee2aaSAndroid Build Coastguard Worker             // The more detailed per-element checks have determined the draw is clipped out.
539*c8dee2aaSAndroid Build Coastguard Worker             return DrawInfluence::kClipOut;
540*c8dee2aaSAndroid Build Coastguard Worker 
541*c8dee2aaSAndroid Build Coastguard Worker         case SimplifyResult::kBOnly:
542*c8dee2aaSAndroid Build Coastguard Worker             // This element does not affect the draw
543*c8dee2aaSAndroid Build Coastguard Worker             return DrawInfluence::kNone;
544*c8dee2aaSAndroid Build Coastguard Worker 
545*c8dee2aaSAndroid Build Coastguard Worker         case SimplifyResult::kAOnly:
546*c8dee2aaSAndroid Build Coastguard Worker             // If this were the only element, we could replace the draw's geometry but that only
547*c8dee2aaSAndroid Build Coastguard Worker             // gives us a win if we know that the clip element would only be used by this draw.
548*c8dee2aaSAndroid Build Coastguard Worker             // For now, just fall through to regular clip handling.
549*c8dee2aaSAndroid Build Coastguard Worker             [[fallthrough]];
550*c8dee2aaSAndroid Build Coastguard Worker 
551*c8dee2aaSAndroid Build Coastguard Worker         case SimplifyResult::kBoth:
552*c8dee2aaSAndroid Build Coastguard Worker             return DrawInfluence::kIntersect;
553*c8dee2aaSAndroid Build Coastguard Worker     }
554*c8dee2aaSAndroid Build Coastguard Worker 
555*c8dee2aaSAndroid Build Coastguard Worker     SkUNREACHABLE;
556*c8dee2aaSAndroid Build Coastguard Worker }
557*c8dee2aaSAndroid Build Coastguard Worker 
updateForDraw(const BoundsManager * boundsManager,const Rect & drawBounds,PaintersDepth drawZ)558*c8dee2aaSAndroid Build Coastguard Worker CompressedPaintersOrder ClipStack::RawElement::updateForDraw(const BoundsManager* boundsManager,
559*c8dee2aaSAndroid Build Coastguard Worker                                                              const Rect& drawBounds,
560*c8dee2aaSAndroid Build Coastguard Worker                                                              PaintersDepth drawZ) {
561*c8dee2aaSAndroid Build Coastguard Worker     SkASSERT(!this->isInvalid());
562*c8dee2aaSAndroid Build Coastguard Worker     SkASSERT(!drawBounds.isEmptyNegativeOrNaN());
563*c8dee2aaSAndroid Build Coastguard Worker 
564*c8dee2aaSAndroid Build Coastguard Worker     if (!this->hasPendingDraw()) {
565*c8dee2aaSAndroid Build Coastguard Worker         // No usage yet so we need an order that we will use when drawing to just the depth
566*c8dee2aaSAndroid Build Coastguard Worker         // attachment. It is sufficient to use the next CompressedPaintersOrder after the
567*c8dee2aaSAndroid Build Coastguard Worker         // most recent draw under this clip's outer bounds. It is necessary to use the
568*c8dee2aaSAndroid Build Coastguard Worker         // entire clip's outer bounds because the order has to be determined before the
569*c8dee2aaSAndroid Build Coastguard Worker         // final usage bounds are known and a subsequent draw could require a completely
570*c8dee2aaSAndroid Build Coastguard Worker         // different portion of the clip than this triggering draw.
571*c8dee2aaSAndroid Build Coastguard Worker         //
572*c8dee2aaSAndroid Build Coastguard Worker         // Lazily determining the order has several benefits to computing it when the clip
573*c8dee2aaSAndroid Build Coastguard Worker         // element was first created:
574*c8dee2aaSAndroid Build Coastguard Worker         //  - Elements that are invalidated by nested clips before draws are made do not
575*c8dee2aaSAndroid Build Coastguard Worker         //    waste time in the BoundsManager.
576*c8dee2aaSAndroid Build Coastguard Worker         //  - Elements that never actually modify a draw (e.g. a defensive clip) do not
577*c8dee2aaSAndroid Build Coastguard Worker         //    waste time in the BoundsManager.
578*c8dee2aaSAndroid Build Coastguard Worker         //  - A draw that triggers clip usage on multiple elements will more likely assign
579*c8dee2aaSAndroid Build Coastguard Worker         //    the same order to those elements, meaning their depth-only draws are more
580*c8dee2aaSAndroid Build Coastguard Worker         //    likely to batch in the final DrawPass.
581*c8dee2aaSAndroid Build Coastguard Worker         //
582*c8dee2aaSAndroid Build Coastguard Worker         // However, it does mean that clip elements can have the same order as each other,
583*c8dee2aaSAndroid Build Coastguard Worker         // or as later draws (e.g. after the clip has been popped off the stack). Any
584*c8dee2aaSAndroid Build Coastguard Worker         // overlap between clips or draws is addressed when the clip is drawn by selecting
585*c8dee2aaSAndroid Build Coastguard Worker         // an appropriate DisjointStencilIndex value. Stencil-aside, this order assignment
586*c8dee2aaSAndroid Build Coastguard Worker         // logic, max Z tracking, and the depth test during rasterization are able to
587*c8dee2aaSAndroid Build Coastguard Worker         // resolve everything correctly even if clips have the same order value.
588*c8dee2aaSAndroid Build Coastguard Worker         // See go/clip-stack-order for a detailed analysis of why this works.
589*c8dee2aaSAndroid Build Coastguard Worker         fOrder = boundsManager->getMostRecentDraw(fOuterBounds).next();
590*c8dee2aaSAndroid Build Coastguard Worker         fUsageBounds = drawBounds;
591*c8dee2aaSAndroid Build Coastguard Worker         fMaxZ = drawZ;
592*c8dee2aaSAndroid Build Coastguard Worker     } else {
593*c8dee2aaSAndroid Build Coastguard Worker         // Earlier draws have already used this element so we cannot change where the
594*c8dee2aaSAndroid Build Coastguard Worker         // depth-only draw will be sorted to, but we need to ensure we cover the new draw's
595*c8dee2aaSAndroid Build Coastguard Worker         // bounds and use a Z value that will clip out its pixels as appropriate.
596*c8dee2aaSAndroid Build Coastguard Worker         fUsageBounds.join(drawBounds);
597*c8dee2aaSAndroid Build Coastguard Worker         if (drawZ > fMaxZ) {
598*c8dee2aaSAndroid Build Coastguard Worker             fMaxZ = drawZ;
599*c8dee2aaSAndroid Build Coastguard Worker         }
600*c8dee2aaSAndroid Build Coastguard Worker     }
601*c8dee2aaSAndroid Build Coastguard Worker 
602*c8dee2aaSAndroid Build Coastguard Worker     return fOrder;
603*c8dee2aaSAndroid Build Coastguard Worker }
604*c8dee2aaSAndroid Build Coastguard Worker 
clipType() const605*c8dee2aaSAndroid Build Coastguard Worker ClipStack::ClipState ClipStack::RawElement::clipType() const {
606*c8dee2aaSAndroid Build Coastguard Worker     // Map from the internal shape kind to the clip state enum
607*c8dee2aaSAndroid Build Coastguard Worker     switch (fShape.type()) {
608*c8dee2aaSAndroid Build Coastguard Worker         case Shape::Type::kEmpty:
609*c8dee2aaSAndroid Build Coastguard Worker             return ClipState::kEmpty;
610*c8dee2aaSAndroid Build Coastguard Worker 
611*c8dee2aaSAndroid Build Coastguard Worker         case Shape::Type::kRect:
612*c8dee2aaSAndroid Build Coastguard Worker             return fOp == SkClipOp::kIntersect &&
613*c8dee2aaSAndroid Build Coastguard Worker                    fLocalToDevice.type() == Transform::Type::kIdentity
614*c8dee2aaSAndroid Build Coastguard Worker                         ? ClipState::kDeviceRect : ClipState::kComplex;
615*c8dee2aaSAndroid Build Coastguard Worker 
616*c8dee2aaSAndroid Build Coastguard Worker         case Shape::Type::kRRect:
617*c8dee2aaSAndroid Build Coastguard Worker             return fOp == SkClipOp::kIntersect &&
618*c8dee2aaSAndroid Build Coastguard Worker                    fLocalToDevice.type() == Transform::Type::kIdentity
619*c8dee2aaSAndroid Build Coastguard Worker                         ? ClipState::kDeviceRRect : ClipState::kComplex;
620*c8dee2aaSAndroid Build Coastguard Worker 
621*c8dee2aaSAndroid Build Coastguard Worker         case Shape::Type::kArc:
622*c8dee2aaSAndroid Build Coastguard Worker         case Shape::Type::kLine:
623*c8dee2aaSAndroid Build Coastguard Worker             // These types should never become RawElements, but call them kComplex in release builds
624*c8dee2aaSAndroid Build Coastguard Worker             SkASSERT(false);
625*c8dee2aaSAndroid Build Coastguard Worker             [[fallthrough]];
626*c8dee2aaSAndroid Build Coastguard Worker 
627*c8dee2aaSAndroid Build Coastguard Worker         case Shape::Type::kPath:
628*c8dee2aaSAndroid Build Coastguard Worker             return ClipState::kComplex;
629*c8dee2aaSAndroid Build Coastguard Worker     }
630*c8dee2aaSAndroid Build Coastguard Worker     SkUNREACHABLE;
631*c8dee2aaSAndroid Build Coastguard Worker }
632*c8dee2aaSAndroid Build Coastguard Worker 
633*c8dee2aaSAndroid Build Coastguard Worker ///////////////////////////////////////////////////////////////////////////////
634*c8dee2aaSAndroid Build Coastguard Worker // ClipStack::SaveRecord
635*c8dee2aaSAndroid Build Coastguard Worker 
SaveRecord(const Rect & deviceBounds)636*c8dee2aaSAndroid Build Coastguard Worker ClipStack::SaveRecord::SaveRecord(const Rect& deviceBounds)
637*c8dee2aaSAndroid Build Coastguard Worker         : fInnerBounds(deviceBounds)
638*c8dee2aaSAndroid Build Coastguard Worker         , fOuterBounds(deviceBounds)
639*c8dee2aaSAndroid Build Coastguard Worker         , fShader(nullptr)
640*c8dee2aaSAndroid Build Coastguard Worker         , fStartingElementIndex(0)
641*c8dee2aaSAndroid Build Coastguard Worker         , fOldestValidIndex(0)
642*c8dee2aaSAndroid Build Coastguard Worker         , fDeferredSaveCount(0)
643*c8dee2aaSAndroid Build Coastguard Worker         , fStackOp(SkClipOp::kIntersect)
644*c8dee2aaSAndroid Build Coastguard Worker         , fState(ClipState::kWideOpen)  {}
645*c8dee2aaSAndroid Build Coastguard Worker 
SaveRecord(const SaveRecord & prior,int startingElementIndex)646*c8dee2aaSAndroid Build Coastguard Worker ClipStack::SaveRecord::SaveRecord(const SaveRecord& prior,
647*c8dee2aaSAndroid Build Coastguard Worker                                   int startingElementIndex)
648*c8dee2aaSAndroid Build Coastguard Worker         : fInnerBounds(prior.fInnerBounds)
649*c8dee2aaSAndroid Build Coastguard Worker         , fOuterBounds(prior.fOuterBounds)
650*c8dee2aaSAndroid Build Coastguard Worker         , fShader(prior.fShader)
651*c8dee2aaSAndroid Build Coastguard Worker         , fStartingElementIndex(startingElementIndex)
652*c8dee2aaSAndroid Build Coastguard Worker         , fOldestValidIndex(prior.fOldestValidIndex)
653*c8dee2aaSAndroid Build Coastguard Worker         , fDeferredSaveCount(0)
654*c8dee2aaSAndroid Build Coastguard Worker         , fStackOp(prior.fStackOp)
655*c8dee2aaSAndroid Build Coastguard Worker         , fState(prior.fState) {
656*c8dee2aaSAndroid Build Coastguard Worker     // If the prior record added an element, this one will insert into the same index
657*c8dee2aaSAndroid Build Coastguard Worker     // (that's okay since we'll remove it when this record is popped off the stack).
658*c8dee2aaSAndroid Build Coastguard Worker     SkASSERT(startingElementIndex >= prior.fStartingElementIndex);
659*c8dee2aaSAndroid Build Coastguard Worker }
660*c8dee2aaSAndroid Build Coastguard Worker 
state() const661*c8dee2aaSAndroid Build Coastguard Worker ClipStack::ClipState ClipStack::SaveRecord::state() const {
662*c8dee2aaSAndroid Build Coastguard Worker     if (fShader && fState != ClipState::kEmpty) {
663*c8dee2aaSAndroid Build Coastguard Worker         return ClipState::kComplex;
664*c8dee2aaSAndroid Build Coastguard Worker     } else {
665*c8dee2aaSAndroid Build Coastguard Worker         return fState;
666*c8dee2aaSAndroid Build Coastguard Worker     }
667*c8dee2aaSAndroid Build Coastguard Worker }
668*c8dee2aaSAndroid Build Coastguard Worker 
scissor(const Rect & deviceBounds,const Rect & drawBounds) const669*c8dee2aaSAndroid Build Coastguard Worker Rect ClipStack::SaveRecord::scissor(const Rect& deviceBounds, const Rect& drawBounds) const {
670*c8dee2aaSAndroid Build Coastguard Worker     // This should only be called when the clip stack actually has something non-trivial to evaluate
671*c8dee2aaSAndroid Build Coastguard Worker     // It is effectively a reduced version of Simplify() dealing only with device-space bounds and
672*c8dee2aaSAndroid Build Coastguard Worker     // returning the intersection results.
673*c8dee2aaSAndroid Build Coastguard Worker     SkASSERT(this->state() != ClipState::kEmpty && this->state() != ClipState::kWideOpen);
674*c8dee2aaSAndroid Build Coastguard Worker     SkASSERT(deviceBounds.contains(drawBounds)); // This should have already been handled.
675*c8dee2aaSAndroid Build Coastguard Worker 
676*c8dee2aaSAndroid Build Coastguard Worker     if (fStackOp == SkClipOp::kDifference) {
677*c8dee2aaSAndroid Build Coastguard Worker         // kDifference nominally uses the draw's bounds minus the save record's inner bounds as the
678*c8dee2aaSAndroid Build Coastguard Worker         // scissor. However, if the draw doesn't intersect the clip at all then it doesn't have any
679*c8dee2aaSAndroid Build Coastguard Worker         // visual effect and we can switch to the device bounds as the canonical scissor.
680*c8dee2aaSAndroid Build Coastguard Worker         if (!fOuterBounds.intersects(drawBounds)) {
681*c8dee2aaSAndroid Build Coastguard Worker             return deviceBounds;
682*c8dee2aaSAndroid Build Coastguard Worker         } else {
683*c8dee2aaSAndroid Build Coastguard Worker             // This automatically detects the case where the draw is contained in inner bounds and
684*c8dee2aaSAndroid Build Coastguard Worker             // would be entirely clipped out.
685*c8dee2aaSAndroid Build Coastguard Worker             return subtract(drawBounds, fInnerBounds, /*exact=*/true);
686*c8dee2aaSAndroid Build Coastguard Worker         }
687*c8dee2aaSAndroid Build Coastguard Worker     } else {
688*c8dee2aaSAndroid Build Coastguard Worker         // kIntersect nominally uses the save record's outer bounds as the scissor. However, if the
689*c8dee2aaSAndroid Build Coastguard Worker         // draw is contained entirely within those bounds, it doesn't have any visual effect so
690*c8dee2aaSAndroid Build Coastguard Worker         // switch to using the device bounds as the canonical scissor to minimize state changes.
691*c8dee2aaSAndroid Build Coastguard Worker         if (fOuterBounds.contains(drawBounds)) {
692*c8dee2aaSAndroid Build Coastguard Worker             return deviceBounds;
693*c8dee2aaSAndroid Build Coastguard Worker         } else {
694*c8dee2aaSAndroid Build Coastguard Worker             // This automatically detects the case where the draw does not intersect the clip.
695*c8dee2aaSAndroid Build Coastguard Worker             return fOuterBounds;
696*c8dee2aaSAndroid Build Coastguard Worker         }
697*c8dee2aaSAndroid Build Coastguard Worker     }
698*c8dee2aaSAndroid Build Coastguard Worker }
699*c8dee2aaSAndroid Build Coastguard Worker 
removeElements(RawElement::Stack * elements,Device * device)700*c8dee2aaSAndroid Build Coastguard Worker void ClipStack::SaveRecord::removeElements(RawElement::Stack* elements, Device* device) {
701*c8dee2aaSAndroid Build Coastguard Worker     while (elements->count() > fStartingElementIndex) {
702*c8dee2aaSAndroid Build Coastguard Worker         // Since the element is being deleted now, it won't be in the ClipStack when the Device
703*c8dee2aaSAndroid Build Coastguard Worker         // calls recordDeferredClipDraws(). Record the clip's draw now (if it needs it).
704*c8dee2aaSAndroid Build Coastguard Worker         elements->back().drawClip(device);
705*c8dee2aaSAndroid Build Coastguard Worker         elements->pop_back();
706*c8dee2aaSAndroid Build Coastguard Worker     }
707*c8dee2aaSAndroid Build Coastguard Worker }
708*c8dee2aaSAndroid Build Coastguard Worker 
restoreElements(RawElement::Stack * elements)709*c8dee2aaSAndroid Build Coastguard Worker void ClipStack::SaveRecord::restoreElements(RawElement::Stack* elements) {
710*c8dee2aaSAndroid Build Coastguard Worker     // Presumably this SaveRecord is the new top of the stack, and so it owns the elements
711*c8dee2aaSAndroid Build Coastguard Worker     // from its starting index to restoreCount - 1. Elements from the old save record have
712*c8dee2aaSAndroid Build Coastguard Worker     // been destroyed already, so their indices would have been >= restoreCount, and any
713*c8dee2aaSAndroid Build Coastguard Worker     // still-present element can be un-invalidated based on that.
714*c8dee2aaSAndroid Build Coastguard Worker     int i = elements->count() - 1;
715*c8dee2aaSAndroid Build Coastguard Worker     for (RawElement& e : elements->ritems()) {
716*c8dee2aaSAndroid Build Coastguard Worker         if (i < fOldestValidIndex) {
717*c8dee2aaSAndroid Build Coastguard Worker             break;
718*c8dee2aaSAndroid Build Coastguard Worker         }
719*c8dee2aaSAndroid Build Coastguard Worker         e.restoreValid(*this);
720*c8dee2aaSAndroid Build Coastguard Worker         --i;
721*c8dee2aaSAndroid Build Coastguard Worker     }
722*c8dee2aaSAndroid Build Coastguard Worker }
723*c8dee2aaSAndroid Build Coastguard Worker 
addShader(sk_sp<SkShader> shader)724*c8dee2aaSAndroid Build Coastguard Worker void ClipStack::SaveRecord::addShader(sk_sp<SkShader> shader) {
725*c8dee2aaSAndroid Build Coastguard Worker     SkASSERT(shader);
726*c8dee2aaSAndroid Build Coastguard Worker     SkASSERT(this->canBeUpdated());
727*c8dee2aaSAndroid Build Coastguard Worker     if (!fShader) {
728*c8dee2aaSAndroid Build Coastguard Worker         fShader = std::move(shader);
729*c8dee2aaSAndroid Build Coastguard Worker     } else {
730*c8dee2aaSAndroid Build Coastguard Worker         // The total coverage is computed by multiplying the coverage from each element (shape or
731*c8dee2aaSAndroid Build Coastguard Worker         // shader), but since multiplication is associative, we can use kSrcIn blending to make
732*c8dee2aaSAndroid Build Coastguard Worker         // a new shader that represents 'shader' * 'fShader'
733*c8dee2aaSAndroid Build Coastguard Worker         fShader = SkShaders::Blend(SkBlendMode::kSrcIn, std::move(shader), fShader);
734*c8dee2aaSAndroid Build Coastguard Worker     }
735*c8dee2aaSAndroid Build Coastguard Worker }
736*c8dee2aaSAndroid Build Coastguard Worker 
addElement(RawElement && toAdd,RawElement::Stack * elements,Device * device)737*c8dee2aaSAndroid Build Coastguard Worker bool ClipStack::SaveRecord::addElement(RawElement&& toAdd,
738*c8dee2aaSAndroid Build Coastguard Worker                                        RawElement::Stack* elements,
739*c8dee2aaSAndroid Build Coastguard Worker                                        Device* device) {
740*c8dee2aaSAndroid Build Coastguard Worker     // Validity check the element's state first
741*c8dee2aaSAndroid Build Coastguard Worker     toAdd.validate();
742*c8dee2aaSAndroid Build Coastguard Worker 
743*c8dee2aaSAndroid Build Coastguard Worker     // And we shouldn't be adding an element if we have a deferred save
744*c8dee2aaSAndroid Build Coastguard Worker     SkASSERT(this->canBeUpdated());
745*c8dee2aaSAndroid Build Coastguard Worker 
746*c8dee2aaSAndroid Build Coastguard Worker     if (fState == ClipState::kEmpty) {
747*c8dee2aaSAndroid Build Coastguard Worker         // The clip is already empty, and we only shrink, so there's no need to record this element.
748*c8dee2aaSAndroid Build Coastguard Worker         return false;
749*c8dee2aaSAndroid Build Coastguard Worker     } else if (toAdd.shape().isEmpty()) {
750*c8dee2aaSAndroid Build Coastguard Worker         // An empty difference op should have been detected earlier, since it's a no-op
751*c8dee2aaSAndroid Build Coastguard Worker         SkASSERT(toAdd.op() == SkClipOp::kIntersect);
752*c8dee2aaSAndroid Build Coastguard Worker         fState = ClipState::kEmpty;
753*c8dee2aaSAndroid Build Coastguard Worker         this->removeElements(elements, device);
754*c8dee2aaSAndroid Build Coastguard Worker         return true;
755*c8dee2aaSAndroid Build Coastguard Worker     }
756*c8dee2aaSAndroid Build Coastguard Worker 
757*c8dee2aaSAndroid Build Coastguard Worker     // Here we treat the SaveRecord as a "TransformedShape" with the identity transform, and a shape
758*c8dee2aaSAndroid Build Coastguard Worker     // equal to its outer bounds. This lets us get accurate intersection tests against the new
759*c8dee2aaSAndroid Build Coastguard Worker     // element, but we pass true to skip more detailed contains checks because the SaveRecord's
760*c8dee2aaSAndroid Build Coastguard Worker     // shape is potentially very different from its aggregate outer bounds.
761*c8dee2aaSAndroid Build Coastguard Worker     Shape outerSaveBounds{fOuterBounds};
762*c8dee2aaSAndroid Build Coastguard Worker     TransformedShape save{kIdentity, outerSaveBounds, fOuterBounds, fInnerBounds, fStackOp,
763*c8dee2aaSAndroid Build Coastguard Worker                           /*containsChecksOnlyBounds=*/true};
764*c8dee2aaSAndroid Build Coastguard Worker 
765*c8dee2aaSAndroid Build Coastguard Worker     // In this invocation, 'A' refers to the existing stack's bounds and 'B' refers to the new
766*c8dee2aaSAndroid Build Coastguard Worker     // element.
767*c8dee2aaSAndroid Build Coastguard Worker     switch (Simplify(save, toAdd)) {
768*c8dee2aaSAndroid Build Coastguard Worker         case SimplifyResult::kEmpty:
769*c8dee2aaSAndroid Build Coastguard Worker             // The combination results in an empty clip
770*c8dee2aaSAndroid Build Coastguard Worker             fState = ClipState::kEmpty;
771*c8dee2aaSAndroid Build Coastguard Worker             this->removeElements(elements, device);
772*c8dee2aaSAndroid Build Coastguard Worker             return true;
773*c8dee2aaSAndroid Build Coastguard Worker 
774*c8dee2aaSAndroid Build Coastguard Worker         case SimplifyResult::kAOnly:
775*c8dee2aaSAndroid Build Coastguard Worker             // The combination would not be any different than the existing clip
776*c8dee2aaSAndroid Build Coastguard Worker             return false;
777*c8dee2aaSAndroid Build Coastguard Worker 
778*c8dee2aaSAndroid Build Coastguard Worker         case SimplifyResult::kBOnly:
779*c8dee2aaSAndroid Build Coastguard Worker             // The combination would invalidate the entire existing stack and can be replaced with
780*c8dee2aaSAndroid Build Coastguard Worker             // just the new element.
781*c8dee2aaSAndroid Build Coastguard Worker             this->replaceWithElement(std::move(toAdd), elements, device);
782*c8dee2aaSAndroid Build Coastguard Worker             return true;
783*c8dee2aaSAndroid Build Coastguard Worker 
784*c8dee2aaSAndroid Build Coastguard Worker         case SimplifyResult::kBoth:
785*c8dee2aaSAndroid Build Coastguard Worker             // The new element combines in a complex manner, so update the stack's bounds based on
786*c8dee2aaSAndroid Build Coastguard Worker             // the combination of its and the new element's ops (handled below)
787*c8dee2aaSAndroid Build Coastguard Worker             break;
788*c8dee2aaSAndroid Build Coastguard Worker     }
789*c8dee2aaSAndroid Build Coastguard Worker 
790*c8dee2aaSAndroid Build Coastguard Worker     if (fState == ClipState::kWideOpen) {
791*c8dee2aaSAndroid Build Coastguard Worker         // When the stack was wide open and the clip effect was kBoth, the "complex" manner is
792*c8dee2aaSAndroid Build Coastguard Worker         // simply to keep the element and update the stack bounds to be the element's intersected
793*c8dee2aaSAndroid Build Coastguard Worker         // with the device.
794*c8dee2aaSAndroid Build Coastguard Worker         this->replaceWithElement(std::move(toAdd), elements, device);
795*c8dee2aaSAndroid Build Coastguard Worker         return true;
796*c8dee2aaSAndroid Build Coastguard Worker     }
797*c8dee2aaSAndroid Build Coastguard Worker 
798*c8dee2aaSAndroid Build Coastguard Worker     // Some form of actual clip element(s) to combine with.
799*c8dee2aaSAndroid Build Coastguard Worker     if (fStackOp == SkClipOp::kIntersect) {
800*c8dee2aaSAndroid Build Coastguard Worker         if (toAdd.op() == SkClipOp::kIntersect) {
801*c8dee2aaSAndroid Build Coastguard Worker             // Intersect (stack) + Intersect (toAdd)
802*c8dee2aaSAndroid Build Coastguard Worker             //  - Bounds updates is simply the paired intersections of outer and inner.
803*c8dee2aaSAndroid Build Coastguard Worker             fOuterBounds.intersect(toAdd.outerBounds());
804*c8dee2aaSAndroid Build Coastguard Worker             fInnerBounds.intersect(toAdd.innerBounds());
805*c8dee2aaSAndroid Build Coastguard Worker             // Outer should not have become empty, but is allowed to if there's no intersection.
806*c8dee2aaSAndroid Build Coastguard Worker             SkASSERT(!fOuterBounds.isEmptyNegativeOrNaN());
807*c8dee2aaSAndroid Build Coastguard Worker         } else {
808*c8dee2aaSAndroid Build Coastguard Worker             // Intersect (stack) + Difference (toAdd)
809*c8dee2aaSAndroid Build Coastguard Worker             //  - Shrink the stack's outer bounds if the difference op's inner bounds completely
810*c8dee2aaSAndroid Build Coastguard Worker             //    cuts off an edge.
811*c8dee2aaSAndroid Build Coastguard Worker             //  - Shrink the stack's inner bounds to completely exclude the op's outer bounds.
812*c8dee2aaSAndroid Build Coastguard Worker             fOuterBounds = subtract(fOuterBounds, toAdd.innerBounds(), /* exact */ true);
813*c8dee2aaSAndroid Build Coastguard Worker             fInnerBounds = subtract(fInnerBounds, toAdd.outerBounds(), /* exact */ false);
814*c8dee2aaSAndroid Build Coastguard Worker         }
815*c8dee2aaSAndroid Build Coastguard Worker     } else {
816*c8dee2aaSAndroid Build Coastguard Worker         if (toAdd.op() == SkClipOp::kIntersect) {
817*c8dee2aaSAndroid Build Coastguard Worker             // Difference (stack) + Intersect (toAdd)
818*c8dee2aaSAndroid Build Coastguard Worker             //  - Bounds updates are just the mirror of Intersect(stack) + Difference(toAdd)
819*c8dee2aaSAndroid Build Coastguard Worker             Rect oldOuter = fOuterBounds;
820*c8dee2aaSAndroid Build Coastguard Worker             fOuterBounds = subtract(toAdd.outerBounds(), fInnerBounds, /* exact */ true);
821*c8dee2aaSAndroid Build Coastguard Worker             fInnerBounds = subtract(toAdd.innerBounds(), oldOuter,     /* exact */ false);
822*c8dee2aaSAndroid Build Coastguard Worker         } else {
823*c8dee2aaSAndroid Build Coastguard Worker             // Difference (stack) + Difference (toAdd)
824*c8dee2aaSAndroid Build Coastguard Worker             //  - The updated outer bounds is the union of outer bounds and the inner becomes the
825*c8dee2aaSAndroid Build Coastguard Worker             //    largest of the two possible inner bounds
826*c8dee2aaSAndroid Build Coastguard Worker             fOuterBounds.join(toAdd.outerBounds());
827*c8dee2aaSAndroid Build Coastguard Worker             if (toAdd.innerBounds().area() > fInnerBounds.area()) {
828*c8dee2aaSAndroid Build Coastguard Worker                 fInnerBounds = toAdd.innerBounds();
829*c8dee2aaSAndroid Build Coastguard Worker             }
830*c8dee2aaSAndroid Build Coastguard Worker         }
831*c8dee2aaSAndroid Build Coastguard Worker     }
832*c8dee2aaSAndroid Build Coastguard Worker 
833*c8dee2aaSAndroid Build Coastguard Worker     // If we get here, we're keeping the new element and the stack's bounds have been updated.
834*c8dee2aaSAndroid Build Coastguard Worker     // We ought to have caught the cases where the stack bounds resemble an empty or wide open
835*c8dee2aaSAndroid Build Coastguard Worker     // clip, so assert that's the case.
836*c8dee2aaSAndroid Build Coastguard Worker     SkASSERT(!fOuterBounds.isEmptyNegativeOrNaN() &&
837*c8dee2aaSAndroid Build Coastguard Worker              (fInnerBounds.isEmptyNegativeOrNaN() || fOuterBounds.contains(fInnerBounds)));
838*c8dee2aaSAndroid Build Coastguard Worker 
839*c8dee2aaSAndroid Build Coastguard Worker     return this->appendElement(std::move(toAdd), elements, device);
840*c8dee2aaSAndroid Build Coastguard Worker }
841*c8dee2aaSAndroid Build Coastguard Worker 
appendElement(RawElement && toAdd,RawElement::Stack * elements,Device * device)842*c8dee2aaSAndroid Build Coastguard Worker bool ClipStack::SaveRecord::appendElement(RawElement&& toAdd,
843*c8dee2aaSAndroid Build Coastguard Worker                                           RawElement::Stack* elements,
844*c8dee2aaSAndroid Build Coastguard Worker                                           Device* device) {
845*c8dee2aaSAndroid Build Coastguard Worker     // Update past elements to account for the new element
846*c8dee2aaSAndroid Build Coastguard Worker     int i = elements->count() - 1;
847*c8dee2aaSAndroid Build Coastguard Worker 
848*c8dee2aaSAndroid Build Coastguard Worker     // After the loop, elements between [max(youngestValid, startingIndex)+1, count-1] can be
849*c8dee2aaSAndroid Build Coastguard Worker     // removed from the stack (these are the active elements that have been invalidated by the
850*c8dee2aaSAndroid Build Coastguard Worker     // newest element; since it's the active part of the stack, no restore() can bring them back).
851*c8dee2aaSAndroid Build Coastguard Worker     int youngestValid = fStartingElementIndex - 1;
852*c8dee2aaSAndroid Build Coastguard Worker     // After the loop, elements between [0, oldestValid-1] are all invalid. The value of oldestValid
853*c8dee2aaSAndroid Build Coastguard Worker     // becomes the save record's new fLastValidIndex value.
854*c8dee2aaSAndroid Build Coastguard Worker     int oldestValid = elements->count();
855*c8dee2aaSAndroid Build Coastguard Worker     // After the loop, this is the earliest active element that was invalidated. It may be
856*c8dee2aaSAndroid Build Coastguard Worker     // older in the stack than earliestValid, so cannot be popped off, but can be used to store
857*c8dee2aaSAndroid Build Coastguard Worker     // the new element instead of allocating more.
858*c8dee2aaSAndroid Build Coastguard Worker     RawElement* oldestActiveInvalid = nullptr;
859*c8dee2aaSAndroid Build Coastguard Worker     int oldestActiveInvalidIndex = elements->count();
860*c8dee2aaSAndroid Build Coastguard Worker 
861*c8dee2aaSAndroid Build Coastguard Worker     for (RawElement& existing : elements->ritems()) {
862*c8dee2aaSAndroid Build Coastguard Worker         if (i < fOldestValidIndex) {
863*c8dee2aaSAndroid Build Coastguard Worker             break;
864*c8dee2aaSAndroid Build Coastguard Worker         }
865*c8dee2aaSAndroid Build Coastguard Worker         // We don't need to pass the actual index that toAdd will be saved to; just the minimum
866*c8dee2aaSAndroid Build Coastguard Worker         // index of this save record, since that will result in the same restoration behavior later.
867*c8dee2aaSAndroid Build Coastguard Worker         existing.updateForElement(&toAdd, *this);
868*c8dee2aaSAndroid Build Coastguard Worker 
869*c8dee2aaSAndroid Build Coastguard Worker         if (toAdd.isInvalid()) {
870*c8dee2aaSAndroid Build Coastguard Worker             if (existing.isInvalid()) {
871*c8dee2aaSAndroid Build Coastguard Worker                 // Both new and old invalid implies the entire clip becomes empty
872*c8dee2aaSAndroid Build Coastguard Worker                 fState = ClipState::kEmpty;
873*c8dee2aaSAndroid Build Coastguard Worker                 return true;
874*c8dee2aaSAndroid Build Coastguard Worker             } else {
875*c8dee2aaSAndroid Build Coastguard Worker                 // The new element doesn't change the clip beyond what the old element already does
876*c8dee2aaSAndroid Build Coastguard Worker                 return false;
877*c8dee2aaSAndroid Build Coastguard Worker             }
878*c8dee2aaSAndroid Build Coastguard Worker         } else if (existing.isInvalid()) {
879*c8dee2aaSAndroid Build Coastguard Worker             // The new element cancels out the old element. The new element may have been modified
880*c8dee2aaSAndroid Build Coastguard Worker             // to account for the old element's geometry.
881*c8dee2aaSAndroid Build Coastguard Worker             if (i >= fStartingElementIndex) {
882*c8dee2aaSAndroid Build Coastguard Worker                 // Still active, so the invalidated index could be used to store the new element
883*c8dee2aaSAndroid Build Coastguard Worker                 oldestActiveInvalid = &existing;
884*c8dee2aaSAndroid Build Coastguard Worker                 oldestActiveInvalidIndex = i;
885*c8dee2aaSAndroid Build Coastguard Worker             }
886*c8dee2aaSAndroid Build Coastguard Worker         } else {
887*c8dee2aaSAndroid Build Coastguard Worker             // Keep both new and old elements
888*c8dee2aaSAndroid Build Coastguard Worker             oldestValid = i;
889*c8dee2aaSAndroid Build Coastguard Worker             if (i > youngestValid) {
890*c8dee2aaSAndroid Build Coastguard Worker                 youngestValid = i;
891*c8dee2aaSAndroid Build Coastguard Worker             }
892*c8dee2aaSAndroid Build Coastguard Worker         }
893*c8dee2aaSAndroid Build Coastguard Worker 
894*c8dee2aaSAndroid Build Coastguard Worker         --i;
895*c8dee2aaSAndroid Build Coastguard Worker     }
896*c8dee2aaSAndroid Build Coastguard Worker 
897*c8dee2aaSAndroid Build Coastguard Worker     // Post-iteration validity check
898*c8dee2aaSAndroid Build Coastguard Worker     SkASSERT(oldestValid == elements->count() ||
899*c8dee2aaSAndroid Build Coastguard Worker              (oldestValid >= fOldestValidIndex && oldestValid < elements->count()));
900*c8dee2aaSAndroid Build Coastguard Worker     SkASSERT(youngestValid == fStartingElementIndex - 1 ||
901*c8dee2aaSAndroid Build Coastguard Worker              (youngestValid >= fStartingElementIndex && youngestValid < elements->count()));
902*c8dee2aaSAndroid Build Coastguard Worker     SkASSERT((oldestActiveInvalid && oldestActiveInvalidIndex >= fStartingElementIndex &&
903*c8dee2aaSAndroid Build Coastguard Worker               oldestActiveInvalidIndex < elements->count()) || !oldestActiveInvalid);
904*c8dee2aaSAndroid Build Coastguard Worker 
905*c8dee2aaSAndroid Build Coastguard Worker     // Update final state
906*c8dee2aaSAndroid Build Coastguard Worker     SkASSERT(oldestValid >= fOldestValidIndex);
907*c8dee2aaSAndroid Build Coastguard Worker     fOldestValidIndex = std::min(oldestValid, oldestActiveInvalidIndex);
908*c8dee2aaSAndroid Build Coastguard Worker     fState = oldestValid == elements->count() ? toAdd.clipType() : ClipState::kComplex;
909*c8dee2aaSAndroid Build Coastguard Worker     if (fStackOp == SkClipOp::kDifference && toAdd.op() == SkClipOp::kIntersect) {
910*c8dee2aaSAndroid Build Coastguard Worker         // The stack remains in difference mode only as long as all elements are difference
911*c8dee2aaSAndroid Build Coastguard Worker         fStackOp = SkClipOp::kIntersect;
912*c8dee2aaSAndroid Build Coastguard Worker     }
913*c8dee2aaSAndroid Build Coastguard Worker 
914*c8dee2aaSAndroid Build Coastguard Worker     int targetCount = youngestValid + 1;
915*c8dee2aaSAndroid Build Coastguard Worker     if (!oldestActiveInvalid || oldestActiveInvalidIndex >= targetCount) {
916*c8dee2aaSAndroid Build Coastguard Worker         // toAdd will be stored right after youngestValid
917*c8dee2aaSAndroid Build Coastguard Worker         targetCount++;
918*c8dee2aaSAndroid Build Coastguard Worker         oldestActiveInvalid = nullptr;
919*c8dee2aaSAndroid Build Coastguard Worker     }
920*c8dee2aaSAndroid Build Coastguard Worker     while (elements->count() > targetCount) {
921*c8dee2aaSAndroid Build Coastguard Worker         SkASSERT(oldestActiveInvalid != &elements->back()); // shouldn't delete what we'll reuse
922*c8dee2aaSAndroid Build Coastguard Worker         elements->back().drawClip(device);
923*c8dee2aaSAndroid Build Coastguard Worker         elements->pop_back();
924*c8dee2aaSAndroid Build Coastguard Worker     }
925*c8dee2aaSAndroid Build Coastguard Worker     if (oldestActiveInvalid) {
926*c8dee2aaSAndroid Build Coastguard Worker         oldestActiveInvalid->drawClip(device);
927*c8dee2aaSAndroid Build Coastguard Worker         *oldestActiveInvalid = std::move(toAdd);
928*c8dee2aaSAndroid Build Coastguard Worker     } else if (elements->count() < targetCount) {
929*c8dee2aaSAndroid Build Coastguard Worker         elements->push_back(std::move(toAdd));
930*c8dee2aaSAndroid Build Coastguard Worker     } else {
931*c8dee2aaSAndroid Build Coastguard Worker         elements->back().drawClip(device);
932*c8dee2aaSAndroid Build Coastguard Worker         elements->back() = std::move(toAdd);
933*c8dee2aaSAndroid Build Coastguard Worker     }
934*c8dee2aaSAndroid Build Coastguard Worker 
935*c8dee2aaSAndroid Build Coastguard Worker     return true;
936*c8dee2aaSAndroid Build Coastguard Worker }
937*c8dee2aaSAndroid Build Coastguard Worker 
replaceWithElement(RawElement && toAdd,RawElement::Stack * elements,Device * device)938*c8dee2aaSAndroid Build Coastguard Worker void ClipStack::SaveRecord::replaceWithElement(RawElement&& toAdd,
939*c8dee2aaSAndroid Build Coastguard Worker                                                RawElement::Stack* elements,
940*c8dee2aaSAndroid Build Coastguard Worker                                                Device* device) {
941*c8dee2aaSAndroid Build Coastguard Worker     // The aggregate state of the save record mirrors the element
942*c8dee2aaSAndroid Build Coastguard Worker     fInnerBounds = toAdd.innerBounds();
943*c8dee2aaSAndroid Build Coastguard Worker     fOuterBounds = toAdd.outerBounds();
944*c8dee2aaSAndroid Build Coastguard Worker     fStackOp = toAdd.op();
945*c8dee2aaSAndroid Build Coastguard Worker     fState = toAdd.clipType();
946*c8dee2aaSAndroid Build Coastguard Worker 
947*c8dee2aaSAndroid Build Coastguard Worker     // All prior active element can be removed from the stack: [startingIndex, count - 1]
948*c8dee2aaSAndroid Build Coastguard Worker     int targetCount = fStartingElementIndex + 1;
949*c8dee2aaSAndroid Build Coastguard Worker     while (elements->count() > targetCount) {
950*c8dee2aaSAndroid Build Coastguard Worker         elements->back().drawClip(device);
951*c8dee2aaSAndroid Build Coastguard Worker         elements->pop_back();
952*c8dee2aaSAndroid Build Coastguard Worker     }
953*c8dee2aaSAndroid Build Coastguard Worker     if (elements->count() < targetCount) {
954*c8dee2aaSAndroid Build Coastguard Worker         elements->push_back(std::move(toAdd));
955*c8dee2aaSAndroid Build Coastguard Worker     } else {
956*c8dee2aaSAndroid Build Coastguard Worker         elements->back().drawClip(device);
957*c8dee2aaSAndroid Build Coastguard Worker         elements->back() = std::move(toAdd);
958*c8dee2aaSAndroid Build Coastguard Worker     }
959*c8dee2aaSAndroid Build Coastguard Worker 
960*c8dee2aaSAndroid Build Coastguard Worker     SkASSERT(elements->count() == fStartingElementIndex + 1);
961*c8dee2aaSAndroid Build Coastguard Worker 
962*c8dee2aaSAndroid Build Coastguard Worker     // This invalidates all older elements that are owned by save records lower in the clip stack.
963*c8dee2aaSAndroid Build Coastguard Worker     fOldestValidIndex = fStartingElementIndex;
964*c8dee2aaSAndroid Build Coastguard Worker }
965*c8dee2aaSAndroid Build Coastguard Worker 
966*c8dee2aaSAndroid Build Coastguard Worker ///////////////////////////////////////////////////////////////////////////////
967*c8dee2aaSAndroid Build Coastguard Worker // ClipStack
968*c8dee2aaSAndroid Build Coastguard Worker 
969*c8dee2aaSAndroid Build Coastguard Worker // NOTE: Based on draw calls in all GMs, SKPs, and SVGs as of 08/20, 98% use a clip stack with
970*c8dee2aaSAndroid Build Coastguard Worker // one Element and up to two SaveRecords, thus the inline size for RawElement::Stack and
971*c8dee2aaSAndroid Build Coastguard Worker // SaveRecord::Stack (this conveniently keeps the size of ClipStack manageable). The max
972*c8dee2aaSAndroid Build Coastguard Worker // encountered element stack depth was 5 and the max save depth was 6. Using an increment of 8 for
973*c8dee2aaSAndroid Build Coastguard Worker // these stacks means that clip management will incur a single allocation for the remaining 2%
974*c8dee2aaSAndroid Build Coastguard Worker // of the draws, with extra head room for more complex clips encountered in the wild.
975*c8dee2aaSAndroid Build Coastguard Worker static constexpr int kElementStackIncrement = 8;
976*c8dee2aaSAndroid Build Coastguard Worker static constexpr int kSaveStackIncrement = 8;
977*c8dee2aaSAndroid Build Coastguard Worker 
ClipStack(Device * owningDevice)978*c8dee2aaSAndroid Build Coastguard Worker ClipStack::ClipStack(Device* owningDevice)
979*c8dee2aaSAndroid Build Coastguard Worker         : fElements(kElementStackIncrement)
980*c8dee2aaSAndroid Build Coastguard Worker         , fSaves(kSaveStackIncrement)
981*c8dee2aaSAndroid Build Coastguard Worker         , fDevice(owningDevice) {
982*c8dee2aaSAndroid Build Coastguard Worker     // Start with a save record that is wide open
983*c8dee2aaSAndroid Build Coastguard Worker     fSaves.emplace_back(this->deviceBounds());
984*c8dee2aaSAndroid Build Coastguard Worker }
985*c8dee2aaSAndroid Build Coastguard Worker 
986*c8dee2aaSAndroid Build Coastguard Worker ClipStack::~ClipStack() = default;
987*c8dee2aaSAndroid Build Coastguard Worker 
save()988*c8dee2aaSAndroid Build Coastguard Worker void ClipStack::save() {
989*c8dee2aaSAndroid Build Coastguard Worker     SkASSERT(!fSaves.empty());
990*c8dee2aaSAndroid Build Coastguard Worker     fSaves.back().pushSave();
991*c8dee2aaSAndroid Build Coastguard Worker }
992*c8dee2aaSAndroid Build Coastguard Worker 
restore()993*c8dee2aaSAndroid Build Coastguard Worker void ClipStack::restore() {
994*c8dee2aaSAndroid Build Coastguard Worker     SkASSERT(!fSaves.empty());
995*c8dee2aaSAndroid Build Coastguard Worker     SaveRecord& current = fSaves.back();
996*c8dee2aaSAndroid Build Coastguard Worker     if (current.popSave()) {
997*c8dee2aaSAndroid Build Coastguard Worker         // This was just a deferred save being undone, so the record doesn't need to be removed yet
998*c8dee2aaSAndroid Build Coastguard Worker         return;
999*c8dee2aaSAndroid Build Coastguard Worker     }
1000*c8dee2aaSAndroid Build Coastguard Worker 
1001*c8dee2aaSAndroid Build Coastguard Worker     // When we remove a save record, we delete all elements >= its starting index and any masks
1002*c8dee2aaSAndroid Build Coastguard Worker     // that were rasterized for it.
1003*c8dee2aaSAndroid Build Coastguard Worker     current.removeElements(&fElements, fDevice);
1004*c8dee2aaSAndroid Build Coastguard Worker 
1005*c8dee2aaSAndroid Build Coastguard Worker     fSaves.pop_back();
1006*c8dee2aaSAndroid Build Coastguard Worker     // Restore any remaining elements that were only invalidated by the now-removed save record.
1007*c8dee2aaSAndroid Build Coastguard Worker     fSaves.back().restoreElements(&fElements);
1008*c8dee2aaSAndroid Build Coastguard Worker }
1009*c8dee2aaSAndroid Build Coastguard Worker 
deviceBounds() const1010*c8dee2aaSAndroid Build Coastguard Worker Rect ClipStack::deviceBounds() const {
1011*c8dee2aaSAndroid Build Coastguard Worker     return Rect::WH(fDevice->width(), fDevice->height());
1012*c8dee2aaSAndroid Build Coastguard Worker }
1013*c8dee2aaSAndroid Build Coastguard Worker 
conservativeBounds() const1014*c8dee2aaSAndroid Build Coastguard Worker Rect ClipStack::conservativeBounds() const {
1015*c8dee2aaSAndroid Build Coastguard Worker     const SaveRecord& current = this->currentSaveRecord();
1016*c8dee2aaSAndroid Build Coastguard Worker     if (current.state() == ClipState::kEmpty) {
1017*c8dee2aaSAndroid Build Coastguard Worker         return Rect::InfiniteInverted();
1018*c8dee2aaSAndroid Build Coastguard Worker     } else if (current.state() == ClipState::kWideOpen) {
1019*c8dee2aaSAndroid Build Coastguard Worker         return this->deviceBounds();
1020*c8dee2aaSAndroid Build Coastguard Worker     } else {
1021*c8dee2aaSAndroid Build Coastguard Worker         if (current.op() == SkClipOp::kDifference) {
1022*c8dee2aaSAndroid Build Coastguard Worker             // The outer/inner bounds represent what's cut out, so full bounds remains the device
1023*c8dee2aaSAndroid Build Coastguard Worker             // bounds, minus any fully clipped content that spans the device edge.
1024*c8dee2aaSAndroid Build Coastguard Worker             return subtract(this->deviceBounds(), current.innerBounds(), /* exact */ true);
1025*c8dee2aaSAndroid Build Coastguard Worker         } else {
1026*c8dee2aaSAndroid Build Coastguard Worker             SkASSERT(this->deviceBounds().contains(current.outerBounds()));
1027*c8dee2aaSAndroid Build Coastguard Worker             return current.outerBounds();
1028*c8dee2aaSAndroid Build Coastguard Worker         }
1029*c8dee2aaSAndroid Build Coastguard Worker     }
1030*c8dee2aaSAndroid Build Coastguard Worker }
1031*c8dee2aaSAndroid Build Coastguard Worker 
writableSaveRecord(bool * wasDeferred)1032*c8dee2aaSAndroid Build Coastguard Worker ClipStack::SaveRecord& ClipStack::writableSaveRecord(bool* wasDeferred) {
1033*c8dee2aaSAndroid Build Coastguard Worker     SaveRecord& current = fSaves.back();
1034*c8dee2aaSAndroid Build Coastguard Worker     if (current.canBeUpdated()) {
1035*c8dee2aaSAndroid Build Coastguard Worker         // Current record is still open, so it can be modified directly
1036*c8dee2aaSAndroid Build Coastguard Worker         *wasDeferred = false;
1037*c8dee2aaSAndroid Build Coastguard Worker         return current;
1038*c8dee2aaSAndroid Build Coastguard Worker     } else {
1039*c8dee2aaSAndroid Build Coastguard Worker         // Must undefer the save to get a new record.
1040*c8dee2aaSAndroid Build Coastguard Worker         SkAssertResult(current.popSave());
1041*c8dee2aaSAndroid Build Coastguard Worker         *wasDeferred = true;
1042*c8dee2aaSAndroid Build Coastguard Worker         return fSaves.emplace_back(current, fElements.count());
1043*c8dee2aaSAndroid Build Coastguard Worker     }
1044*c8dee2aaSAndroid Build Coastguard Worker }
1045*c8dee2aaSAndroid Build Coastguard Worker 
clipShader(sk_sp<SkShader> shader)1046*c8dee2aaSAndroid Build Coastguard Worker void ClipStack::clipShader(sk_sp<SkShader> shader) {
1047*c8dee2aaSAndroid Build Coastguard Worker     // Shaders can't bring additional coverage
1048*c8dee2aaSAndroid Build Coastguard Worker     if (this->currentSaveRecord().state() == ClipState::kEmpty) {
1049*c8dee2aaSAndroid Build Coastguard Worker         return;
1050*c8dee2aaSAndroid Build Coastguard Worker     }
1051*c8dee2aaSAndroid Build Coastguard Worker 
1052*c8dee2aaSAndroid Build Coastguard Worker     bool wasDeferred;
1053*c8dee2aaSAndroid Build Coastguard Worker     this->writableSaveRecord(&wasDeferred).addShader(std::move(shader));
1054*c8dee2aaSAndroid Build Coastguard Worker     // Geometry elements are not invalidated by updating the clip shader
1055*c8dee2aaSAndroid Build Coastguard Worker     // TODO(b/238763003): Integrating clipShader into graphite needs more thought, particularly how
1056*c8dee2aaSAndroid Build Coastguard Worker     // to handle the shader explosion and where to put the effects in the GraphicsPipelineDesc.
1057*c8dee2aaSAndroid Build Coastguard Worker     // One idea is to use sample locations and draw the clipShader into the depth buffer.
1058*c8dee2aaSAndroid Build Coastguard Worker     // Another is resolve the clip shader into an alpha mask image that is sampled by the draw.
1059*c8dee2aaSAndroid Build Coastguard Worker }
1060*c8dee2aaSAndroid Build Coastguard Worker 
clipShape(const Transform & localToDevice,const Shape & shape,SkClipOp op,PixelSnapping snapping)1061*c8dee2aaSAndroid Build Coastguard Worker void ClipStack::clipShape(const Transform& localToDevice,
1062*c8dee2aaSAndroid Build Coastguard Worker                           const Shape& shape,
1063*c8dee2aaSAndroid Build Coastguard Worker                           SkClipOp op,
1064*c8dee2aaSAndroid Build Coastguard Worker                           PixelSnapping snapping) {
1065*c8dee2aaSAndroid Build Coastguard Worker     if (this->currentSaveRecord().state() == ClipState::kEmpty) {
1066*c8dee2aaSAndroid Build Coastguard Worker         return;
1067*c8dee2aaSAndroid Build Coastguard Worker     }
1068*c8dee2aaSAndroid Build Coastguard Worker 
1069*c8dee2aaSAndroid Build Coastguard Worker     // This will apply the transform if it's shape-type preserving, and clip the element's bounds
1070*c8dee2aaSAndroid Build Coastguard Worker     // to the device bounds (NOT the conservative clip bounds, since those are based on the net
1071*c8dee2aaSAndroid Build Coastguard Worker     // effect of all elements while device bounds clipping happens implicitly. During addElement,
1072*c8dee2aaSAndroid Build Coastguard Worker     // we may still be able to invalidate some older elements).
1073*c8dee2aaSAndroid Build Coastguard Worker     // NOTE: Does not try to simplify the shape type by inspecting the SkPath.
1074*c8dee2aaSAndroid Build Coastguard Worker     RawElement element{this->deviceBounds(), localToDevice, shape, op, snapping};
1075*c8dee2aaSAndroid Build Coastguard Worker 
1076*c8dee2aaSAndroid Build Coastguard Worker     // An empty op means do nothing (for difference), or close the save record, so we try and detect
1077*c8dee2aaSAndroid Build Coastguard Worker     // that early before doing additional unnecessary save record allocation.
1078*c8dee2aaSAndroid Build Coastguard Worker     if (element.shape().isEmpty()) {
1079*c8dee2aaSAndroid Build Coastguard Worker         if (element.op() == SkClipOp::kDifference) {
1080*c8dee2aaSAndroid Build Coastguard Worker             // If the shape is empty and we're subtracting, this has no effect on the clip
1081*c8dee2aaSAndroid Build Coastguard Worker             return;
1082*c8dee2aaSAndroid Build Coastguard Worker         }
1083*c8dee2aaSAndroid Build Coastguard Worker         // else we will make the clip empty, but we need a new save record to record that change
1084*c8dee2aaSAndroid Build Coastguard Worker         // in the clip state; fall through to below and updateForElement() will handle it.
1085*c8dee2aaSAndroid Build Coastguard Worker     }
1086*c8dee2aaSAndroid Build Coastguard Worker 
1087*c8dee2aaSAndroid Build Coastguard Worker     bool wasDeferred;
1088*c8dee2aaSAndroid Build Coastguard Worker     SaveRecord& save = this->writableSaveRecord(&wasDeferred);
1089*c8dee2aaSAndroid Build Coastguard Worker     SkDEBUGCODE(int elementCount = fElements.count();)
1090*c8dee2aaSAndroid Build Coastguard Worker     if (!save.addElement(std::move(element), &fElements, fDevice)) {
1091*c8dee2aaSAndroid Build Coastguard Worker         if (wasDeferred) {
1092*c8dee2aaSAndroid Build Coastguard Worker             // We made a new save record, but ended up not adding an element to the stack.
1093*c8dee2aaSAndroid Build Coastguard Worker             // So instead of keeping an empty save record around, pop it off and restore the counter
1094*c8dee2aaSAndroid Build Coastguard Worker             SkASSERT(elementCount == fElements.count());
1095*c8dee2aaSAndroid Build Coastguard Worker             fSaves.pop_back();
1096*c8dee2aaSAndroid Build Coastguard Worker             fSaves.back().pushSave();
1097*c8dee2aaSAndroid Build Coastguard Worker         }
1098*c8dee2aaSAndroid Build Coastguard Worker     }
1099*c8dee2aaSAndroid Build Coastguard Worker }
1100*c8dee2aaSAndroid Build Coastguard Worker 
1101*c8dee2aaSAndroid Build Coastguard Worker // Decide whether we can use this shape to do analytic clipping. Only rects and certain
1102*c8dee2aaSAndroid Build Coastguard Worker // rrects are supported. We assume these have been pre-transformed by the RawElement
1103*c8dee2aaSAndroid Build Coastguard Worker // constructor, so only identity transforms are allowed.
1104*c8dee2aaSAndroid Build Coastguard Worker namespace {
can_apply_analytic_clip(const Shape & shape,const Transform & localToDevice)1105*c8dee2aaSAndroid Build Coastguard Worker CircularRRectClip can_apply_analytic_clip(const Shape& shape,
1106*c8dee2aaSAndroid Build Coastguard Worker                                           const Transform& localToDevice) {
1107*c8dee2aaSAndroid Build Coastguard Worker     if (localToDevice.type() != Transform::Type::kIdentity) {
1108*c8dee2aaSAndroid Build Coastguard Worker         return {};
1109*c8dee2aaSAndroid Build Coastguard Worker     }
1110*c8dee2aaSAndroid Build Coastguard Worker 
1111*c8dee2aaSAndroid Build Coastguard Worker     // The circular rrect clip only handles rrect radii >= kRadiusMin.
1112*c8dee2aaSAndroid Build Coastguard Worker     static constexpr SkScalar kRadiusMin = SK_ScalarHalf;
1113*c8dee2aaSAndroid Build Coastguard Worker 
1114*c8dee2aaSAndroid Build Coastguard Worker     // Can handle Rect directly.
1115*c8dee2aaSAndroid Build Coastguard Worker     if (shape.isRect()) {
1116*c8dee2aaSAndroid Build Coastguard Worker         return {shape.rect(), kRadiusMin, CircularRRectClip::kNone_EdgeFlag, shape.inverted()};
1117*c8dee2aaSAndroid Build Coastguard Worker     }
1118*c8dee2aaSAndroid Build Coastguard Worker 
1119*c8dee2aaSAndroid Build Coastguard Worker     // Otherwise we only handle certain kinds of RRects.
1120*c8dee2aaSAndroid Build Coastguard Worker     if (!shape.isRRect()) {
1121*c8dee2aaSAndroid Build Coastguard Worker         return {};
1122*c8dee2aaSAndroid Build Coastguard Worker     }
1123*c8dee2aaSAndroid Build Coastguard Worker 
1124*c8dee2aaSAndroid Build Coastguard Worker     const SkRRect& rrect = shape.rrect();
1125*c8dee2aaSAndroid Build Coastguard Worker     if (rrect.isOval() || rrect.isSimple()) {
1126*c8dee2aaSAndroid Build Coastguard Worker         SkVector radii = SkRRectPriv::GetSimpleRadii(rrect);
1127*c8dee2aaSAndroid Build Coastguard Worker         if (radii.fX < kRadiusMin || radii.fY < kRadiusMin) {
1128*c8dee2aaSAndroid Build Coastguard Worker             // In this case the corners are extremely close to rectangular and we collapse the
1129*c8dee2aaSAndroid Build Coastguard Worker             // clip to a rectangular clip.
1130*c8dee2aaSAndroid Build Coastguard Worker             return {rrect.rect(), kRadiusMin, CircularRRectClip::kNone_EdgeFlag, shape.inverted()};
1131*c8dee2aaSAndroid Build Coastguard Worker         }
1132*c8dee2aaSAndroid Build Coastguard Worker         if (SkScalarNearlyEqual(radii.fX, radii.fY)) {
1133*c8dee2aaSAndroid Build Coastguard Worker             return {rrect.rect(), radii.fX, CircularRRectClip::kAll_EdgeFlag, shape.inverted()};
1134*c8dee2aaSAndroid Build Coastguard Worker         } else {
1135*c8dee2aaSAndroid Build Coastguard Worker             return {};
1136*c8dee2aaSAndroid Build Coastguard Worker         }
1137*c8dee2aaSAndroid Build Coastguard Worker     }
1138*c8dee2aaSAndroid Build Coastguard Worker 
1139*c8dee2aaSAndroid Build Coastguard Worker     if (rrect.isComplex() || rrect.isNinePatch()) {
1140*c8dee2aaSAndroid Build Coastguard Worker         // Check for the "tab" cases - two adjacent circular corners and two square corners.
1141*c8dee2aaSAndroid Build Coastguard Worker         constexpr uint32_t kCornerFlags[4] = {
1142*c8dee2aaSAndroid Build Coastguard Worker             CircularRRectClip::kTop_EdgeFlag | CircularRRectClip::kLeft_EdgeFlag,
1143*c8dee2aaSAndroid Build Coastguard Worker             CircularRRectClip::kTop_EdgeFlag | CircularRRectClip::kRight_EdgeFlag,
1144*c8dee2aaSAndroid Build Coastguard Worker             CircularRRectClip::kBottom_EdgeFlag | CircularRRectClip::kRight_EdgeFlag,
1145*c8dee2aaSAndroid Build Coastguard Worker             CircularRRectClip::kBottom_EdgeFlag | CircularRRectClip::kLeft_EdgeFlag,
1146*c8dee2aaSAndroid Build Coastguard Worker         };
1147*c8dee2aaSAndroid Build Coastguard Worker         SkScalar circularRadius = 0;
1148*c8dee2aaSAndroid Build Coastguard Worker         uint32_t edgeFlags = 0;
1149*c8dee2aaSAndroid Build Coastguard Worker         for (int corner = 0; corner < 4; ++corner) {
1150*c8dee2aaSAndroid Build Coastguard Worker             SkVector radii = rrect.radii((SkRRect::Corner)corner);
1151*c8dee2aaSAndroid Build Coastguard Worker             // Can only handle circular radii.
1152*c8dee2aaSAndroid Build Coastguard Worker             // Also applies to corners with both zero and non-zero radii.
1153*c8dee2aaSAndroid Build Coastguard Worker             if (!SkScalarNearlyEqual(radii.fX, radii.fY)) {
1154*c8dee2aaSAndroid Build Coastguard Worker                 return {};
1155*c8dee2aaSAndroid Build Coastguard Worker             }
1156*c8dee2aaSAndroid Build Coastguard Worker             if (radii.fX < kRadiusMin || radii.fY < kRadiusMin) {
1157*c8dee2aaSAndroid Build Coastguard Worker                 // The corner is square, so no need to flag as circular.
1158*c8dee2aaSAndroid Build Coastguard Worker                 continue;
1159*c8dee2aaSAndroid Build Coastguard Worker             }
1160*c8dee2aaSAndroid Build Coastguard Worker             // First circular corner seen
1161*c8dee2aaSAndroid Build Coastguard Worker             if (!edgeFlags) {
1162*c8dee2aaSAndroid Build Coastguard Worker                 circularRadius = radii.fX;
1163*c8dee2aaSAndroid Build Coastguard Worker             } else if (!SkScalarNearlyEqual(radii.fX, circularRadius)) {
1164*c8dee2aaSAndroid Build Coastguard Worker                 // Radius doesn't match previously seen circular radius
1165*c8dee2aaSAndroid Build Coastguard Worker                 return {};
1166*c8dee2aaSAndroid Build Coastguard Worker             }
1167*c8dee2aaSAndroid Build Coastguard Worker             edgeFlags |= kCornerFlags[corner];
1168*c8dee2aaSAndroid Build Coastguard Worker         }
1169*c8dee2aaSAndroid Build Coastguard Worker 
1170*c8dee2aaSAndroid Build Coastguard Worker         if (edgeFlags == CircularRRectClip::kNone_EdgeFlag) {
1171*c8dee2aaSAndroid Build Coastguard Worker             // It's a rect
1172*c8dee2aaSAndroid Build Coastguard Worker             return {rrect.rect(), kRadiusMin, edgeFlags, shape.inverted()};
1173*c8dee2aaSAndroid Build Coastguard Worker         } else {
1174*c8dee2aaSAndroid Build Coastguard Worker             // If any rounded corner pairs are non-adjacent or if there are three rounded
1175*c8dee2aaSAndroid Build Coastguard Worker             // corners all edge flags will be set, which is not valid.
1176*c8dee2aaSAndroid Build Coastguard Worker             if (edgeFlags == CircularRRectClip::kAll_EdgeFlag) {
1177*c8dee2aaSAndroid Build Coastguard Worker                 return {};
1178*c8dee2aaSAndroid Build Coastguard Worker             // At least one corner is rounded, or two adjacent corners are rounded.
1179*c8dee2aaSAndroid Build Coastguard Worker             } else {
1180*c8dee2aaSAndroid Build Coastguard Worker                 return {rrect.rect(), circularRadius, edgeFlags, shape.inverted()};
1181*c8dee2aaSAndroid Build Coastguard Worker             }
1182*c8dee2aaSAndroid Build Coastguard Worker         }
1183*c8dee2aaSAndroid Build Coastguard Worker     }
1184*c8dee2aaSAndroid Build Coastguard Worker 
1185*c8dee2aaSAndroid Build Coastguard Worker     return {};
1186*c8dee2aaSAndroid Build Coastguard Worker }
1187*c8dee2aaSAndroid Build Coastguard Worker }  // anonymous namespace
1188*c8dee2aaSAndroid Build Coastguard Worker 
visitClipStackForDraw(const Transform & localToDevice,const Geometry & geometry,const SkStrokeRec & style,bool outsetBoundsForAA,ClipStack::ElementList * outEffectiveElements) const1189*c8dee2aaSAndroid Build Coastguard Worker Clip ClipStack::visitClipStackForDraw(const Transform& localToDevice,
1190*c8dee2aaSAndroid Build Coastguard Worker                                       const Geometry& geometry,
1191*c8dee2aaSAndroid Build Coastguard Worker                                       const SkStrokeRec& style,
1192*c8dee2aaSAndroid Build Coastguard Worker                                       bool outsetBoundsForAA,
1193*c8dee2aaSAndroid Build Coastguard Worker                                       ClipStack::ElementList* outEffectiveElements) const {
1194*c8dee2aaSAndroid Build Coastguard Worker     static const Clip kClippedOut = {
1195*c8dee2aaSAndroid Build Coastguard Worker             Rect::InfiniteInverted(), Rect::InfiniteInverted(), SkIRect::MakeEmpty(),
1196*c8dee2aaSAndroid Build Coastguard Worker             /* analyticClip= */ {}, /* shader= */ nullptr};
1197*c8dee2aaSAndroid Build Coastguard Worker 
1198*c8dee2aaSAndroid Build Coastguard Worker     const SaveRecord& cs = this->currentSaveRecord();
1199*c8dee2aaSAndroid Build Coastguard Worker     if (cs.state() == ClipState::kEmpty) {
1200*c8dee2aaSAndroid Build Coastguard Worker         // We know the draw is clipped out so don't bother computing the base draw bounds.
1201*c8dee2aaSAndroid Build Coastguard Worker         return kClippedOut;
1202*c8dee2aaSAndroid Build Coastguard Worker     }
1203*c8dee2aaSAndroid Build Coastguard Worker     // Compute draw bounds, clipped only to our device bounds since we need to return that even if
1204*c8dee2aaSAndroid Build Coastguard Worker     // the clip stack is known to be wide-open.
1205*c8dee2aaSAndroid Build Coastguard Worker     const Rect deviceBounds = this->deviceBounds();
1206*c8dee2aaSAndroid Build Coastguard Worker 
1207*c8dee2aaSAndroid Build Coastguard Worker     // When 'style' isn't fill, 'shape' describes the pre-stroke shape so we can't use it to check
1208*c8dee2aaSAndroid Build Coastguard Worker     // against clip elements and so 'styledShape' will be set to the bounds post-stroking.
1209*c8dee2aaSAndroid Build Coastguard Worker     SkTCopyOnFirstWrite<Shape> styledShape;
1210*c8dee2aaSAndroid Build Coastguard Worker     if (geometry.isShape()) {
1211*c8dee2aaSAndroid Build Coastguard Worker         styledShape.init(geometry.shape());
1212*c8dee2aaSAndroid Build Coastguard Worker     } else {
1213*c8dee2aaSAndroid Build Coastguard Worker         // The geometry is something special like text or vertices, in which case it's definitely
1214*c8dee2aaSAndroid Build Coastguard Worker         // not a shape that could simplify cleanly with the clip stack.
1215*c8dee2aaSAndroid Build Coastguard Worker         styledShape.initIfNeeded(geometry.bounds());
1216*c8dee2aaSAndroid Build Coastguard Worker     }
1217*c8dee2aaSAndroid Build Coastguard Worker 
1218*c8dee2aaSAndroid Build Coastguard Worker     auto origSize = geometry.bounds().size();
1219*c8dee2aaSAndroid Build Coastguard Worker     if (!SkIsFinite(origSize.x(), origSize.y())) {
1220*c8dee2aaSAndroid Build Coastguard Worker         // Discard all non-finite geometry as if it were clipped out
1221*c8dee2aaSAndroid Build Coastguard Worker         return kClippedOut;
1222*c8dee2aaSAndroid Build Coastguard Worker     }
1223*c8dee2aaSAndroid Build Coastguard Worker 
1224*c8dee2aaSAndroid Build Coastguard Worker     // Inverse-filled shapes always fill the entire device (restricted to the clip).
1225*c8dee2aaSAndroid Build Coastguard Worker     // Query the invertedness of the shape before any of the `setRect` calls below, which can
1226*c8dee2aaSAndroid Build Coastguard Worker     // modify it.
1227*c8dee2aaSAndroid Build Coastguard Worker     bool infiniteBounds = styledShape->inverted();
1228*c8dee2aaSAndroid Build Coastguard Worker 
1229*c8dee2aaSAndroid Build Coastguard Worker     // Discard fills and strokes that cannot produce any coverage: an empty fill, or a
1230*c8dee2aaSAndroid Build Coastguard Worker     // zero-length stroke that has butt caps. Otherwise the stroke style applies to a vertical
1231*c8dee2aaSAndroid Build Coastguard Worker     // or horizontal line (making it non-empty), or it's a zero-length path segment that
1232*c8dee2aaSAndroid Build Coastguard Worker     // must produce round or square caps (making it non-empty):
1233*c8dee2aaSAndroid Build Coastguard Worker     //     https://www.w3.org/TR/SVG11/implnote.html#PathElementImplementationNotes
1234*c8dee2aaSAndroid Build Coastguard Worker     if (!infiniteBounds && (styledShape->isLine() || any(origSize == 0.f))) {
1235*c8dee2aaSAndroid Build Coastguard Worker         if (style.isFillStyle() || (style.getCap() == SkPaint::kButt_Cap && all(origSize == 0.f))) {
1236*c8dee2aaSAndroid Build Coastguard Worker             return kClippedOut;
1237*c8dee2aaSAndroid Build Coastguard Worker         }
1238*c8dee2aaSAndroid Build Coastguard Worker     }
1239*c8dee2aaSAndroid Build Coastguard Worker 
1240*c8dee2aaSAndroid Build Coastguard Worker     Rect transformedShapeBounds;
1241*c8dee2aaSAndroid Build Coastguard Worker     bool shapeInDeviceSpace = false;
1242*c8dee2aaSAndroid Build Coastguard Worker 
1243*c8dee2aaSAndroid Build Coastguard Worker     // Some renderers make the drawn area larger than the geometry for anti-aliasing
1244*c8dee2aaSAndroid Build Coastguard Worker     float rendererOutset = outsetBoundsForAA ? localToDevice.localAARadius(styledShape->bounds())
1245*c8dee2aaSAndroid Build Coastguard Worker                                              : 0.f;
1246*c8dee2aaSAndroid Build Coastguard Worker     if (!SkIsFinite(rendererOutset)) {
1247*c8dee2aaSAndroid Build Coastguard Worker         transformedShapeBounds = deviceBounds;
1248*c8dee2aaSAndroid Build Coastguard Worker         infiniteBounds = true;
1249*c8dee2aaSAndroid Build Coastguard Worker     } else {
1250*c8dee2aaSAndroid Build Coastguard Worker         // Will be in device space once style/AA outsets and the localToDevice transform are
1251*c8dee2aaSAndroid Build Coastguard Worker         // applied.
1252*c8dee2aaSAndroid Build Coastguard Worker         transformedShapeBounds = styledShape->bounds();
1253*c8dee2aaSAndroid Build Coastguard Worker 
1254*c8dee2aaSAndroid Build Coastguard Worker         // Regular filled shapes and strokes get larger based on style and transform
1255*c8dee2aaSAndroid Build Coastguard Worker         if (!style.isHairlineStyle() || rendererOutset != 0.0f) {
1256*c8dee2aaSAndroid Build Coastguard Worker             float localStyleOutset = style.getInflationRadius() + rendererOutset;
1257*c8dee2aaSAndroid Build Coastguard Worker             transformedShapeBounds.outset(localStyleOutset);
1258*c8dee2aaSAndroid Build Coastguard Worker 
1259*c8dee2aaSAndroid Build Coastguard Worker             if (!style.isFillStyle() || rendererOutset != 0.0f) {
1260*c8dee2aaSAndroid Build Coastguard Worker                 // While this loses any shape type, the bounds remain local so hopefully tests are
1261*c8dee2aaSAndroid Build Coastguard Worker                 // fairly accurate.
1262*c8dee2aaSAndroid Build Coastguard Worker                 styledShape.writable()->setRect(transformedShapeBounds);
1263*c8dee2aaSAndroid Build Coastguard Worker             }
1264*c8dee2aaSAndroid Build Coastguard Worker         }
1265*c8dee2aaSAndroid Build Coastguard Worker 
1266*c8dee2aaSAndroid Build Coastguard Worker         transformedShapeBounds = localToDevice.mapRect(transformedShapeBounds);
1267*c8dee2aaSAndroid Build Coastguard Worker 
1268*c8dee2aaSAndroid Build Coastguard Worker         // Hairlines get an extra pixel *after* transforming to device space, unless the renderer
1269*c8dee2aaSAndroid Build Coastguard Worker         // has already defined an outset
1270*c8dee2aaSAndroid Build Coastguard Worker         if (style.isHairlineStyle() && rendererOutset == 0.0f) {
1271*c8dee2aaSAndroid Build Coastguard Worker             transformedShapeBounds.outset(0.5f);
1272*c8dee2aaSAndroid Build Coastguard Worker             // and the associated transform must be kIdentity since the bounds have been mapped by
1273*c8dee2aaSAndroid Build Coastguard Worker             // localToDevice already.
1274*c8dee2aaSAndroid Build Coastguard Worker             styledShape.writable()->setRect(transformedShapeBounds);
1275*c8dee2aaSAndroid Build Coastguard Worker             shapeInDeviceSpace = true;
1276*c8dee2aaSAndroid Build Coastguard Worker         }
1277*c8dee2aaSAndroid Build Coastguard Worker 
1278*c8dee2aaSAndroid Build Coastguard Worker         // Restrict bounds to the device limits.
1279*c8dee2aaSAndroid Build Coastguard Worker         transformedShapeBounds.intersect(deviceBounds);
1280*c8dee2aaSAndroid Build Coastguard Worker     }
1281*c8dee2aaSAndroid Build Coastguard Worker 
1282*c8dee2aaSAndroid Build Coastguard Worker     Rect drawBounds;  // defined in device space
1283*c8dee2aaSAndroid Build Coastguard Worker     if (infiniteBounds) {
1284*c8dee2aaSAndroid Build Coastguard Worker         drawBounds = deviceBounds;
1285*c8dee2aaSAndroid Build Coastguard Worker         styledShape.writable()->setRect(drawBounds);
1286*c8dee2aaSAndroid Build Coastguard Worker         shapeInDeviceSpace = true;
1287*c8dee2aaSAndroid Build Coastguard Worker     } else {
1288*c8dee2aaSAndroid Build Coastguard Worker         drawBounds = transformedShapeBounds;
1289*c8dee2aaSAndroid Build Coastguard Worker     }
1290*c8dee2aaSAndroid Build Coastguard Worker 
1291*c8dee2aaSAndroid Build Coastguard Worker     if (drawBounds.isEmptyNegativeOrNaN() || cs.state() == ClipState::kWideOpen) {
1292*c8dee2aaSAndroid Build Coastguard Worker         // Either the draw is off screen, so it's clipped out regardless of the state of the
1293*c8dee2aaSAndroid Build Coastguard Worker         // SaveRecord, or there are no elements to apply to the draw. In both cases, 'drawBounds'
1294*c8dee2aaSAndroid Build Coastguard Worker         // has the correct value, the scissor is the device bounds (ignored if clipped-out).
1295*c8dee2aaSAndroid Build Coastguard Worker         return Clip(drawBounds, transformedShapeBounds, deviceBounds.asSkIRect(), {}, cs.shader());
1296*c8dee2aaSAndroid Build Coastguard Worker     }
1297*c8dee2aaSAndroid Build Coastguard Worker 
1298*c8dee2aaSAndroid Build Coastguard Worker     // We don't evaluate Simplify() on the SaveRecord and the draw because a reduced version of
1299*c8dee2aaSAndroid Build Coastguard Worker     // Simplify is effectively performed in computing the scissor rect.
1300*c8dee2aaSAndroid Build Coastguard Worker     // Given that, we can skip iterating over the clip elements when:
1301*c8dee2aaSAndroid Build Coastguard Worker     //  - the draw's *scissored* bounds are empty, which happens when the draw was clipped out.
1302*c8dee2aaSAndroid Build Coastguard Worker     //  - the scissored bounds are contained in our inner bounds, which happens if all we need to
1303*c8dee2aaSAndroid Build Coastguard Worker     //    apply to the draw is the computed scissor rect.
1304*c8dee2aaSAndroid Build Coastguard Worker     // TODO: The Clip's scissor is defined in terms of integer pixel coords, but if we move to
1305*c8dee2aaSAndroid Build Coastguard Worker     // clip plane distances in the vertex shader, it can be defined in terms of the original float
1306*c8dee2aaSAndroid Build Coastguard Worker     // coordinates.
1307*c8dee2aaSAndroid Build Coastguard Worker     Rect scissor = cs.scissor(deviceBounds, drawBounds).makeRoundOut();
1308*c8dee2aaSAndroid Build Coastguard Worker     drawBounds.intersect(scissor);
1309*c8dee2aaSAndroid Build Coastguard Worker     transformedShapeBounds.intersect(scissor);
1310*c8dee2aaSAndroid Build Coastguard Worker     if (drawBounds.isEmptyNegativeOrNaN() || cs.innerBounds().contains(drawBounds)) {
1311*c8dee2aaSAndroid Build Coastguard Worker         // Like above, in both cases drawBounds holds the right value.
1312*c8dee2aaSAndroid Build Coastguard Worker         return Clip(drawBounds, transformedShapeBounds, scissor.asSkIRect(), {}, cs.shader());
1313*c8dee2aaSAndroid Build Coastguard Worker     }
1314*c8dee2aaSAndroid Build Coastguard Worker 
1315*c8dee2aaSAndroid Build Coastguard Worker     // If we made it here, the clip stack affects the draw in a complex way so iterate each element.
1316*c8dee2aaSAndroid Build Coastguard Worker     // A draw is a transformed shape that "intersects" the clip. We use empty inner bounds because
1317*c8dee2aaSAndroid Build Coastguard Worker     // there's currently no way to re-write the draw as the clip's geometry, so there's no need to
1318*c8dee2aaSAndroid Build Coastguard Worker     // check if the draw contains the clip (vice versa is still checked and represents an unclipped
1319*c8dee2aaSAndroid Build Coastguard Worker     // draw so is very useful to identify).
1320*c8dee2aaSAndroid Build Coastguard Worker     TransformedShape draw{shapeInDeviceSpace ? kIdentity : localToDevice,
1321*c8dee2aaSAndroid Build Coastguard Worker                           *styledShape,
1322*c8dee2aaSAndroid Build Coastguard Worker                           /*outerBounds=*/drawBounds,
1323*c8dee2aaSAndroid Build Coastguard Worker                           /*innerBounds=*/Rect::InfiniteInverted(),
1324*c8dee2aaSAndroid Build Coastguard Worker                           /*op=*/SkClipOp::kIntersect,
1325*c8dee2aaSAndroid Build Coastguard Worker                           /*containsChecksOnlyBounds=*/true};
1326*c8dee2aaSAndroid Build Coastguard Worker 
1327*c8dee2aaSAndroid Build Coastguard Worker     SkASSERT(outEffectiveElements);
1328*c8dee2aaSAndroid Build Coastguard Worker     SkASSERT(outEffectiveElements->empty());
1329*c8dee2aaSAndroid Build Coastguard Worker     int i = fElements.count();
1330*c8dee2aaSAndroid Build Coastguard Worker     CircularRRectClip analyticClip;
1331*c8dee2aaSAndroid Build Coastguard Worker     for (const RawElement& e : fElements.ritems()) {
1332*c8dee2aaSAndroid Build Coastguard Worker         --i;
1333*c8dee2aaSAndroid Build Coastguard Worker         if (i < cs.oldestElementIndex()) {
1334*c8dee2aaSAndroid Build Coastguard Worker             // All earlier elements have been invalidated by elements already processed so the draw
1335*c8dee2aaSAndroid Build Coastguard Worker             // can't be affected by them and cannot contribute to their usage bounds.
1336*c8dee2aaSAndroid Build Coastguard Worker             break;
1337*c8dee2aaSAndroid Build Coastguard Worker         }
1338*c8dee2aaSAndroid Build Coastguard Worker 
1339*c8dee2aaSAndroid Build Coastguard Worker         auto influence = e.testForDraw(draw);
1340*c8dee2aaSAndroid Build Coastguard Worker         if (influence == RawElement::DrawInfluence::kClipOut) {
1341*c8dee2aaSAndroid Build Coastguard Worker             outEffectiveElements->clear();
1342*c8dee2aaSAndroid Build Coastguard Worker             return kClippedOut;
1343*c8dee2aaSAndroid Build Coastguard Worker         }
1344*c8dee2aaSAndroid Build Coastguard Worker         if (influence == RawElement::DrawInfluence::kIntersect) {
1345*c8dee2aaSAndroid Build Coastguard Worker             if (analyticClip.isEmpty()) {
1346*c8dee2aaSAndroid Build Coastguard Worker                 analyticClip = can_apply_analytic_clip(e.shape(), e.localToDevice());
1347*c8dee2aaSAndroid Build Coastguard Worker                 if (!analyticClip.isEmpty()) {
1348*c8dee2aaSAndroid Build Coastguard Worker                     continue;
1349*c8dee2aaSAndroid Build Coastguard Worker                 }
1350*c8dee2aaSAndroid Build Coastguard Worker             }
1351*c8dee2aaSAndroid Build Coastguard Worker             outEffectiveElements->push_back(&e);
1352*c8dee2aaSAndroid Build Coastguard Worker         }
1353*c8dee2aaSAndroid Build Coastguard Worker     }
1354*c8dee2aaSAndroid Build Coastguard Worker 
1355*c8dee2aaSAndroid Build Coastguard Worker     return Clip(drawBounds, transformedShapeBounds, scissor.asSkIRect(), analyticClip, cs.shader());
1356*c8dee2aaSAndroid Build Coastguard Worker }
1357*c8dee2aaSAndroid Build Coastguard Worker 
updateClipStateForDraw(const Clip & clip,const ElementList & effectiveElements,const BoundsManager * boundsManager,PaintersDepth z)1358*c8dee2aaSAndroid Build Coastguard Worker CompressedPaintersOrder ClipStack::updateClipStateForDraw(const Clip& clip,
1359*c8dee2aaSAndroid Build Coastguard Worker                                                           const ElementList& effectiveElements,
1360*c8dee2aaSAndroid Build Coastguard Worker                                                           const BoundsManager* boundsManager,
1361*c8dee2aaSAndroid Build Coastguard Worker                                                           PaintersDepth z) {
1362*c8dee2aaSAndroid Build Coastguard Worker     if (clip.isClippedOut()) {
1363*c8dee2aaSAndroid Build Coastguard Worker         return DrawOrder::kNoIntersection;
1364*c8dee2aaSAndroid Build Coastguard Worker     }
1365*c8dee2aaSAndroid Build Coastguard Worker 
1366*c8dee2aaSAndroid Build Coastguard Worker     SkDEBUGCODE(const SaveRecord& cs = this->currentSaveRecord();)
1367*c8dee2aaSAndroid Build Coastguard Worker     SkASSERT(cs.state() != ClipState::kEmpty);
1368*c8dee2aaSAndroid Build Coastguard Worker 
1369*c8dee2aaSAndroid Build Coastguard Worker     CompressedPaintersOrder maxClipOrder = DrawOrder::kNoIntersection;
1370*c8dee2aaSAndroid Build Coastguard Worker     for (int i = 0; i < effectiveElements.size(); ++i) {
1371*c8dee2aaSAndroid Build Coastguard Worker         // ClipStack owns the elements in the `clipState` so it's OK to downcast and cast away
1372*c8dee2aaSAndroid Build Coastguard Worker         // const.
1373*c8dee2aaSAndroid Build Coastguard Worker         // TODO: Enforce the ownership? In debug builds we could invalidate a `ClipStateForDraw` if
1374*c8dee2aaSAndroid Build Coastguard Worker         // its element pointers become dangling and assert validity here.
1375*c8dee2aaSAndroid Build Coastguard Worker         const RawElement* e = static_cast<const RawElement*>(effectiveElements[i]);
1376*c8dee2aaSAndroid Build Coastguard Worker         CompressedPaintersOrder order =
1377*c8dee2aaSAndroid Build Coastguard Worker                 const_cast<RawElement*>(e)->updateForDraw(boundsManager, clip.drawBounds(), z);
1378*c8dee2aaSAndroid Build Coastguard Worker         maxClipOrder = std::max(order, maxClipOrder);
1379*c8dee2aaSAndroid Build Coastguard Worker     }
1380*c8dee2aaSAndroid Build Coastguard Worker 
1381*c8dee2aaSAndroid Build Coastguard Worker     return maxClipOrder;
1382*c8dee2aaSAndroid Build Coastguard Worker }
1383*c8dee2aaSAndroid Build Coastguard Worker 
recordDeferredClipDraws()1384*c8dee2aaSAndroid Build Coastguard Worker void ClipStack::recordDeferredClipDraws() {
1385*c8dee2aaSAndroid Build Coastguard Worker     for (auto& e : fElements.items()) {
1386*c8dee2aaSAndroid Build Coastguard Worker         // When a Device requires all clip elements to be recorded, we have to iterate all elements,
1387*c8dee2aaSAndroid Build Coastguard Worker         // and will draw clip shapes for elements that are still marked as invalid from the clip
1388*c8dee2aaSAndroid Build Coastguard Worker         // stack, including those that are older than the current save record's oldest valid index,
1389*c8dee2aaSAndroid Build Coastguard Worker         // because they could have accumulated draw usage prior to being invalidated, but weren't
1390*c8dee2aaSAndroid Build Coastguard Worker         // flushed when they were invalidated because of an intervening save.
1391*c8dee2aaSAndroid Build Coastguard Worker         e.drawClip(fDevice);
1392*c8dee2aaSAndroid Build Coastguard Worker     }
1393*c8dee2aaSAndroid Build Coastguard Worker }
1394*c8dee2aaSAndroid Build Coastguard Worker 
1395*c8dee2aaSAndroid Build Coastguard Worker }  // namespace skgpu::graphite
1396