• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2010 Google Inc.
3  *
4  * Use of this source code is governed by a BSD-style license that can be
5  * found in the LICENSE file.
6  */
7 
8 #ifndef GrClip_DEFINED
9 #define GrClip_DEFINED
10 
11 #include "include/core/SkRRect.h"
12 #include "include/core/SkRect.h"
13 #include "src/gpu/ganesh/GrAppliedClip.h"
14 
15 class GrDrawOp;
16 namespace skgpu { namespace v1 { class SurfaceDrawContext; }}
17 
18 /**
19  * GrClip is an abstract base class for applying a clip. It constructs a clip mask if necessary, and
20  * fills out a GrAppliedClip instructing the caller on how to set up the draw state.
21  */
22 class GrClip {
23 public:
24     enum class Effect {
25         // The clip conservatively modifies the draw's coverage but doesn't eliminate the draw
26         kClipped,
27         // The clip definitely does not modify the draw's coverage and the draw can be performed
28         // without clipping (beyond the automatic device bounds clip).
29         kUnclipped,
30         // The clip definitely eliminates all of the draw's coverage and the draw can be skipped
31         kClippedOut
32     };
33 
34     struct PreClipResult {
35         Effect  fEffect;
36         SkRRect fRRect; // Ignore if 'isRRect' is false
37         GrAA    fAA;    // Ignore if 'isRRect' is false
38         bool    fIsRRect;
39 
PreClipResultPreClipResult40         PreClipResult(Effect effect) : fEffect(effect), fIsRRect(false) {}
PreClipResultPreClipResult41         PreClipResult(SkRect rect, GrAA aa) : PreClipResult(SkRRect::MakeRect(rect), aa) {}
PreClipResultPreClipResult42         PreClipResult(SkRRect rrect, GrAA aa)
43                 : fEffect(Effect::kClipped)
44                 , fRRect(rrect)
45                 , fAA(aa)
46                 , fIsRRect(true) {}
47     };
48 
~GrClip()49     virtual ~GrClip() {}
50 
51     /**
52      * Compute a conservative pixel bounds restricted to the given render target dimensions.
53      * The returned bounds represent the limits of pixels that can be drawn; anything outside of the
54      * bounds will be entirely clipped out.
55      */
56     virtual SkIRect getConservativeBounds() const = 0;
57 
58     /**
59      * This computes a GrAppliedClip from the clip which in turn can be used to build a GrPipeline.
60      * To determine the appropriate clipping implementation the GrClip subclass must know whether
61      * the draw will enable HW AA or uses the stencil buffer. On input 'bounds' is a conservative
62      * bounds of the draw that is to be clipped. If kClipped or kUnclipped is returned, the 'bounds'
63      * will have been updated to be contained within the clip bounds (or the device's, for wide-open
64      * clips). If kNoDraw is returned, 'bounds' and the applied clip are in an undetermined state
65      * and should be ignored (and the draw should be skipped).
66      */
67     virtual Effect apply(GrRecordingContext*, skgpu::v1::SurfaceDrawContext*, GrDrawOp*, GrAAType,
68                          GrAppliedClip*, SkRect* bounds) const = 0;
69 
70     /**
71      * Perform preliminary, conservative analysis on the draw bounds as if it were provided to
72      * apply(). The results of this are returned the PreClipResults struct, where 'result.fEffect'
73      * corresponds to what 'apply' would return. If this value is kUnclipped or kNoDraw, then it
74      * can be assumed that apply() would also always result in the same Effect.
75      *
76      * If kClipped is returned, apply() may further refine the effect to kUnclipped or kNoDraw,
77      * with one exception. When 'result.fIsRRect' is true, preApply() reports the single round rect
78      * and anti-aliased state that would act as an intersection on the draw geometry. If no further
79      * action is taken to modify the draw, apply() will represent this round rect in the applied
80      * clip.
81      *
82      * When set, 'result.fRRect' will intersect with the render target bounds but may extend
83      * beyond it. If the render target bounds are the only clip effect on the draw, this is reported
84      * as kUnclipped and not as a degenerate rrect that matches the bounds.
85      */
preApply(const SkRect & drawBounds,GrAA aa)86     virtual PreClipResult preApply(const SkRect& drawBounds, GrAA aa) const {
87         SkIRect pixelBounds = GetPixelIBounds(drawBounds, aa);
88         bool outside = !SkIRect::Intersects(pixelBounds, this->getConservativeBounds());
89         return outside ? Effect::kClippedOut : Effect::kClipped;
90     }
91 
92     /**
93      * This is the maximum distance that a draw may extend beyond a clip's boundary and still count
94      * count as "on the other side". We leave some slack because floating point rounding error is
95      * likely to blame. The rationale for 1e-3 is that in the coverage case (and barring unexpected
96      * rounding), as long as coverage stays within 0.5 * 1/256 of its intended value it shouldn't
97      * have any effect on the final pixel values.
98      */
99     constexpr static SkScalar kBoundsTolerance = 1e-3f;
100 
101     /**
102      * This is the slack around a half-pixel vertex coordinate where we don't trust the GPU's
103      * rasterizer to round consistently. The rounding method is not defined in GPU specs, and
104      * rasterizer precision frequently introduces errors where a fraction < 1/2 still rounds up.
105      *
106      * For non-AA bounds edges, an edge value between 0.45 and 0.55 will round in or round out
107      * depending on what side its on. Outside of this range, the non-AA edge will snap using round()
108      */
109     constexpr static SkScalar kHalfPixelRoundingTolerance = 5e-2f;
110 
111     /**
112      * Returns true if the given draw bounds count as entirely inside the clip.
113 
114      * @param innerClipBounds   device-space rect fully contained by the clip
115      * @param drawBounds        device-space bounds of the query region.
116      */
IsInsideClip(const SkIRect & innerClipBounds,const SkRect & drawBounds,GrAA aa)117     static bool IsInsideClip(const SkIRect& innerClipBounds, const SkRect& drawBounds, GrAA aa) {
118         return innerClipBounds.contains(GetPixelIBounds(drawBounds, aa));
119     }
120 
121     /**
122      * Returns true if the given draw bounds count as entirely outside the clip.
123 
124      * @param outerClipBounds   device-space rect that contains the clip
125      * @param drawBounds        device-space bounds of the query region.
126      * @param aa                whether or not the draw will use anti-aliasing
127      */
IsOutsideClip(const SkIRect & outerClipBounds,const SkRect & drawBounds,GrAA aa)128     static bool IsOutsideClip(const SkIRect& outerClipBounds, const SkRect& drawBounds, GrAA aa) {
129         return !SkIRect::Intersects(outerClipBounds, GetPixelIBounds(drawBounds, aa));
130     }
131 
132     // Modifies the behavior of GetPixelIBounds
133     enum class BoundsType {
134         /**
135          * Returns the tightest integer pixel bounding box such that the rasterization of a shape
136          * contained in the analytic 'bounds', using the 'aa' method, will only have non-zero
137          * coverage for pixels inside the returned bounds. Pixels outside the bounds will either
138          * not be touched, or will have 0 coverage that creates no visual change.
139          */
140         kExterior,
141         /**
142          * Returns the largest integer pixel bounding box such that were 'bounds' to be rendered as
143          * a solid fill using 'aa', every pixel in the returned bounds will have full coverage.
144          *
145          * This effectively determines the pixels that are definitely covered by a draw or clip. It
146          * effectively performs the opposite operations as GetOuterPixelBounds. It rounds in instead
147          * of out for coverage AA and non-AA near pixel centers.
148          */
149         kInterior
150     };
151 
152     /**
153      * Convert the analytic bounds of a shape into an integer pixel bounds, where the given aa type
154      * is used when the shape is rendered. The bounds mode can be used to query exterior or interior
155      * pixel boundaries. Interior bounds only make sense when its know that the analytic bounds
156      * are filled completely.
157      *
158      * NOTE: When using kExterior_Bounds, some coverage-AA rendering methods may still touch a pixel
159      * center outside of these bounds but will evaluate to 0 coverage. This is visually acceptable,
160      * but an additional outset of 1px should be used for dst proxy access.
161      */
162     static SkIRect GetPixelIBounds(const SkRect& bounds, GrAA aa,
163                                    BoundsType mode = BoundsType::kExterior) {
164         auto roundLow = [aa](float v) {
165             v += kBoundsTolerance;
166             return aa == GrAA::kNo ? SkScalarRoundToInt(v - kHalfPixelRoundingTolerance)
167                                    : SkScalarFloorToInt(v);
168         };
169         auto roundHigh = [aa](float v) {
170             v -= kBoundsTolerance;
171             return aa == GrAA::kNo ? SkScalarRoundToInt(v + kHalfPixelRoundingTolerance)
172                                    : SkScalarCeilToInt(v);
173         };
174 
175         if (bounds.isEmpty()) {
176             return SkIRect::MakeEmpty();
177         }
178 
179         if (mode == BoundsType::kExterior) {
180             return SkIRect::MakeLTRB(roundLow(bounds.fLeft),   roundLow(bounds.fTop),
181                                      roundHigh(bounds.fRight), roundHigh(bounds.fBottom));
182         } else {
183             return SkIRect::MakeLTRB(roundHigh(bounds.fLeft), roundHigh(bounds.fTop),
184                                      roundLow(bounds.fRight), roundLow(bounds.fBottom));
185         }
186     }
187 
188     /**
189      * Returns true if the given rect counts as aligned with pixel boundaries.
190      */
IsPixelAligned(const SkRect & rect)191     static bool IsPixelAligned(const SkRect& rect) {
192         return SkScalarAbs(SkScalarRoundToScalar(rect.fLeft) - rect.fLeft) <= kBoundsTolerance &&
193                SkScalarAbs(SkScalarRoundToScalar(rect.fTop) - rect.fTop) <= kBoundsTolerance &&
194                SkScalarAbs(SkScalarRoundToScalar(rect.fRight) - rect.fRight) <= kBoundsTolerance &&
195                SkScalarAbs(SkScalarRoundToScalar(rect.fBottom) - rect.fBottom) <= kBoundsTolerance;
196     }
197 };
198 
199 
200 /**
201  * GrHardClip never uses coverage FPs. It can only enforce the clip using the already-existing
202  * stencil buffer contents and/or fixed-function state like scissor. Always aliased if MSAA is off.
203  */
204 class GrHardClip : public GrClip {
205 public:
206     /**
207      * Sets the appropriate hardware state modifications on GrAppliedHardClip that will implement
208      * the clip. On input 'bounds' is a conservative bounds of the draw that is to be clipped. After
209      * return 'bounds' has been intersected with a conservative bounds of the clip.
210      */
211     virtual Effect apply(GrAppliedHardClip* out, SkIRect* bounds) const = 0;
212 
213 private:
apply(GrRecordingContext *,skgpu::v1::SurfaceDrawContext *,GrDrawOp *,GrAAType aa,GrAppliedClip * out,SkRect * bounds)214     Effect apply(GrRecordingContext*,
215                  skgpu::v1::SurfaceDrawContext*,
216                  GrDrawOp*,
217                  GrAAType aa,
218                  GrAppliedClip* out,
219                  SkRect* bounds) const final {
220         SkIRect pixelBounds = GetPixelIBounds(*bounds, GrAA(aa != GrAAType::kNone));
221         Effect effect = this->apply(&out->hardClip(), &pixelBounds);
222         bounds->intersect(SkRect::Make(pixelBounds));
223         return effect;
224     }
225 };
226 
227 #endif
228