diff --git a/include/core/SkRandom.h b/include/core/SkRandom.h index c8d71996e..73dc4917d 100644 --- a/include/core/SkRandom.h +++ b/include/core/SkRandom.h @@ -111,6 +111,13 @@ public: */ bool nextBool() { return this->nextU() >= 0x80000000; } + /** A biased version of nextBool(). + */ + bool nextBiasedBool(SkScalar fractionTrue) { + SkASSERT(fractionTrue >= 0 && fractionTrue <= SK_Scalar1); + return this->nextUScalar1() <= fractionTrue; + } + /** Return the next pseudo random number as a signed 64bit value. */ void next64(Sk64* a) { diff --git a/src/gpu/GrClipMaskManager.cpp b/src/gpu/GrClipMaskManager.cpp index cf1d2b3c5..2f24b22fc 100644 --- a/src/gpu/GrClipMaskManager.cpp +++ b/src/gpu/GrClipMaskManager.cpp @@ -130,7 +130,7 @@ void GrReduceClipStack(const SkClipStack& stack, case SkRegion::kIntersect_Op: if (*resultsAreBounded) { // check if the shape intersected contains the entire bounds and therefore can - // be skipped or it is outside the entire bounds and therfore makes the clip + // be skipped or it is outside the entire bounds and therefore makes the clip // empty. if (clip->isInverseFilled()) { if (clip->contains(*resultBounds)) { @@ -154,8 +154,8 @@ void GrReduceClipStack(const SkClipStack& stack, break; case SkRegion::kUnion_Op: if (*resultsAreBounded) { - // If the unioned shape contains the entire bounds then after this element - // the bounds is entirely inside the clip. If the unioned shape is outside the + // If the union-ed shape contains the entire bounds then after this element + // the bounds is entirely inside the clip. If the union-ed shape is outside the // bounds then this op can be skipped. if (clip->isInverseFilled()) { if (clip->contains(*resultBounds)) { @@ -179,6 +179,10 @@ void GrReduceClipStack(const SkClipStack& stack, break; case SkRegion::kXOR_Op: if (*resultsAreBounded) { + // If the bounds is entirely inside the shape being xor-ed then the effect is + // to flip the inside/outside state of every point in the bounds. We may be + // able to take advantage of this in the forward pass. If the xor-ed shape + // doesn't intersect the bounds then it can be skipped. if (clip->isInverseFilled()) { if (clip->contains(*resultBounds)) { skippable = true; @@ -198,6 +202,10 @@ void GrReduceClipStack(const SkClipStack& stack, } break; case SkRegion::kReverseDifference_Op: + // When the bounds is entirely within the rev-diff shape then this behaves like xor + // and reverses every point inside the bounds. If the shape is completely outside + // the bounds then we know after this element is applied that the bounds will be + // all outside the current clip.B if (*resultsAreBounded) { if (clip->isInverseFilled()) { if (clip->contains(*resultBounds)) { @@ -220,7 +228,11 @@ void GrReduceClipStack(const SkClipStack& stack, } break; case SkRegion::kReplace_Op: - if (*resultsAreBounded) { + // Replace will always terminate our walk. We will either begin the forward walk + // at the replace op or detect here than the shape is either completely inside + // or completely outside the bounds. In this latter case it can be skipped by + // setting the correct value for initialState. + if (*resultsAreBounded) { if (clip->isInverseFilled()) { if (clip->contains(*resultBounds)) { *initialState = kAllOut_InitialState; @@ -276,9 +288,11 @@ void GrReduceClipStack(const SkClipStack& stack, bool skippable = false; switch (clip->fOp) { case SkRegion::kDifference_Op: + // subtracting from the empty set yields the empty set. skippable = kAllOut_InitialState == *initialState; break; case SkRegion::kIntersect_Op: + // intersecting with the empty set yields the empty set skippable = kAllOut_InitialState == *initialState; break; case SkRegion::kUnion_Op: diff --git a/tests/ClipStackTest.cpp b/tests/ClipStackTest.cpp index 8e4d301c9..8eb1102de 100644 --- a/tests/ClipStackTest.cpp +++ b/tests/ClipStackTest.cpp @@ -647,25 +647,44 @@ static void test_iter_rect_merging(skiatest::Reporter* reporter) { /////////////////////////////////////////////////////////////////////////////////////////////////// #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); -typedef void (*AddElementFunc) (const SkRect& rect, bool aa, SkRegion::Op op, SkClipStack* stack); - -static void add_round_rect(const SkRect& rect, bool aa, SkRegion::Op op, SkClipStack* stack) { +static void add_round_rect(const SkRect& rect, bool invert, SkRegion::Op op, SkClipStack* stack) { SkPath path; SkScalar rx = rect.width() / 10; - SkScalar ry = rect.width() / 20; + SkScalar ry = rect.height() / 20; path.addRoundRect(rect, rx, ry); - stack->clipDevPath(path, op, aa); + if (invert) { + path.setFillType(SkPath::kInverseWinding_FillType); + } + stack->clipDevPath(path, op, false); }; -static void add_rect(const SkRect& rect, bool aa, SkRegion::Op op, SkClipStack* stack) { - stack->clipDevRect(rect, op, aa); +static void add_rect(const SkRect& rect, bool invert, SkRegion::Op op, SkClipStack* stack) { + if (invert) { + SkPath path; + path.addRect(rect); + path.setFillType(SkPath::kInverseWinding_FillType); + stack->clipDevPath(path, op, false); + } else { + stack->clipDevRect(rect, op, false); + } }; -static void add_oval(const SkRect& rect, bool aa, SkRegion::Op op, SkClipStack* stack) { +static void add_oval(const SkRect& rect, bool invert, SkRegion::Op op, SkClipStack* stack) { SkPath path; path.addOval(rect); - stack->clipDevPath(path, op, aa); + if (invert) { + path.setFillType(SkPath::kInverseWinding_FillType); + } + stack->clipDevPath(path, op, false); }; static void add_elem_to_stack(const SkClipStack::Iter::Clip& clip, SkClipStack* stack) { @@ -706,11 +725,12 @@ static void print_clip(const SkClipStack::Iter::Clip& clip) { "RD", "RP", }; - if (clip.fRect || clip.fPath) { + if (NULL != clip.fRect || NULL != clip.fPath) { const SkRect& bounds = clip.getBounds(); - SkDebugf("%s %s [%f %f] x [%f %f]\n", + SkDebugf("%s %s %s [%f %f] x [%f %f]\n", kOpStrs[clip.fOp], - (clip.fRect ? "R" : "P"), + (NULL != clip.fRect ? "R" : "P"), + ((NULL != clip.fPath && clip.fPath->isInverseFillType() ? "I" : " ")), bounds.fLeft, bounds.fRight, bounds.fTop, bounds.fBottom); } else { SkDebugf("EM\n"); @@ -748,6 +768,9 @@ static void test_reduced_clip_stack(skiatest::Reporter* reporter) { // 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 AddElementFunc kElementFuncs[] = { add_rect, add_round_rect, @@ -781,10 +804,8 @@ static void test_reduced_clip_stack(skiatest::Reporter* reporter) { SkRect rect = SkRect::MakeXYWH(xy.fX, xy.fY, size.fWidth, size.fHeight); - // AA is always disabled. The optimizer can cause changes in aa rasterization of the - // clip stack. A fractional edge repeated in different elements may be rasterized fewer - // times using the reduced stack. - kElementFuncs[r.nextULessThan(SK_ARRAY_COUNT(kElementFuncs))](rect, false, op, &stack); + bool invert = r.nextBiasedBool(kFractionInverted); + kElementFuncs[r.nextULessThan(SK_ARRAY_COUNT(kElementFuncs))](rect, invert, op, &stack); if (doSave) { stack.save(); }