| 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 | |