1 /*
2 * Copyright 2011 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 "include/core/SkCanvas.h"
9 #include "include/core/SkClipOp.h"
10 #include "include/core/SkImageInfo.h"
11 #include "include/core/SkMatrix.h"
12 #include "include/core/SkPath.h"
13 #include "include/core/SkPoint.h"
14 #include "include/core/SkRRect.h"
15 #include "include/core/SkRect.h"
16 #include "include/core/SkRefCnt.h"
17 #include "include/core/SkRegion.h"
18 #include "include/core/SkScalar.h"
19 #include "include/core/SkSize.h"
20 #include "include/core/SkString.h"
21 #include "include/core/SkSurface.h"
22 #include "include/core/SkTypes.h"
23 #include "include/gpu/GrConfig.h"
24 #include "include/gpu/GrContext.h"
25 #include "include/gpu/GrTexture.h"
26 #include "include/private/GrResourceKey.h"
27 #include "include/private/SkTemplates.h"
28 #include "include/utils/SkRandom.h"
29 #include "src/core/SkClipOpPriv.h"
30 #include "src/core/SkClipStack.h"
31 #include "src/core/SkTLList.h"
32 #include "src/gpu/GrClip.h"
33 #include "src/gpu/GrClipStackClip.h"
34 #include "src/gpu/GrContextPriv.h"
35 #include "src/gpu/GrReducedClip.h"
36 #include "src/gpu/GrResourceCache.h"
37 #include "src/gpu/GrTextureProxy.h"
38 #include "tests/Test.h"
39 #include "tools/gpu/GrContextFactory.h"
40
41 #include <cstring>
42 #include <initializer_list>
43 #include <new>
44
45 class GrCaps;
46
47 typedef GrReducedClip::ElementList ElementList;
48 typedef GrReducedClip::InitialState InitialState;
49
test_assign_and_comparison(skiatest::Reporter * reporter)50 static void test_assign_and_comparison(skiatest::Reporter* reporter) {
51 SkClipStack s;
52 bool doAA = false;
53
54 REPORTER_ASSERT(reporter, 0 == s.getSaveCount());
55
56 // Build up a clip stack with a path, an empty clip, and a rect.
57 s.save();
58 REPORTER_ASSERT(reporter, 1 == s.getSaveCount());
59
60 SkPath p;
61 p.moveTo(5, 6);
62 p.lineTo(7, 8);
63 p.lineTo(5, 9);
64 p.close();
65 s.clipPath(p, SkMatrix::I(), kIntersect_SkClipOp, doAA);
66
67 s.save();
68 REPORTER_ASSERT(reporter, 2 == s.getSaveCount());
69
70 SkRect r = SkRect::MakeLTRB(1, 2, 3, 4);
71 s.clipRect(r, SkMatrix::I(), kIntersect_SkClipOp, doAA);
72 r = SkRect::MakeLTRB(10, 11, 12, 13);
73 s.clipRect(r, SkMatrix::I(), kIntersect_SkClipOp, doAA);
74
75 s.save();
76 REPORTER_ASSERT(reporter, 3 == s.getSaveCount());
77
78 r = SkRect::MakeLTRB(14, 15, 16, 17);
79 s.clipRect(r, SkMatrix::I(), kUnion_SkClipOp, doAA);
80
81 // Test that assignment works.
82 SkClipStack copy = s;
83 REPORTER_ASSERT(reporter, s == copy);
84
85 // Test that different save levels triggers not equal.
86 s.restore();
87 REPORTER_ASSERT(reporter, 2 == s.getSaveCount());
88 REPORTER_ASSERT(reporter, s != copy);
89
90 // Test that an equal, but not copied version is equal.
91 s.save();
92 REPORTER_ASSERT(reporter, 3 == s.getSaveCount());
93 r = SkRect::MakeLTRB(14, 15, 16, 17);
94 s.clipRect(r, SkMatrix::I(), kUnion_SkClipOp, doAA);
95 REPORTER_ASSERT(reporter, s == copy);
96
97 // Test that a different op on one level triggers not equal.
98 s.restore();
99 REPORTER_ASSERT(reporter, 2 == s.getSaveCount());
100 s.save();
101 REPORTER_ASSERT(reporter, 3 == s.getSaveCount());
102 r = SkRect::MakeLTRB(14, 15, 16, 17);
103 s.clipRect(r, SkMatrix::I(), kIntersect_SkClipOp, doAA);
104 REPORTER_ASSERT(reporter, s != copy);
105
106 // Test that version constructed with rect-path rather than a rect is still considered equal.
107 s.restore();
108 s.save();
109 SkPath rp;
110 rp.addRect(r);
111 s.clipPath(rp, SkMatrix::I(), kUnion_SkClipOp, doAA);
112 REPORTER_ASSERT(reporter, s == copy);
113
114 // Test that different rects triggers not equal.
115 s.restore();
116 REPORTER_ASSERT(reporter, 2 == s.getSaveCount());
117 s.save();
118 REPORTER_ASSERT(reporter, 3 == s.getSaveCount());
119
120 r = SkRect::MakeLTRB(24, 25, 26, 27);
121 s.clipRect(r, SkMatrix::I(), kUnion_SkClipOp, doAA);
122 REPORTER_ASSERT(reporter, s != copy);
123
124 // Sanity check
125 s.restore();
126 REPORTER_ASSERT(reporter, 2 == s.getSaveCount());
127
128 copy.restore();
129 REPORTER_ASSERT(reporter, 2 == copy.getSaveCount());
130 REPORTER_ASSERT(reporter, s == copy);
131 s.restore();
132 REPORTER_ASSERT(reporter, 1 == s.getSaveCount());
133 copy.restore();
134 REPORTER_ASSERT(reporter, 1 == copy.getSaveCount());
135 REPORTER_ASSERT(reporter, s == copy);
136
137 // Test that different paths triggers not equal.
138 s.restore();
139 REPORTER_ASSERT(reporter, 0 == s.getSaveCount());
140 s.save();
141 REPORTER_ASSERT(reporter, 1 == s.getSaveCount());
142
143 p.addRect(r);
144 s.clipPath(p, SkMatrix::I(), kIntersect_SkClipOp, doAA);
145 REPORTER_ASSERT(reporter, s != copy);
146 }
147
assert_count(skiatest::Reporter * reporter,const SkClipStack & stack,int count)148 static void assert_count(skiatest::Reporter* reporter, const SkClipStack& stack,
149 int count) {
150 SkClipStack::B2TIter iter(stack);
151 int counter = 0;
152 while (iter.next()) {
153 counter += 1;
154 }
155 REPORTER_ASSERT(reporter, count == counter);
156 }
157
158 // Exercise the SkClipStack's bottom to top and bidirectional iterators
159 // (including the skipToTopmost functionality)
test_iterators(skiatest::Reporter * reporter)160 static void test_iterators(skiatest::Reporter* reporter) {
161 SkClipStack stack;
162
163 static const SkRect gRects[] = {
164 { 0, 0, 40, 40 },
165 { 60, 0, 100, 40 },
166 { 0, 60, 40, 100 },
167 { 60, 60, 100, 100 }
168 };
169
170 for (size_t i = 0; i < SK_ARRAY_COUNT(gRects); i++) {
171 // the union op will prevent these from being fused together
172 stack.clipRect(gRects[i], SkMatrix::I(), kUnion_SkClipOp, false);
173 }
174
175 assert_count(reporter, stack, 4);
176
177 // bottom to top iteration
178 {
179 const SkClipStack::Element* element = nullptr;
180
181 SkClipStack::B2TIter iter(stack);
182 int i;
183
184 for (i = 0, element = iter.next(); element; ++i, element = iter.next()) {
185 REPORTER_ASSERT(reporter, SkClipStack::Element::DeviceSpaceType::kRect ==
186 element->getDeviceSpaceType());
187 REPORTER_ASSERT(reporter, element->getDeviceSpaceRect() == gRects[i]);
188 }
189
190 SkASSERT(i == 4);
191 }
192
193 // top to bottom iteration
194 {
195 const SkClipStack::Element* element = nullptr;
196
197 SkClipStack::Iter iter(stack, SkClipStack::Iter::kTop_IterStart);
198 int i;
199
200 for (i = 3, element = iter.prev(); element; --i, element = iter.prev()) {
201 REPORTER_ASSERT(reporter, SkClipStack::Element::DeviceSpaceType::kRect ==
202 element->getDeviceSpaceType());
203 REPORTER_ASSERT(reporter, element->getDeviceSpaceRect() == gRects[i]);
204 }
205
206 SkASSERT(i == -1);
207 }
208
209 // skipToTopmost
210 {
211 const SkClipStack::Element* element = nullptr;
212
213 SkClipStack::Iter iter(stack, SkClipStack::Iter::kBottom_IterStart);
214
215 element = iter.skipToTopmost(kUnion_SkClipOp);
216 REPORTER_ASSERT(reporter, SkClipStack::Element::DeviceSpaceType::kRect ==
217 element->getDeviceSpaceType());
218 REPORTER_ASSERT(reporter, element->getDeviceSpaceRect() == gRects[3]);
219 }
220 }
221
222 // Exercise the SkClipStack's getConservativeBounds computation
test_bounds(skiatest::Reporter * reporter,SkClipStack::Element::DeviceSpaceType primType)223 static void test_bounds(skiatest::Reporter* reporter,
224 SkClipStack::Element::DeviceSpaceType primType) {
225 static const int gNumCases = 20;
226 static const SkRect gAnswerRectsBW[gNumCases] = {
227 // A op B
228 { 40, 40, 50, 50 },
229 { 10, 10, 50, 50 },
230 { 10, 10, 80, 80 },
231 { 10, 10, 80, 80 },
232 { 40, 40, 80, 80 },
233
234 // invA op B
235 { 40, 40, 80, 80 },
236 { 0, 0, 100, 100 },
237 { 0, 0, 100, 100 },
238 { 0, 0, 100, 100 },
239 { 40, 40, 50, 50 },
240
241 // A op invB
242 { 10, 10, 50, 50 },
243 { 40, 40, 50, 50 },
244 { 0, 0, 100, 100 },
245 { 0, 0, 100, 100 },
246 { 0, 0, 100, 100 },
247
248 // invA op invB
249 { 0, 0, 100, 100 },
250 { 40, 40, 80, 80 },
251 { 0, 0, 100, 100 },
252 { 10, 10, 80, 80 },
253 { 10, 10, 50, 50 },
254 };
255
256 static const SkClipOp gOps[] = {
257 kIntersect_SkClipOp,
258 kDifference_SkClipOp,
259 kUnion_SkClipOp,
260 kXOR_SkClipOp,
261 kReverseDifference_SkClipOp
262 };
263
264 SkRect rectA, rectB;
265
266 rectA.iset(10, 10, 50, 50);
267 rectB.iset(40, 40, 80, 80);
268
269 SkRRect rrectA, rrectB;
270 rrectA.setOval(rectA);
271 rrectB.setRectXY(rectB, SkIntToScalar(1), SkIntToScalar(2));
272
273 SkPath pathA, pathB;
274
275 pathA.addRoundRect(rectA, SkIntToScalar(5), SkIntToScalar(5));
276 pathB.addRoundRect(rectB, SkIntToScalar(5), SkIntToScalar(5));
277
278 SkClipStack stack;
279 SkRect devClipBound;
280 bool isIntersectionOfRects = false;
281
282 int testCase = 0;
283 int numBitTests = SkClipStack::Element::DeviceSpaceType::kPath == primType ? 4 : 1;
284 for (int invBits = 0; invBits < numBitTests; ++invBits) {
285 for (size_t op = 0; op < SK_ARRAY_COUNT(gOps); ++op) {
286
287 stack.save();
288 bool doInvA = SkToBool(invBits & 1);
289 bool doInvB = SkToBool(invBits & 2);
290
291 pathA.setFillType(doInvA ? SkPath::kInverseEvenOdd_FillType :
292 SkPath::kEvenOdd_FillType);
293 pathB.setFillType(doInvB ? SkPath::kInverseEvenOdd_FillType :
294 SkPath::kEvenOdd_FillType);
295
296 switch (primType) {
297 case SkClipStack::Element::DeviceSpaceType::kEmpty:
298 SkDEBUGFAIL("Don't call this with kEmpty.");
299 break;
300 case SkClipStack::Element::DeviceSpaceType::kRect:
301 stack.clipRect(rectA, SkMatrix::I(), kIntersect_SkClipOp, false);
302 stack.clipRect(rectB, SkMatrix::I(), gOps[op], false);
303 break;
304 case SkClipStack::Element::DeviceSpaceType::kRRect:
305 stack.clipRRect(rrectA, SkMatrix::I(), kIntersect_SkClipOp, false);
306 stack.clipRRect(rrectB, SkMatrix::I(), gOps[op], false);
307 break;
308 case SkClipStack::Element::DeviceSpaceType::kPath:
309 stack.clipPath(pathA, SkMatrix::I(), kIntersect_SkClipOp, false);
310 stack.clipPath(pathB, SkMatrix::I(), gOps[op], false);
311 break;
312 }
313
314 REPORTER_ASSERT(reporter, !stack.isWideOpen());
315 REPORTER_ASSERT(reporter, SkClipStack::kWideOpenGenID != stack.getTopmostGenID());
316
317 stack.getConservativeBounds(0, 0, 100, 100, &devClipBound,
318 &isIntersectionOfRects);
319
320 if (SkClipStack::Element::DeviceSpaceType::kRect == primType) {
321 REPORTER_ASSERT(reporter, isIntersectionOfRects ==
322 (gOps[op] == kIntersect_SkClipOp));
323 } else {
324 REPORTER_ASSERT(reporter, !isIntersectionOfRects);
325 }
326
327 SkASSERT(testCase < gNumCases);
328 REPORTER_ASSERT(reporter, devClipBound == gAnswerRectsBW[testCase]);
329 ++testCase;
330
331 stack.restore();
332 }
333 }
334 }
335
336 // Test out 'isWideOpen' entry point
test_isWideOpen(skiatest::Reporter * reporter)337 static void test_isWideOpen(skiatest::Reporter* reporter) {
338 {
339 // Empty stack is wide open. Wide open stack means that gen id is wide open.
340 SkClipStack stack;
341 REPORTER_ASSERT(reporter, stack.isWideOpen());
342 REPORTER_ASSERT(reporter, SkClipStack::kWideOpenGenID == stack.getTopmostGenID());
343 }
344
345 SkRect rectA, rectB;
346
347 rectA.iset(10, 10, 40, 40);
348 rectB.iset(50, 50, 80, 80);
349
350 // Stack should initially be wide open
351 {
352 SkClipStack stack;
353
354 REPORTER_ASSERT(reporter, stack.isWideOpen());
355 REPORTER_ASSERT(reporter, SkClipStack::kWideOpenGenID == stack.getTopmostGenID());
356 }
357
358 // Test out case where the user specifies a union that includes everything
359 {
360 SkClipStack stack;
361
362 SkPath clipA, clipB;
363
364 clipA.addRoundRect(rectA, SkIntToScalar(5), SkIntToScalar(5));
365 clipA.setFillType(SkPath::kInverseEvenOdd_FillType);
366
367 clipB.addRoundRect(rectB, SkIntToScalar(5), SkIntToScalar(5));
368 clipB.setFillType(SkPath::kInverseEvenOdd_FillType);
369
370 stack.clipPath(clipA, SkMatrix::I(), kReplace_SkClipOp, false);
371 stack.clipPath(clipB, SkMatrix::I(), kUnion_SkClipOp, false);
372
373 REPORTER_ASSERT(reporter, stack.isWideOpen());
374 REPORTER_ASSERT(reporter, SkClipStack::kWideOpenGenID == stack.getTopmostGenID());
375 }
376
377 // Test out union w/ a wide open clip
378 {
379 SkClipStack stack;
380
381 stack.clipRect(rectA, SkMatrix::I(), kUnion_SkClipOp, false);
382
383 REPORTER_ASSERT(reporter, stack.isWideOpen());
384 REPORTER_ASSERT(reporter, SkClipStack::kWideOpenGenID == stack.getTopmostGenID());
385 }
386
387 // Test out empty difference from a wide open clip
388 {
389 SkClipStack stack;
390
391 SkRect emptyRect;
392 emptyRect.setEmpty();
393
394 stack.clipRect(emptyRect, SkMatrix::I(), kDifference_SkClipOp, false);
395
396 REPORTER_ASSERT(reporter, stack.isWideOpen());
397 REPORTER_ASSERT(reporter, SkClipStack::kWideOpenGenID == stack.getTopmostGenID());
398 }
399
400 // Test out return to wide open
401 {
402 SkClipStack stack;
403
404 stack.save();
405
406 stack.clipRect(rectA, SkMatrix::I(), kReplace_SkClipOp, false);
407
408 REPORTER_ASSERT(reporter, !stack.isWideOpen());
409 REPORTER_ASSERT(reporter, SkClipStack::kWideOpenGenID != stack.getTopmostGenID());
410
411 stack.restore();
412
413 REPORTER_ASSERT(reporter, stack.isWideOpen());
414 REPORTER_ASSERT(reporter, SkClipStack::kWideOpenGenID == stack.getTopmostGenID());
415 }
416 }
417
count(const SkClipStack & stack)418 static int count(const SkClipStack& stack) {
419
420 SkClipStack::Iter iter(stack, SkClipStack::Iter::kTop_IterStart);
421
422 const SkClipStack::Element* element = nullptr;
423 int count = 0;
424
425 for (element = iter.prev(); element; element = iter.prev(), ++count) {
426 }
427
428 return count;
429 }
430
test_rect_inverse_fill(skiatest::Reporter * reporter)431 static void test_rect_inverse_fill(skiatest::Reporter* reporter) {
432 // non-intersecting rectangles
433 SkRect rect = SkRect::MakeLTRB(0, 0, 10, 10);
434
435 SkPath path;
436 path.addRect(rect);
437 path.toggleInverseFillType();
438 SkClipStack stack;
439 stack.clipPath(path, SkMatrix::I(), kIntersect_SkClipOp, false);
440
441 SkRect bounds;
442 SkClipStack::BoundsType boundsType;
443 stack.getBounds(&bounds, &boundsType);
444 REPORTER_ASSERT(reporter, SkClipStack::kInsideOut_BoundsType == boundsType);
445 REPORTER_ASSERT(reporter, bounds == rect);
446 }
447
test_rect_replace(skiatest::Reporter * reporter)448 static void test_rect_replace(skiatest::Reporter* reporter) {
449 SkRect rect = SkRect::MakeWH(100, 100);
450 SkRect rect2 = SkRect::MakeXYWH(50, 50, 100, 100);
451
452 SkRect bound;
453 SkClipStack::BoundsType type;
454 bool isIntersectionOfRects;
455
456 // Adding a new rect with the replace operator should not increase
457 // the stack depth. BW replacing BW.
458 {
459 SkClipStack stack;
460 REPORTER_ASSERT(reporter, 0 == count(stack));
461 stack.clipRect(rect, SkMatrix::I(), kReplace_SkClipOp, false);
462 REPORTER_ASSERT(reporter, 1 == count(stack));
463 stack.clipRect(rect, SkMatrix::I(), kReplace_SkClipOp, false);
464 REPORTER_ASSERT(reporter, 1 == count(stack));
465 }
466
467 // Adding a new rect with the replace operator should not increase
468 // the stack depth. AA replacing AA.
469 {
470 SkClipStack stack;
471 REPORTER_ASSERT(reporter, 0 == count(stack));
472 stack.clipRect(rect, SkMatrix::I(), kReplace_SkClipOp, true);
473 REPORTER_ASSERT(reporter, 1 == count(stack));
474 stack.clipRect(rect, SkMatrix::I(), kReplace_SkClipOp, true);
475 REPORTER_ASSERT(reporter, 1 == count(stack));
476 }
477
478 // Adding a new rect with the replace operator should not increase
479 // the stack depth. BW replacing AA replacing BW.
480 {
481 SkClipStack stack;
482 REPORTER_ASSERT(reporter, 0 == count(stack));
483 stack.clipRect(rect, SkMatrix::I(), kReplace_SkClipOp, false);
484 REPORTER_ASSERT(reporter, 1 == count(stack));
485 stack.clipRect(rect, SkMatrix::I(), kReplace_SkClipOp, true);
486 REPORTER_ASSERT(reporter, 1 == count(stack));
487 stack.clipRect(rect, SkMatrix::I(), kReplace_SkClipOp, false);
488 REPORTER_ASSERT(reporter, 1 == count(stack));
489 }
490
491 // Make sure replace clip rects don't collapse too much.
492 {
493 SkClipStack stack;
494 stack.clipRect(rect, SkMatrix::I(), kReplace_SkClipOp, false);
495 stack.clipRect(rect2, SkMatrix::I(), kIntersect_SkClipOp, false);
496 REPORTER_ASSERT(reporter, 1 == count(stack));
497
498 stack.save();
499 stack.clipRect(rect, SkMatrix::I(), kReplace_SkClipOp, false);
500 REPORTER_ASSERT(reporter, 2 == count(stack));
501 stack.getBounds(&bound, &type, &isIntersectionOfRects);
502 REPORTER_ASSERT(reporter, bound == rect);
503 stack.restore();
504 REPORTER_ASSERT(reporter, 1 == count(stack));
505
506 stack.save();
507 stack.clipRect(rect, SkMatrix::I(), kReplace_SkClipOp, false);
508 stack.clipRect(rect, SkMatrix::I(), kReplace_SkClipOp, false);
509 REPORTER_ASSERT(reporter, 2 == count(stack));
510 stack.restore();
511 REPORTER_ASSERT(reporter, 1 == count(stack));
512
513 stack.save();
514 stack.clipRect(rect, SkMatrix::I(), kReplace_SkClipOp, false);
515 stack.clipRect(rect2, SkMatrix::I(), kIntersect_SkClipOp, false);
516 stack.clipRect(rect, SkMatrix::I(), kReplace_SkClipOp, false);
517 REPORTER_ASSERT(reporter, 2 == count(stack));
518 stack.restore();
519 REPORTER_ASSERT(reporter, 1 == count(stack));
520 }
521 }
522
523 // Simplified path-based version of test_rect_replace.
test_path_replace(skiatest::Reporter * reporter)524 static void test_path_replace(skiatest::Reporter* reporter) {
525 SkRect rect = SkRect::MakeWH(100, 100);
526 SkPath path;
527 path.addCircle(50, 50, 50);
528
529 // Replace operation doesn't grow the stack.
530 {
531 SkClipStack stack;
532 REPORTER_ASSERT(reporter, 0 == count(stack));
533 stack.clipPath(path, SkMatrix::I(), kReplace_SkClipOp, false);
534 REPORTER_ASSERT(reporter, 1 == count(stack));
535 stack.clipPath(path, SkMatrix::I(), kReplace_SkClipOp, false);
536 REPORTER_ASSERT(reporter, 1 == count(stack));
537 }
538
539 // Replacing rect with path.
540 {
541 SkClipStack stack;
542 stack.clipRect(rect, SkMatrix::I(), kReplace_SkClipOp, true);
543 REPORTER_ASSERT(reporter, 1 == count(stack));
544 stack.clipPath(path, SkMatrix::I(), kReplace_SkClipOp, true);
545 REPORTER_ASSERT(reporter, 1 == count(stack));
546 }
547 }
548
549 // Test out SkClipStack's merging of rect clips. In particular exercise
550 // merging of aa vs. bw rects.
test_rect_merging(skiatest::Reporter * reporter)551 static void test_rect_merging(skiatest::Reporter* reporter) {
552
553 SkRect overlapLeft = SkRect::MakeLTRB(10, 10, 50, 50);
554 SkRect overlapRight = SkRect::MakeLTRB(40, 40, 80, 80);
555
556 SkRect nestedParent = SkRect::MakeLTRB(10, 10, 90, 90);
557 SkRect nestedChild = SkRect::MakeLTRB(40, 40, 60, 60);
558
559 SkRect bound;
560 SkClipStack::BoundsType type;
561 bool isIntersectionOfRects;
562
563 // all bw overlapping - should merge
564 {
565 SkClipStack stack;
566
567 stack.clipRect(overlapLeft, SkMatrix::I(), kReplace_SkClipOp, false);
568
569 stack.clipRect(overlapRight, SkMatrix::I(), kIntersect_SkClipOp, false);
570
571 REPORTER_ASSERT(reporter, 1 == count(stack));
572
573 stack.getBounds(&bound, &type, &isIntersectionOfRects);
574
575 REPORTER_ASSERT(reporter, isIntersectionOfRects);
576 }
577
578 // all aa overlapping - should merge
579 {
580 SkClipStack stack;
581
582 stack.clipRect(overlapLeft, SkMatrix::I(), kReplace_SkClipOp, true);
583
584 stack.clipRect(overlapRight, SkMatrix::I(), kIntersect_SkClipOp, true);
585
586 REPORTER_ASSERT(reporter, 1 == count(stack));
587
588 stack.getBounds(&bound, &type, &isIntersectionOfRects);
589
590 REPORTER_ASSERT(reporter, isIntersectionOfRects);
591 }
592
593 // mixed overlapping - should _not_ merge
594 {
595 SkClipStack stack;
596
597 stack.clipRect(overlapLeft, SkMatrix::I(), kReplace_SkClipOp, true);
598
599 stack.clipRect(overlapRight, SkMatrix::I(), kIntersect_SkClipOp, false);
600
601 REPORTER_ASSERT(reporter, 2 == count(stack));
602
603 stack.getBounds(&bound, &type, &isIntersectionOfRects);
604
605 REPORTER_ASSERT(reporter, !isIntersectionOfRects);
606 }
607
608 // mixed nested (bw inside aa) - should merge
609 {
610 SkClipStack stack;
611
612 stack.clipRect(nestedParent, SkMatrix::I(), kReplace_SkClipOp, true);
613
614 stack.clipRect(nestedChild, SkMatrix::I(), kIntersect_SkClipOp, false);
615
616 REPORTER_ASSERT(reporter, 1 == count(stack));
617
618 stack.getBounds(&bound, &type, &isIntersectionOfRects);
619
620 REPORTER_ASSERT(reporter, isIntersectionOfRects);
621 }
622
623 // mixed nested (aa inside bw) - should merge
624 {
625 SkClipStack stack;
626
627 stack.clipRect(nestedParent, SkMatrix::I(), kReplace_SkClipOp, false);
628
629 stack.clipRect(nestedChild, SkMatrix::I(), kIntersect_SkClipOp, true);
630
631 REPORTER_ASSERT(reporter, 1 == count(stack));
632
633 stack.getBounds(&bound, &type, &isIntersectionOfRects);
634
635 REPORTER_ASSERT(reporter, isIntersectionOfRects);
636 }
637
638 // reverse nested (aa inside bw) - should _not_ merge
639 {
640 SkClipStack stack;
641
642 stack.clipRect(nestedChild, SkMatrix::I(), kReplace_SkClipOp, false);
643
644 stack.clipRect(nestedParent, SkMatrix::I(), kIntersect_SkClipOp, true);
645
646 REPORTER_ASSERT(reporter, 2 == count(stack));
647
648 stack.getBounds(&bound, &type, &isIntersectionOfRects);
649
650 REPORTER_ASSERT(reporter, !isIntersectionOfRects);
651 }
652 }
653
test_quickContains(skiatest::Reporter * reporter)654 static void test_quickContains(skiatest::Reporter* reporter) {
655 SkRect testRect = SkRect::MakeLTRB(10, 10, 40, 40);
656 SkRect insideRect = SkRect::MakeLTRB(20, 20, 30, 30);
657 SkRect intersectingRect = SkRect::MakeLTRB(25, 25, 50, 50);
658 SkRect outsideRect = SkRect::MakeLTRB(0, 0, 50, 50);
659 SkRect nonIntersectingRect = SkRect::MakeLTRB(100, 100, 110, 110);
660
661 SkPath insideCircle;
662 insideCircle.addCircle(25, 25, 5);
663 SkPath intersectingCircle;
664 intersectingCircle.addCircle(25, 40, 10);
665 SkPath outsideCircle;
666 outsideCircle.addCircle(25, 25, 50);
667 SkPath nonIntersectingCircle;
668 nonIntersectingCircle.addCircle(100, 100, 5);
669
670 {
671 SkClipStack stack;
672 stack.clipRect(outsideRect, SkMatrix::I(), kDifference_SkClipOp, false);
673 // return false because quickContains currently does not care for kDifference_SkClipOp
674 REPORTER_ASSERT(reporter, false == stack.quickContains(testRect));
675 }
676
677 // Replace Op tests
678 {
679 SkClipStack stack;
680 stack.clipRect(outsideRect, SkMatrix::I(), kReplace_SkClipOp, false);
681 REPORTER_ASSERT(reporter, true == stack.quickContains(testRect));
682 }
683
684 {
685 SkClipStack stack;
686 stack.clipRect(insideRect, SkMatrix::I(), kIntersect_SkClipOp, false);
687 stack.save(); // To prevent in-place substitution by replace OP
688 stack.clipRect(outsideRect, SkMatrix::I(), kReplace_SkClipOp, false);
689 REPORTER_ASSERT(reporter, true == stack.quickContains(testRect));
690 stack.restore();
691 }
692
693 {
694 SkClipStack stack;
695 stack.clipRect(outsideRect, SkMatrix::I(), kIntersect_SkClipOp, false);
696 stack.save(); // To prevent in-place substitution by replace OP
697 stack.clipRect(insideRect, SkMatrix::I(), kReplace_SkClipOp, false);
698 REPORTER_ASSERT(reporter, false == stack.quickContains(testRect));
699 stack.restore();
700 }
701
702 // Verify proper traversal of multi-element clip
703 {
704 SkClipStack stack;
705 stack.clipRect(insideRect, SkMatrix::I(), kIntersect_SkClipOp, false);
706 // Use a path for second clip to prevent in-place intersection
707 stack.clipPath(outsideCircle, SkMatrix::I(), kIntersect_SkClipOp, false);
708 REPORTER_ASSERT(reporter, false == stack.quickContains(testRect));
709 }
710
711 // Intersect Op tests with rectangles
712 {
713 SkClipStack stack;
714 stack.clipRect(outsideRect, SkMatrix::I(), kIntersect_SkClipOp, false);
715 REPORTER_ASSERT(reporter, true == stack.quickContains(testRect));
716 }
717
718 {
719 SkClipStack stack;
720 stack.clipRect(insideRect, SkMatrix::I(), kIntersect_SkClipOp, false);
721 REPORTER_ASSERT(reporter, false == stack.quickContains(testRect));
722 }
723
724 {
725 SkClipStack stack;
726 stack.clipRect(intersectingRect, SkMatrix::I(), kIntersect_SkClipOp, false);
727 REPORTER_ASSERT(reporter, false == stack.quickContains(testRect));
728 }
729
730 {
731 SkClipStack stack;
732 stack.clipRect(nonIntersectingRect, SkMatrix::I(), kIntersect_SkClipOp, false);
733 REPORTER_ASSERT(reporter, false == stack.quickContains(testRect));
734 }
735
736 // Intersect Op tests with circle paths
737 {
738 SkClipStack stack;
739 stack.clipPath(outsideCircle, SkMatrix::I(), kIntersect_SkClipOp, false);
740 REPORTER_ASSERT(reporter, true == stack.quickContains(testRect));
741 }
742
743 {
744 SkClipStack stack;
745 stack.clipPath(insideCircle, SkMatrix::I(), kIntersect_SkClipOp, false);
746 REPORTER_ASSERT(reporter, false == stack.quickContains(testRect));
747 }
748
749 {
750 SkClipStack stack;
751 stack.clipPath(intersectingCircle, SkMatrix::I(), kIntersect_SkClipOp, false);
752 REPORTER_ASSERT(reporter, false == stack.quickContains(testRect));
753 }
754
755 {
756 SkClipStack stack;
757 stack.clipPath(nonIntersectingCircle, SkMatrix::I(), kIntersect_SkClipOp, false);
758 REPORTER_ASSERT(reporter, false == stack.quickContains(testRect));
759 }
760
761 // Intersect Op tests with inverse filled rectangles
762 {
763 SkClipStack stack;
764 SkPath path;
765 path.addRect(outsideRect);
766 path.toggleInverseFillType();
767 stack.clipPath(path, SkMatrix::I(), kIntersect_SkClipOp, false);
768 REPORTER_ASSERT(reporter, false == stack.quickContains(testRect));
769 }
770
771 {
772 SkClipStack stack;
773 SkPath path;
774 path.addRect(insideRect);
775 path.toggleInverseFillType();
776 stack.clipPath(path, SkMatrix::I(), kIntersect_SkClipOp, false);
777 REPORTER_ASSERT(reporter, false == stack.quickContains(testRect));
778 }
779
780 {
781 SkClipStack stack;
782 SkPath path;
783 path.addRect(intersectingRect);
784 path.toggleInverseFillType();
785 stack.clipPath(path, SkMatrix::I(), kIntersect_SkClipOp, false);
786 REPORTER_ASSERT(reporter, false == stack.quickContains(testRect));
787 }
788
789 {
790 SkClipStack stack;
791 SkPath path;
792 path.addRect(nonIntersectingRect);
793 path.toggleInverseFillType();
794 stack.clipPath(path, SkMatrix::I(), kIntersect_SkClipOp, false);
795 REPORTER_ASSERT(reporter, true == stack.quickContains(testRect));
796 }
797
798 // Intersect Op tests with inverse filled circles
799 {
800 SkClipStack stack;
801 SkPath path = outsideCircle;
802 path.toggleInverseFillType();
803 stack.clipPath(path, SkMatrix::I(), kIntersect_SkClipOp, false);
804 REPORTER_ASSERT(reporter, false == stack.quickContains(testRect));
805 }
806
807 {
808 SkClipStack stack;
809 SkPath path = insideCircle;
810 path.toggleInverseFillType();
811 stack.clipPath(path, SkMatrix::I(), kIntersect_SkClipOp, false);
812 REPORTER_ASSERT(reporter, false == stack.quickContains(testRect));
813 }
814
815 {
816 SkClipStack stack;
817 SkPath path = intersectingCircle;
818 path.toggleInverseFillType();
819 stack.clipPath(path, SkMatrix::I(), kIntersect_SkClipOp, false);
820 REPORTER_ASSERT(reporter, false == stack.quickContains(testRect));
821 }
822
823 {
824 SkClipStack stack;
825 SkPath path = nonIntersectingCircle;
826 path.toggleInverseFillType();
827 stack.clipPath(path, SkMatrix::I(), kIntersect_SkClipOp, false);
828 REPORTER_ASSERT(reporter, true == stack.quickContains(testRect));
829 }
830 }
831
set_region_to_stack(const SkClipStack & stack,const SkIRect & bounds,SkRegion * region)832 static void set_region_to_stack(const SkClipStack& stack, const SkIRect& bounds, SkRegion* region) {
833 region->setRect(bounds);
834 SkClipStack::Iter iter(stack, SkClipStack::Iter::kBottom_IterStart);
835 while (const SkClipStack::Element *element = iter.next()) {
836 SkRegion elemRegion;
837 SkRegion boundsRgn(bounds);
838 SkPath path;
839
840 switch (element->getDeviceSpaceType()) {
841 case SkClipStack::Element::DeviceSpaceType::kEmpty:
842 elemRegion.setEmpty();
843 break;
844 default:
845 element->asDeviceSpacePath(&path);
846 elemRegion.setPath(path, boundsRgn);
847 break;
848 }
849 region->op(elemRegion, (SkRegion::Op)element->getOp());
850 }
851 }
852
test_invfill_diff_bug(skiatest::Reporter * reporter)853 static void test_invfill_diff_bug(skiatest::Reporter* reporter) {
854 SkClipStack stack;
855 stack.clipRect({10, 10, 20, 20}, SkMatrix::I(), kIntersect_SkClipOp, false);
856
857 SkPath path;
858 path.addRect({30, 10, 40, 20});
859 path.setFillType(SkPath::kInverseWinding_FillType);
860 stack.clipPath(path, SkMatrix::I(), kDifference_SkClipOp, false);
861
862 REPORTER_ASSERT(reporter, SkClipStack::kEmptyGenID == stack.getTopmostGenID());
863
864 SkRect stackBounds;
865 SkClipStack::BoundsType stackBoundsType;
866 stack.getBounds(&stackBounds, &stackBoundsType);
867
868 REPORTER_ASSERT(reporter, stackBounds.isEmpty());
869 REPORTER_ASSERT(reporter, SkClipStack::kNormal_BoundsType == stackBoundsType);
870
871 SkRegion region;
872 set_region_to_stack(stack, {0, 0, 50, 30}, ®ion);
873
874 REPORTER_ASSERT(reporter, region.isEmpty());
875 }
876
877 ///////////////////////////////////////////////////////////////////////////////////////////////////
878
879 // Functions that add a shape to the clip stack. The shape is computed from a rectangle.
880 // AA is always disabled since the clip stack reducer can cause changes in aa rasterization of the
881 // stack. A fractional edge repeated in different elements may be rasterized fewer times using the
882 // reduced stack.
883 typedef void (*AddElementFunc) (const SkRect& rect,
884 bool invert,
885 SkClipOp op,
886 SkClipStack* stack,
887 bool doAA);
888
add_round_rect(const SkRect & rect,bool invert,SkClipOp op,SkClipStack * stack,bool doAA)889 static void add_round_rect(const SkRect& rect, bool invert, SkClipOp op, SkClipStack* stack,
890 bool doAA) {
891 SkScalar rx = rect.width() / 10;
892 SkScalar ry = rect.height() / 20;
893 if (invert) {
894 SkPath path;
895 path.addRoundRect(rect, rx, ry);
896 path.setFillType(SkPath::kInverseWinding_FillType);
897 stack->clipPath(path, SkMatrix::I(), op, doAA);
898 } else {
899 SkRRect rrect;
900 rrect.setRectXY(rect, rx, ry);
901 stack->clipRRect(rrect, SkMatrix::I(), op, doAA);
902 }
903 };
904
add_rect(const SkRect & rect,bool invert,SkClipOp op,SkClipStack * stack,bool doAA)905 static void add_rect(const SkRect& rect, bool invert, SkClipOp op, SkClipStack* stack,
906 bool doAA) {
907 if (invert) {
908 SkPath path;
909 path.addRect(rect);
910 path.setFillType(SkPath::kInverseWinding_FillType);
911 stack->clipPath(path, SkMatrix::I(), op, doAA);
912 } else {
913 stack->clipRect(rect, SkMatrix::I(), op, doAA);
914 }
915 };
916
add_oval(const SkRect & rect,bool invert,SkClipOp op,SkClipStack * stack,bool doAA)917 static void add_oval(const SkRect& rect, bool invert, SkClipOp op, SkClipStack* stack,
918 bool doAA) {
919 SkPath path;
920 path.addOval(rect);
921 if (invert) {
922 path.setFillType(SkPath::kInverseWinding_FillType);
923 }
924 stack->clipPath(path, SkMatrix::I(), op, doAA);
925 };
926
add_elem_to_stack(const SkClipStack::Element & element,SkClipStack * stack)927 static void add_elem_to_stack(const SkClipStack::Element& element, SkClipStack* stack) {
928 switch (element.getDeviceSpaceType()) {
929 case SkClipStack::Element::DeviceSpaceType::kRect:
930 stack->clipRect(element.getDeviceSpaceRect(), SkMatrix::I(), element.getOp(),
931 element.isAA());
932 break;
933 case SkClipStack::Element::DeviceSpaceType::kRRect:
934 stack->clipRRect(element.getDeviceSpaceRRect(), SkMatrix::I(), element.getOp(),
935 element.isAA());
936 break;
937 case SkClipStack::Element::DeviceSpaceType::kPath:
938 stack->clipPath(element.getDeviceSpacePath(), SkMatrix::I(), element.getOp(),
939 element.isAA());
940 break;
941 case SkClipStack::Element::DeviceSpaceType::kEmpty:
942 SkDEBUGFAIL("Why did the reducer produce an explicit empty.");
943 stack->clipEmpty();
944 break;
945 }
946 }
947
test_reduced_clip_stack(skiatest::Reporter * reporter)948 static void test_reduced_clip_stack(skiatest::Reporter* reporter) {
949 // We construct random clip stacks, reduce them, and then rasterize both versions to verify that
950 // they are equal.
951
952 // All the clip elements will be contained within these bounds.
953 static const SkIRect kIBounds = SkIRect::MakeWH(100, 100);
954 static const SkRect kBounds = SkRect::Make(kIBounds);
955
956 enum {
957 kNumTests = 250,
958 kMinElemsPerTest = 1,
959 kMaxElemsPerTest = 50,
960 };
961
962 // min/max size of a clip element as a fraction of kBounds.
963 static const SkScalar kMinElemSizeFrac = SK_Scalar1 / 5;
964 static const SkScalar kMaxElemSizeFrac = SK_Scalar1;
965
966 static const SkClipOp kOps[] = {
967 kDifference_SkClipOp,
968 kIntersect_SkClipOp,
969 kUnion_SkClipOp,
970 kXOR_SkClipOp,
971 kReverseDifference_SkClipOp,
972 kReplace_SkClipOp,
973 };
974
975 // Replace operations short-circuit the optimizer. We want to make sure that we test this code
976 // path a little bit but we don't want it to prevent us from testing many longer traversals in
977 // the optimizer.
978 static const int kReplaceDiv = 4 * kMaxElemsPerTest;
979
980 // We want to test inverse fills. However, they are quite rare in practice so don't over do it.
981 static const SkScalar kFractionInverted = SK_Scalar1 / kMaxElemsPerTest;
982
983 static const SkScalar kFractionAntialiased = 0.25;
984
985 static const AddElementFunc kElementFuncs[] = {
986 add_rect,
987 add_round_rect,
988 add_oval,
989 };
990
991 SkRandom r;
992
993 for (int i = 0; i < kNumTests; ++i) {
994 SkString testCase;
995 testCase.printf("Iteration %d", i);
996
997 // Randomly generate a clip stack.
998 SkClipStack stack;
999 int numElems = r.nextRangeU(kMinElemsPerTest, kMaxElemsPerTest);
1000 bool doAA = r.nextBiasedBool(kFractionAntialiased);
1001 for (int e = 0; e < numElems; ++e) {
1002 SkClipOp op = kOps[r.nextULessThan(SK_ARRAY_COUNT(kOps))];
1003 if (op == kReplace_SkClipOp) {
1004 if (r.nextU() % kReplaceDiv) {
1005 --e;
1006 continue;
1007 }
1008 }
1009
1010 // saves can change the clip stack behavior when an element is added.
1011 bool doSave = r.nextBool();
1012
1013 SkSize size = SkSize::Make(
1014 kBounds.width() * r.nextRangeScalar(kMinElemSizeFrac, kMaxElemSizeFrac),
1015 kBounds.height() * r.nextRangeScalar(kMinElemSizeFrac, kMaxElemSizeFrac));
1016
1017 SkPoint xy = {r.nextRangeScalar(kBounds.fLeft, kBounds.fRight - size.fWidth),
1018 r.nextRangeScalar(kBounds.fTop, kBounds.fBottom - size.fHeight)};
1019
1020 SkRect rect;
1021 if (doAA) {
1022 rect.setXYWH(xy.fX, xy.fY, size.fWidth, size.fHeight);
1023 if (GrClip::IsPixelAligned(rect)) {
1024 // Don't create an element that may accidentally become not antialiased.
1025 rect.outset(0.5f, 0.5f);
1026 }
1027 SkASSERT(!GrClip::IsPixelAligned(rect));
1028 } else {
1029 rect.setXYWH(SkScalarFloorToScalar(xy.fX),
1030 SkScalarFloorToScalar(xy.fY),
1031 SkScalarCeilToScalar(size.fWidth),
1032 SkScalarCeilToScalar(size.fHeight));
1033 }
1034
1035 bool invert = r.nextBiasedBool(kFractionInverted);
1036
1037 kElementFuncs[r.nextULessThan(SK_ARRAY_COUNT(kElementFuncs))](rect, invert, op, &stack,
1038 doAA);
1039 if (doSave) {
1040 stack.save();
1041 }
1042 }
1043
1044 auto context = GrContext::MakeMock(nullptr);
1045 const GrCaps* caps = context->priv().caps();
1046
1047 // Zero the memory we will new the GrReducedClip into. This ensures the elements gen ID
1048 // will be kInvalidGenID if left uninitialized.
1049 SkAlignedSTStorage<1, GrReducedClip> storage;
1050 memset(storage.get(), 0, sizeof(GrReducedClip));
1051 GR_STATIC_ASSERT(0 == SkClipStack::kInvalidGenID);
1052
1053 // Get the reduced version of the stack.
1054 SkRect queryBounds = kBounds;
1055 queryBounds.outset(kBounds.width() / 2, kBounds.height() / 2);
1056 const GrReducedClip* reduced = new (storage.get()) GrReducedClip(stack, queryBounds, caps);
1057
1058 REPORTER_ASSERT(reporter,
1059 reduced->maskElements().isEmpty() ||
1060 SkClipStack::kInvalidGenID != reduced->maskGenID(),
1061 testCase.c_str());
1062
1063 if (!reduced->maskElements().isEmpty()) {
1064 REPORTER_ASSERT(reporter, reduced->hasScissor(), testCase.c_str());
1065 SkRect stackBounds;
1066 SkClipStack::BoundsType stackBoundsType;
1067 stack.getBounds(&stackBounds, &stackBoundsType);
1068 REPORTER_ASSERT(reporter, reduced->maskRequiresAA() == doAA, testCase.c_str());
1069 }
1070
1071 // Build a new clip stack based on the reduced clip elements
1072 SkClipStack reducedStack;
1073 if (GrReducedClip::InitialState::kAllOut == reduced->initialState()) {
1074 // whether the result is bounded or not, the whole plane should start outside the clip.
1075 reducedStack.clipEmpty();
1076 }
1077 for (ElementList::Iter iter(reduced->maskElements()); iter.get(); iter.next()) {
1078 add_elem_to_stack(*iter.get(), &reducedStack);
1079 }
1080
1081 SkIRect scissor = reduced->hasScissor() ? reduced->scissor() : kIBounds;
1082
1083 // GrReducedClipStack assumes that the final result is clipped to the returned bounds
1084 reducedStack.clipDevRect(scissor, kIntersect_SkClipOp);
1085 stack.clipDevRect(scissor, kIntersect_SkClipOp);
1086
1087 // convert both the original stack and reduced stack to SkRegions and see if they're equal
1088 SkRegion region;
1089 set_region_to_stack(stack, scissor, ®ion);
1090
1091 SkRegion reducedRegion;
1092 set_region_to_stack(reducedStack, scissor, &reducedRegion);
1093
1094 REPORTER_ASSERT(reporter, region == reducedRegion, testCase.c_str());
1095
1096 reduced->~GrReducedClip();
1097 }
1098 }
1099
1100 #ifdef SK_BUILD_FOR_WIN
1101 #define SUPPRESS_VISIBILITY_WARNING
1102 #else
1103 #define SUPPRESS_VISIBILITY_WARNING __attribute__((visibility("hidden")))
1104 #endif
1105
test_reduced_clip_stack_genid(skiatest::Reporter * reporter)1106 static void test_reduced_clip_stack_genid(skiatest::Reporter* reporter) {
1107 {
1108 SkClipStack stack;
1109 stack.clipRect(SkRect::MakeXYWH(0, 0, 100, 100), SkMatrix::I(), kReplace_SkClipOp,
1110 true);
1111 stack.clipRect(SkRect::MakeXYWH(0, 0, SkScalar(50.3), SkScalar(50.3)), SkMatrix::I(),
1112 kReplace_SkClipOp, true);
1113 SkRect bounds = SkRect::MakeXYWH(0, 0, 100, 100);
1114
1115 auto context = GrContext::MakeMock(nullptr);
1116 const GrCaps* caps = context->priv().caps();
1117
1118 SkAlignedSTStorage<1, GrReducedClip> storage;
1119 memset(storage.get(), 0, sizeof(GrReducedClip));
1120 GR_STATIC_ASSERT(0 == SkClipStack::kInvalidGenID);
1121 const GrReducedClip* reduced = new (storage.get()) GrReducedClip(stack, bounds, caps);
1122
1123 REPORTER_ASSERT(reporter, reduced->maskElements().count() == 1);
1124 // Clips will be cached based on the generation id. Make sure the gen id is valid.
1125 REPORTER_ASSERT(reporter, SkClipStack::kInvalidGenID != reduced->maskGenID());
1126
1127 reduced->~GrReducedClip();
1128 }
1129 {
1130 SkClipStack stack;
1131
1132 // Create a clip with following 25.3, 25.3 boxes which are 25 apart:
1133 // A B
1134 // C D
1135
1136 stack.clipRect(SkRect::MakeXYWH(0, 0, SkScalar(25.3), SkScalar(25.3)), SkMatrix::I(),
1137 kReplace_SkClipOp, true);
1138 uint32_t genIDA = stack.getTopmostGenID();
1139 stack.clipRect(SkRect::MakeXYWH(50, 0, SkScalar(25.3), SkScalar(25.3)), SkMatrix::I(),
1140 kUnion_SkClipOp, true);
1141 uint32_t genIDB = stack.getTopmostGenID();
1142 stack.clipRect(SkRect::MakeXYWH(0, 50, SkScalar(25.3), SkScalar(25.3)), SkMatrix::I(),
1143 kUnion_SkClipOp, true);
1144 uint32_t genIDC = stack.getTopmostGenID();
1145 stack.clipRect(SkRect::MakeXYWH(50, 50, SkScalar(25.3), SkScalar(25.3)), SkMatrix::I(),
1146 kUnion_SkClipOp, true);
1147 uint32_t genIDD = stack.getTopmostGenID();
1148
1149
1150 #define IXYWH SkIRect::MakeXYWH
1151 #define XYWH SkRect::MakeXYWH
1152
1153 SkIRect stackBounds = IXYWH(0, 0, 76, 76);
1154
1155 // The base test is to test each rect in two ways:
1156 // 1) The box dimensions. (Should reduce to "all in", no elements).
1157 // 2) A bit over the box dimensions.
1158 // In the case 2, test that the generation id is what is expected.
1159 // The rects are of fractional size so that case 2 never gets optimized to an empty element
1160 // list.
1161
1162 // Not passing in tighter bounds is tested for consistency.
1163 static const struct SUPPRESS_VISIBILITY_WARNING {
1164 SkRect testBounds;
1165 int reducedClipCount;
1166 uint32_t reducedGenID;
1167 InitialState initialState;
1168 SkIRect clipIRect;
1169 // parameter.
1170 } testCases[] = {
1171
1172 // Rect A.
1173 { XYWH(0, 0, 25, 25), 0, SkClipStack::kInvalidGenID, GrReducedClip::InitialState::kAllIn, IXYWH(0, 0, 25, 25) },
1174 { XYWH(0.1f, 0.1f, 25.1f, 25.1f), 0, SkClipStack::kInvalidGenID, GrReducedClip::InitialState::kAllIn, IXYWH(0, 0, 26, 26) },
1175 { XYWH(0, 0, 27, 27), 1, genIDA, GrReducedClip::InitialState::kAllOut, IXYWH(0, 0, 26, 26)},
1176
1177 // Rect B.
1178 { XYWH(50, 0, 25, 25), 0, SkClipStack::kInvalidGenID, GrReducedClip::InitialState::kAllIn, IXYWH(50, 0, 25, 25) },
1179 { XYWH(50, 0, 25.3f, 25.3f), 0, SkClipStack::kInvalidGenID, GrReducedClip::InitialState::kAllIn, IXYWH(50, 0, 26, 26) },
1180 { XYWH(50, 0, 27, 27), 1, genIDB, GrReducedClip::InitialState::kAllOut, IXYWH(50, 0, 26, 27) },
1181
1182 // Rect C.
1183 { XYWH(0, 50, 25, 25), 0, SkClipStack::kInvalidGenID, GrReducedClip::InitialState::kAllIn, IXYWH(0, 50, 25, 25) },
1184 { XYWH(0.2f, 50.1f, 25.1f, 25.2f), 0, SkClipStack::kInvalidGenID, GrReducedClip::InitialState::kAllIn, IXYWH(0, 50, 26, 26) },
1185 { XYWH(0, 50, 27, 27), 1, genIDC, GrReducedClip::InitialState::kAllOut, IXYWH(0, 50, 27, 26) },
1186
1187 // Rect D.
1188 { XYWH(50, 50, 25, 25), 0, SkClipStack::kInvalidGenID, GrReducedClip::InitialState::kAllIn, IXYWH(50, 50, 25, 25)},
1189 { XYWH(50.3f, 50.3f, 25, 25), 0, SkClipStack::kInvalidGenID, GrReducedClip::InitialState::kAllIn, IXYWH(50, 50, 26, 26)},
1190 { XYWH(50, 50, 27, 27), 1, genIDD, GrReducedClip::InitialState::kAllOut, IXYWH(50, 50, 26, 26)},
1191
1192 // Other tests:
1193 { XYWH(0, 0, 100, 100), 4, genIDD, GrReducedClip::InitialState::kAllOut, stackBounds },
1194
1195 // Rect in the middle, touches none.
1196 { XYWH(26, 26, 24, 24), 0, SkClipStack::kInvalidGenID, GrReducedClip::InitialState::kAllOut, IXYWH(26, 26, 24, 24) },
1197
1198 // Rect in the middle, touches all the rects. GenID is the last rect.
1199 { XYWH(24, 24, 27, 27), 4, genIDD, GrReducedClip::InitialState::kAllOut, IXYWH(24, 24, 27, 27) },
1200 };
1201
1202 #undef XYWH
1203 #undef IXYWH
1204 auto context = GrContext::MakeMock(nullptr);
1205 const GrCaps* caps = context->priv().caps();
1206
1207 for (size_t i = 0; i < SK_ARRAY_COUNT(testCases); ++i) {
1208 const GrReducedClip reduced(stack, testCases[i].testBounds, caps);
1209 REPORTER_ASSERT(reporter, reduced.maskElements().count() ==
1210 testCases[i].reducedClipCount);
1211 SkASSERT(reduced.maskElements().count() == testCases[i].reducedClipCount);
1212 if (reduced.maskElements().count()) {
1213 REPORTER_ASSERT(reporter, reduced.maskGenID() == testCases[i].reducedGenID);
1214 SkASSERT(reduced.maskGenID() == testCases[i].reducedGenID);
1215 }
1216 REPORTER_ASSERT(reporter, reduced.initialState() == testCases[i].initialState);
1217 SkASSERT(reduced.initialState() == testCases[i].initialState);
1218 REPORTER_ASSERT(reporter, reduced.hasScissor());
1219 SkASSERT(reduced.hasScissor());
1220 REPORTER_ASSERT(reporter, reduced.scissor() == testCases[i].clipIRect);
1221 SkASSERT(reduced.scissor() == testCases[i].clipIRect);
1222 }
1223 }
1224 }
1225
test_reduced_clip_stack_no_aa_crash(skiatest::Reporter * reporter)1226 static void test_reduced_clip_stack_no_aa_crash(skiatest::Reporter* reporter) {
1227 SkClipStack stack;
1228 stack.clipDevRect(SkIRect::MakeXYWH(0, 0, 100, 100), kReplace_SkClipOp);
1229 stack.clipDevRect(SkIRect::MakeXYWH(0, 0, 50, 50), kReplace_SkClipOp);
1230 SkRect bounds = SkRect::MakeXYWH(0, 0, 100, 100);
1231
1232 auto context = GrContext::MakeMock(nullptr);
1233 const GrCaps* caps = context->priv().caps();
1234
1235 // At the time, this would crash.
1236 const GrReducedClip reduced(stack, bounds, caps);
1237 REPORTER_ASSERT(reporter, reduced.maskElements().isEmpty());
1238 }
1239
1240 enum class ClipMethod {
1241 kSkipDraw,
1242 kIgnoreClip,
1243 kScissor,
1244 kAAElements
1245 };
1246
test_aa_query(skiatest::Reporter * reporter,const SkString & testName,const SkClipStack & stack,const SkMatrix & queryXform,const SkRect & preXformQuery,ClipMethod expectedMethod,int numExpectedElems=0)1247 static void test_aa_query(skiatest::Reporter* reporter, const SkString& testName,
1248 const SkClipStack& stack, const SkMatrix& queryXform,
1249 const SkRect& preXformQuery, ClipMethod expectedMethod,
1250 int numExpectedElems = 0) {
1251 auto context = GrContext::MakeMock(nullptr);
1252 const GrCaps* caps = context->priv().caps();
1253
1254 SkRect queryBounds;
1255 queryXform.mapRect(&queryBounds, preXformQuery);
1256 const GrReducedClip reduced(stack, queryBounds, caps);
1257
1258 SkClipStack::BoundsType stackBoundsType;
1259 SkRect stackBounds;
1260 stack.getBounds(&stackBounds, &stackBoundsType);
1261
1262 switch (expectedMethod) {
1263 case ClipMethod::kSkipDraw:
1264 SkASSERT(0 == numExpectedElems);
1265 REPORTER_ASSERT(reporter, reduced.maskElements().isEmpty(), testName.c_str());
1266 REPORTER_ASSERT(reporter,
1267 GrReducedClip::InitialState::kAllOut == reduced.initialState(),
1268 testName.c_str());
1269 return;
1270 case ClipMethod::kIgnoreClip:
1271 SkASSERT(0 == numExpectedElems);
1272 REPORTER_ASSERT(
1273 reporter,
1274 !reduced.hasScissor() || GrClip::IsInsideClip(reduced.scissor(), queryBounds),
1275 testName.c_str());
1276 REPORTER_ASSERT(reporter, reduced.maskElements().isEmpty(), testName.c_str());
1277 REPORTER_ASSERT(reporter,
1278 GrReducedClip::InitialState::kAllIn == reduced.initialState(),
1279 testName.c_str());
1280 return;
1281 case ClipMethod::kScissor: {
1282 SkASSERT(SkClipStack::kNormal_BoundsType == stackBoundsType);
1283 SkASSERT(0 == numExpectedElems);
1284 SkIRect expectedScissor;
1285 stackBounds.round(&expectedScissor);
1286 REPORTER_ASSERT(reporter, reduced.maskElements().isEmpty(), testName.c_str());
1287 REPORTER_ASSERT(reporter, reduced.hasScissor(), testName.c_str());
1288 REPORTER_ASSERT(reporter, expectedScissor == reduced.scissor(), testName.c_str());
1289 REPORTER_ASSERT(reporter,
1290 GrReducedClip::InitialState::kAllIn == reduced.initialState(),
1291 testName.c_str());
1292 return;
1293 }
1294 case ClipMethod::kAAElements: {
1295 SkIRect expectedClipIBounds = GrClip::GetPixelIBounds(queryBounds);
1296 if (SkClipStack::kNormal_BoundsType == stackBoundsType) {
1297 SkAssertResult(expectedClipIBounds.intersect(GrClip::GetPixelIBounds(stackBounds)));
1298 }
1299 REPORTER_ASSERT(reporter, numExpectedElems == reduced.maskElements().count(),
1300 testName.c_str());
1301 REPORTER_ASSERT(reporter, reduced.hasScissor(), testName.c_str());
1302 REPORTER_ASSERT(reporter, expectedClipIBounds == reduced.scissor(), testName.c_str());
1303 REPORTER_ASSERT(reporter,
1304 reduced.maskElements().isEmpty() || reduced.maskRequiresAA(),
1305 testName.c_str());
1306 break;
1307 }
1308 }
1309 }
1310
test_reduced_clip_stack_aa(skiatest::Reporter * reporter)1311 static void test_reduced_clip_stack_aa(skiatest::Reporter* reporter) {
1312 constexpr SkScalar IL = 2, IT = 1, IR = 6, IB = 7; // Pixel aligned rect.
1313 constexpr SkScalar L = 2.2f, T = 1.7f, R = 5.8f, B = 7.3f; // Generic rect.
1314 constexpr SkScalar l = 3.3f, t = 2.8f, r = 4.7f, b = 6.2f; // Small rect contained in R.
1315
1316 SkRect alignedRect = {IL, IT, IR, IB};
1317 SkRect rect = {L, T, R, B};
1318 SkRect innerRect = {l, t, r, b};
1319
1320 SkMatrix m;
1321 m.setIdentity();
1322
1323 constexpr SkScalar kMinScale = 2.0001f;
1324 constexpr SkScalar kMaxScale = 3;
1325 constexpr int kNumIters = 8;
1326
1327 SkString name;
1328 SkRandom rand;
1329
1330 for (int i = 0; i < kNumIters; ++i) {
1331 // Pixel-aligned rect (iior=true).
1332 name.printf("Pixel-aligned rect test, iter %i", i);
1333 SkClipStack stack;
1334 stack.clipRect(alignedRect, SkMatrix::I(), kIntersect_SkClipOp, true);
1335 test_aa_query(reporter, name, stack, m, {IL, IT, IR, IB}, ClipMethod::kIgnoreClip);
1336 test_aa_query(reporter, name, stack, m, {IL, IT-1, IR, IT}, ClipMethod::kSkipDraw);
1337 test_aa_query(reporter, name, stack, m, {IL, IT-2, IR, IB}, ClipMethod::kScissor);
1338
1339 // Rect (iior=true).
1340 name.printf("Rect test, iter %i", i);
1341 stack.reset();
1342 stack.clipRect(rect, SkMatrix::I(), kIntersect_SkClipOp, true);
1343 test_aa_query(reporter, name, stack, m, {L, T, R, B}, ClipMethod::kIgnoreClip);
1344 test_aa_query(reporter, name, stack, m, {L-.1f, T, L, B}, ClipMethod::kSkipDraw);
1345 test_aa_query(reporter, name, stack, m, {L-.1f, T, L+.1f, B}, ClipMethod::kAAElements, 1);
1346
1347 // Difference rect (iior=false, inside-out bounds).
1348 name.printf("Difference rect test, iter %i", i);
1349 stack.reset();
1350 stack.clipRect(rect, SkMatrix::I(), kDifference_SkClipOp, true);
1351 test_aa_query(reporter, name, stack, m, {L, T, R, B}, ClipMethod::kSkipDraw);
1352 test_aa_query(reporter, name, stack, m, {L, T-.1f, R, T}, ClipMethod::kIgnoreClip);
1353 test_aa_query(reporter, name, stack, m, {L, T-.1f, R, T+.1f}, ClipMethod::kAAElements, 1);
1354
1355 // Complex clip (iior=false, normal bounds).
1356 name.printf("Complex clip test, iter %i", i);
1357 stack.reset();
1358 stack.clipRect(rect, SkMatrix::I(), kIntersect_SkClipOp, true);
1359 stack.clipRect(innerRect, SkMatrix::I(), kXOR_SkClipOp, true);
1360 test_aa_query(reporter, name, stack, m, {l, t, r, b}, ClipMethod::kSkipDraw);
1361 test_aa_query(reporter, name, stack, m, {r-.1f, t, R, b}, ClipMethod::kAAElements, 1);
1362 test_aa_query(reporter, name, stack, m, {r-.1f, t, R+.1f, b}, ClipMethod::kAAElements, 2);
1363 test_aa_query(reporter, name, stack, m, {r, t, R+.1f, b}, ClipMethod::kAAElements, 1);
1364 test_aa_query(reporter, name, stack, m, {r, t, R, b}, ClipMethod::kIgnoreClip);
1365 test_aa_query(reporter, name, stack, m, {R, T, R+.1f, B}, ClipMethod::kSkipDraw);
1366
1367 // Complex clip where outer rect is pixel aligned (iior=false, normal bounds).
1368 name.printf("Aligned Complex clip test, iter %i", i);
1369 stack.reset();
1370 stack.clipRect(alignedRect, SkMatrix::I(), kIntersect_SkClipOp, true);
1371 stack.clipRect(innerRect, SkMatrix::I(), kXOR_SkClipOp, true);
1372 test_aa_query(reporter, name, stack, m, {l, t, r, b}, ClipMethod::kSkipDraw);
1373 test_aa_query(reporter, name, stack, m, {l, b-.1f, r, IB}, ClipMethod::kAAElements, 1);
1374 test_aa_query(reporter, name, stack, m, {l, b-.1f, r, IB+.1f}, ClipMethod::kAAElements, 1);
1375 test_aa_query(reporter, name, stack, m, {l, b, r, IB+.1f}, ClipMethod::kAAElements, 0);
1376 test_aa_query(reporter, name, stack, m, {l, b, r, IB}, ClipMethod::kIgnoreClip);
1377 test_aa_query(reporter, name, stack, m, {IL, IB, IR, IB+.1f}, ClipMethod::kSkipDraw);
1378
1379 // Apply random transforms and try again. This ensures the clip stack reduction is hardened
1380 // against FP rounding error.
1381 SkScalar sx = rand.nextRangeScalar(kMinScale, kMaxScale);
1382 sx = SkScalarFloorToScalar(sx * alignedRect.width()) / alignedRect.width();
1383 SkScalar sy = rand.nextRangeScalar(kMinScale, kMaxScale);
1384 sy = SkScalarFloorToScalar(sy * alignedRect.height()) / alignedRect.height();
1385 SkScalar tx = SkScalarRoundToScalar(sx * alignedRect.x()) - sx * alignedRect.x();
1386 SkScalar ty = SkScalarRoundToScalar(sy * alignedRect.y()) - sy * alignedRect.y();
1387
1388 SkMatrix xform = SkMatrix::MakeScale(sx, sy);
1389 xform.postTranslate(tx, ty);
1390 xform.mapRect(&alignedRect);
1391 xform.mapRect(&rect);
1392 xform.mapRect(&innerRect);
1393 m.postConcat(xform);
1394 }
1395 }
1396
test_tiny_query_bounds_assertion_bug(skiatest::Reporter * reporter)1397 static void test_tiny_query_bounds_assertion_bug(skiatest::Reporter* reporter) {
1398 // https://bugs.chromium.org/p/skia/issues/detail?id=5990
1399 const SkRect clipBounds = SkRect::MakeXYWH(1.5f, 100, 1000, 1000);
1400
1401 SkClipStack rectStack;
1402 rectStack.clipRect(clipBounds, SkMatrix::I(), kIntersect_SkClipOp, true);
1403
1404 SkPath clipPath;
1405 clipPath.moveTo(clipBounds.left(), clipBounds.top());
1406 clipPath.quadTo(clipBounds.right(), clipBounds.top(),
1407 clipBounds.right(), clipBounds.bottom());
1408 clipPath.quadTo(clipBounds.left(), clipBounds.bottom(),
1409 clipBounds.left(), clipBounds.top());
1410 SkClipStack pathStack;
1411 pathStack.clipPath(clipPath, SkMatrix::I(), kIntersect_SkClipOp, true);
1412
1413 auto context = GrContext::MakeMock(nullptr);
1414 const GrCaps* caps = context->priv().caps();
1415
1416 for (const SkClipStack& stack : {rectStack, pathStack}) {
1417 for (SkRect queryBounds : {SkRect::MakeXYWH(53, 60, GrClip::kBoundsTolerance, 1000),
1418 SkRect::MakeXYWH(53, 60, GrClip::kBoundsTolerance/2, 1000),
1419 SkRect::MakeXYWH(53, 160, 1000, GrClip::kBoundsTolerance),
1420 SkRect::MakeXYWH(53, 160, 1000, GrClip::kBoundsTolerance/2)}) {
1421 const GrReducedClip reduced(stack, queryBounds, caps);
1422 REPORTER_ASSERT(reporter, !reduced.hasScissor());
1423 REPORTER_ASSERT(reporter, reduced.maskElements().isEmpty());
1424 REPORTER_ASSERT(reporter,
1425 GrReducedClip::InitialState::kAllOut == reduced.initialState());
1426 }
1427 }
1428 }
1429
test_is_rrect_deep_rect_stack(skiatest::Reporter * reporter)1430 static void test_is_rrect_deep_rect_stack(skiatest::Reporter* reporter) {
1431 static constexpr SkRect kTargetBounds = SkRect::MakeWH(1000, 500);
1432 // All antialiased or all not antialiased.
1433 for (bool aa : {false, true}) {
1434 SkClipStack stack;
1435 for (int i = 0; i <= 100; ++i) {
1436 stack.save();
1437 stack.clipRect(SkRect::MakeLTRB(i, 0.5, kTargetBounds.width(), kTargetBounds.height()),
1438 SkMatrix::I(), SkClipOp::kIntersect, aa);
1439 }
1440 SkRRect rrect;
1441 bool isAA;
1442 SkRRect expected = SkRRect::MakeRect(
1443 SkRect::MakeLTRB(100, 0.5, kTargetBounds.width(), kTargetBounds.height()));
1444 if (stack.isRRect(kTargetBounds, &rrect, &isAA)) {
1445 REPORTER_ASSERT(reporter, rrect == expected);
1446 REPORTER_ASSERT(reporter, aa == isAA);
1447 } else {
1448 ERRORF(reporter, "Expected to be an rrect.");
1449 }
1450 }
1451 // Mixed AA and non-AA without simple containment.
1452 SkClipStack stack;
1453 for (int i = 0; i <= 100; ++i) {
1454 bool aa = i & 0b1;
1455 int j = 100 - i;
1456 stack.save();
1457 stack.clipRect(SkRect::MakeLTRB(i, j + 0.5, kTargetBounds.width(), kTargetBounds.height()),
1458 SkMatrix::I(), SkClipOp::kIntersect, aa);
1459 }
1460 SkRRect rrect;
1461 bool isAA;
1462 REPORTER_ASSERT(reporter, !stack.isRRect(kTargetBounds, &rrect, &isAA));
1463 }
1464
DEF_TEST(ClipStack,reporter)1465 DEF_TEST(ClipStack, reporter) {
1466 SkClipStack stack;
1467
1468 REPORTER_ASSERT(reporter, 0 == stack.getSaveCount());
1469 assert_count(reporter, stack, 0);
1470
1471 static const SkIRect gRects[] = {
1472 { 0, 0, 100, 100 },
1473 { 25, 25, 125, 125 },
1474 { 0, 0, 1000, 1000 },
1475 { 0, 0, 75, 75 }
1476 };
1477 for (size_t i = 0; i < SK_ARRAY_COUNT(gRects); i++) {
1478 stack.clipDevRect(gRects[i], kIntersect_SkClipOp);
1479 }
1480
1481 // all of the above rects should have been intersected, leaving only 1 rect
1482 SkClipStack::B2TIter iter(stack);
1483 const SkClipStack::Element* element = iter.next();
1484 SkRect answer;
1485 answer.iset(25, 25, 75, 75);
1486
1487 REPORTER_ASSERT(reporter, element);
1488 REPORTER_ASSERT(reporter,
1489 SkClipStack::Element::DeviceSpaceType::kRect == element->getDeviceSpaceType());
1490 REPORTER_ASSERT(reporter, kIntersect_SkClipOp == element->getOp());
1491 REPORTER_ASSERT(reporter, element->getDeviceSpaceRect() == answer);
1492 // now check that we only had one in our iterator
1493 REPORTER_ASSERT(reporter, !iter.next());
1494
1495 stack.reset();
1496 REPORTER_ASSERT(reporter, 0 == stack.getSaveCount());
1497 assert_count(reporter, stack, 0);
1498
1499 test_assign_and_comparison(reporter);
1500 test_iterators(reporter);
1501 test_bounds(reporter, SkClipStack::Element::DeviceSpaceType::kRect);
1502 test_bounds(reporter, SkClipStack::Element::DeviceSpaceType::kRRect);
1503 test_bounds(reporter, SkClipStack::Element::DeviceSpaceType::kPath);
1504 test_isWideOpen(reporter);
1505 test_rect_merging(reporter);
1506 test_rect_replace(reporter);
1507 test_rect_inverse_fill(reporter);
1508 test_path_replace(reporter);
1509 test_quickContains(reporter);
1510 test_invfill_diff_bug(reporter);
1511
1512 test_reduced_clip_stack(reporter);
1513 test_reduced_clip_stack_genid(reporter);
1514 test_reduced_clip_stack_no_aa_crash(reporter);
1515 test_reduced_clip_stack_aa(reporter);
1516 test_tiny_query_bounds_assertion_bug(reporter);
1517 test_is_rrect_deep_rect_stack(reporter);
1518 }
1519
1520 //////////////////////////////////////////////////////////////////////////////
1521
testingOnly_createClipMask(GrContext * context) const1522 sk_sp<GrTextureProxy> GrClipStackClip::testingOnly_createClipMask(GrContext* context) const {
1523 const GrReducedClip reducedClip(*fStack, SkRect::MakeWH(512, 512), 0);
1524 return this->createSoftwareClipMask(context, reducedClip, nullptr);
1525 }
1526
1527 // Verify that clip masks are freed up when the clip state that generated them goes away.
DEF_GPUTEST_FOR_ALL_CONTEXTS(ClipMaskCache,reporter,ctxInfo)1528 DEF_GPUTEST_FOR_ALL_CONTEXTS(ClipMaskCache, reporter, ctxInfo) {
1529 // This test uses resource key tags which only function in debug builds.
1530 #ifdef SK_DEBUG
1531 GrContext* context = ctxInfo.grContext();
1532 SkClipStack stack;
1533
1534 SkPath path;
1535 path.addCircle(10, 10, 8);
1536 path.addCircle(15, 15, 8);
1537 path.setFillType(SkPath::kEvenOdd_FillType);
1538
1539 static const char* kTag = GrClipStackClip::kMaskTestTag;
1540 GrResourceCache* cache = context->priv().getResourceCache();
1541
1542 static constexpr int kN = 5;
1543
1544 for (int i = 0; i < kN; ++i) {
1545 SkMatrix m;
1546 m.setTranslate(0.5, 0.5);
1547 stack.save();
1548 stack.clipPath(path, m, SkClipOp::kIntersect, true);
1549 sk_sp<GrTextureProxy> mask = GrClipStackClip(&stack).testingOnly_createClipMask(context);
1550 mask->instantiate(context->priv().resourceProvider());
1551 GrTexture* tex = mask->peekTexture();
1552 REPORTER_ASSERT(reporter, 0 == strcmp(tex->getUniqueKey().tag(), kTag));
1553 // Make sure mask isn't pinned in cache.
1554 mask.reset(nullptr);
1555 context->flush();
1556 REPORTER_ASSERT(reporter, i + 1 == cache->countUniqueKeysWithTag(kTag));
1557 }
1558
1559 for (int i = 0; i < kN; ++i) {
1560 stack.restore();
1561 cache->purgeAsNeeded();
1562 REPORTER_ASSERT(reporter, kN - (i + 1) == cache->countUniqueKeysWithTag(kTag));
1563 }
1564 #endif
1565 }
1566
DEF_GPUTEST_FOR_ALL_CONTEXTS(canvas_private_clipRgn,reporter,ctxInfo)1567 DEF_GPUTEST_FOR_ALL_CONTEXTS(canvas_private_clipRgn, reporter, ctxInfo) {
1568 GrContext* context = ctxInfo.grContext();
1569
1570 const int w = 10;
1571 const int h = 10;
1572 SkImageInfo info = SkImageInfo::Make(w, h, kRGBA_8888_SkColorType, kPremul_SkAlphaType);
1573 sk_sp<SkSurface> surf = SkSurface::MakeRenderTarget(context, SkBudgeted::kNo, info);
1574 SkCanvas* canvas = surf->getCanvas();
1575 SkRegion rgn;
1576
1577 canvas->temporary_internal_getRgnClip(&rgn);
1578 REPORTER_ASSERT(reporter, rgn.isRect());
1579 REPORTER_ASSERT(reporter, rgn.getBounds() == SkIRect::MakeWH(w, h));
1580
1581 canvas->save();
1582 canvas->clipRect(SkRect::MakeWH(5, 5), kDifference_SkClipOp);
1583 canvas->temporary_internal_getRgnClip(&rgn);
1584 REPORTER_ASSERT(reporter, rgn.isComplex());
1585 REPORTER_ASSERT(reporter, rgn.getBounds() == SkIRect::MakeWH(w, h));
1586 canvas->restore();
1587
1588 canvas->save();
1589 canvas->clipRRect(SkRRect::MakeOval(SkRect::MakeLTRB(3, 3, 7, 7)));
1590 canvas->temporary_internal_getRgnClip(&rgn);
1591 REPORTER_ASSERT(reporter, rgn.isComplex());
1592 REPORTER_ASSERT(reporter, rgn.getBounds() == SkIRect::MakeLTRB(3, 3, 7, 7));
1593 canvas->restore();
1594 }
1595