Compact the clipstack for kReplace_Op'd geometry

When adding a clip rect or clip path to the stack with the
kReplace_Op operation, remove all previous elements within the
same save frame (elements with fSaveCount equal to the current
fSaveCount of the stack). This prevents unbounded growth of the
clipstack for long-lived instances that gets reused.

Addresses https://code.google.com/p/skia/issues/detail?id=748

R=robertphillips@google.com

Author: fs@opera.com

Review URL: https://chromiumcodereview.appspot.com/16160020

git-svn-id: http://skia.googlecode.com/svn/trunk@9502 2bbb7eff-a529-9590-31e7-b0007b416f81
This commit is contained in:
commit-bot@chromium.org 2013-06-11 11:01:48 +00:00
parent 7fb5373fb7
commit 6fbe54c663
3 changed files with 165 additions and 43 deletions

View File

@ -452,6 +452,11 @@ private:
mutable SkTDArray<ClipCallbackData> fCallbackData;
/**
* Restore the stack back to the specified save count.
*/
void restoreTo(int saveCount);
/**
* Invoke all the purge callbacks passing in element's generation ID.
*/

View File

@ -443,9 +443,13 @@ void SkClipStack::save() {
void SkClipStack::restore() {
fSaveCount -= 1;
restoreTo(fSaveCount);
}
void SkClipStack::restoreTo(int saveCount) {
while (!fDeque.empty()) {
Element* element = (Element*)fDeque.back();
if (element->fSaveCount <= fSaveCount) {
if (element->fSaveCount <= saveCount) {
break;
}
this->purgeClip(element);
@ -528,32 +532,37 @@ void SkClipStack::clipDevRect(const SkRect& rect, SkRegion::Op op, bool doAA) {
SkDeque::Iter iter(fDeque, SkDeque::Iter::kBack_IterStart);
Element* element = (Element*) iter.prev();
if (element && element->canBeIntersectedInPlace(fSaveCount, op)) {
switch (element->fType) {
case Element::kEmpty_Type:
element->checkEmpty();
return;
case Element::kRect_Type:
if (element->rectRectIntersectAllowed(rect, doAA)) {
this->purgeClip(element);
if (!element->fRect.intersect(rect)) {
if (NULL != element) {
if (element->canBeIntersectedInPlace(fSaveCount, op)) {
switch (element->fType) {
case Element::kEmpty_Type:
element->checkEmpty();
return;
case Element::kRect_Type:
if (element->rectRectIntersectAllowed(rect, doAA)) {
this->purgeClip(element);
if (!element->fRect.intersect(rect)) {
element->setEmpty();
return;
}
element->fDoAA = doAA;
Element* prev = (Element*) iter.prev();
element->updateBoundAndGenID(prev);
return;
}
break;
case Element::kPath_Type:
if (!SkRect::Intersects(element->fPath.getBounds(), rect)) {
this->purgeClip(element);
element->setEmpty();
return;
}
element->fDoAA = doAA;
Element* prev = (Element*) iter.prev();
element->updateBoundAndGenID(prev);
return;
}
break;
case Element::kPath_Type:
if (!SkRect::Intersects(element->fPath.getBounds(), rect)) {
this->purgeClip(element);
element->setEmpty();
return;
}
break;
break;
}
} else if (SkRegion::kReplace_Op == op) {
this->restoreTo(fSaveCount - 1);
element = (Element*) fDeque.back();
}
}
new (fDeque.push_back()) Element(fSaveCount, rect, op, doAA);
@ -571,26 +580,31 @@ void SkClipStack::clipDevPath(const SkPath& path, SkRegion::Op op, bool doAA) {
}
Element* element = (Element*)fDeque.back();
if (element && element->canBeIntersectedInPlace(fSaveCount, op)) {
const SkRect& pathBounds = path.getBounds();
switch (element->fType) {
case Element::kEmpty_Type:
element->checkEmpty();
return;
case Element::kRect_Type:
if (!SkRect::Intersects(element->fRect, pathBounds)) {
this->purgeClip(element);
element->setEmpty();
if (NULL != element) {
if (element->canBeIntersectedInPlace(fSaveCount, op)) {
const SkRect& pathBounds = path.getBounds();
switch (element->fType) {
case Element::kEmpty_Type:
element->checkEmpty();
return;
}
break;
case Element::kPath_Type:
if (!SkRect::Intersects(element->fPath.getBounds(), pathBounds)) {
this->purgeClip(element);
element->setEmpty();
return;
}
break;
case Element::kRect_Type:
if (!SkRect::Intersects(element->fRect, pathBounds)) {
this->purgeClip(element);
element->setEmpty();
return;
}
break;
case Element::kPath_Type:
if (!SkRect::Intersects(element->fPath.getBounds(), pathBounds)) {
this->purgeClip(element);
element->setEmpty();
return;
}
break;
}
} else if (SkRegion::kReplace_Op == op) {
this->restoreTo(fSaveCount - 1);
element = (Element*) fDeque.back();
}
}
new (fDeque.push_back()) Element(fSaveCount, path, op, doAA);

View File

@ -392,6 +392,107 @@ static void test_rect_inverse_fill(skiatest::Reporter* reporter) {
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) {
@ -950,7 +1051,9 @@ static void TestClipStack(skiatest::Reporter* reporter) {
test_bounds(reporter, false); // once with paths
test_isWideOpen(reporter);
test_rect_merging(reporter);
test_rect_replace(reporter);
test_rect_inverse_fill(reporter);
test_path_replace(reporter);
test_quickContains(reporter);
#if SK_SUPPORT_GPU
test_reduced_clip_stack(reporter);