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