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