blob: 8eb1102de89a7bcbd071fe12cbf2b104213e0821 [file] [log] [blame]
epoger@google.comec3ed6a2011-07-28 14:26:00 +00001
2/*
3 * Copyright 2011 Google Inc.
4 *
5 * Use of this source code is governed by a BSD-style license that can be
6 * found in the LICENSE file.
7 */
reed@google.combdee9fc2011-02-22 20:17:43 +00008#include "Test.h"
bsalomon@google.coma4e13c82012-11-26 21:38:37 +00009#if SK_SUPPORT_GPU
10 #include "GrClipMaskManager.h"
11#endif
reed@google.combdee9fc2011-02-22 20:17:43 +000012#include "SkClipStack.h"
vandebo@chromium.org1e1c36f2011-05-03 16:26:09 +000013#include "SkPath.h"
bsalomon@google.com51a62862012-11-26 21:19:43 +000014#include "SkRandom.h"
vandebo@chromium.org1e1c36f2011-05-03 16:26:09 +000015#include "SkRect.h"
bsalomon@google.com51a62862012-11-26 21:19:43 +000016#include "SkRegion.h"
robertphillips@google.com80214e22012-07-20 15:33:18 +000017
18
vandebo@chromium.org1e1c36f2011-05-03 16:26:09 +000019static void test_assign_and_comparison(skiatest::Reporter* reporter) {
20 SkClipStack s;
reed@google.comd9f2dea2011-10-12 14:43:27 +000021 bool doAA = false;
vandebo@chromium.org1e1c36f2011-05-03 16:26:09 +000022
robertphillips@google.com80214e22012-07-20 15:33:18 +000023 REPORTER_ASSERT(reporter, 0 == s.getSaveCount());
24
vandebo@chromium.org1e1c36f2011-05-03 16:26:09 +000025 // Build up a clip stack with a path, an empty clip, and a rect.
26 s.save();
robertphillips@google.com80214e22012-07-20 15:33:18 +000027 REPORTER_ASSERT(reporter, 1 == s.getSaveCount());
28
vandebo@chromium.org1e1c36f2011-05-03 16:26:09 +000029 SkPath p;
30 p.moveTo(5, 6);
31 p.lineTo(7, 8);
32 p.lineTo(5, 9);
33 p.close();
reed@google.comd9f2dea2011-10-12 14:43:27 +000034 s.clipDevPath(p, SkRegion::kIntersect_Op, doAA);
vandebo@chromium.org1e1c36f2011-05-03 16:26:09 +000035
36 s.save();
robertphillips@google.com80214e22012-07-20 15:33:18 +000037 REPORTER_ASSERT(reporter, 2 == s.getSaveCount());
38
vandebo@chromium.org1e1c36f2011-05-03 16:26:09 +000039 SkRect r = SkRect::MakeLTRB(1, 2, 3, 4);
reed@google.comd9f2dea2011-10-12 14:43:27 +000040 s.clipDevRect(r, SkRegion::kIntersect_Op, doAA);
vandebo@chromium.org1e1c36f2011-05-03 16:26:09 +000041 r = SkRect::MakeLTRB(10, 11, 12, 13);
reed@google.comd9f2dea2011-10-12 14:43:27 +000042 s.clipDevRect(r, SkRegion::kIntersect_Op, doAA);
vandebo@chromium.org1e1c36f2011-05-03 16:26:09 +000043
44 s.save();
robertphillips@google.com80214e22012-07-20 15:33:18 +000045 REPORTER_ASSERT(reporter, 3 == s.getSaveCount());
46
vandebo@chromium.org1e1c36f2011-05-03 16:26:09 +000047 r = SkRect::MakeLTRB(14, 15, 16, 17);
reed@google.comd9f2dea2011-10-12 14:43:27 +000048 s.clipDevRect(r, SkRegion::kUnion_Op, doAA);
vandebo@chromium.org1e1c36f2011-05-03 16:26:09 +000049
50 // Test that assignment works.
51 SkClipStack copy = s;
52 REPORTER_ASSERT(reporter, s == copy);
53
54 // Test that different save levels triggers not equal.
55 s.restore();
robertphillips@google.com80214e22012-07-20 15:33:18 +000056 REPORTER_ASSERT(reporter, 2 == s.getSaveCount());
vandebo@chromium.org1e1c36f2011-05-03 16:26:09 +000057 REPORTER_ASSERT(reporter, s != copy);
58
59 // Test that an equal, but not copied version is equal.
60 s.save();
robertphillips@google.com80214e22012-07-20 15:33:18 +000061 REPORTER_ASSERT(reporter, 3 == s.getSaveCount());
62
vandebo@chromium.org1e1c36f2011-05-03 16:26:09 +000063 r = SkRect::MakeLTRB(14, 15, 16, 17);
reed@google.comd9f2dea2011-10-12 14:43:27 +000064 s.clipDevRect(r, SkRegion::kUnion_Op, doAA);
vandebo@chromium.org1e1c36f2011-05-03 16:26:09 +000065 REPORTER_ASSERT(reporter, s == copy);
66
67 // Test that a different op on one level triggers not equal.
68 s.restore();
robertphillips@google.com80214e22012-07-20 15:33:18 +000069 REPORTER_ASSERT(reporter, 2 == s.getSaveCount());
vandebo@chromium.org1e1c36f2011-05-03 16:26:09 +000070 s.save();
robertphillips@google.com80214e22012-07-20 15:33:18 +000071 REPORTER_ASSERT(reporter, 3 == s.getSaveCount());
72
vandebo@chromium.org1e1c36f2011-05-03 16:26:09 +000073 r = SkRect::MakeLTRB(14, 15, 16, 17);
reed@google.comd9f2dea2011-10-12 14:43:27 +000074 s.clipDevRect(r, SkRegion::kIntersect_Op, doAA);
vandebo@chromium.org1e1c36f2011-05-03 16:26:09 +000075 REPORTER_ASSERT(reporter, s != copy);
76
77 // Test that different state (clip type) triggers not equal.
tomhudson@google.com4c433722012-03-09 16:48:20 +000078 // NO LONGER VALID: if a path contains only a rect, we turn
79 // it into a bare rect for performance reasons (working
80 // around Chromium/JavaScript bad pattern).
81/*
vandebo@chromium.org1e1c36f2011-05-03 16:26:09 +000082 s.restore();
83 s.save();
84 SkPath rp;
85 rp.addRect(r);
reed@google.comd9f2dea2011-10-12 14:43:27 +000086 s.clipDevPath(rp, SkRegion::kUnion_Op, doAA);
vandebo@chromium.org1e1c36f2011-05-03 16:26:09 +000087 REPORTER_ASSERT(reporter, s != copy);
tomhudson@google.com4c433722012-03-09 16:48:20 +000088*/
vandebo@chromium.org1e1c36f2011-05-03 16:26:09 +000089
90 // Test that different rects triggers not equal.
91 s.restore();
robertphillips@google.com80214e22012-07-20 15:33:18 +000092 REPORTER_ASSERT(reporter, 2 == s.getSaveCount());
vandebo@chromium.org1e1c36f2011-05-03 16:26:09 +000093 s.save();
robertphillips@google.com80214e22012-07-20 15:33:18 +000094 REPORTER_ASSERT(reporter, 3 == s.getSaveCount());
95
vandebo@chromium.org1e1c36f2011-05-03 16:26:09 +000096 r = SkRect::MakeLTRB(24, 25, 26, 27);
reed@google.comd9f2dea2011-10-12 14:43:27 +000097 s.clipDevRect(r, SkRegion::kUnion_Op, doAA);
vandebo@chromium.org1e1c36f2011-05-03 16:26:09 +000098 REPORTER_ASSERT(reporter, s != copy);
99
100 // Sanity check
101 s.restore();
robertphillips@google.com80214e22012-07-20 15:33:18 +0000102 REPORTER_ASSERT(reporter, 2 == s.getSaveCount());
103
vandebo@chromium.org1e1c36f2011-05-03 16:26:09 +0000104 copy.restore();
robertphillips@google.com80214e22012-07-20 15:33:18 +0000105 REPORTER_ASSERT(reporter, 2 == copy.getSaveCount());
vandebo@chromium.org1e1c36f2011-05-03 16:26:09 +0000106 REPORTER_ASSERT(reporter, s == copy);
107 s.restore();
robertphillips@google.com80214e22012-07-20 15:33:18 +0000108 REPORTER_ASSERT(reporter, 1 == s.getSaveCount());
vandebo@chromium.org1e1c36f2011-05-03 16:26:09 +0000109 copy.restore();
robertphillips@google.com80214e22012-07-20 15:33:18 +0000110 REPORTER_ASSERT(reporter, 1 == copy.getSaveCount());
vandebo@chromium.org1e1c36f2011-05-03 16:26:09 +0000111 REPORTER_ASSERT(reporter, s == copy);
112
113 // Test that different paths triggers not equal.
114 s.restore();
robertphillips@google.com80214e22012-07-20 15:33:18 +0000115 REPORTER_ASSERT(reporter, 0 == s.getSaveCount());
vandebo@chromium.org1e1c36f2011-05-03 16:26:09 +0000116 s.save();
robertphillips@google.com80214e22012-07-20 15:33:18 +0000117 REPORTER_ASSERT(reporter, 1 == s.getSaveCount());
118
vandebo@chromium.org1e1c36f2011-05-03 16:26:09 +0000119 p.addRect(r);
reed@google.comd9f2dea2011-10-12 14:43:27 +0000120 s.clipDevPath(p, SkRegion::kIntersect_Op, doAA);
vandebo@chromium.org1e1c36f2011-05-03 16:26:09 +0000121 REPORTER_ASSERT(reporter, s != copy);
122}
reed@google.combdee9fc2011-02-22 20:17:43 +0000123
124static void assert_count(skiatest::Reporter* reporter, const SkClipStack& stack,
125 int count) {
robertphillips@google.com80214e22012-07-20 15:33:18 +0000126 SkClipStack::B2TIter iter(stack);
reed@google.combdee9fc2011-02-22 20:17:43 +0000127 int counter = 0;
128 while (iter.next()) {
129 counter += 1;
130 }
131 REPORTER_ASSERT(reporter, count == counter);
132}
133
robertphillips@google.com08eacc12012-08-02 12:49:00 +0000134// Exercise the SkClipStack's bottom to top and bidirectional iterators
135// (including the skipToTopmost functionality)
robertphillips@google.com80214e22012-07-20 15:33:18 +0000136static void test_iterators(skiatest::Reporter* reporter) {
137 SkClipStack stack;
138
139 static const SkRect gRects[] = {
140 { 0, 0, 40, 40 },
141 { 60, 0, 100, 40 },
142 { 0, 60, 40, 100 },
143 { 60, 60, 100, 100 }
144 };
145
146 for (size_t i = 0; i < SK_ARRAY_COUNT(gRects); i++) {
147 // the union op will prevent these from being fused together
148 stack.clipDevRect(gRects[i], SkRegion::kUnion_Op, false);
149 }
150
151 assert_count(reporter, stack, 4);
152
153 // bottom to top iteration
154 {
155 const SkClipStack::B2TIter::Clip* clip = NULL;
156
157 SkClipStack::B2TIter iter(stack);
158 int i;
159
160 for (i = 0, clip = iter.next(); clip; ++i, clip = iter.next()) {
161 REPORTER_ASSERT(reporter, *clip->fRect == gRects[i]);
162 }
163
164 SkASSERT(i == 4);
165 }
166
167 // top to bottom iteration
168 {
169 const SkClipStack::Iter::Clip* clip = NULL;
170
171 SkClipStack::Iter iter(stack, SkClipStack::Iter::kTop_IterStart);
172 int i;
173
174 for (i = 3, clip = iter.prev(); clip; --i, clip = iter.prev()) {
175 REPORTER_ASSERT(reporter, *clip->fRect == gRects[i]);
176 }
177
178 SkASSERT(i == -1);
179 }
180
181 // skipToTopmost
182 {
183 const SkClipStack::Iter::Clip*clip = NULL;
184
185 SkClipStack::Iter iter(stack, SkClipStack::Iter::kBottom_IterStart);
186
187 clip = iter.skipToTopmost(SkRegion::kUnion_Op);
188 REPORTER_ASSERT(reporter, *clip->fRect == gRects[3]);
189 }
190}
191
robertphillips@google.com08eacc12012-08-02 12:49:00 +0000192// Exercise the SkClipStack's getConservativeBounds computation
robertphillips@google.com4c2a2f72012-07-24 22:07:50 +0000193static void test_bounds(skiatest::Reporter* reporter, bool useRects) {
robertphillips@google.com607fe072012-07-24 13:54:00 +0000194
195 static const int gNumCases = 20;
196 static const SkRect gAnswerRectsBW[gNumCases] = {
197 // A op B
198 { 40, 40, 50, 50 },
199 { 10, 10, 50, 50 },
200 { 10, 10, 80, 80 },
201 { 10, 10, 80, 80 },
202 { 40, 40, 80, 80 },
203
204 // invA op B
205 { 40, 40, 80, 80 },
206 { 0, 0, 100, 100 },
207 { 0, 0, 100, 100 },
208 { 0, 0, 100, 100 },
209 { 40, 40, 50, 50 },
210
211 // A op invB
212 { 10, 10, 50, 50 },
213 { 40, 40, 50, 50 },
214 { 0, 0, 100, 100 },
215 { 0, 0, 100, 100 },
216 { 0, 0, 100, 100 },
217
218 // invA op invB
219 { 0, 0, 100, 100 },
220 { 40, 40, 80, 80 },
221 { 0, 0, 100, 100 },
222 { 10, 10, 80, 80 },
223 { 10, 10, 50, 50 },
224 };
225
226 static const SkRegion::Op gOps[] = {
227 SkRegion::kIntersect_Op,
228 SkRegion::kDifference_Op,
229 SkRegion::kUnion_Op,
230 SkRegion::kXOR_Op,
231 SkRegion::kReverseDifference_Op
232 };
233
234 SkRect rectA, rectB;
235
236 rectA.iset(10, 10, 50, 50);
237 rectB.iset(40, 40, 80, 80);
238
239 SkPath clipA, clipB;
240
241 clipA.addRoundRect(rectA, SkIntToScalar(5), SkIntToScalar(5));
242 clipB.addRoundRect(rectB, SkIntToScalar(5), SkIntToScalar(5));
243
244 SkClipStack stack;
robertphillips@google.com7b112892012-07-31 15:18:21 +0000245 SkRect devClipBound;
robertphillips@google.com4c2a2f72012-07-24 22:07:50 +0000246 bool isIntersectionOfRects = false;
robertphillips@google.com607fe072012-07-24 13:54:00 +0000247
248 int testCase = 0;
robertphillips@google.com4c2a2f72012-07-24 22:07:50 +0000249 int numBitTests = useRects ? 1 : 4;
250 for (int invBits = 0; invBits < numBitTests; ++invBits) {
robertphillips@google.com607fe072012-07-24 13:54:00 +0000251 for (size_t op = 0; op < SK_ARRAY_COUNT(gOps); ++op) {
252
253 stack.save();
254 bool doInvA = SkToBool(invBits & 1);
255 bool doInvB = SkToBool(invBits & 2);
256
257 clipA.setFillType(doInvA ? SkPath::kInverseEvenOdd_FillType :
258 SkPath::kEvenOdd_FillType);
259 clipB.setFillType(doInvB ? SkPath::kInverseEvenOdd_FillType :
260 SkPath::kEvenOdd_FillType);
261
robertphillips@google.com4c2a2f72012-07-24 22:07:50 +0000262 if (useRects) {
263 stack.clipDevRect(rectA, SkRegion::kIntersect_Op, false);
264 stack.clipDevRect(rectB, gOps[op], false);
265 } else {
266 stack.clipDevPath(clipA, SkRegion::kIntersect_Op, false);
267 stack.clipDevPath(clipB, gOps[op], false);
268 }
robertphillips@google.com607fe072012-07-24 13:54:00 +0000269
robertphillips@google.comcc6493b2012-07-26 18:39:13 +0000270 REPORTER_ASSERT(reporter, !stack.isWideOpen());
271
robertphillips@google.com7b112892012-07-31 15:18:21 +0000272 stack.getConservativeBounds(0, 0, 100, 100, &devClipBound,
robertphillips@google.com4c2a2f72012-07-24 22:07:50 +0000273 &isIntersectionOfRects);
274
275 if (useRects) {
rmistry@google.comd6176b02012-08-23 18:14:13 +0000276 REPORTER_ASSERT(reporter, isIntersectionOfRects ==
robertphillips@google.com4c2a2f72012-07-24 22:07:50 +0000277 (gOps[op] == SkRegion::kIntersect_Op));
278 } else {
279 REPORTER_ASSERT(reporter, !isIntersectionOfRects);
280 }
robertphillips@google.com607fe072012-07-24 13:54:00 +0000281
282 SkASSERT(testCase < gNumCases);
robertphillips@google.com7b112892012-07-31 15:18:21 +0000283 REPORTER_ASSERT(reporter, devClipBound == gAnswerRectsBW[testCase]);
robertphillips@google.com607fe072012-07-24 13:54:00 +0000284 ++testCase;
285
286 stack.restore();
287 }
288 }
289}
290
robertphillips@google.comcc6493b2012-07-26 18:39:13 +0000291// Test out 'isWideOpen' entry point
292static void test_isWideOpen(skiatest::Reporter* reporter) {
293
294 SkRect rectA, rectB;
295
296 rectA.iset(10, 10, 40, 40);
297 rectB.iset(50, 50, 80, 80);
298
299 // Stack should initially be wide open
300 {
301 SkClipStack stack;
302
303 REPORTER_ASSERT(reporter, stack.isWideOpen());
304 }
305
306 // Test out case where the user specifies a union that includes everything
307 {
308 SkClipStack stack;
309
310 SkPath clipA, clipB;
311
312 clipA.addRoundRect(rectA, SkIntToScalar(5), SkIntToScalar(5));
313 clipA.setFillType(SkPath::kInverseEvenOdd_FillType);
314
315 clipB.addRoundRect(rectB, SkIntToScalar(5), SkIntToScalar(5));
316 clipB.setFillType(SkPath::kInverseEvenOdd_FillType);
317
318 stack.clipDevPath(clipA, SkRegion::kReplace_Op, false);
319 stack.clipDevPath(clipB, SkRegion::kUnion_Op, false);
320
321 REPORTER_ASSERT(reporter, stack.isWideOpen());
322 }
323
324 // Test out union w/ a wide open clip
325 {
326 SkClipStack stack;
327
328 stack.clipDevRect(rectA, SkRegion::kUnion_Op, false);
329
330 REPORTER_ASSERT(reporter, stack.isWideOpen());
331 }
332
333 // Test out empty difference from a wide open clip
334 {
335 SkClipStack stack;
336
337 SkRect emptyRect;
338 emptyRect.setEmpty();
339
340 stack.clipDevRect(emptyRect, SkRegion::kDifference_Op, false);
341
342 REPORTER_ASSERT(reporter, stack.isWideOpen());
343 }
344
345 // Test out return to wide open
346 {
347 SkClipStack stack;
348
349 stack.save();
350
351 stack.clipDevRect(rectA, SkRegion::kReplace_Op, false);
352
353 REPORTER_ASSERT(reporter, !stack.isWideOpen());
354
355 stack.restore();
356
357 REPORTER_ASSERT(reporter, stack.isWideOpen());
358 }
359}
360
bsalomon@google.com100abf42012-09-05 17:40:04 +0000361static int count(const SkClipStack& stack) {
robertphillips@google.com08eacc12012-08-02 12:49:00 +0000362
363 SkClipStack::Iter iter(stack, SkClipStack::Iter::kTop_IterStart);
364
365 const SkClipStack::Iter::Clip* clip = NULL;
366 int count = 0;
367
368 for (clip = iter.prev(); clip; clip = iter.prev(), ++count) {
369 ;
370 }
371
372 return count;
373}
374
375// Test out SkClipStack's merging of rect clips. In particular exercise
376// merging of aa vs. bw rects.
377static void test_rect_merging(skiatest::Reporter* reporter) {
378
379 SkRect overlapLeft = SkRect::MakeLTRB(10, 10, 50, 50);
380 SkRect overlapRight = SkRect::MakeLTRB(40, 40, 80, 80);
381
382 SkRect nestedParent = SkRect::MakeLTRB(10, 10, 90, 90);
383 SkRect nestedChild = SkRect::MakeLTRB(40, 40, 60, 60);
384
385 SkRect bound;
386 SkClipStack::BoundsType type;
387 bool isIntersectionOfRects;
388
389 // all bw overlapping - should merge
390 {
391 SkClipStack stack;
392
393 stack.clipDevRect(overlapLeft, SkRegion::kReplace_Op, false);
394
395 stack.clipDevRect(overlapRight, SkRegion::kIntersect_Op, false);
396
397 REPORTER_ASSERT(reporter, 1 == count(stack));
398
399 stack.getBounds(&bound, &type, &isIntersectionOfRects);
400
401 REPORTER_ASSERT(reporter, isIntersectionOfRects);
402 }
403
404 // all aa overlapping - should merge
405 {
406 SkClipStack stack;
407
408 stack.clipDevRect(overlapLeft, SkRegion::kReplace_Op, true);
409
410 stack.clipDevRect(overlapRight, SkRegion::kIntersect_Op, true);
411
412 REPORTER_ASSERT(reporter, 1 == count(stack));
413
414 stack.getBounds(&bound, &type, &isIntersectionOfRects);
415
416 REPORTER_ASSERT(reporter, isIntersectionOfRects);
417 }
418
419 // mixed overlapping - should _not_ merge
420 {
421 SkClipStack stack;
422
423 stack.clipDevRect(overlapLeft, SkRegion::kReplace_Op, true);
424
425 stack.clipDevRect(overlapRight, SkRegion::kIntersect_Op, false);
426
427 REPORTER_ASSERT(reporter, 2 == count(stack));
428
429 stack.getBounds(&bound, &type, &isIntersectionOfRects);
430
431 REPORTER_ASSERT(reporter, !isIntersectionOfRects);
432 }
433
434 // mixed nested (bw inside aa) - should merge
435 {
436 SkClipStack stack;
437
438 stack.clipDevRect(nestedParent, SkRegion::kReplace_Op, true);
439
440 stack.clipDevRect(nestedChild, SkRegion::kIntersect_Op, false);
441
442 REPORTER_ASSERT(reporter, 1 == count(stack));
443
444 stack.getBounds(&bound, &type, &isIntersectionOfRects);
445
446 REPORTER_ASSERT(reporter, isIntersectionOfRects);
447 }
448
449 // mixed nested (aa inside bw) - should merge
450 {
451 SkClipStack stack;
452
453 stack.clipDevRect(nestedParent, SkRegion::kReplace_Op, false);
454
455 stack.clipDevRect(nestedChild, SkRegion::kIntersect_Op, true);
456
457 REPORTER_ASSERT(reporter, 1 == count(stack));
458
459 stack.getBounds(&bound, &type, &isIntersectionOfRects);
460
461 REPORTER_ASSERT(reporter, isIntersectionOfRects);
462 }
463
464 // reverse nested (aa inside bw) - should _not_ merge
465 {
466 SkClipStack stack;
467
468 stack.clipDevRect(nestedChild, SkRegion::kReplace_Op, false);
469
470 stack.clipDevRect(nestedParent, SkRegion::kIntersect_Op, true);
471
472 REPORTER_ASSERT(reporter, 2 == count(stack));
473
474 stack.getBounds(&bound, &type, &isIntersectionOfRects);
475
476 REPORTER_ASSERT(reporter, !isIntersectionOfRects);
477 }
478}
robertphillips@google.comcc6493b2012-07-26 18:39:13 +0000479
bsalomon@google.come8ca6c62012-11-07 21:19:10 +0000480
481// This is similar to the above test but tests the iterator's ability to merge rects in the
482// middle of a clip stack's sequence using nextCombined(). There is a save after every clip
483// element to prevent the clip stack from merging the rectangles as they are added.
484static void test_iter_rect_merging(skiatest::Reporter* reporter) {
485
486 SkRect overlapLeft = SkRect::MakeLTRB(10, 10, 50, 50);
487 SkRect overlapRight = SkRect::MakeLTRB(40, 40, 80, 80);
488
489 SkRect nestedParent = SkRect::MakeLTRB(10, 10, 90, 90);
490 SkRect nestedChild = SkRect::MakeLTRB(40, 40, 60, 60);
491
492 SkRect farAway = SkRect::MakeLTRB(1000, 1000, 1010, 1010);
493
494 SkRect overlapIntersect;
495 overlapIntersect.intersect(overlapLeft, overlapRight);
496
497 SkPath path1, path2;
498 path1.addCircle(SkIntToScalar(30), SkIntToScalar(30), SkIntToScalar(1000));
499 path2.addOval(SkRect::MakeWH(500, 600));
500
501 const SkClipStack::Iter::Clip* clip;
502
503 // call nextCombined with an empty clip stack
504 {
505 SkClipStack stack;
506 SkClipStack::Iter iter(stack, SkClipStack::Iter::kBottom_IterStart);
507 REPORTER_ASSERT(reporter, NULL == iter.nextCombined());
508 }
509
510 // two bw overlapping - should merge, bracketed by paths
511 {
512 SkClipStack stack;
513 stack.clipDevPath(path1, SkRegion::kIntersect_Op, false); stack.save();
514
515 stack.clipDevRect(overlapLeft, SkRegion::kIntersect_Op, false); stack.save();
516
517 stack.clipDevRect(overlapRight, SkRegion::kIntersect_Op, false); stack.save();
518
519 stack.clipDevPath(path2, SkRegion::kIntersect_Op, false); stack.save();
520
521 SkClipStack::Iter iter(stack, SkClipStack::Iter::kBottom_IterStart);
522
523 clip = iter.nextCombined();
524 REPORTER_ASSERT(reporter, *clip->fPath == path1 && !clip->fDoAA);
525
526 clip = iter.nextCombined();
527 REPORTER_ASSERT(reporter, !clip->fDoAA && *clip->fRect == overlapIntersect);
528
529 clip = iter.nextCombined();
530 REPORTER_ASSERT(reporter, *clip->fPath == path2 && !clip->fDoAA);
531
532 clip = iter.nextCombined();
533 REPORTER_ASSERT(reporter, NULL == clip);
534 }
535
536 // same as above but rects are aa and no final path.
537 {
538 SkClipStack stack;
539 stack.clipDevPath(path1, SkRegion::kIntersect_Op, false); stack.save();
540
541 stack.clipDevRect(overlapLeft, SkRegion::kIntersect_Op, true); stack.save();
542
543 stack.clipDevRect(overlapRight, SkRegion::kIntersect_Op, true); stack.save();
544
545 SkClipStack::Iter iter(stack, SkClipStack::Iter::kBottom_IterStart);
546
547 clip = iter.nextCombined();
548 REPORTER_ASSERT(reporter, *clip->fPath == path1 && !clip->fDoAA);
549
550 clip = iter.nextCombined();
551 REPORTER_ASSERT(reporter, clip->fDoAA && *clip->fRect == overlapIntersect);
552
553 clip = iter.nextCombined();
554 REPORTER_ASSERT(reporter, NULL == clip);
555 }
556
557 // mixed overlapping - no paths - should _not_ merge
558 {
559 SkClipStack stack;
560
561 stack.clipDevRect(overlapLeft, SkRegion::kIntersect_Op, true); stack.save();
562 stack.clipDevRect(overlapRight, SkRegion::kIntersect_Op, false); stack.save();
563
564 SkClipStack::Iter iter(stack, SkClipStack::Iter::kBottom_IterStart);
565
566 clip = iter.nextCombined();
567 REPORTER_ASSERT(reporter, clip->fDoAA && *clip->fRect == overlapLeft);
568
569 clip = iter.nextCombined();
570 REPORTER_ASSERT(reporter, !clip->fDoAA && *clip->fRect == overlapRight);
571
572 clip = iter.nextCombined();
573 REPORTER_ASSERT(reporter, NULL == clip);
574 }
575
576 // three rects in a row where the third rect uses a non-intersect op.
577 {
578 SkClipStack stack;
579
580 stack.clipDevRect(overlapLeft, SkRegion::kIntersect_Op, true); stack.save();
581 stack.clipDevRect(overlapRight, SkRegion::kIntersect_Op, true); stack.save();
582 stack.clipDevRect(nestedParent, SkRegion::kXOR_Op, true); stack.save();
583
584 SkClipStack::Iter iter(stack, SkClipStack::Iter::kBottom_IterStart);
585
586 clip = iter.nextCombined();
587 REPORTER_ASSERT(reporter, clip->fDoAA && *clip->fRect == overlapIntersect);
588 clip = iter.nextCombined();
589 REPORTER_ASSERT(reporter, clip->fDoAA && *clip->fRect == nestedParent);
590 clip = iter.nextCombined();
591 REPORTER_ASSERT(reporter, NULL == clip);
592 }
593
594 // mixed nested (bw inside aa) - should merge
595 {
596 SkClipStack stack;
597 stack.clipDevRect(nestedParent, SkRegion::kIntersect_Op, false); stack.save();
598
599 stack.clipDevRect(nestedChild, SkRegion::kIntersect_Op, true); stack.save();
600
601 SkClipStack::Iter iter(stack, SkClipStack::Iter::kBottom_IterStart);
skia.committer@gmail.com72b2e6f2012-11-08 02:03:56 +0000602
bsalomon@google.come8ca6c62012-11-07 21:19:10 +0000603 clip = iter.nextCombined();
604 REPORTER_ASSERT(reporter, clip->fDoAA && *clip->fRect == nestedChild);
605
606 clip = iter.nextCombined();
607 REPORTER_ASSERT(reporter, NULL == clip);
608 }
609
610 // mixed nested (aa inside bw) - should merge
611 {
612 SkClipStack stack;
613 stack.clipDevRect(nestedChild, SkRegion::kIntersect_Op, false); stack.save();
614
615 stack.clipDevRect(nestedParent, SkRegion::kIntersect_Op, true); stack.save();
616
617 SkClipStack::Iter iter(stack, SkClipStack::Iter::kBottom_IterStart);
skia.committer@gmail.com72b2e6f2012-11-08 02:03:56 +0000618
bsalomon@google.come8ca6c62012-11-07 21:19:10 +0000619 clip = iter.nextCombined();
620 REPORTER_ASSERT(reporter, !clip->fDoAA && *clip->fRect == nestedChild);
621
622 clip = iter.nextCombined();
623 REPORTER_ASSERT(reporter, NULL == clip);
624 }
625
626 // three rect intersects in a row where result is empty after the second.
627 {
628 SkClipStack stack;
629
630 stack.clipDevRect(overlapLeft, SkRegion::kIntersect_Op, false); stack.save();
631 stack.clipDevRect(farAway, SkRegion::kIntersect_Op, false); stack.save();
632 stack.clipDevRect(overlapRight, SkRegion::kIntersect_Op, false); stack.save();
633
634 SkClipStack::Iter iter(stack, SkClipStack::Iter::kBottom_IterStart);
635
636 clip = iter.nextCombined();
637 REPORTER_ASSERT(reporter, clip->fRect->isEmpty());
638
639 clip = iter.nextCombined();
640 REPORTER_ASSERT(reporter, *clip->fRect == overlapRight);
641
642 clip = iter.nextCombined();
643 REPORTER_ASSERT(reporter, NULL == clip);
644 }
645}
646
bsalomon@google.com51a62862012-11-26 21:19:43 +0000647///////////////////////////////////////////////////////////////////////////////////////////////////
648
bsalomon@google.coma4e13c82012-11-26 21:38:37 +0000649#if SK_SUPPORT_GPU
bsalomon@google.com705e8402012-11-27 15:43:57 +0000650// Functions that add a shape to the clip stack. The shape is computed from a rectangle.
651// AA is always disabled since the clip stack reducer can cause changes in aa rasterization of the
652// stack. A fractional edge repeated in different elements may be rasterized fewer times using the
653// reduced stack.
654typedef void (*AddElementFunc) (const SkRect& rect,
655 bool invert,
656 SkRegion::Op op,
657 SkClipStack* stack);
bsalomon@google.coma4e13c82012-11-26 21:38:37 +0000658
bsalomon@google.com705e8402012-11-27 15:43:57 +0000659static void add_round_rect(const SkRect& rect, bool invert, SkRegion::Op op, SkClipStack* stack) {
bsalomon@google.com51a62862012-11-26 21:19:43 +0000660 SkPath path;
661 SkScalar rx = rect.width() / 10;
bsalomon@google.com705e8402012-11-27 15:43:57 +0000662 SkScalar ry = rect.height() / 20;
bsalomon@google.com51a62862012-11-26 21:19:43 +0000663 path.addRoundRect(rect, rx, ry);
bsalomon@google.com705e8402012-11-27 15:43:57 +0000664 if (invert) {
665 path.setFillType(SkPath::kInverseWinding_FillType);
666 }
667 stack->clipDevPath(path, op, false);
bsalomon@google.com51a62862012-11-26 21:19:43 +0000668};
669
bsalomon@google.com705e8402012-11-27 15:43:57 +0000670static void add_rect(const SkRect& rect, bool invert, SkRegion::Op op, SkClipStack* stack) {
671 if (invert) {
672 SkPath path;
673 path.addRect(rect);
674 path.setFillType(SkPath::kInverseWinding_FillType);
675 stack->clipDevPath(path, op, false);
676 } else {
677 stack->clipDevRect(rect, op, false);
678 }
bsalomon@google.com51a62862012-11-26 21:19:43 +0000679};
680
bsalomon@google.com705e8402012-11-27 15:43:57 +0000681static void add_oval(const SkRect& rect, bool invert, SkRegion::Op op, SkClipStack* stack) {
bsalomon@google.com51a62862012-11-26 21:19:43 +0000682 SkPath path;
683 path.addOval(rect);
bsalomon@google.com705e8402012-11-27 15:43:57 +0000684 if (invert) {
685 path.setFillType(SkPath::kInverseWinding_FillType);
686 }
687 stack->clipDevPath(path, op, false);
bsalomon@google.com51a62862012-11-26 21:19:43 +0000688};
689
690static void add_elem_to_stack(const SkClipStack::Iter::Clip& clip, SkClipStack* stack) {
691 if (NULL != clip.fPath) {
692 stack->clipDevPath(*clip.fPath, clip.fOp, clip.fDoAA);
693 } else if (NULL != clip.fRect) {
694 stack->clipDevRect(*clip.fRect, clip.fOp, clip.fDoAA);
695 }
696}
697
698static void add_elem_to_region(const SkClipStack::Iter::Clip& clip,
699 const SkIRect& bounds,
700 SkRegion* region) {
701 SkRegion elemRegion;
702 SkRegion boundsRgn(bounds);
703
704 if (NULL != clip.fPath) {
705 elemRegion.setPath(*clip.fPath, boundsRgn);
706 } else if (NULL != clip.fRect) {
707 SkPath path;
708 path.addRect(*clip.fRect);
709 elemRegion.setPath(path, boundsRgn);
710 } else {
711 // TODO: Figure out why we sometimes get here in the reduced clip stack.
712 region->setEmpty();
713 return;
714 }
715 region->op(elemRegion, clip.fOp);
716}
717
718// This can assist with debugging the clip stack reduction code when the test below fails.
719static void print_clip(const SkClipStack::Iter::Clip& clip) {
720 static const char* kOpStrs[] = {
721 "DF",
722 "IS",
723 "UN",
724 "XR",
725 "RD",
726 "RP",
727 };
bsalomon@google.com705e8402012-11-27 15:43:57 +0000728 if (NULL != clip.fRect || NULL != clip.fPath) {
bsalomon@google.com51a62862012-11-26 21:19:43 +0000729 const SkRect& bounds = clip.getBounds();
bsalomon@google.com705e8402012-11-27 15:43:57 +0000730 SkDebugf("%s %s %s [%f %f] x [%f %f]\n",
bsalomon@google.com51a62862012-11-26 21:19:43 +0000731 kOpStrs[clip.fOp],
bsalomon@google.com705e8402012-11-27 15:43:57 +0000732 (NULL != clip.fRect ? "R" : "P"),
733 ((NULL != clip.fPath && clip.fPath->isInverseFillType() ? "I" : " ")),
bsalomon@google.com51a62862012-11-26 21:19:43 +0000734 bounds.fLeft, bounds.fRight, bounds.fTop, bounds.fBottom);
735 } else {
736 SkDebugf("EM\n");
737 }
738}
739
740static void test_reduced_clip_stack(skiatest::Reporter* reporter) {
741 // We construct random clip stacks, reduce them, and then rasterize both versions to verify that
skia.committer@gmail.com8ccf5902012-11-27 02:01:19 +0000742 // they are equal.
bsalomon@google.com51a62862012-11-26 21:19:43 +0000743
744 // All the clip elements will be contained within these bounds.
745 static const SkRect kBounds = SkRect::MakeWH(100, 100);
746
747 enum {
748 kNumTests = 200,
749 kMinElemsPerTest = 1,
750 kMaxElemsPerTest = 50,
751 };
752
753 // min/max size of a clip element as a fraction of kBounds.
754 static const SkScalar kMinElemSizeFrac = SK_Scalar1 / 5;
755 static const SkScalar kMaxElemSizeFrac = SK_Scalar1;
756
757 static const SkRegion::Op kOps[] = {
758 SkRegion::kDifference_Op,
759 SkRegion::kIntersect_Op,
760 SkRegion::kUnion_Op,
761 SkRegion::kXOR_Op,
762 SkRegion::kReverseDifference_Op,
763 SkRegion::kReplace_Op,
764 };
765
766 // Replace operations short-circuit the optimizer. We want to make sure that we test this code
767 // path a little bit but we don't want it to prevent us from testing many longer traversals in
768 // the optimizer.
769 static const int kReplaceDiv = 4 * kMaxElemsPerTest;
770
bsalomon@google.com705e8402012-11-27 15:43:57 +0000771 // We want to test inverse fills. However, they are quite rare in practice so don't over do it.
772 static const SkScalar kFractionInverted = SK_Scalar1 / kMaxElemsPerTest;
773
bsalomon@google.com51a62862012-11-26 21:19:43 +0000774 static const AddElementFunc kElementFuncs[] = {
775 add_rect,
776 add_round_rect,
777 add_oval,
778 };
779
780 SkRandom r;
781
782 for (int i = 0; i < kNumTests; ++i) {
783 // Randomly generate a clip stack.
784 SkClipStack stack;
785 int numElems = r.nextRangeU(kMinElemsPerTest, kMaxElemsPerTest);
786 for (int e = 0; e < numElems; ++e) {
787 SkRegion::Op op = kOps[r.nextULessThan(SK_ARRAY_COUNT(kOps))];
788 if (op == SkRegion::kReplace_Op) {
789 if (r.nextU() % kReplaceDiv) {
790 --e;
791 continue;
792 }
793 }
skia.committer@gmail.com8ccf5902012-11-27 02:01:19 +0000794
bsalomon@google.com51a62862012-11-26 21:19:43 +0000795 // saves can change the clip stack behavior when an element is added.
796 bool doSave = r.nextBool();
skia.committer@gmail.com8ccf5902012-11-27 02:01:19 +0000797
bsalomon@google.com51a62862012-11-26 21:19:43 +0000798 SkSize size = SkSize::Make(
799 SkScalarFloorToScalar(SkScalarMul(kBounds.width(), r.nextRangeScalar(kMinElemSizeFrac, kMaxElemSizeFrac))),
800 SkScalarFloorToScalar(SkScalarMul(kBounds.height(), r.nextRangeScalar(kMinElemSizeFrac, kMaxElemSizeFrac))));
801
802 SkPoint xy = {SkScalarFloorToScalar(r.nextRangeScalar(kBounds.fLeft, kBounds.fRight - size.fWidth)),
803 SkScalarFloorToScalar(r.nextRangeScalar(kBounds.fTop, kBounds.fBottom - size.fHeight))};
804
805 SkRect rect = SkRect::MakeXYWH(xy.fX, xy.fY, size.fWidth, size.fHeight);
806
bsalomon@google.com705e8402012-11-27 15:43:57 +0000807 bool invert = r.nextBiasedBool(kFractionInverted);
808 kElementFuncs[r.nextULessThan(SK_ARRAY_COUNT(kElementFuncs))](rect, invert, op, &stack);
bsalomon@google.com51a62862012-11-26 21:19:43 +0000809 if (doSave) {
810 stack.save();
811 }
812 }
813
814 // Get the reduced version of the stack.
815 SkTDArray<SkClipStack::Iter::Clip> reducedClips;
816 SkRect resultBounds;
817 bool bounded;
818 GrReducedClip::InitialState initial;
819 GrReducedClip::GrReduceClipStack(stack, &reducedClips, &resultBounds, &bounded, &initial);
820
821 // Build a new clip stack based on the reduced clip elements
822 SkClipStack reducedStack;
823 if (GrReducedClip::kAllOut_InitialState == initial) {
824 // whether the result is bounded or not, the whole plane should start outside the clip.
825 reducedStack.clipEmpty();
826 }
827 for (int c = 0; c < reducedClips.count(); ++c) {
828 add_elem_to_stack(reducedClips[c], &reducedStack);
829 }
830 if (bounded) {
831 // GrReduceClipStack() assumes that there is an implicit clip to the bounds
832 reducedStack.clipDevRect(resultBounds, SkRegion::kIntersect_Op, true);
833 }
834
835 // convert both the original stack and reduced stack to SkRegions and see if they're equal
836 SkRect inflatedBounds = kBounds;
837 inflatedBounds.outset(kBounds.width() / 2, kBounds.height() / 2);
838 SkIRect inflatedIBounds;
839 inflatedBounds.roundOut(&inflatedIBounds);
840
841 SkRegion region;
842 SkRegion reducedRegion;
843
844 region.setRect(inflatedIBounds);
845 const SkClipStack::Iter::Clip* clip;
846 SkClipStack::Iter iter(stack, SkClipStack::Iter::kBottom_IterStart);
847 while ((clip = iter.next())) {
848 add_elem_to_region(*clip, inflatedIBounds, &region);
849 }
850
851 reducedRegion.setRect(inflatedIBounds);
852 iter.reset(reducedStack, SkClipStack::Iter::kBottom_IterStart);
853 while ((clip = iter.next())) {
854 add_elem_to_region(*clip, inflatedIBounds, &reducedRegion);
855 }
856
857 REPORTER_ASSERT(reporter, region == reducedRegion);
858 }
859}
860
bsalomon@google.coma4e13c82012-11-26 21:38:37 +0000861#endif
bsalomon@google.com51a62862012-11-26 21:19:43 +0000862///////////////////////////////////////////////////////////////////////////////////////////////////
863
reed@google.combdee9fc2011-02-22 20:17:43 +0000864static void TestClipStack(skiatest::Reporter* reporter) {
865 SkClipStack stack;
866
robertphillips@google.com80214e22012-07-20 15:33:18 +0000867 REPORTER_ASSERT(reporter, 0 == stack.getSaveCount());
reed@google.combdee9fc2011-02-22 20:17:43 +0000868 assert_count(reporter, stack, 0);
869
870 static const SkIRect gRects[] = {
871 { 0, 0, 100, 100 },
872 { 25, 25, 125, 125 },
873 { 0, 0, 1000, 1000 },
874 { 0, 0, 75, 75 }
875 };
876 for (size_t i = 0; i < SK_ARRAY_COUNT(gRects); i++) {
reed@google.comd9f2dea2011-10-12 14:43:27 +0000877 stack.clipDevRect(gRects[i], SkRegion::kIntersect_Op);
reed@google.combdee9fc2011-02-22 20:17:43 +0000878 }
879
880 // all of the above rects should have been intersected, leaving only 1 rect
robertphillips@google.com80214e22012-07-20 15:33:18 +0000881 SkClipStack::B2TIter iter(stack);
882 const SkClipStack::B2TIter::Clip* clip = iter.next();
epoger@google.com2047f002011-05-17 17:36:59 +0000883 SkRect answer;
884 answer.iset(25, 25, 75, 75);
reed@google.combdee9fc2011-02-22 20:17:43 +0000885
886 REPORTER_ASSERT(reporter, clip);
887 REPORTER_ASSERT(reporter, clip->fRect);
888 REPORTER_ASSERT(reporter, !clip->fPath);
889 REPORTER_ASSERT(reporter, SkRegion::kIntersect_Op == clip->fOp);
890 REPORTER_ASSERT(reporter, *clip->fRect == answer);
891 // now check that we only had one in our iterator
892 REPORTER_ASSERT(reporter, !iter.next());
893
894 stack.reset();
robertphillips@google.com80214e22012-07-20 15:33:18 +0000895 REPORTER_ASSERT(reporter, 0 == stack.getSaveCount());
reed@google.combdee9fc2011-02-22 20:17:43 +0000896 assert_count(reporter, stack, 0);
vandebo@chromium.org1e1c36f2011-05-03 16:26:09 +0000897
898 test_assign_and_comparison(reporter);
robertphillips@google.com80214e22012-07-20 15:33:18 +0000899 test_iterators(reporter);
robertphillips@google.com08eacc12012-08-02 12:49:00 +0000900 test_bounds(reporter, true); // once with rects
901 test_bounds(reporter, false); // once with paths
robertphillips@google.comcc6493b2012-07-26 18:39:13 +0000902 test_isWideOpen(reporter);
robertphillips@google.com08eacc12012-08-02 12:49:00 +0000903 test_rect_merging(reporter);
bsalomon@google.come8ca6c62012-11-07 21:19:10 +0000904 test_iter_rect_merging(reporter);
bsalomon@google.come7b3d292012-11-26 21:42:32 +0000905#if SK_SUPPORT_GPU
bsalomon@google.com51a62862012-11-26 21:19:43 +0000906 test_reduced_clip_stack(reporter);
bsalomon@google.come7b3d292012-11-26 21:42:32 +0000907#endif
reed@google.combdee9fc2011-02-22 20:17:43 +0000908}
909
910#include "TestClassDef.h"
911DEFINE_TESTCLASS("ClipStack", TestClipStackClass, TestClipStack)