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/GrRenderTargetContext.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 */
20class GrClip {
21public:
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
38 PreClipResult(Effect effect) : fEffect(effect), fIsRRect(false) {}
39 PreClipResult(SkRect rect, GrAA aa) : PreClipResult(SkRRect::MakeRect(rect), aa) {}
40 PreClipResult(SkRRect rrect, GrAA aa)
41 : fEffect(Effect::kClipped)
42 , fRRect(rrect)
43 , fAA(aa)
44 , fIsRRect(true) {}
45 };
46
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*, GrRenderTargetContext*, 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 */
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>
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>
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 */
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 */
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 */
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 */
236class GrHardClip : public GrClip {
237public:
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
245private:
246 Effect apply(GrRecordingContext*, GrRenderTargetContext* 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