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