Fix GPU assumption that clipstack begins with intersect or replace.


Review URL: http://codereview.appspot.com/4977043/



git-svn-id: http://skia.googlecode.com/svn/trunk@2183 2bbb7eff-a529-9590-31e7-b0007b416f81
diff --git a/gpu/include/GrClip.h b/gpu/include/GrClip.h
index 7de6c10..ba6eb44 100644
--- a/gpu/include/GrClip.h
+++ b/gpu/include/GrClip.h
@@ -61,7 +61,9 @@
     GrSetOp getOp(int i) const { return fList[i].fOp; }
 
     bool isRect() const {
-        if (1 == fList.count() && kRect_ClipType == fList[0].fType) {
+        if (1 == fList.count() && kRect_ClipType == fList[0].fType && 
+            (kIntersect_SetOp == fList[0].fOp ||
+             kReplace_SetOp == fList[0].fOp)) {
             // if we determined that the clip is a single rect
             // we ought to have also used that rect as the bounds.
             GrAssert(fConservativeBoundsValid);
diff --git a/gpu/src/GrClip.cpp b/gpu/src/GrClip.cpp
index ade8c2b..afa143a 100644
--- a/gpu/src/GrClip.cpp
+++ b/gpu/src/GrClip.cpp
@@ -61,6 +61,7 @@
         fList.push_back();
         fList.back().fRect = r;
         fList.back().fType = kRect_ClipType;
+        fList.back().fOp = kReplace_SetOp;
         fConservativeBounds = r;
         fConservativeBoundsValid = true;
     }
@@ -75,6 +76,7 @@
         fList.push_back();
         fList.back().fRect.set(r);
         fList.back().fType = kRect_ClipType;
+        fList.back().fOp = kReplace_SetOp;
         fConservativeBounds.set(r);
         fConservativeBoundsValid = true;
     }
@@ -110,7 +112,7 @@
                     }
                     ++rectCount;
                     if (isectRectValid) {
-                        if (1 == rectCount || kIntersect_SetOp == e.fOp) {
+                        if (kIntersect_SetOp == e.fOp) {
                             GrAssert(fList.count() <= 2);
                             if (fList.count() > 1) {
                                 GrAssert(2 == rectCount);
diff --git a/gpu/src/GrGpu.cpp b/gpu/src/GrGpu.cpp
index b492952..46ce46c 100644
--- a/gpu/src/GrGpu.cpp
+++ b/gpu/src/GrGpu.cpp
@@ -415,6 +415,87 @@
     #define SET_RANDOM_COLOR
 #endif
 
+namespace {
+// determines how many elements at the head of the clip can be skipped and
+// whether the initial clear should be to the inside- or outside-the-clip value,
+// and what op should be used to draw the first element that isn't skipped.
+int process_initial_clip_elements(const GrClip& clip,
+                                  bool* clearToInside,
+                                  GrSetOp* startOp) {
+
+    // logically before the first element of the clip stack is 
+    // processed the clip is entirely open. However, depending on the
+    // first set op we may prefer to clear to 0 for performance. We may
+    // also be able to skip the initial clip paths/rects. We loop until
+    // we cannot skip an element.
+    int curr;
+    bool done = false;
+    *clearToInside = true;
+    int count = clip.getElementCount();
+
+    for (curr = 0; curr < count && !done; ++curr) {
+        switch (clip.getOp(curr)) {
+            case kReplace_SetOp:
+                // replace ignores everything previous
+                *startOp = kReplace_SetOp;
+                *clearToInside = false;
+                done = true;
+                break;
+            case kIntersect_SetOp:
+                // if everything is initially clearToInside then intersect is
+                // same as clear to 0 and treat as a replace. Otherwise,
+                // set stays empty.
+                if (*clearToInside) {
+                    *startOp = kReplace_SetOp;
+                    *clearToInside = false;
+                    done = true;
+                }
+                break;
+                // we can skip a leading union.
+            case kUnion_SetOp:
+                // if everything is initially outside then union is
+                // same as replace. Otherwise, every pixel is still 
+                // clearToInside
+                if (!*clearToInside) {
+                    *startOp = kReplace_SetOp;
+                    done = true;
+                }
+                break;
+            case kXor_SetOp:
+                // xor is same as difference or replace both of which
+                // can be 1-pass instead of 2 for xor.
+                if (*clearToInside) {
+                    *startOp = kDifference_SetOp;
+                } else {
+                    *startOp = kReplace_SetOp;
+                }
+                done = true;
+                break;
+            case kDifference_SetOp:
+                // if all pixels are clearToInside then we have to process the
+                // difference, otherwise it has no effect and all pixels
+                // remain outside.
+                if (*clearToInside) {
+                    *startOp = kDifference_SetOp;
+                    done = true;
+                }
+                break;
+            case kReverseDifference_SetOp:
+                // if all pixels are clearToInside then reverse difference
+                // produces empty set. Otherise it is same as replace
+                if (*clearToInside) {
+                    *clearToInside = false;
+                } else {
+                    *startOp = kReplace_SetOp;
+                    done = true;
+                }
+                break;
+        }
+    }
+    return done ? curr-1 : count;
+}
+}
+
 bool GrGpu::setupClipAndFlushState(GrPrimitiveType type) {
     const GrIRect* r = NULL;
     GrIRect clipRect;
@@ -475,7 +556,6 @@
             AutoGeometryPush agp(this);
 
             this->setViewMatrix(GrMatrix::I());
-            this->clearStencilClip(clipRect);
             this->flushScissor(NULL);
 #if !VISUALIZE_COMPLEX_CLIP
             this->enableState(kNoColorWrites_StateBit);
@@ -485,21 +565,17 @@
             int count = clip.getElementCount();
             int clipBit = stencilBuffer->bits();
             clipBit = (1 << (clipBit-1));
+            
+            bool clearToInside;
+            GrSetOp startOp;
+            int start = process_initial_clip_elements(clip, &clearToInside,
+                                                      &startOp);
 
-            // often we'll see the first two elements of the clip are
-            // the full rt size and another element intersected with it.
-            // We can skip the first full-size rect and save a big rect draw.
-            int firstElement = 0;
-            if (clip.getElementCount() > 1 &&
-                kRect_ClipType == clip.getElementType(0) &&
-                kIntersect_SetOp == clip.getOp(1)&&
-                clip.getRect(0).contains(bounds)) {
-                firstElement = 1;
-            }
+            this->clearStencilClip(clipRect, clearToInside);
 
             // walk through each clip element and perform its set op
             // with the existing clip.
-            for (int c = firstElement; c < count; ++c) {
+            for (int c = start; c < count; ++c) {
                 GrPathFill fill;
                 bool fillInverted;
                 // enabled at bottom of loop
@@ -534,7 +610,7 @@
                     arp.set(pr, this, clipPath, fill, NULL);
                 }
 
-                GrSetOp op = firstElement == c ? kReplace_SetOp : clip.getOp(c);
+                GrSetOp op = (c == start) ? startOp : clip.getOp(c);
                 int passes;
                 GrStencilSettings stencilSettings[GrStencilSettings::kMaxStencilClipPasses];
 
diff --git a/gpu/src/GrGpu.h b/gpu/src/GrGpu.h
index 1c947ec..88b50b1 100644
--- a/gpu/src/GrGpu.h
+++ b/gpu/src/GrGpu.h
@@ -487,8 +487,11 @@
     // Sets the scissor rect, or disables if rect is NULL.
     virtual void flushScissor(const GrIRect* rect) = 0;
 
-    // GrGpu subclass removes the clip from the stencil buffer
-    virtual void clearStencilClip(const GrIRect& rect) = 0;
+    // GrGpu subclass sets clip bit in the stencil buffer. The subclass is
+    // free to clear the remaining bits to zero if masked clears are more
+    // expensive than clearing all bits.
+    virtual void clearStencilClip(const GrIRect& rect, bool insideClip) = 0;
+
     // clears the entire stencil buffer to 0
     virtual void clearStencil() = 0;
 
diff --git a/gpu/src/GrGpuGL.cpp b/gpu/src/GrGpuGL.cpp
index 936ada3..5054229 100644
--- a/gpu/src/GrGpuGL.cpp
+++ b/gpu/src/GrGpuGL.cpp
@@ -1388,15 +1388,15 @@
     fHWDrawState.fStencilSettings.invalidate();
 }
 
-void GrGpuGL::clearStencilClip(const GrIRect& rect) {
+void GrGpuGL::clearStencilClip(const GrIRect& rect, bool insideClip) {
     GrAssert(NULL != fCurrDrawState.fRenderTarget);
 
     // this should only be called internally when we know we have a
     // stencil buffer.
     GrAssert(NULL != fCurrDrawState.fRenderTarget->getStencilBuffer());
-#if 0
     GrGLint stencilBitCount = 
         fCurrDrawState.fRenderTarget->getStencilBuffer()->bits();
+#if 0
     GrAssert(stencilBitCount > 0);
     GrGLint clipStencilMask  = (1 << (stencilBitCount - 1));
 #else
@@ -1407,10 +1407,16 @@
     // zero the client's clip bits. So we just clear the whole thing.
     static const GrGLint clipStencilMask  = ~0;
 #endif
+    GrGLint value;
+    if (insideClip) {
+        value = (1 << (stencilBitCount - 1));
+    } else {
+        value = 0;
+    }
     this->flushRenderTarget(&GrIRect::EmptyIRect());
     this->flushScissor(&rect);
     GL_CALL(StencilMask(clipStencilMask));
-    GL_CALL(ClearStencil(0));
+    GL_CALL(ClearStencil(value));
     GL_CALL(Clear(GR_GL_STENCIL_BUFFER_BIT));
     fHWDrawState.fStencilSettings.invalidate();
 }
diff --git a/gpu/src/GrGpuGL.h b/gpu/src/GrGpuGL.h
index 30a50e1..47aca3c 100644
--- a/gpu/src/GrGpuGL.h
+++ b/gpu/src/GrGpuGL.h
@@ -105,7 +105,7 @@
                                      uint32_t numVertices);
     virtual void flushScissor(const GrIRect* rect);
     virtual void clearStencil();
-    virtual void clearStencilClip(const GrIRect& rect);
+    virtual void clearStencilClip(const GrIRect& rect, bool insideClip);
     virtual int getMaxEdges() const;
 
     // binds texture unit in GL