blob: 2940f6a682a6db215667f0361dc18d34c9b1be02 [file] [log] [blame]
bsalomon@google.com170bd792012-12-05 22:26:11 +00001/*
2 * Copyright 2012 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#include "GrReducedClip.h"
9
csmartdaltoncbecb082016-07-22 08:59:08 -070010#include "GrClip.h"
11
bsalomon@google.com170bd792012-12-05 22:26:11 +000012typedef SkClipStack::Element Element;
bsalomon@google.com170bd792012-12-05 22:26:11 +000013
csmartdaltoncbecb082016-07-22 08:59:08 -070014static GrReducedClip::InitialState reduced_stack_walker(const SkClipStack& stack,
15 const SkRect& queryBounds,
16 const SkIRect& clipIBounds,
17 GrReducedClip::ElementList* result,
18 int32_t* resultGenID,
19 bool* requiresAA) {
bsalomon@google.com170bd792012-12-05 22:26:11 +000020
tfarinabf54e492014-10-23 17:47:18 -070021 // walk backwards until we get to:
22 // a) the beginning
23 // b) an operation that is known to make the bounds all inside/outside
24 // c) a replace operation
25
26 static const GrReducedClip::InitialState kUnknown_InitialState =
27 static_cast<GrReducedClip::InitialState>(-1);
csmartdaltoncbecb082016-07-22 08:59:08 -070028 GrReducedClip::InitialState initialState = kUnknown_InitialState;
tfarinabf54e492014-10-23 17:47:18 -070029
30 // During our backwards walk, track whether we've seen ops that either grow or shrink the clip.
31 // TODO: track these per saved clip so that we can consider them on the forward pass.
32 bool embiggens = false;
33 bool emsmallens = false;
34
csmartdaltoncbecb082016-07-22 08:59:08 -070035 // We use a slightly relaxed set of query bounds for element containment tests. This is to
36 // account for floating point rounding error that may have occurred during coord transforms.
37 SkRect relaxedQueryBounds = queryBounds.makeInset(GrClip::kBoundsTolerance,
38 GrClip::kBoundsTolerance);
39
tfarinabf54e492014-10-23 17:47:18 -070040 SkClipStack::Iter iter(stack, SkClipStack::Iter::kTop_IterStart);
41 int numAAElements = 0;
csmartdaltoncbecb082016-07-22 08:59:08 -070042 while (kUnknown_InitialState == initialState) {
tfarinabf54e492014-10-23 17:47:18 -070043 const Element* element = iter.prev();
halcanary96fcdcc2015-08-27 07:41:13 -070044 if (nullptr == element) {
csmartdaltoncbecb082016-07-22 08:59:08 -070045 initialState = GrReducedClip::kAllIn_InitialState;
tfarinabf54e492014-10-23 17:47:18 -070046 break;
47 }
48 if (SkClipStack::kEmptyGenID == element->getGenID()) {
csmartdaltoncbecb082016-07-22 08:59:08 -070049 initialState = GrReducedClip::kAllOut_InitialState;
tfarinabf54e492014-10-23 17:47:18 -070050 break;
51 }
52 if (SkClipStack::kWideOpenGenID == element->getGenID()) {
csmartdaltoncbecb082016-07-22 08:59:08 -070053 initialState = GrReducedClip::kAllIn_InitialState;
tfarinabf54e492014-10-23 17:47:18 -070054 break;
55 }
56
57 bool skippable = false;
58 bool isFlip = false; // does this op just flip the in/out state of every point in the bounds
59
60 switch (element->getOp()) {
61 case SkRegion::kDifference_Op:
62 // check if the shape subtracted either contains the entire bounds (and makes
63 // the clip empty) or is outside the bounds and therefore can be skipped.
64 if (element->isInverseFilled()) {
csmartdaltoncbecb082016-07-22 08:59:08 -070065 if (element->contains(relaxedQueryBounds)) {
tfarinabf54e492014-10-23 17:47:18 -070066 skippable = true;
csmartdaltoncbecb082016-07-22 08:59:08 -070067 } else if (GrClip::IsOutsideClip(element->getBounds(), queryBounds)) {
68 initialState = GrReducedClip::kAllOut_InitialState;
tfarinabf54e492014-10-23 17:47:18 -070069 skippable = true;
70 }
71 } else {
csmartdaltoncbecb082016-07-22 08:59:08 -070072 if (element->contains(relaxedQueryBounds)) {
73 initialState = GrReducedClip::kAllOut_InitialState;
tfarinabf54e492014-10-23 17:47:18 -070074 skippable = true;
csmartdaltoncbecb082016-07-22 08:59:08 -070075 } else if (GrClip::IsOutsideClip(element->getBounds(), queryBounds)) {
tfarinabf54e492014-10-23 17:47:18 -070076 skippable = true;
77 }
78 }
79 if (!skippable) {
80 emsmallens = true;
81 }
82 break;
83 case SkRegion::kIntersect_Op:
84 // check if the shape intersected contains the entire bounds and therefore can
85 // be skipped or it is outside the entire bounds and therefore makes the clip
86 // empty.
87 if (element->isInverseFilled()) {
csmartdaltoncbecb082016-07-22 08:59:08 -070088 if (element->contains(relaxedQueryBounds)) {
89 initialState = GrReducedClip::kAllOut_InitialState;
tfarinabf54e492014-10-23 17:47:18 -070090 skippable = true;
csmartdaltoncbecb082016-07-22 08:59:08 -070091 } else if (GrClip::IsOutsideClip(element->getBounds(), queryBounds)) {
tfarinabf54e492014-10-23 17:47:18 -070092 skippable = true;
93 }
94 } else {
csmartdaltoncbecb082016-07-22 08:59:08 -070095 if (element->contains(relaxedQueryBounds)) {
tfarinabf54e492014-10-23 17:47:18 -070096 skippable = true;
csmartdaltoncbecb082016-07-22 08:59:08 -070097 } else if (GrClip::IsOutsideClip(element->getBounds(), queryBounds)) {
98 initialState = GrReducedClip::kAllOut_InitialState;
tfarinabf54e492014-10-23 17:47:18 -070099 skippable = true;
100 }
101 }
102 if (!skippable) {
103 emsmallens = true;
104 }
105 break;
106 case SkRegion::kUnion_Op:
107 // If the union-ed shape contains the entire bounds then after this element
108 // the bounds is entirely inside the clip. If the union-ed shape is outside the
109 // bounds then this op can be skipped.
110 if (element->isInverseFilled()) {
csmartdaltoncbecb082016-07-22 08:59:08 -0700111 if (element->contains(relaxedQueryBounds)) {
tfarinabf54e492014-10-23 17:47:18 -0700112 skippable = true;
csmartdaltoncbecb082016-07-22 08:59:08 -0700113 } else if (GrClip::IsOutsideClip(element->getBounds(), queryBounds)) {
114 initialState = GrReducedClip::kAllIn_InitialState;
tfarinabf54e492014-10-23 17:47:18 -0700115 skippable = true;
116 }
117 } else {
csmartdaltoncbecb082016-07-22 08:59:08 -0700118 if (element->contains(relaxedQueryBounds)) {
119 initialState = GrReducedClip::kAllIn_InitialState;
tfarinabf54e492014-10-23 17:47:18 -0700120 skippable = true;
csmartdaltoncbecb082016-07-22 08:59:08 -0700121 } else if (GrClip::IsOutsideClip(element->getBounds(), queryBounds)) {
tfarinabf54e492014-10-23 17:47:18 -0700122 skippable = true;
123 }
124 }
125 if (!skippable) {
126 embiggens = true;
127 }
128 break;
129 case SkRegion::kXOR_Op:
130 // If the bounds is entirely inside the shape being xor-ed then the effect is
131 // to flip the inside/outside state of every point in the bounds. We may be
132 // able to take advantage of this in the forward pass. If the xor-ed shape
133 // doesn't intersect the bounds then it can be skipped.
134 if (element->isInverseFilled()) {
csmartdaltoncbecb082016-07-22 08:59:08 -0700135 if (element->contains(relaxedQueryBounds)) {
tfarinabf54e492014-10-23 17:47:18 -0700136 skippable = true;
csmartdaltoncbecb082016-07-22 08:59:08 -0700137 } else if (GrClip::IsOutsideClip(element->getBounds(), queryBounds)) {
tfarinabf54e492014-10-23 17:47:18 -0700138 isFlip = true;
139 }
140 } else {
csmartdaltoncbecb082016-07-22 08:59:08 -0700141 if (element->contains(relaxedQueryBounds)) {
tfarinabf54e492014-10-23 17:47:18 -0700142 isFlip = true;
csmartdaltoncbecb082016-07-22 08:59:08 -0700143 } else if (GrClip::IsOutsideClip(element->getBounds(), queryBounds)) {
tfarinabf54e492014-10-23 17:47:18 -0700144 skippable = true;
145 }
146 }
147 if (!skippable) {
148 emsmallens = embiggens = true;
149 }
150 break;
151 case SkRegion::kReverseDifference_Op:
152 // When the bounds is entirely within the rev-diff shape then this behaves like xor
153 // and reverses every point inside the bounds. If the shape is completely outside
154 // the bounds then we know after this element is applied that the bounds will be
155 // all outside the current clip.B
156 if (element->isInverseFilled()) {
csmartdaltoncbecb082016-07-22 08:59:08 -0700157 if (element->contains(relaxedQueryBounds)) {
158 initialState = GrReducedClip::kAllOut_InitialState;
tfarinabf54e492014-10-23 17:47:18 -0700159 skippable = true;
csmartdaltoncbecb082016-07-22 08:59:08 -0700160 } else if (GrClip::IsOutsideClip(element->getBounds(), queryBounds)) {
tfarinabf54e492014-10-23 17:47:18 -0700161 isFlip = true;
162 }
163 } else {
csmartdaltoncbecb082016-07-22 08:59:08 -0700164 if (element->contains(relaxedQueryBounds)) {
tfarinabf54e492014-10-23 17:47:18 -0700165 isFlip = true;
csmartdaltoncbecb082016-07-22 08:59:08 -0700166 } else if (GrClip::IsOutsideClip(element->getBounds(), queryBounds)) {
167 initialState = GrReducedClip::kAllOut_InitialState;
tfarinabf54e492014-10-23 17:47:18 -0700168 skippable = true;
169 }
170 }
171 if (!skippable) {
172 emsmallens = embiggens = true;
173 }
174 break;
csmartdaltoncbecb082016-07-22 08:59:08 -0700175
tfarinabf54e492014-10-23 17:47:18 -0700176 case SkRegion::kReplace_Op:
177 // Replace will always terminate our walk. We will either begin the forward walk
178 // at the replace op or detect here than the shape is either completely inside
179 // or completely outside the bounds. In this latter case it can be skipped by
180 // setting the correct value for initialState.
181 if (element->isInverseFilled()) {
csmartdaltoncbecb082016-07-22 08:59:08 -0700182 if (element->contains(relaxedQueryBounds)) {
183 initialState = GrReducedClip::kAllOut_InitialState;
tfarinabf54e492014-10-23 17:47:18 -0700184 skippable = true;
csmartdaltoncbecb082016-07-22 08:59:08 -0700185 } else if (GrClip::IsOutsideClip(element->getBounds(), queryBounds)) {
186 initialState = GrReducedClip::kAllIn_InitialState;
tfarinabf54e492014-10-23 17:47:18 -0700187 skippable = true;
188 }
189 } else {
csmartdaltoncbecb082016-07-22 08:59:08 -0700190 if (element->contains(relaxedQueryBounds)) {
191 initialState = GrReducedClip::kAllIn_InitialState;
tfarinabf54e492014-10-23 17:47:18 -0700192 skippable = true;
csmartdaltoncbecb082016-07-22 08:59:08 -0700193 } else if (GrClip::IsOutsideClip(element->getBounds(), queryBounds)) {
194 initialState = GrReducedClip::kAllOut_InitialState;
tfarinabf54e492014-10-23 17:47:18 -0700195 skippable = true;
196 }
197 }
198 if (!skippable) {
csmartdaltoncbecb082016-07-22 08:59:08 -0700199 initialState = GrReducedClip::kAllOut_InitialState;
tfarinabf54e492014-10-23 17:47:18 -0700200 embiggens = emsmallens = true;
201 }
202 break;
203 default:
204 SkDEBUGFAIL("Unexpected op.");
205 break;
206 }
207 if (!skippable) {
208 if (0 == result->count()) {
209 // This will be the last element. Record the stricter genID.
210 *resultGenID = element->getGenID();
211 }
212
213 // if it is a flip, change it to a bounds-filling rect
214 if (isFlip) {
215 SkASSERT(SkRegion::kXOR_Op == element->getOp() ||
216 SkRegion::kReverseDifference_Op == element->getOp());
csmartdaltoncbecb082016-07-22 08:59:08 -0700217 result->addToHead(SkRect::Make(clipIBounds), SkRegion::kReverseDifference_Op,
218 false);
tfarinabf54e492014-10-23 17:47:18 -0700219 } else {
220 Element* newElement = result->addToHead(*element);
221 if (newElement->isAA()) {
222 ++numAAElements;
223 }
224 // Intersecting an inverse shape is the same as differencing the non-inverse shape.
225 // Replacing with an inverse shape is the same as setting initialState=kAllIn and
226 // differencing the non-inverse shape.
227 bool isReplace = SkRegion::kReplace_Op == newElement->getOp();
228 if (newElement->isInverseFilled() &&
229 (SkRegion::kIntersect_Op == newElement->getOp() || isReplace)) {
230 newElement->invertShapeFillType();
231 newElement->setOp(SkRegion::kDifference_Op);
232 if (isReplace) {
csmartdaltoncbecb082016-07-22 08:59:08 -0700233 SkASSERT(GrReducedClip::kAllOut_InitialState == initialState);
234 initialState = GrReducedClip::kAllIn_InitialState;
tfarinabf54e492014-10-23 17:47:18 -0700235 }
236 }
237 }
238 }
239 }
240
csmartdaltoncbecb082016-07-22 08:59:08 -0700241 if ((GrReducedClip::kAllOut_InitialState == initialState && !embiggens) ||
242 (GrReducedClip::kAllIn_InitialState == initialState && !emsmallens)) {
tfarinabf54e492014-10-23 17:47:18 -0700243 result->reset();
csmartdaltoncbecb082016-07-22 08:59:08 -0700244 numAAElements = 0;
tfarinabf54e492014-10-23 17:47:18 -0700245 } else {
246 Element* element = result->headIter().get();
247 while (element) {
248 bool skippable = false;
249 switch (element->getOp()) {
250 case SkRegion::kDifference_Op:
251 // subtracting from the empty set yields the empty set.
csmartdaltoncbecb082016-07-22 08:59:08 -0700252 skippable = GrReducedClip::kAllOut_InitialState == initialState;
tfarinabf54e492014-10-23 17:47:18 -0700253 break;
254 case SkRegion::kIntersect_Op:
255 // intersecting with the empty set yields the empty set
csmartdaltoncbecb082016-07-22 08:59:08 -0700256 if (GrReducedClip::kAllOut_InitialState == initialState) {
tfarinabf54e492014-10-23 17:47:18 -0700257 skippable = true;
258 } else {
259 // We can clear to zero and then simply draw the clip element.
csmartdaltoncbecb082016-07-22 08:59:08 -0700260 initialState = GrReducedClip::kAllOut_InitialState;
tfarinabf54e492014-10-23 17:47:18 -0700261 element->setOp(SkRegion::kReplace_Op);
262 }
263 break;
264 case SkRegion::kUnion_Op:
csmartdaltoncbecb082016-07-22 08:59:08 -0700265 if (GrReducedClip::kAllIn_InitialState == initialState) {
tfarinabf54e492014-10-23 17:47:18 -0700266 // unioning the infinite plane with anything is a no-op.
267 skippable = true;
268 } else {
269 // unioning the empty set with a shape is the shape.
270 element->setOp(SkRegion::kReplace_Op);
271 }
272 break;
273 case SkRegion::kXOR_Op:
csmartdaltoncbecb082016-07-22 08:59:08 -0700274 if (GrReducedClip::kAllOut_InitialState == initialState) {
tfarinabf54e492014-10-23 17:47:18 -0700275 // xor could be changed to diff in the kAllIn case, not sure it's a win.
276 element->setOp(SkRegion::kReplace_Op);
277 }
278 break;
279 case SkRegion::kReverseDifference_Op:
csmartdaltoncbecb082016-07-22 08:59:08 -0700280 if (GrReducedClip::kAllIn_InitialState == initialState) {
tfarinabf54e492014-10-23 17:47:18 -0700281 // subtracting the whole plane will yield the empty set.
282 skippable = true;
csmartdaltoncbecb082016-07-22 08:59:08 -0700283 initialState = GrReducedClip::kAllOut_InitialState;
tfarinabf54e492014-10-23 17:47:18 -0700284 } else {
285 // this picks up flips inserted in the backwards pass.
286 skippable = element->isInverseFilled() ?
csmartdaltoncbecb082016-07-22 08:59:08 -0700287 GrClip::IsOutsideClip(element->getBounds(), queryBounds) :
288 element->contains(relaxedQueryBounds);
tfarinabf54e492014-10-23 17:47:18 -0700289 if (skippable) {
csmartdaltoncbecb082016-07-22 08:59:08 -0700290 initialState = GrReducedClip::kAllIn_InitialState;
tfarinabf54e492014-10-23 17:47:18 -0700291 } else {
292 element->setOp(SkRegion::kReplace_Op);
293 }
294 }
295 break;
296 case SkRegion::kReplace_Op:
297 skippable = false; // we would have skipped it in the backwards walk if we
298 // could've.
299 break;
300 default:
301 SkDEBUGFAIL("Unexpected op.");
302 break;
303 }
304 if (!skippable) {
305 break;
306 } else {
307 if (element->isAA()) {
308 --numAAElements;
309 }
310 result->popHead();
311 element = result->headIter().get();
312 }
313 }
314 }
bsalomon00ee2a82016-07-08 03:28:34 -0700315 *requiresAA = numAAElements > 0;
tfarinabf54e492014-10-23 17:47:18 -0700316
317 if (0 == result->count()) {
csmartdaltoncbecb082016-07-22 08:59:08 -0700318 if (initialState == GrReducedClip::kAllIn_InitialState) {
tfarinabf54e492014-10-23 17:47:18 -0700319 *resultGenID = SkClipStack::kWideOpenGenID;
320 } else {
321 *resultGenID = SkClipStack::kEmptyGenID;
322 }
323 }
csmartdaltoncbecb082016-07-22 08:59:08 -0700324
325 SkASSERT(SkClipStack::kInvalidGenID != *resultGenID);
326 return initialState;
tfarinabf54e492014-10-23 17:47:18 -0700327}
bsalomon@google.com34cd70a2012-12-06 14:23:20 +0000328
bsalomon@google.com170bd792012-12-05 22:26:11 +0000329/*
bsalomon@google.comc6b3e482012-12-07 20:43:52 +0000330There are plenty of optimizations that could be added here. Maybe flips could be folded into
bsalomon@google.com170bd792012-12-05 22:26:11 +0000331earlier operations. Or would inserting flips and reversing earlier ops ever be a win? Perhaps
332for the case where the bounds are kInsideOut_BoundsType. We could restrict earlier operations
333based on later intersect operations, and perhaps remove intersect-rects. We could optionally
334take a rect in case the caller knows a bound on what is to be drawn through this clip.
335*/
csmartdaltoncbecb082016-07-22 08:59:08 -0700336GrReducedClip::InitialState GrReducedClip::ReduceClipStack(const SkClipStack& stack,
337 const SkRect& queryBounds,
338 ElementList* result,
339 int32_t* resultGenID,
340 SkIRect* clipIBounds,
341 bool* requiresAA) {
342 SkASSERT(!queryBounds.isEmpty());
bsalomon@google.com170bd792012-12-05 22:26:11 +0000343 result->reset();
344
commit-bot@chromium.orgd3e58422013-11-05 15:03:08 +0000345 // The clip established by the element list might be cached based on the last
346 // generation id. When we make early returns, we do not know what was the generation
347 // id that lead to the state. Make a conservative guess.
348 *resultGenID = stack.getTopmostGenID();
349
csmartdaltoncbecb082016-07-22 08:59:08 -0700350 // TODO: instead devise a way of telling the caller to disregard some or all of the clip bounds.
351 *clipIBounds = GrClip::GetPixelIBounds(queryBounds);
352
bsalomon@google.com170bd792012-12-05 22:26:11 +0000353 if (stack.isWideOpen()) {
csmartdaltoncbecb082016-07-22 08:59:08 -0700354 return kAllIn_InitialState;
bsalomon@google.com170bd792012-12-05 22:26:11 +0000355 }
356
357 SkClipStack::BoundsType stackBoundsType;
358 SkRect stackBounds;
359 bool iior;
360 stack.getBounds(&stackBounds, &stackBoundsType, &iior);
361
csmartdaltoncbecb082016-07-22 08:59:08 -0700362 if (stackBounds.isEmpty() || GrClip::IsOutsideClip(stackBounds, queryBounds)) {
363 bool insideOut = SkClipStack::kInsideOut_BoundsType == stackBoundsType;
364 return insideOut ? kAllIn_InitialState : kAllOut_InitialState;
bsalomon@google.com170bd792012-12-05 22:26:11 +0000365 }
366
csmartdaltoncbecb082016-07-22 08:59:08 -0700367 if (iior) {
368 // "Is intersection of rects" means the clip is a single rect indicated by the stack bounds.
369 // This should only be true if aa/non-aa status matches among all elements.
370 SkASSERT(SkClipStack::kNormal_BoundsType == stackBoundsType);
371 SkClipStack::Iter iter(stack, SkClipStack::Iter::kTop_IterStart);
372 if (!iter.prev()->isAA() || GrClip::IsPixelAligned(stackBounds)) {
373 // The clip is a non-aa rect. This is the one spot where we can actually implement the
374 // clip (using clipIBounds) rather than just telling the caller what it should be.
375 stackBounds.round(clipIBounds);
376 return kAllIn_InitialState;
377 }
378 if (GrClip::IsInsideClip(stackBounds, queryBounds)) {
379 return kAllIn_InitialState;
380 }
381
382 // Implement the clip with an AA rect element.
383 result->addToHead(stackBounds, SkRegion::kReplace_Op, true/*doAA*/);
384 *requiresAA = true;
385
386 SkAssertResult(clipIBounds->intersect(GrClip::GetPixelIBounds(stackBounds)));
387 return kAllOut_InitialState;
388 }
389
390 SkRect tighterQuery = queryBounds;
391 if (SkClipStack::kNormal_BoundsType == stackBoundsType) {
392 // Tighten the query by introducing a new clip at the stack's pixel boundaries. (This new
393 // clip will be enforced by the scissor through clipIBounds.)
394 SkAssertResult(tighterQuery.intersect(GrClip::GetPixelBounds(stackBounds)));
395 *clipIBounds = GrClip::GetPixelIBounds(tighterQuery);
396 }
skia.committer@gmail.comd21444a2012-12-07 02:01:25 +0000397
bsalomon@google.com34cd70a2012-12-06 14:23:20 +0000398 // Now that we have determined the bounds to use and filtered out the trivial cases, call the
399 // helper that actually walks the stack.
csmartdaltoncbecb082016-07-22 08:59:08 -0700400 return reduced_stack_walker(stack, tighterQuery, *clipIBounds, result, resultGenID, requiresAA);
bsalomon@google.com34cd70a2012-12-06 14:23:20 +0000401}