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 | */ |
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 | |
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 | */ |
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: |
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 | |