GrAppliedClip was about at its limit for how many "make" functions it could have. Window rectangles would push it over the edge. This change makes it so GrDrawTarget supplies the original draw bounds to the constructor, and then GrClip adds the various required clipping techniques. BUG=skia: GOLD_TRYBOT_URL= https://gold.skia.org/search?issue=2246113002 Review-Url: https://codereview.chromium.org/2246113002
1386 lines
51 KiB
C++
1386 lines
51 KiB
C++
/*
|
|
* Copyright 2011 Google Inc.
|
|
*
|
|
* Use of this source code is governed by a BSD-style license that can be
|
|
* found in the LICENSE file.
|
|
*/
|
|
|
|
#include "Test.h"
|
|
#include "SkClipStack.h"
|
|
#include "SkPath.h"
|
|
#include "SkRandom.h"
|
|
#include "SkRect.h"
|
|
#include "SkRegion.h"
|
|
|
|
#if SK_SUPPORT_GPU
|
|
#include "GrReducedClip.h"
|
|
typedef GrReducedClip::ElementList ElementList;
|
|
typedef GrReducedClip::InitialState InitialState;
|
|
#endif
|
|
|
|
static void test_assign_and_comparison(skiatest::Reporter* reporter) {
|
|
SkClipStack s;
|
|
bool doAA = false;
|
|
|
|
REPORTER_ASSERT(reporter, 0 == s.getSaveCount());
|
|
|
|
// Build up a clip stack with a path, an empty clip, and a rect.
|
|
s.save();
|
|
REPORTER_ASSERT(reporter, 1 == s.getSaveCount());
|
|
|
|
SkPath p;
|
|
p.moveTo(5, 6);
|
|
p.lineTo(7, 8);
|
|
p.lineTo(5, 9);
|
|
p.close();
|
|
s.clipDevPath(p, SkRegion::kIntersect_Op, doAA);
|
|
|
|
s.save();
|
|
REPORTER_ASSERT(reporter, 2 == s.getSaveCount());
|
|
|
|
SkRect r = SkRect::MakeLTRB(1, 2, 3, 4);
|
|
s.clipDevRect(r, SkRegion::kIntersect_Op, doAA);
|
|
r = SkRect::MakeLTRB(10, 11, 12, 13);
|
|
s.clipDevRect(r, SkRegion::kIntersect_Op, doAA);
|
|
|
|
s.save();
|
|
REPORTER_ASSERT(reporter, 3 == s.getSaveCount());
|
|
|
|
r = SkRect::MakeLTRB(14, 15, 16, 17);
|
|
s.clipDevRect(r, SkRegion::kUnion_Op, doAA);
|
|
|
|
// Test that assignment works.
|
|
SkClipStack copy = s;
|
|
REPORTER_ASSERT(reporter, s == copy);
|
|
|
|
// Test that different save levels triggers not equal.
|
|
s.restore();
|
|
REPORTER_ASSERT(reporter, 2 == s.getSaveCount());
|
|
REPORTER_ASSERT(reporter, s != copy);
|
|
|
|
// Test that an equal, but not copied version is equal.
|
|
s.save();
|
|
REPORTER_ASSERT(reporter, 3 == s.getSaveCount());
|
|
r = SkRect::MakeLTRB(14, 15, 16, 17);
|
|
s.clipDevRect(r, SkRegion::kUnion_Op, doAA);
|
|
REPORTER_ASSERT(reporter, s == copy);
|
|
|
|
// Test that a different op on one level triggers not equal.
|
|
s.restore();
|
|
REPORTER_ASSERT(reporter, 2 == s.getSaveCount());
|
|
s.save();
|
|
REPORTER_ASSERT(reporter, 3 == s.getSaveCount());
|
|
r = SkRect::MakeLTRB(14, 15, 16, 17);
|
|
s.clipDevRect(r, SkRegion::kIntersect_Op, doAA);
|
|
REPORTER_ASSERT(reporter, s != copy);
|
|
|
|
// Test that version constructed with rect-path rather than a rect is still considered equal.
|
|
s.restore();
|
|
s.save();
|
|
SkPath rp;
|
|
rp.addRect(r);
|
|
s.clipDevPath(rp, SkRegion::kUnion_Op, doAA);
|
|
REPORTER_ASSERT(reporter, s == copy);
|
|
|
|
// Test that different rects triggers not equal.
|
|
s.restore();
|
|
REPORTER_ASSERT(reporter, 2 == s.getSaveCount());
|
|
s.save();
|
|
REPORTER_ASSERT(reporter, 3 == s.getSaveCount());
|
|
|
|
r = SkRect::MakeLTRB(24, 25, 26, 27);
|
|
s.clipDevRect(r, SkRegion::kUnion_Op, doAA);
|
|
REPORTER_ASSERT(reporter, s != copy);
|
|
|
|
// Sanity check
|
|
s.restore();
|
|
REPORTER_ASSERT(reporter, 2 == s.getSaveCount());
|
|
|
|
copy.restore();
|
|
REPORTER_ASSERT(reporter, 2 == copy.getSaveCount());
|
|
REPORTER_ASSERT(reporter, s == copy);
|
|
s.restore();
|
|
REPORTER_ASSERT(reporter, 1 == s.getSaveCount());
|
|
copy.restore();
|
|
REPORTER_ASSERT(reporter, 1 == copy.getSaveCount());
|
|
REPORTER_ASSERT(reporter, s == copy);
|
|
|
|
// Test that different paths triggers not equal.
|
|
s.restore();
|
|
REPORTER_ASSERT(reporter, 0 == s.getSaveCount());
|
|
s.save();
|
|
REPORTER_ASSERT(reporter, 1 == s.getSaveCount());
|
|
|
|
p.addRect(r);
|
|
s.clipDevPath(p, SkRegion::kIntersect_Op, doAA);
|
|
REPORTER_ASSERT(reporter, s != copy);
|
|
}
|
|
|
|
static void assert_count(skiatest::Reporter* reporter, const SkClipStack& stack,
|
|
int count) {
|
|
SkClipStack::B2TIter iter(stack);
|
|
int counter = 0;
|
|
while (iter.next()) {
|
|
counter += 1;
|
|
}
|
|
REPORTER_ASSERT(reporter, count == counter);
|
|
}
|
|
|
|
// Exercise the SkClipStack's bottom to top and bidirectional iterators
|
|
// (including the skipToTopmost functionality)
|
|
static void test_iterators(skiatest::Reporter* reporter) {
|
|
SkClipStack stack;
|
|
|
|
static const SkRect gRects[] = {
|
|
{ 0, 0, 40, 40 },
|
|
{ 60, 0, 100, 40 },
|
|
{ 0, 60, 40, 100 },
|
|
{ 60, 60, 100, 100 }
|
|
};
|
|
|
|
for (size_t i = 0; i < SK_ARRAY_COUNT(gRects); i++) {
|
|
// the union op will prevent these from being fused together
|
|
stack.clipDevRect(gRects[i], SkRegion::kUnion_Op, false);
|
|
}
|
|
|
|
assert_count(reporter, stack, 4);
|
|
|
|
// bottom to top iteration
|
|
{
|
|
const SkClipStack::Element* element = nullptr;
|
|
|
|
SkClipStack::B2TIter iter(stack);
|
|
int i;
|
|
|
|
for (i = 0, element = iter.next(); element; ++i, element = iter.next()) {
|
|
REPORTER_ASSERT(reporter, SkClipStack::Element::kRect_Type == element->getType());
|
|
REPORTER_ASSERT(reporter, element->getRect() == gRects[i]);
|
|
}
|
|
|
|
SkASSERT(i == 4);
|
|
}
|
|
|
|
// top to bottom iteration
|
|
{
|
|
const SkClipStack::Element* element = nullptr;
|
|
|
|
SkClipStack::Iter iter(stack, SkClipStack::Iter::kTop_IterStart);
|
|
int i;
|
|
|
|
for (i = 3, element = iter.prev(); element; --i, element = iter.prev()) {
|
|
REPORTER_ASSERT(reporter, SkClipStack::Element::kRect_Type == element->getType());
|
|
REPORTER_ASSERT(reporter, element->getRect() == gRects[i]);
|
|
}
|
|
|
|
SkASSERT(i == -1);
|
|
}
|
|
|
|
// skipToTopmost
|
|
{
|
|
const SkClipStack::Element* element = nullptr;
|
|
|
|
SkClipStack::Iter iter(stack, SkClipStack::Iter::kBottom_IterStart);
|
|
|
|
element = iter.skipToTopmost(SkRegion::kUnion_Op);
|
|
REPORTER_ASSERT(reporter, SkClipStack::Element::kRect_Type == element->getType());
|
|
REPORTER_ASSERT(reporter, element->getRect() == gRects[3]);
|
|
}
|
|
}
|
|
|
|
// Exercise the SkClipStack's getConservativeBounds computation
|
|
static void test_bounds(skiatest::Reporter* reporter, SkClipStack::Element::Type primType) {
|
|
static const int gNumCases = 20;
|
|
static const SkRect gAnswerRectsBW[gNumCases] = {
|
|
// A op B
|
|
{ 40, 40, 50, 50 },
|
|
{ 10, 10, 50, 50 },
|
|
{ 10, 10, 80, 80 },
|
|
{ 10, 10, 80, 80 },
|
|
{ 40, 40, 80, 80 },
|
|
|
|
// invA op B
|
|
{ 40, 40, 80, 80 },
|
|
{ 0, 0, 100, 100 },
|
|
{ 0, 0, 100, 100 },
|
|
{ 0, 0, 100, 100 },
|
|
{ 40, 40, 50, 50 },
|
|
|
|
// A op invB
|
|
{ 10, 10, 50, 50 },
|
|
{ 40, 40, 50, 50 },
|
|
{ 0, 0, 100, 100 },
|
|
{ 0, 0, 100, 100 },
|
|
{ 0, 0, 100, 100 },
|
|
|
|
// invA op invB
|
|
{ 0, 0, 100, 100 },
|
|
{ 40, 40, 80, 80 },
|
|
{ 0, 0, 100, 100 },
|
|
{ 10, 10, 80, 80 },
|
|
{ 10, 10, 50, 50 },
|
|
};
|
|
|
|
static const SkRegion::Op gOps[] = {
|
|
SkRegion::kIntersect_Op,
|
|
SkRegion::kDifference_Op,
|
|
SkRegion::kUnion_Op,
|
|
SkRegion::kXOR_Op,
|
|
SkRegion::kReverseDifference_Op
|
|
};
|
|
|
|
SkRect rectA, rectB;
|
|
|
|
rectA.iset(10, 10, 50, 50);
|
|
rectB.iset(40, 40, 80, 80);
|
|
|
|
SkRRect rrectA, rrectB;
|
|
rrectA.setOval(rectA);
|
|
rrectB.setRectXY(rectB, SkIntToScalar(1), SkIntToScalar(2));
|
|
|
|
SkPath pathA, pathB;
|
|
|
|
pathA.addRoundRect(rectA, SkIntToScalar(5), SkIntToScalar(5));
|
|
pathB.addRoundRect(rectB, SkIntToScalar(5), SkIntToScalar(5));
|
|
|
|
SkClipStack stack;
|
|
SkRect devClipBound;
|
|
bool isIntersectionOfRects = false;
|
|
|
|
int testCase = 0;
|
|
int numBitTests = SkClipStack::Element::kPath_Type == primType ? 4 : 1;
|
|
for (int invBits = 0; invBits < numBitTests; ++invBits) {
|
|
for (size_t op = 0; op < SK_ARRAY_COUNT(gOps); ++op) {
|
|
|
|
stack.save();
|
|
bool doInvA = SkToBool(invBits & 1);
|
|
bool doInvB = SkToBool(invBits & 2);
|
|
|
|
pathA.setFillType(doInvA ? SkPath::kInverseEvenOdd_FillType :
|
|
SkPath::kEvenOdd_FillType);
|
|
pathB.setFillType(doInvB ? SkPath::kInverseEvenOdd_FillType :
|
|
SkPath::kEvenOdd_FillType);
|
|
|
|
switch (primType) {
|
|
case SkClipStack::Element::kEmpty_Type:
|
|
SkDEBUGFAIL("Don't call this with kEmpty.");
|
|
break;
|
|
case SkClipStack::Element::kRect_Type:
|
|
stack.clipDevRect(rectA, SkRegion::kIntersect_Op, false);
|
|
stack.clipDevRect(rectB, gOps[op], false);
|
|
break;
|
|
case SkClipStack::Element::kRRect_Type:
|
|
stack.clipDevRRect(rrectA, SkRegion::kIntersect_Op, false);
|
|
stack.clipDevRRect(rrectB, gOps[op], false);
|
|
break;
|
|
case SkClipStack::Element::kPath_Type:
|
|
stack.clipDevPath(pathA, SkRegion::kIntersect_Op, false);
|
|
stack.clipDevPath(pathB, gOps[op], false);
|
|
break;
|
|
}
|
|
|
|
REPORTER_ASSERT(reporter, !stack.isWideOpen());
|
|
REPORTER_ASSERT(reporter, SkClipStack::kWideOpenGenID != stack.getTopmostGenID());
|
|
|
|
stack.getConservativeBounds(0, 0, 100, 100, &devClipBound,
|
|
&isIntersectionOfRects);
|
|
|
|
if (SkClipStack::Element::kRect_Type == primType) {
|
|
REPORTER_ASSERT(reporter, isIntersectionOfRects ==
|
|
(gOps[op] == SkRegion::kIntersect_Op));
|
|
} else {
|
|
REPORTER_ASSERT(reporter, !isIntersectionOfRects);
|
|
}
|
|
|
|
SkASSERT(testCase < gNumCases);
|
|
REPORTER_ASSERT(reporter, devClipBound == gAnswerRectsBW[testCase]);
|
|
++testCase;
|
|
|
|
stack.restore();
|
|
}
|
|
}
|
|
}
|
|
|
|
// Test out 'isWideOpen' entry point
|
|
static void test_isWideOpen(skiatest::Reporter* reporter) {
|
|
{
|
|
// Empty stack is wide open. Wide open stack means that gen id is wide open.
|
|
SkClipStack stack;
|
|
REPORTER_ASSERT(reporter, stack.isWideOpen());
|
|
REPORTER_ASSERT(reporter, SkClipStack::kWideOpenGenID == stack.getTopmostGenID());
|
|
}
|
|
|
|
SkRect rectA, rectB;
|
|
|
|
rectA.iset(10, 10, 40, 40);
|
|
rectB.iset(50, 50, 80, 80);
|
|
|
|
// Stack should initially be wide open
|
|
{
|
|
SkClipStack stack;
|
|
|
|
REPORTER_ASSERT(reporter, stack.isWideOpen());
|
|
REPORTER_ASSERT(reporter, SkClipStack::kWideOpenGenID == stack.getTopmostGenID());
|
|
}
|
|
|
|
// Test out case where the user specifies a union that includes everything
|
|
{
|
|
SkClipStack stack;
|
|
|
|
SkPath clipA, clipB;
|
|
|
|
clipA.addRoundRect(rectA, SkIntToScalar(5), SkIntToScalar(5));
|
|
clipA.setFillType(SkPath::kInverseEvenOdd_FillType);
|
|
|
|
clipB.addRoundRect(rectB, SkIntToScalar(5), SkIntToScalar(5));
|
|
clipB.setFillType(SkPath::kInverseEvenOdd_FillType);
|
|
|
|
stack.clipDevPath(clipA, SkRegion::kReplace_Op, false);
|
|
stack.clipDevPath(clipB, SkRegion::kUnion_Op, false);
|
|
|
|
REPORTER_ASSERT(reporter, stack.isWideOpen());
|
|
REPORTER_ASSERT(reporter, SkClipStack::kWideOpenGenID == stack.getTopmostGenID());
|
|
}
|
|
|
|
// Test out union w/ a wide open clip
|
|
{
|
|
SkClipStack stack;
|
|
|
|
stack.clipDevRect(rectA, SkRegion::kUnion_Op, false);
|
|
|
|
REPORTER_ASSERT(reporter, stack.isWideOpen());
|
|
REPORTER_ASSERT(reporter, SkClipStack::kWideOpenGenID == stack.getTopmostGenID());
|
|
}
|
|
|
|
// Test out empty difference from a wide open clip
|
|
{
|
|
SkClipStack stack;
|
|
|
|
SkRect emptyRect;
|
|
emptyRect.setEmpty();
|
|
|
|
stack.clipDevRect(emptyRect, SkRegion::kDifference_Op, false);
|
|
|
|
REPORTER_ASSERT(reporter, stack.isWideOpen());
|
|
REPORTER_ASSERT(reporter, SkClipStack::kWideOpenGenID == stack.getTopmostGenID());
|
|
}
|
|
|
|
// Test out return to wide open
|
|
{
|
|
SkClipStack stack;
|
|
|
|
stack.save();
|
|
|
|
stack.clipDevRect(rectA, SkRegion::kReplace_Op, false);
|
|
|
|
REPORTER_ASSERT(reporter, !stack.isWideOpen());
|
|
REPORTER_ASSERT(reporter, SkClipStack::kWideOpenGenID != stack.getTopmostGenID());
|
|
|
|
stack.restore();
|
|
|
|
REPORTER_ASSERT(reporter, stack.isWideOpen());
|
|
REPORTER_ASSERT(reporter, SkClipStack::kWideOpenGenID == stack.getTopmostGenID());
|
|
}
|
|
}
|
|
|
|
static int count(const SkClipStack& stack) {
|
|
|
|
SkClipStack::Iter iter(stack, SkClipStack::Iter::kTop_IterStart);
|
|
|
|
const SkClipStack::Element* element = nullptr;
|
|
int count = 0;
|
|
|
|
for (element = iter.prev(); element; element = iter.prev(), ++count) {
|
|
;
|
|
}
|
|
|
|
return count;
|
|
}
|
|
|
|
static void test_rect_inverse_fill(skiatest::Reporter* reporter) {
|
|
// non-intersecting rectangles
|
|
SkRect rect = SkRect::MakeLTRB(0, 0, 10, 10);
|
|
|
|
SkPath path;
|
|
path.addRect(rect);
|
|
path.toggleInverseFillType();
|
|
SkClipStack stack;
|
|
stack.clipDevPath(path, SkRegion::kIntersect_Op, false);
|
|
|
|
SkRect bounds;
|
|
SkClipStack::BoundsType boundsType;
|
|
stack.getBounds(&bounds, &boundsType);
|
|
REPORTER_ASSERT(reporter, SkClipStack::kInsideOut_BoundsType == boundsType);
|
|
REPORTER_ASSERT(reporter, bounds == rect);
|
|
}
|
|
|
|
static void test_rect_replace(skiatest::Reporter* reporter) {
|
|
SkRect rect = SkRect::MakeWH(100, 100);
|
|
SkRect rect2 = SkRect::MakeXYWH(50, 50, 100, 100);
|
|
|
|
SkRect bound;
|
|
SkClipStack::BoundsType type;
|
|
bool isIntersectionOfRects;
|
|
|
|
// Adding a new rect with the replace operator should not increase
|
|
// the stack depth. BW replacing BW.
|
|
{
|
|
SkClipStack stack;
|
|
REPORTER_ASSERT(reporter, 0 == count(stack));
|
|
stack.clipDevRect(rect, SkRegion::kReplace_Op, false);
|
|
REPORTER_ASSERT(reporter, 1 == count(stack));
|
|
stack.clipDevRect(rect, SkRegion::kReplace_Op, false);
|
|
REPORTER_ASSERT(reporter, 1 == count(stack));
|
|
}
|
|
|
|
// Adding a new rect with the replace operator should not increase
|
|
// the stack depth. AA replacing AA.
|
|
{
|
|
SkClipStack stack;
|
|
REPORTER_ASSERT(reporter, 0 == count(stack));
|
|
stack.clipDevRect(rect, SkRegion::kReplace_Op, true);
|
|
REPORTER_ASSERT(reporter, 1 == count(stack));
|
|
stack.clipDevRect(rect, SkRegion::kReplace_Op, true);
|
|
REPORTER_ASSERT(reporter, 1 == count(stack));
|
|
}
|
|
|
|
// Adding a new rect with the replace operator should not increase
|
|
// the stack depth. BW replacing AA replacing BW.
|
|
{
|
|
SkClipStack stack;
|
|
REPORTER_ASSERT(reporter, 0 == count(stack));
|
|
stack.clipDevRect(rect, SkRegion::kReplace_Op, false);
|
|
REPORTER_ASSERT(reporter, 1 == count(stack));
|
|
stack.clipDevRect(rect, SkRegion::kReplace_Op, true);
|
|
REPORTER_ASSERT(reporter, 1 == count(stack));
|
|
stack.clipDevRect(rect, SkRegion::kReplace_Op, false);
|
|
REPORTER_ASSERT(reporter, 1 == count(stack));
|
|
}
|
|
|
|
// Make sure replace clip rects don't collapse too much.
|
|
{
|
|
SkClipStack stack;
|
|
stack.clipDevRect(rect, SkRegion::kReplace_Op, false);
|
|
stack.clipDevRect(rect2, SkRegion::kIntersect_Op, false);
|
|
REPORTER_ASSERT(reporter, 1 == count(stack));
|
|
|
|
stack.save();
|
|
stack.clipDevRect(rect, SkRegion::kReplace_Op, false);
|
|
REPORTER_ASSERT(reporter, 2 == count(stack));
|
|
stack.getBounds(&bound, &type, &isIntersectionOfRects);
|
|
REPORTER_ASSERT(reporter, bound == rect);
|
|
stack.restore();
|
|
REPORTER_ASSERT(reporter, 1 == count(stack));
|
|
|
|
stack.save();
|
|
stack.clipDevRect(rect, SkRegion::kReplace_Op, false);
|
|
stack.clipDevRect(rect, SkRegion::kReplace_Op, false);
|
|
REPORTER_ASSERT(reporter, 2 == count(stack));
|
|
stack.restore();
|
|
REPORTER_ASSERT(reporter, 1 == count(stack));
|
|
|
|
stack.save();
|
|
stack.clipDevRect(rect, SkRegion::kReplace_Op, false);
|
|
stack.clipDevRect(rect2, SkRegion::kIntersect_Op, false);
|
|
stack.clipDevRect(rect, SkRegion::kReplace_Op, false);
|
|
REPORTER_ASSERT(reporter, 2 == count(stack));
|
|
stack.restore();
|
|
REPORTER_ASSERT(reporter, 1 == count(stack));
|
|
}
|
|
}
|
|
|
|
// Simplified path-based version of test_rect_replace.
|
|
static void test_path_replace(skiatest::Reporter* reporter) {
|
|
SkRect rect = SkRect::MakeWH(100, 100);
|
|
SkPath path;
|
|
path.addCircle(50, 50, 50);
|
|
|
|
// Replace operation doesn't grow the stack.
|
|
{
|
|
SkClipStack stack;
|
|
REPORTER_ASSERT(reporter, 0 == count(stack));
|
|
stack.clipDevPath(path, SkRegion::kReplace_Op, false);
|
|
REPORTER_ASSERT(reporter, 1 == count(stack));
|
|
stack.clipDevPath(path, SkRegion::kReplace_Op, false);
|
|
REPORTER_ASSERT(reporter, 1 == count(stack));
|
|
}
|
|
|
|
// Replacing rect with path.
|
|
{
|
|
SkClipStack stack;
|
|
stack.clipDevRect(rect, SkRegion::kReplace_Op, true);
|
|
REPORTER_ASSERT(reporter, 1 == count(stack));
|
|
stack.clipDevPath(path, SkRegion::kReplace_Op, true);
|
|
REPORTER_ASSERT(reporter, 1 == count(stack));
|
|
}
|
|
}
|
|
|
|
// Test out SkClipStack's merging of rect clips. In particular exercise
|
|
// merging of aa vs. bw rects.
|
|
static void test_rect_merging(skiatest::Reporter* reporter) {
|
|
|
|
SkRect overlapLeft = SkRect::MakeLTRB(10, 10, 50, 50);
|
|
SkRect overlapRight = SkRect::MakeLTRB(40, 40, 80, 80);
|
|
|
|
SkRect nestedParent = SkRect::MakeLTRB(10, 10, 90, 90);
|
|
SkRect nestedChild = SkRect::MakeLTRB(40, 40, 60, 60);
|
|
|
|
SkRect bound;
|
|
SkClipStack::BoundsType type;
|
|
bool isIntersectionOfRects;
|
|
|
|
// all bw overlapping - should merge
|
|
{
|
|
SkClipStack stack;
|
|
|
|
stack.clipDevRect(overlapLeft, SkRegion::kReplace_Op, false);
|
|
|
|
stack.clipDevRect(overlapRight, SkRegion::kIntersect_Op, false);
|
|
|
|
REPORTER_ASSERT(reporter, 1 == count(stack));
|
|
|
|
stack.getBounds(&bound, &type, &isIntersectionOfRects);
|
|
|
|
REPORTER_ASSERT(reporter, isIntersectionOfRects);
|
|
}
|
|
|
|
// all aa overlapping - should merge
|
|
{
|
|
SkClipStack stack;
|
|
|
|
stack.clipDevRect(overlapLeft, SkRegion::kReplace_Op, true);
|
|
|
|
stack.clipDevRect(overlapRight, SkRegion::kIntersect_Op, true);
|
|
|
|
REPORTER_ASSERT(reporter, 1 == count(stack));
|
|
|
|
stack.getBounds(&bound, &type, &isIntersectionOfRects);
|
|
|
|
REPORTER_ASSERT(reporter, isIntersectionOfRects);
|
|
}
|
|
|
|
// mixed overlapping - should _not_ merge
|
|
{
|
|
SkClipStack stack;
|
|
|
|
stack.clipDevRect(overlapLeft, SkRegion::kReplace_Op, true);
|
|
|
|
stack.clipDevRect(overlapRight, SkRegion::kIntersect_Op, false);
|
|
|
|
REPORTER_ASSERT(reporter, 2 == count(stack));
|
|
|
|
stack.getBounds(&bound, &type, &isIntersectionOfRects);
|
|
|
|
REPORTER_ASSERT(reporter, !isIntersectionOfRects);
|
|
}
|
|
|
|
// mixed nested (bw inside aa) - should merge
|
|
{
|
|
SkClipStack stack;
|
|
|
|
stack.clipDevRect(nestedParent, SkRegion::kReplace_Op, true);
|
|
|
|
stack.clipDevRect(nestedChild, SkRegion::kIntersect_Op, false);
|
|
|
|
REPORTER_ASSERT(reporter, 1 == count(stack));
|
|
|
|
stack.getBounds(&bound, &type, &isIntersectionOfRects);
|
|
|
|
REPORTER_ASSERT(reporter, isIntersectionOfRects);
|
|
}
|
|
|
|
// mixed nested (aa inside bw) - should merge
|
|
{
|
|
SkClipStack stack;
|
|
|
|
stack.clipDevRect(nestedParent, SkRegion::kReplace_Op, false);
|
|
|
|
stack.clipDevRect(nestedChild, SkRegion::kIntersect_Op, true);
|
|
|
|
REPORTER_ASSERT(reporter, 1 == count(stack));
|
|
|
|
stack.getBounds(&bound, &type, &isIntersectionOfRects);
|
|
|
|
REPORTER_ASSERT(reporter, isIntersectionOfRects);
|
|
}
|
|
|
|
// reverse nested (aa inside bw) - should _not_ merge
|
|
{
|
|
SkClipStack stack;
|
|
|
|
stack.clipDevRect(nestedChild, SkRegion::kReplace_Op, false);
|
|
|
|
stack.clipDevRect(nestedParent, SkRegion::kIntersect_Op, true);
|
|
|
|
REPORTER_ASSERT(reporter, 2 == count(stack));
|
|
|
|
stack.getBounds(&bound, &type, &isIntersectionOfRects);
|
|
|
|
REPORTER_ASSERT(reporter, !isIntersectionOfRects);
|
|
}
|
|
}
|
|
|
|
static void test_quickContains(skiatest::Reporter* reporter) {
|
|
SkRect testRect = SkRect::MakeLTRB(10, 10, 40, 40);
|
|
SkRect insideRect = SkRect::MakeLTRB(20, 20, 30, 30);
|
|
SkRect intersectingRect = SkRect::MakeLTRB(25, 25, 50, 50);
|
|
SkRect outsideRect = SkRect::MakeLTRB(0, 0, 50, 50);
|
|
SkRect nonIntersectingRect = SkRect::MakeLTRB(100, 100, 110, 110);
|
|
|
|
SkPath insideCircle;
|
|
insideCircle.addCircle(25, 25, 5);
|
|
SkPath intersectingCircle;
|
|
intersectingCircle.addCircle(25, 40, 10);
|
|
SkPath outsideCircle;
|
|
outsideCircle.addCircle(25, 25, 50);
|
|
SkPath nonIntersectingCircle;
|
|
nonIntersectingCircle.addCircle(100, 100, 5);
|
|
|
|
{
|
|
SkClipStack stack;
|
|
stack.clipDevRect(outsideRect, SkRegion::kDifference_Op, false);
|
|
// return false because quickContains currently does not care for kDifference_Op
|
|
REPORTER_ASSERT(reporter, false == stack.quickContains(testRect));
|
|
}
|
|
|
|
// Replace Op tests
|
|
{
|
|
SkClipStack stack;
|
|
stack.clipDevRect(outsideRect, SkRegion::kReplace_Op, false);
|
|
REPORTER_ASSERT(reporter, true == stack.quickContains(testRect));
|
|
}
|
|
|
|
{
|
|
SkClipStack stack;
|
|
stack.clipDevRect(insideRect, SkRegion::kIntersect_Op, false);
|
|
stack.save(); // To prevent in-place substitution by replace OP
|
|
stack.clipDevRect(outsideRect, SkRegion::kReplace_Op, false);
|
|
REPORTER_ASSERT(reporter, true == stack.quickContains(testRect));
|
|
stack.restore();
|
|
}
|
|
|
|
{
|
|
SkClipStack stack;
|
|
stack.clipDevRect(outsideRect, SkRegion::kIntersect_Op, false);
|
|
stack.save(); // To prevent in-place substitution by replace OP
|
|
stack.clipDevRect(insideRect, SkRegion::kReplace_Op, false);
|
|
REPORTER_ASSERT(reporter, false == stack.quickContains(testRect));
|
|
stack.restore();
|
|
}
|
|
|
|
// Verify proper traversal of multi-element clip
|
|
{
|
|
SkClipStack stack;
|
|
stack.clipDevRect(insideRect, SkRegion::kIntersect_Op, false);
|
|
// Use a path for second clip to prevent in-place intersection
|
|
stack.clipDevPath(outsideCircle, SkRegion::kIntersect_Op, false);
|
|
REPORTER_ASSERT(reporter, false == stack.quickContains(testRect));
|
|
}
|
|
|
|
// Intersect Op tests with rectangles
|
|
{
|
|
SkClipStack stack;
|
|
stack.clipDevRect(outsideRect, SkRegion::kIntersect_Op, false);
|
|
REPORTER_ASSERT(reporter, true == stack.quickContains(testRect));
|
|
}
|
|
|
|
{
|
|
SkClipStack stack;
|
|
stack.clipDevRect(insideRect, SkRegion::kIntersect_Op, false);
|
|
REPORTER_ASSERT(reporter, false == stack.quickContains(testRect));
|
|
}
|
|
|
|
{
|
|
SkClipStack stack;
|
|
stack.clipDevRect(intersectingRect, SkRegion::kIntersect_Op, false);
|
|
REPORTER_ASSERT(reporter, false == stack.quickContains(testRect));
|
|
}
|
|
|
|
{
|
|
SkClipStack stack;
|
|
stack.clipDevRect(nonIntersectingRect, SkRegion::kIntersect_Op, false);
|
|
REPORTER_ASSERT(reporter, false == stack.quickContains(testRect));
|
|
}
|
|
|
|
// Intersect Op tests with circle paths
|
|
{
|
|
SkClipStack stack;
|
|
stack.clipDevPath(outsideCircle, SkRegion::kIntersect_Op, false);
|
|
REPORTER_ASSERT(reporter, true == stack.quickContains(testRect));
|
|
}
|
|
|
|
{
|
|
SkClipStack stack;
|
|
stack.clipDevPath(insideCircle, SkRegion::kIntersect_Op, false);
|
|
REPORTER_ASSERT(reporter, false == stack.quickContains(testRect));
|
|
}
|
|
|
|
{
|
|
SkClipStack stack;
|
|
stack.clipDevPath(intersectingCircle, SkRegion::kIntersect_Op, false);
|
|
REPORTER_ASSERT(reporter, false == stack.quickContains(testRect));
|
|
}
|
|
|
|
{
|
|
SkClipStack stack;
|
|
stack.clipDevPath(nonIntersectingCircle, SkRegion::kIntersect_Op, false);
|
|
REPORTER_ASSERT(reporter, false == stack.quickContains(testRect));
|
|
}
|
|
|
|
// Intersect Op tests with inverse filled rectangles
|
|
{
|
|
SkClipStack stack;
|
|
SkPath path;
|
|
path.addRect(outsideRect);
|
|
path.toggleInverseFillType();
|
|
stack.clipDevPath(path, SkRegion::kIntersect_Op, false);
|
|
REPORTER_ASSERT(reporter, false == stack.quickContains(testRect));
|
|
}
|
|
|
|
{
|
|
SkClipStack stack;
|
|
SkPath path;
|
|
path.addRect(insideRect);
|
|
path.toggleInverseFillType();
|
|
stack.clipDevPath(path, SkRegion::kIntersect_Op, false);
|
|
REPORTER_ASSERT(reporter, false == stack.quickContains(testRect));
|
|
}
|
|
|
|
{
|
|
SkClipStack stack;
|
|
SkPath path;
|
|
path.addRect(intersectingRect);
|
|
path.toggleInverseFillType();
|
|
stack.clipDevPath(path, SkRegion::kIntersect_Op, false);
|
|
REPORTER_ASSERT(reporter, false == stack.quickContains(testRect));
|
|
}
|
|
|
|
{
|
|
SkClipStack stack;
|
|
SkPath path;
|
|
path.addRect(nonIntersectingRect);
|
|
path.toggleInverseFillType();
|
|
stack.clipDevPath(path, SkRegion::kIntersect_Op, false);
|
|
REPORTER_ASSERT(reporter, true == stack.quickContains(testRect));
|
|
}
|
|
|
|
// Intersect Op tests with inverse filled circles
|
|
{
|
|
SkClipStack stack;
|
|
SkPath path = outsideCircle;
|
|
path.toggleInverseFillType();
|
|
stack.clipDevPath(path, SkRegion::kIntersect_Op, false);
|
|
REPORTER_ASSERT(reporter, false == stack.quickContains(testRect));
|
|
}
|
|
|
|
{
|
|
SkClipStack stack;
|
|
SkPath path = insideCircle;
|
|
path.toggleInverseFillType();
|
|
stack.clipDevPath(path, SkRegion::kIntersect_Op, false);
|
|
REPORTER_ASSERT(reporter, false == stack.quickContains(testRect));
|
|
}
|
|
|
|
{
|
|
SkClipStack stack;
|
|
SkPath path = intersectingCircle;
|
|
path.toggleInverseFillType();
|
|
stack.clipDevPath(path, SkRegion::kIntersect_Op, false);
|
|
REPORTER_ASSERT(reporter, false == stack.quickContains(testRect));
|
|
}
|
|
|
|
{
|
|
SkClipStack stack;
|
|
SkPath path = nonIntersectingCircle;
|
|
path.toggleInverseFillType();
|
|
stack.clipDevPath(path, SkRegion::kIntersect_Op, false);
|
|
REPORTER_ASSERT(reporter, true == stack.quickContains(testRect));
|
|
}
|
|
}
|
|
|
|
static void set_region_to_stack(const SkClipStack& stack, const SkIRect& bounds, SkRegion* region) {
|
|
region->setRect(bounds);
|
|
SkClipStack::Iter iter(stack, SkClipStack::Iter::kBottom_IterStart);
|
|
while (const SkClipStack::Element *element = iter.next()) {
|
|
SkRegion elemRegion;
|
|
SkRegion boundsRgn(bounds);
|
|
SkPath path;
|
|
|
|
switch (element->getType()) {
|
|
case SkClipStack::Element::kEmpty_Type:
|
|
elemRegion.setEmpty();
|
|
break;
|
|
default:
|
|
element->asPath(&path);
|
|
elemRegion.setPath(path, boundsRgn);
|
|
break;
|
|
}
|
|
region->op(elemRegion, element->getOp());
|
|
}
|
|
}
|
|
|
|
static void test_invfill_diff_bug(skiatest::Reporter* reporter) {
|
|
SkClipStack stack;
|
|
stack.clipDevRect({10, 10, 20, 20}, SkRegion::kIntersect_Op, false);
|
|
|
|
SkPath path;
|
|
path.addRect({30, 10, 40, 20});
|
|
path.setFillType(SkPath::kInverseWinding_FillType);
|
|
stack.clipDevPath(path, SkRegion::kDifference_Op, false);
|
|
|
|
REPORTER_ASSERT(reporter, SkClipStack::kEmptyGenID == stack.getTopmostGenID());
|
|
|
|
SkRect stackBounds;
|
|
SkClipStack::BoundsType stackBoundsType;
|
|
stack.getBounds(&stackBounds, &stackBoundsType);
|
|
|
|
REPORTER_ASSERT(reporter, stackBounds.isEmpty());
|
|
REPORTER_ASSERT(reporter, SkClipStack::kNormal_BoundsType == stackBoundsType);
|
|
|
|
SkRegion region;
|
|
set_region_to_stack(stack, {0, 0, 50, 30}, ®ion);
|
|
|
|
REPORTER_ASSERT(reporter, region.isEmpty());
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
#if SK_SUPPORT_GPU
|
|
// Functions that add a shape to the clip stack. The shape is computed from a rectangle.
|
|
// AA is always disabled since the clip stack reducer can cause changes in aa rasterization of the
|
|
// stack. A fractional edge repeated in different elements may be rasterized fewer times using the
|
|
// reduced stack.
|
|
typedef void (*AddElementFunc) (const SkRect& rect,
|
|
bool invert,
|
|
SkRegion::Op op,
|
|
SkClipStack* stack,
|
|
bool doAA);
|
|
|
|
static void add_round_rect(const SkRect& rect, bool invert, SkRegion::Op op, SkClipStack* stack,
|
|
bool doAA) {
|
|
SkScalar rx = rect.width() / 10;
|
|
SkScalar ry = rect.height() / 20;
|
|
if (invert) {
|
|
SkPath path;
|
|
path.addRoundRect(rect, rx, ry);
|
|
path.setFillType(SkPath::kInverseWinding_FillType);
|
|
stack->clipDevPath(path, op, doAA);
|
|
} else {
|
|
SkRRect rrect;
|
|
rrect.setRectXY(rect, rx, ry);
|
|
stack->clipDevRRect(rrect, op, doAA);
|
|
}
|
|
};
|
|
|
|
static void add_rect(const SkRect& rect, bool invert, SkRegion::Op op, SkClipStack* stack,
|
|
bool doAA) {
|
|
if (invert) {
|
|
SkPath path;
|
|
path.addRect(rect);
|
|
path.setFillType(SkPath::kInverseWinding_FillType);
|
|
stack->clipDevPath(path, op, doAA);
|
|
} else {
|
|
stack->clipDevRect(rect, op, doAA);
|
|
}
|
|
};
|
|
|
|
static void add_oval(const SkRect& rect, bool invert, SkRegion::Op op, SkClipStack* stack,
|
|
bool doAA) {
|
|
SkPath path;
|
|
path.addOval(rect);
|
|
if (invert) {
|
|
path.setFillType(SkPath::kInverseWinding_FillType);
|
|
}
|
|
stack->clipDevPath(path, op, doAA);
|
|
};
|
|
|
|
static void add_elem_to_stack(const SkClipStack::Element& element, SkClipStack* stack) {
|
|
switch (element.getType()) {
|
|
case SkClipStack::Element::kRect_Type:
|
|
stack->clipDevRect(element.getRect(), element.getOp(), element.isAA());
|
|
break;
|
|
case SkClipStack::Element::kRRect_Type:
|
|
stack->clipDevRRect(element.getRRect(), element.getOp(), element.isAA());
|
|
break;
|
|
case SkClipStack::Element::kPath_Type:
|
|
stack->clipDevPath(element.getPath(), element.getOp(), element.isAA());
|
|
break;
|
|
case SkClipStack::Element::kEmpty_Type:
|
|
SkDEBUGFAIL("Why did the reducer produce an explicit empty.");
|
|
stack->clipEmpty();
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void test_reduced_clip_stack(skiatest::Reporter* reporter) {
|
|
// We construct random clip stacks, reduce them, and then rasterize both versions to verify that
|
|
// they are equal.
|
|
|
|
// All the clip elements will be contained within these bounds.
|
|
static const SkIRect kIBounds = SkIRect::MakeWH(100, 100);
|
|
static const SkRect kBounds = SkRect::Make(kIBounds);
|
|
|
|
enum {
|
|
kNumTests = 250,
|
|
kMinElemsPerTest = 1,
|
|
kMaxElemsPerTest = 50,
|
|
};
|
|
|
|
// min/max size of a clip element as a fraction of kBounds.
|
|
static const SkScalar kMinElemSizeFrac = SK_Scalar1 / 5;
|
|
static const SkScalar kMaxElemSizeFrac = SK_Scalar1;
|
|
|
|
static const SkRegion::Op kOps[] = {
|
|
SkRegion::kDifference_Op,
|
|
SkRegion::kIntersect_Op,
|
|
SkRegion::kUnion_Op,
|
|
SkRegion::kXOR_Op,
|
|
SkRegion::kReverseDifference_Op,
|
|
SkRegion::kReplace_Op,
|
|
};
|
|
|
|
// Replace operations short-circuit the optimizer. We want to make sure that we test this code
|
|
// path a little bit but we don't want it to prevent us from testing many longer traversals in
|
|
// the optimizer.
|
|
static const int kReplaceDiv = 4 * kMaxElemsPerTest;
|
|
|
|
// We want to test inverse fills. However, they are quite rare in practice so don't over do it.
|
|
static const SkScalar kFractionInverted = SK_Scalar1 / kMaxElemsPerTest;
|
|
|
|
static const SkScalar kFractionAntialiased = 0.25;
|
|
|
|
static const AddElementFunc kElementFuncs[] = {
|
|
add_rect,
|
|
add_round_rect,
|
|
add_oval,
|
|
};
|
|
|
|
SkRandom r;
|
|
|
|
for (int i = 0; i < kNumTests; ++i) {
|
|
SkString testCase;
|
|
testCase.printf("Iteration %d", i);
|
|
|
|
// Randomly generate a clip stack.
|
|
SkClipStack stack;
|
|
int numElems = r.nextRangeU(kMinElemsPerTest, kMaxElemsPerTest);
|
|
bool doAA = r.nextBiasedBool(kFractionAntialiased);
|
|
for (int e = 0; e < numElems; ++e) {
|
|
SkRegion::Op op = kOps[r.nextULessThan(SK_ARRAY_COUNT(kOps))];
|
|
if (op == SkRegion::kReplace_Op) {
|
|
if (r.nextU() % kReplaceDiv) {
|
|
--e;
|
|
continue;
|
|
}
|
|
}
|
|
|
|
// saves can change the clip stack behavior when an element is added.
|
|
bool doSave = r.nextBool();
|
|
|
|
SkSize size = SkSize::Make(
|
|
SkScalarMul(kBounds.width(), r.nextRangeScalar(kMinElemSizeFrac, kMaxElemSizeFrac)),
|
|
SkScalarMul(kBounds.height(), r.nextRangeScalar(kMinElemSizeFrac, kMaxElemSizeFrac)));
|
|
|
|
SkPoint xy = {r.nextRangeScalar(kBounds.fLeft, kBounds.fRight - size.fWidth),
|
|
r.nextRangeScalar(kBounds.fTop, kBounds.fBottom - size.fHeight)};
|
|
|
|
SkRect rect;
|
|
if (doAA) {
|
|
rect.setXYWH(xy.fX, xy.fY, size.fWidth, size.fHeight);
|
|
if (GrClip::IsPixelAligned(rect)) {
|
|
// Don't create an element that may accidentally become not antialiased.
|
|
rect.outset(0.5f, 0.5f);
|
|
}
|
|
SkASSERT(!GrClip::IsPixelAligned(rect));
|
|
} else {
|
|
rect.setXYWH(SkScalarFloorToScalar(xy.fX),
|
|
SkScalarFloorToScalar(xy.fY),
|
|
SkScalarCeilToScalar(size.fWidth),
|
|
SkScalarCeilToScalar(size.fHeight));
|
|
}
|
|
|
|
bool invert = r.nextBiasedBool(kFractionInverted);
|
|
|
|
kElementFuncs[r.nextULessThan(SK_ARRAY_COUNT(kElementFuncs))](rect, invert, op, &stack,
|
|
doAA);
|
|
if (doSave) {
|
|
stack.save();
|
|
}
|
|
}
|
|
|
|
// Get the reduced version of the stack.
|
|
SkRect queryBounds = kBounds;
|
|
queryBounds.outset(kBounds.width() / 2, kBounds.height() / 2);
|
|
const GrReducedClip reduced(stack, queryBounds);
|
|
|
|
REPORTER_ASSERT_MESSAGE(reporter, SkClipStack::kInvalidGenID != reduced.genID(),
|
|
testCase.c_str());
|
|
|
|
if (!reduced.elements().isEmpty()) {
|
|
REPORTER_ASSERT_MESSAGE(reporter, reduced.hasIBounds(), testCase.c_str());
|
|
SkRect stackBounds;
|
|
SkClipStack::BoundsType stackBoundsType;
|
|
stack.getBounds(&stackBounds, &stackBoundsType);
|
|
if (SkClipStack::kNormal_BoundsType == stackBoundsType) {
|
|
// Unless GrReducedClip starts doing some heroic tightening of the clip bounds, this
|
|
// will be true since the stack bounds are completely contained inside the query.
|
|
REPORTER_ASSERT_MESSAGE(reporter, GrClip::IsInsideClip(reduced.ibounds(), stackBounds),
|
|
testCase.c_str());
|
|
}
|
|
REPORTER_ASSERT_MESSAGE(reporter, reduced.requiresAA() == doAA, testCase.c_str());
|
|
}
|
|
|
|
// Build a new clip stack based on the reduced clip elements
|
|
SkClipStack reducedStack;
|
|
if (GrReducedClip::InitialState::kAllOut == reduced.initialState()) {
|
|
// whether the result is bounded or not, the whole plane should start outside the clip.
|
|
reducedStack.clipEmpty();
|
|
}
|
|
for (ElementList::Iter iter(reduced.elements()); iter.get(); iter.next()) {
|
|
add_elem_to_stack(*iter.get(), &reducedStack);
|
|
}
|
|
|
|
SkIRect ibounds = reduced.hasIBounds() ? reduced.ibounds() : kIBounds;
|
|
|
|
// GrReducedClipStack assumes that the final result is clipped to the returned bounds
|
|
reducedStack.clipDevRect(ibounds, SkRegion::kIntersect_Op);
|
|
stack.clipDevRect(ibounds, SkRegion::kIntersect_Op);
|
|
|
|
// convert both the original stack and reduced stack to SkRegions and see if they're equal
|
|
SkRegion region;
|
|
set_region_to_stack(stack, ibounds, ®ion);
|
|
|
|
SkRegion reducedRegion;
|
|
set_region_to_stack(reducedStack, ibounds, &reducedRegion);
|
|
|
|
REPORTER_ASSERT_MESSAGE(reporter, region == reducedRegion, testCase.c_str());
|
|
}
|
|
}
|
|
|
|
#ifdef SK_BUILD_FOR_WIN
|
|
#define SUPPRESS_VISIBILITY_WARNING
|
|
#else
|
|
#define SUPPRESS_VISIBILITY_WARNING __attribute__((visibility("hidden")))
|
|
#endif
|
|
|
|
static void test_reduced_clip_stack_genid(skiatest::Reporter* reporter) {
|
|
{
|
|
SkClipStack stack;
|
|
stack.clipDevRect(SkRect::MakeXYWH(0, 0, 100, 100), SkRegion::kReplace_Op, true);
|
|
stack.clipDevRect(SkRect::MakeXYWH(0, 0, SkScalar(50.3), SkScalar(50.3)), SkRegion::kReplace_Op, true);
|
|
SkRect bounds = SkRect::MakeXYWH(0, 0, 100, 100);
|
|
|
|
const GrReducedClip reduced(stack, bounds);
|
|
|
|
REPORTER_ASSERT(reporter, reduced.elements().count() == 1);
|
|
// Clips will be cached based on the generation id. Make sure the gen id is valid.
|
|
REPORTER_ASSERT(reporter, SkClipStack::kInvalidGenID != reduced.genID());
|
|
}
|
|
{
|
|
SkClipStack stack;
|
|
|
|
// Create a clip with following 25.3, 25.3 boxes which are 25 apart:
|
|
// A B
|
|
// C D
|
|
|
|
stack.clipDevRect(SkRect::MakeXYWH(0, 0, SkScalar(25.3), SkScalar(25.3)), SkRegion::kReplace_Op, true);
|
|
int32_t genIDA = stack.getTopmostGenID();
|
|
stack.clipDevRect(SkRect::MakeXYWH(50, 0, SkScalar(25.3), SkScalar(25.3)), SkRegion::kUnion_Op, true);
|
|
int32_t genIDB = stack.getTopmostGenID();
|
|
stack.clipDevRect(SkRect::MakeXYWH(0, 50, SkScalar(25.3), SkScalar(25.3)), SkRegion::kUnion_Op, true);
|
|
int32_t genIDC = stack.getTopmostGenID();
|
|
stack.clipDevRect(SkRect::MakeXYWH(50, 50, SkScalar(25.3), SkScalar(25.3)), SkRegion::kUnion_Op, true);
|
|
int32_t genIDD = stack.getTopmostGenID();
|
|
|
|
|
|
#define IXYWH SkIRect::MakeXYWH
|
|
#define XYWH SkRect::MakeXYWH
|
|
|
|
SkIRect stackBounds = IXYWH(0, 0, 76, 76);
|
|
|
|
// The base test is to test each rect in two ways:
|
|
// 1) The box dimensions. (Should reduce to "all in", no elements).
|
|
// 2) A bit over the box dimensions.
|
|
// In the case 2, test that the generation id is what is expected.
|
|
// The rects are of fractional size so that case 2 never gets optimized to an empty element
|
|
// list.
|
|
|
|
// Not passing in tighter bounds is tested for consistency.
|
|
static const struct SUPPRESS_VISIBILITY_WARNING {
|
|
SkRect testBounds;
|
|
int reducedClipCount;
|
|
int32_t reducedGenID;
|
|
InitialState initialState;
|
|
SkIRect clipIRect;
|
|
// parameter.
|
|
} testCases[] = {
|
|
|
|
// Rect A.
|
|
{ XYWH(0, 0, 25, 25), 0, SkClipStack::kWideOpenGenID, GrReducedClip::InitialState::kAllIn, IXYWH(0, 0, 25, 25) },
|
|
{ XYWH(0.1f, 0.1f, 25.1f, 25.1f), 0, SkClipStack::kWideOpenGenID, GrReducedClip::InitialState::kAllIn, IXYWH(0, 0, 26, 26) },
|
|
{ XYWH(0, 0, 27, 27), 1, genIDA, GrReducedClip::InitialState::kAllOut, IXYWH(0, 0, 27, 27)},
|
|
|
|
// Rect B.
|
|
{ XYWH(50, 0, 25, 25), 0, SkClipStack::kWideOpenGenID, GrReducedClip::InitialState::kAllIn, IXYWH(50, 0, 25, 25) },
|
|
{ XYWH(50, 0, 25.3f, 25.3f), 0, SkClipStack::kWideOpenGenID, GrReducedClip::InitialState::kAllIn, IXYWH(50, 0, 26, 26) },
|
|
{ XYWH(50, 0, 27, 27), 1, genIDB, GrReducedClip::InitialState::kAllOut, IXYWH(50, 0, 26, 27) },
|
|
|
|
// Rect C.
|
|
{ XYWH(0, 50, 25, 25), 0, SkClipStack::kWideOpenGenID, GrReducedClip::InitialState::kAllIn, IXYWH(0, 50, 25, 25) },
|
|
{ XYWH(0.2f, 50.1f, 25.1f, 25.2f), 0, SkClipStack::kWideOpenGenID, GrReducedClip::InitialState::kAllIn, IXYWH(0, 50, 26, 26) },
|
|
{ XYWH(0, 50, 27, 27), 1, genIDC, GrReducedClip::InitialState::kAllOut, IXYWH(0, 50, 27, 26) },
|
|
|
|
// Rect D.
|
|
{ XYWH(50, 50, 25, 25), 0, SkClipStack::kWideOpenGenID, GrReducedClip::InitialState::kAllIn, IXYWH(50, 50, 25, 25)},
|
|
{ XYWH(50.3f, 50.3f, 25, 25), 0, SkClipStack::kWideOpenGenID, GrReducedClip::InitialState::kAllIn, IXYWH(50, 50, 26, 26)},
|
|
{ XYWH(50, 50, 27, 27), 1, genIDD, GrReducedClip::InitialState::kAllOut, IXYWH(50, 50, 26, 26)},
|
|
|
|
// Other tests:
|
|
{ XYWH(0, 0, 100, 100), 4, genIDD, GrReducedClip::InitialState::kAllOut, stackBounds },
|
|
|
|
// Rect in the middle, touches none.
|
|
{ XYWH(26, 26, 24, 24), 0, SkClipStack::kEmptyGenID, GrReducedClip::InitialState::kAllOut, IXYWH(26, 26, 24, 24) },
|
|
|
|
// Rect in the middle, touches all the rects. GenID is the last rect.
|
|
{ XYWH(24, 24, 27, 27), 4, genIDD, GrReducedClip::InitialState::kAllOut, IXYWH(24, 24, 27, 27) },
|
|
};
|
|
|
|
#undef XYWH
|
|
#undef IXYWH
|
|
|
|
for (size_t i = 0; i < SK_ARRAY_COUNT(testCases); ++i) {
|
|
const GrReducedClip reduced(stack, testCases[i].testBounds);
|
|
REPORTER_ASSERT(reporter, reduced.elements().count() == testCases[i].reducedClipCount);
|
|
SkASSERT(reduced.elements().count() == testCases[i].reducedClipCount);
|
|
REPORTER_ASSERT(reporter, reduced.genID() == testCases[i].reducedGenID);
|
|
SkASSERT(reduced.genID() == testCases[i].reducedGenID);
|
|
REPORTER_ASSERT(reporter, reduced.initialState() == testCases[i].initialState);
|
|
SkASSERT(reduced.initialState() == testCases[i].initialState);
|
|
REPORTER_ASSERT(reporter, reduced.hasIBounds());
|
|
SkASSERT(reduced.hasIBounds());
|
|
REPORTER_ASSERT(reporter, reduced.ibounds() == testCases[i].clipIRect);
|
|
SkASSERT(reduced.ibounds() == testCases[i].clipIRect);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void test_reduced_clip_stack_no_aa_crash(skiatest::Reporter* reporter) {
|
|
SkClipStack stack;
|
|
stack.clipDevRect(SkIRect::MakeXYWH(0, 0, 100, 100), SkRegion::kReplace_Op);
|
|
stack.clipDevRect(SkIRect::MakeXYWH(0, 0, 50, 50), SkRegion::kReplace_Op);
|
|
SkRect bounds = SkRect::MakeXYWH(0, 0, 100, 100);
|
|
|
|
// At the time, this would crash.
|
|
const GrReducedClip reduced(stack, bounds);
|
|
REPORTER_ASSERT(reporter, reduced.elements().isEmpty());
|
|
}
|
|
|
|
enum class ClipMethod {
|
|
kSkipDraw,
|
|
kIgnoreClip,
|
|
kScissor,
|
|
kAAElements
|
|
};
|
|
|
|
static void test_aa_query(skiatest::Reporter* reporter, const SkString& testName,
|
|
const SkClipStack& stack, const SkMatrix& queryXform,
|
|
const SkRect& preXformQuery, ClipMethod expectedMethod,
|
|
int numExpectedElems = 0) {
|
|
SkRect queryBounds;
|
|
queryXform.mapRect(&queryBounds, preXformQuery);
|
|
const GrReducedClip reduced(stack, queryBounds);
|
|
|
|
SkClipStack::BoundsType stackBoundsType;
|
|
SkRect stackBounds;
|
|
stack.getBounds(&stackBounds, &stackBoundsType);
|
|
|
|
switch (expectedMethod) {
|
|
case ClipMethod::kSkipDraw:
|
|
SkASSERT(0 == numExpectedElems);
|
|
REPORTER_ASSERT_MESSAGE(reporter, reduced.elements().isEmpty(), testName.c_str());
|
|
REPORTER_ASSERT_MESSAGE(reporter,
|
|
GrReducedClip::InitialState::kAllOut == reduced.initialState(),
|
|
testName.c_str());
|
|
return;
|
|
case ClipMethod::kIgnoreClip:
|
|
SkASSERT(0 == numExpectedElems);
|
|
REPORTER_ASSERT_MESSAGE(reporter,
|
|
!reduced.hasIBounds() ||
|
|
GrClip::IsInsideClip(reduced.ibounds(), queryBounds),
|
|
testName.c_str());
|
|
REPORTER_ASSERT_MESSAGE(reporter, reduced.elements().isEmpty(), testName.c_str());
|
|
REPORTER_ASSERT_MESSAGE(reporter,
|
|
GrReducedClip::InitialState::kAllIn == reduced.initialState(),
|
|
testName.c_str());
|
|
return;
|
|
case ClipMethod::kScissor: {
|
|
SkASSERT(SkClipStack::kNormal_BoundsType == stackBoundsType);
|
|
SkASSERT(0 == numExpectedElems);
|
|
SkIRect expectedScissor;
|
|
stackBounds.round(&expectedScissor);
|
|
REPORTER_ASSERT_MESSAGE(reporter, reduced.elements().isEmpty(), testName.c_str());
|
|
REPORTER_ASSERT_MESSAGE(reporter, reduced.hasIBounds(), testName.c_str());
|
|
REPORTER_ASSERT_MESSAGE(reporter, expectedScissor == reduced.ibounds(),
|
|
testName.c_str());
|
|
REPORTER_ASSERT_MESSAGE(reporter,
|
|
GrReducedClip::InitialState::kAllIn == reduced.initialState(),
|
|
testName.c_str());
|
|
return;
|
|
}
|
|
case ClipMethod::kAAElements: {
|
|
SkIRect expectedClipIBounds = GrClip::GetPixelIBounds(queryBounds);
|
|
if (SkClipStack::kNormal_BoundsType == stackBoundsType) {
|
|
SkAssertResult(expectedClipIBounds.intersect(GrClip::GetPixelIBounds(stackBounds)));
|
|
}
|
|
REPORTER_ASSERT_MESSAGE(reporter, numExpectedElems == reduced.elements().count(),
|
|
testName.c_str());
|
|
REPORTER_ASSERT_MESSAGE(reporter, reduced.hasIBounds(), testName.c_str());
|
|
REPORTER_ASSERT_MESSAGE(reporter, expectedClipIBounds == reduced.ibounds(),
|
|
testName.c_str());
|
|
REPORTER_ASSERT_MESSAGE(reporter, reduced.requiresAA() == !reduced.elements().isEmpty(),
|
|
testName.c_str());
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
static void test_reduced_clip_stack_aa(skiatest::Reporter* reporter) {
|
|
constexpr SkScalar IL = 2, IT = 1, IR = 6, IB = 7; // Pixel aligned rect.
|
|
constexpr SkScalar L = 2.2f, T = 1.7f, R = 5.8f, B = 7.3f; // Generic rect.
|
|
constexpr SkScalar l = 3.3f, t = 2.8f, r = 4.7f, b = 6.2f; // Small rect contained in R.
|
|
|
|
SkRect alignedRect = {IL, IT, IR, IB};
|
|
SkRect rect = {L, T, R, B};
|
|
SkRect innerRect = {l, t, r, b};
|
|
|
|
SkMatrix m;
|
|
m.setIdentity();
|
|
|
|
constexpr SkScalar kMinScale = 2.0001f;
|
|
constexpr SkScalar kMaxScale = 3;
|
|
constexpr int kNumIters = 8;
|
|
|
|
SkString name;
|
|
SkRandom rand;
|
|
|
|
for (int i = 0; i < kNumIters; ++i) {
|
|
// Pixel-aligned rect (iior=true).
|
|
name.printf("Pixel-aligned rect test, iter %i", i);
|
|
SkClipStack stack;
|
|
stack.clipDevRect(alignedRect, SkRegion::kIntersect_Op, true);
|
|
test_aa_query(reporter, name, stack, m, {IL, IT, IR, IB}, ClipMethod::kIgnoreClip);
|
|
test_aa_query(reporter, name, stack, m, {IL, IT-1, IR, IT}, ClipMethod::kSkipDraw);
|
|
test_aa_query(reporter, name, stack, m, {IL, IT-2, IR, IB}, ClipMethod::kScissor);
|
|
|
|
// Rect (iior=true).
|
|
name.printf("Rect test, iter %i", i);
|
|
stack.reset();
|
|
stack.clipDevRect(rect, SkRegion::kIntersect_Op, true);
|
|
test_aa_query(reporter, name, stack, m, {L, T, R, B}, ClipMethod::kIgnoreClip);
|
|
test_aa_query(reporter, name, stack, m, {L-.1f, T, L, B}, ClipMethod::kSkipDraw);
|
|
test_aa_query(reporter, name, stack, m, {L-.1f, T, L+.1f, B}, ClipMethod::kAAElements, 1);
|
|
|
|
// Difference rect (iior=false, inside-out bounds).
|
|
name.printf("Difference rect test, iter %i", i);
|
|
stack.reset();
|
|
stack.clipDevRect(rect, SkRegion::kDifference_Op, true);
|
|
test_aa_query(reporter, name, stack, m, {L, T, R, B}, ClipMethod::kSkipDraw);
|
|
test_aa_query(reporter, name, stack, m, {L, T-.1f, R, T}, ClipMethod::kIgnoreClip);
|
|
test_aa_query(reporter, name, stack, m, {L, T-.1f, R, T+.1f}, ClipMethod::kAAElements, 1);
|
|
|
|
// Complex clip (iior=false, normal bounds).
|
|
name.printf("Complex clip test, iter %i", i);
|
|
stack.reset();
|
|
stack.clipDevRect(rect, SkRegion::kIntersect_Op, true);
|
|
stack.clipDevRect(innerRect, SkRegion::kXOR_Op, true);
|
|
test_aa_query(reporter, name, stack, m, {l, t, r, b}, ClipMethod::kSkipDraw);
|
|
test_aa_query(reporter, name, stack, m, {r-.1f, t, R, b}, ClipMethod::kAAElements, 1);
|
|
test_aa_query(reporter, name, stack, m, {r-.1f, t, R+.1f, b}, ClipMethod::kAAElements, 2);
|
|
test_aa_query(reporter, name, stack, m, {r, t, R+.1f, b}, ClipMethod::kAAElements, 1);
|
|
test_aa_query(reporter, name, stack, m, {r, t, R, b}, ClipMethod::kIgnoreClip);
|
|
test_aa_query(reporter, name, stack, m, {R, T, R+.1f, B}, ClipMethod::kSkipDraw);
|
|
|
|
// Complex clip where outer rect is pixel aligned (iior=false, normal bounds).
|
|
name.printf("Aligned Complex clip test, iter %i", i);
|
|
stack.reset();
|
|
stack.clipDevRect(alignedRect, SkRegion::kIntersect_Op, true);
|
|
stack.clipDevRect(innerRect, SkRegion::kXOR_Op, true);
|
|
test_aa_query(reporter, name, stack, m, {l, t, r, b}, ClipMethod::kSkipDraw);
|
|
test_aa_query(reporter, name, stack, m, {l, b-.1f, r, IB}, ClipMethod::kAAElements, 1);
|
|
test_aa_query(reporter, name, stack, m, {l, b-.1f, r, IB+.1f}, ClipMethod::kAAElements, 1);
|
|
test_aa_query(reporter, name, stack, m, {l, b, r, IB+.1f}, ClipMethod::kAAElements, 0);
|
|
test_aa_query(reporter, name, stack, m, {l, b, r, IB}, ClipMethod::kIgnoreClip);
|
|
test_aa_query(reporter, name, stack, m, {IL, IB, IR, IB+.1f}, ClipMethod::kSkipDraw);
|
|
|
|
// Apply random transforms and try again. This ensures the clip stack reduction is hardened
|
|
// against FP rounding error.
|
|
SkScalar sx = rand.nextRangeScalar(kMinScale, kMaxScale);
|
|
sx = SkScalarFloorToScalar(sx * alignedRect.width()) / alignedRect.width();
|
|
SkScalar sy = rand.nextRangeScalar(kMinScale, kMaxScale);
|
|
sy = SkScalarFloorToScalar(sy * alignedRect.height()) / alignedRect.height();
|
|
SkScalar tx = SkScalarRoundToScalar(sx * alignedRect.x()) - sx * alignedRect.x();
|
|
SkScalar ty = SkScalarRoundToScalar(sy * alignedRect.y()) - sy * alignedRect.y();
|
|
|
|
SkMatrix xform = SkMatrix::MakeScale(sx, sy);
|
|
xform.postTranslate(tx, ty);
|
|
xform.mapRect(&alignedRect);
|
|
xform.mapRect(&rect);
|
|
xform.mapRect(&innerRect);
|
|
m.postConcat(xform);
|
|
}
|
|
}
|
|
|
|
#endif
|
|
|
|
DEF_TEST(ClipStack, reporter) {
|
|
SkClipStack stack;
|
|
|
|
REPORTER_ASSERT(reporter, 0 == stack.getSaveCount());
|
|
assert_count(reporter, stack, 0);
|
|
|
|
static const SkIRect gRects[] = {
|
|
{ 0, 0, 100, 100 },
|
|
{ 25, 25, 125, 125 },
|
|
{ 0, 0, 1000, 1000 },
|
|
{ 0, 0, 75, 75 }
|
|
};
|
|
for (size_t i = 0; i < SK_ARRAY_COUNT(gRects); i++) {
|
|
stack.clipDevRect(gRects[i], SkRegion::kIntersect_Op);
|
|
}
|
|
|
|
// all of the above rects should have been intersected, leaving only 1 rect
|
|
SkClipStack::B2TIter iter(stack);
|
|
const SkClipStack::Element* element = iter.next();
|
|
SkRect answer;
|
|
answer.iset(25, 25, 75, 75);
|
|
|
|
REPORTER_ASSERT(reporter, element);
|
|
REPORTER_ASSERT(reporter, SkClipStack::Element::kRect_Type == element->getType());
|
|
REPORTER_ASSERT(reporter, SkRegion::kIntersect_Op == element->getOp());
|
|
REPORTER_ASSERT(reporter, element->getRect() == answer);
|
|
// now check that we only had one in our iterator
|
|
REPORTER_ASSERT(reporter, !iter.next());
|
|
|
|
stack.reset();
|
|
REPORTER_ASSERT(reporter, 0 == stack.getSaveCount());
|
|
assert_count(reporter, stack, 0);
|
|
|
|
test_assign_and_comparison(reporter);
|
|
test_iterators(reporter);
|
|
test_bounds(reporter, SkClipStack::Element::kRect_Type);
|
|
test_bounds(reporter, SkClipStack::Element::kRRect_Type);
|
|
test_bounds(reporter, SkClipStack::Element::kPath_Type);
|
|
test_isWideOpen(reporter);
|
|
test_rect_merging(reporter);
|
|
test_rect_replace(reporter);
|
|
test_rect_inverse_fill(reporter);
|
|
test_path_replace(reporter);
|
|
test_quickContains(reporter);
|
|
test_invfill_diff_bug(reporter);
|
|
#if SK_SUPPORT_GPU
|
|
test_reduced_clip_stack(reporter);
|
|
test_reduced_clip_stack_genid(reporter);
|
|
test_reduced_clip_stack_no_aa_crash(reporter);
|
|
test_reduced_clip_stack_aa(reporter);
|
|
#endif
|
|
}
|