SkRecordDraw: incorporate clip into BBH
authormtklein <mtklein@chromium.org>
Fri, 15 Aug 2014 18:49:49 +0000 (11:49 -0700)
committerCommit bot <commit-bot@chromium.org>
Fri, 15 Aug 2014 18:49:49 +0000 (11:49 -0700)
NOTREECHECKS=true

BUG=skia:
R=robertphillips@google.com, senorblanco@chromium.org, mtklein@google.com

Author: mtklein@chromium.org

Review URL: https://codereview.chromium.org/474983002

src/core/SkRecordDraw.cpp
src/core/SkRecorder.cpp
src/core/SkRecorder.h
src/core/SkRecords.h
tests/RecordDrawTest.cpp

index c9e029b..46241d7 100644 (file)
@@ -16,10 +16,16 @@ void SkRecordDraw(const SkRecord& record,
 
     if (NULL != bbh) {
         // Draw only ops that affect pixels in the canvas's current clip.
-        SkIRect devBounds;
-        canvas->getClipDeviceBounds(&devBounds);
+        SkIRect query;
+#if 1   // TODO: Why is this the right way to make the query?  I'd think it'd be the else branch.
+        SkRect clipBounds;
+        canvas->getClipBounds(&clipBounds);
+        clipBounds.roundOut(&query);
+#else
+        canvas->getClipDeviceBounds(&query);
+#endif
         SkTDArray<void*> ops;
-        bbh->search(devBounds, &ops);
+        bbh->search(query, &ops);
 
         // FIXME: QuadTree doesn't send these back in the order we inserted them.  :(
         // Also remove the sort in SkPictureData::getActiveOps()?
@@ -117,7 +123,9 @@ public:
     FillBounds(const SkRecord& record, SkBBoxHierarchy* bbh) : fBounds(record.count()) {
         // Calculate bounds for all ops.  This won't go quite in order, so we'll need
         // to store the bounds separately then feed them in to the BBH later in order.
+        const SkIRect largest = SkIRect::MakeLargest();
         fCTM.setIdentity();
+        fCurrentClipBounds = largest;
         for (fCurrentOp = 0; fCurrentOp < record.count(); fCurrentOp++) {
             record.visit<void>(fCurrentOp, *this);
         }
@@ -130,7 +138,7 @@ public:
 
         // Any control ops not part of any Save/Restore block draw everywhere.
         while (!fControlIndices.isEmpty()) {
-            this->popControl(SkIRect::MakeLargest());
+            this->popControl(largest);
         }
 
         // Finally feed all stored bounds into the BBH.  They'll be returned in this order.
@@ -143,28 +151,44 @@ public:
         bbh->flushDeferredInserts();
     }
 
-    template <typename T> void operator()(const T& r) {
-        this->updateCTM(r);
-        this->trackBounds(r);
+    template <typename T> void operator()(const T& op) {
+        this->updateCTM(op);
+        this->updateClipBounds(op);
+        this->trackBounds(op);
     }
 
 private:
     struct SaveBounds {
-        int controlOps;  // Number of control ops in this Save block, including the Save.
-        SkIRect bounds;  // Bounds of everything in the block.
+        int controlOps;        // Number of control ops in this Save block, including the Save.
+        SkIRect bounds;        // Bounds of everything in the block.
+        const SkPaint* paint;  // Unowned.  If set, adjusts the bounds of all ops in this block.
     };
 
     template <typename T> void updateCTM(const T&) { /* most ops don't change the CTM */ }
-    void updateCTM(const Restore& r)   { fCTM = r.matrix; }
-    void updateCTM(const SetMatrix& r) { fCTM = r.matrix; }
-    void updateCTM(const Concat& r)    { fCTM.preConcat(r.matrix); }
+    void updateCTM(const Restore& op)   { fCTM = op.matrix; }
+    void updateCTM(const SetMatrix& op) { fCTM = op.matrix; }
+    void updateCTM(const Concat& op)    { fCTM.preConcat(op.matrix); }
+
+    template <typename T> void updateClipBounds(const T&) { /* most ops don't change the clip */ }
+    // Each of these devBounds fields is the state of the device bounds after the op.
+    // So Restore's devBounds are those bounds saved by its paired Save or SaveLayer.
+    void updateClipBounds(const Restore& op)    { fCurrentClipBounds = op.devBounds; }
+    void updateClipBounds(const ClipPath& op)   { fCurrentClipBounds = op.devBounds; }
+    void updateClipBounds(const ClipRRect& op)  { fCurrentClipBounds = op.devBounds; }
+    void updateClipBounds(const ClipRect& op)   { fCurrentClipBounds = op.devBounds; }
+    void updateClipBounds(const ClipRegion& op) { fCurrentClipBounds = op.devBounds; }
+    void updateClipBounds(const SaveLayer& op)  {
+        if (op.bounds) {
+            fCurrentClipBounds.intersect(this->adjustAndMap(*op.bounds, op.paint));
+        }
+    }
 
     // The bounds of these ops must be calculated when we hit the Restore
     // from the bounds of the ops in the same Save block.
-    void trackBounds(const Save&)       { this->pushSaveBlock(); }
+    void trackBounds(const Save&)          { this->pushSaveBlock(NULL); }
     // TODO: bounds of SaveLayer may be more complicated?
-    void trackBounds(const SaveLayer&)  { this->pushSaveBlock(); }
-    void trackBounds(const Restore&)    { fBounds[fCurrentOp] = this->popSaveBlock(); }
+    void trackBounds(const SaveLayer& op)  { this->pushSaveBlock(op.paint); }
+    void trackBounds(const Restore&) { fBounds[fCurrentOp] = this->popSaveBlock(); }
 
     void trackBounds(const Concat&)     { this->pushControl(); }
     void trackBounds(const SetMatrix&)  { this->pushControl(); }
@@ -179,12 +203,9 @@ private:
         this->updateSaveBounds(fBounds[fCurrentOp]);
     }
 
-    // TODO: remove this trivially-safe default when done bounding all ops
-    template <typename T> SkIRect bounds(const T&) { return SkIRect::MakeLargest(); }
-
-    void pushSaveBlock() {
+    void pushSaveBlock(const SkPaint* paint) {
         // Starting a new Save block.  Push a new entry to represent that.
-        SaveBounds sb = { 0, SkIRect::MakeEmpty() };
+        SaveBounds sb = { 0, SkIRect::MakeEmpty(), paint };
         fSaveStack.push(sb);
         this->pushControl();
     }
@@ -223,11 +244,54 @@ private:
         }
     }
 
-    SkIRect bounds(const NoOp&) { return SkIRect::MakeEmpty(); }  // NoOps don't draw anywhere.
+    // TODO: Remove this default when done bounding all ops.
+    template <typename T> SkIRect bounds(const T&) { return fCurrentClipBounds; }
+    SkIRect bounds(const Clear&) { return SkIRect::MakeLargest(); }  // Ignores the clip
+    SkIRect bounds(const NoOp&)  { return SkIRect::MakeEmpty(); }    // NoOps don't draw anywhere.
+
+    // Adjust rect for all paints that may affect its geometry, then map it to device space.
+    SkIRect adjustAndMap(SkRect rect, const SkPaint* paint) {
+        // Adjust rect for its own paint.
+        if (paint) {
+            if (paint->canComputeFastBounds()) {
+                rect = paint->computeFastBounds(rect, &rect);
+            } else {
+                // The paint could do anything.  The only safe answer is the current clip.
+                return fCurrentClipBounds;
+            }
+        }
 
-    SkAutoTMalloc<SkIRect> fBounds;  // One for each op in the record.
-    SkMatrix fCTM;
+        // Adjust rect for all the paints from the SaveLayers we're inside.
+        // For SaveLayers, only image filters will affect the bounds.
+        for (int i = fSaveStack.count() - 1; i >= 0; i--) {
+            if (fSaveStack[i].paint && fSaveStack[i].paint->getImageFilter()) {
+                if (paint->canComputeFastBounds()) {
+                    rect = fSaveStack[i].paint->computeFastBounds(rect, &rect);
+                } else {
+                    // Same deal as above.
+                    return fCurrentClipBounds;
+                }
+            }
+        }
+
+        // Map the rect back to device space.
+        fCTM.mapRect(&rect);
+        SkIRect devRect;
+        rect.roundOut(&devRect);
+        return devRect;
+    }
+
+    // Conservative device bounds for each op in the SkRecord.
+    SkAutoTMalloc<SkIRect> fBounds;
+
+    // We walk fCurrentOp through the SkRecord, as we go using updateCTM()
+    // and updateClipBounds() to maintain the exact CTM (fCTM) and conservative
+    // device bounds of the current clip (fCurrentClipBounds).
     unsigned fCurrentOp;
+    SkMatrix fCTM;
+    SkIRect fCurrentClipBounds;
+
+    // Used to track the bounds of Save/Restore blocks and the control ops inside them.
     SkTDArray<SaveBounds> fSaveStack;
     SkTDArray<unsigned>   fControlIndices;
 };
index 53522c2..d49de20 100644 (file)
@@ -230,7 +230,7 @@ SkCanvas::SaveLayerStrategy SkRecorder::willSaveLayer(const SkRect* bounds,
 }
 
 void SkRecorder::didRestore() {
-    APPEND(Restore, this->getTotalMatrix());
+    APPEND(Restore, this->devBounds(), this->getTotalMatrix());
     INHERITED(didRestore);
 }
 
@@ -254,21 +254,21 @@ void SkRecorder::didSetMatrix(const SkMatrix& matrix) {
 }
 
 void SkRecorder::onClipRect(const SkRect& rect, SkRegion::Op op, ClipEdgeStyle edgeStyle) {
-    APPEND(ClipRect, rect, op, edgeStyle == kSoft_ClipEdgeStyle);
     INHERITED(onClipRect, rect, op, edgeStyle);
+    APPEND(ClipRect, this->devBounds(), rect, op, edgeStyle == kSoft_ClipEdgeStyle);
 }
 
 void SkRecorder::onClipRRect(const SkRRect& rrect, SkRegion::Op op, ClipEdgeStyle edgeStyle) {
-    APPEND(ClipRRect, rrect, op, edgeStyle == kSoft_ClipEdgeStyle);
     INHERITED(updateClipConservativelyUsingBounds, rrect.getBounds(), op, false);
+    APPEND(ClipRRect, this->devBounds(), rrect, op, edgeStyle == kSoft_ClipEdgeStyle);
 }
 
 void SkRecorder::onClipPath(const SkPath& path, SkRegion::Op op, ClipEdgeStyle edgeStyle) {
-    APPEND(ClipPath, delay_copy(path), op, edgeStyle == kSoft_ClipEdgeStyle);
     INHERITED(updateClipConservativelyUsingBounds, path.getBounds(), op, path.isInverseFillType());
+    APPEND(ClipPath, this->devBounds(), delay_copy(path), op, edgeStyle == kSoft_ClipEdgeStyle);
 }
 
 void SkRecorder::onClipRegion(const SkRegion& deviceRgn, SkRegion::Op op) {
-    APPEND(ClipRegion, delay_copy(deviceRgn), op);
     INHERITED(onClipRegion, deviceRgn, op);
+    APPEND(ClipRegion, this->devBounds(), delay_copy(deviceRgn), op);
 }
index 97eeb9f..1373f8a 100644 (file)
@@ -110,6 +110,12 @@ private:
     template <typename T>
     T* copy(const T[], size_t count);
 
+    SkIRect devBounds() const {
+        SkIRect devBounds;
+        this->getClipDeviceBounds(&devBounds);
+        return devBounds;
+    }
+
     SkRecord* fRecord;
 };
 
index 3d5b190..bc768d9 100644 (file)
@@ -197,7 +197,7 @@ private:
 
 RECORD0(NoOp);
 
-RECORD1(Restore, SkMatrix, matrix);
+RECORD2(Restore, SkIRect, devBounds, SkMatrix, matrix);
 RECORD0(Save);
 RECORD3(SaveLayer, Optional<SkRect>, bounds, Optional<SkPaint>, paint, SkCanvas::SaveFlags, flags);
 
@@ -207,10 +207,10 @@ RECORD0(PopCull);
 RECORD1(Concat, SkMatrix, matrix);
 RECORD1(SetMatrix, SkMatrix, matrix);
 
-RECORD3(ClipPath, SkPath, path, SkRegion::Op, op, bool, doAA);
-RECORD3(ClipRRect, SkRRect, rrect, SkRegion::Op, op, bool, doAA);
-RECORD3(ClipRect, SkRect, rect, SkRegion::Op, op, bool, doAA);
-RECORD2(ClipRegion, SkRegion, region, SkRegion::Op, op);
+RECORD4(ClipPath,   SkIRect, devBounds, SkPath,   path,   SkRegion::Op, op, bool, doAA);
+RECORD4(ClipRRect,  SkIRect, devBounds, SkRRect,  rrect,  SkRegion::Op, op, bool, doAA);
+RECORD4(ClipRect,   SkIRect, devBounds, SkRect,   rect,   SkRegion::Op, op, bool, doAA);
+RECORD3(ClipRegion, SkIRect, devBounds, SkRegion, region, SkRegion::Op, op);
 
 RECORD1(Clear, SkColor, color);
 // While not strictly required, if you have an SkPaint, it's fastest to put it first.
index 2690013..e850dfe 100644 (file)
@@ -95,3 +95,48 @@ DEF_TEST(RecordDraw_SetMatrixClobber, r) {
     expected.postConcat(translate);
     REPORTER_ASSERT(r, setMatrix->matrix == expected);
 }
+
+struct TestBBH : public SkBBoxHierarchy {
+    virtual void insert(void* data, const SkIRect& bounds, bool defer) SK_OVERRIDE {
+        Entry e = { (uintptr_t)data, bounds };
+        entries.push(e);
+    }
+    virtual int getCount() const SK_OVERRIDE { return entries.count(); }
+
+    virtual void flushDeferredInserts() SK_OVERRIDE {}
+
+    virtual void search(const SkIRect& query, SkTDArray<void*>* results) const SK_OVERRIDE {}
+    virtual void clear() SK_OVERRIDE {}
+    virtual void rewindInserts() SK_OVERRIDE {}
+    virtual int getDepth() const SK_OVERRIDE { return -1; }
+
+    struct Entry {
+        uintptr_t data;
+        SkIRect bounds;
+    };
+    SkTDArray<Entry> entries;
+};
+
+// This test is not meant to make total sense yet.  It's testing the status quo
+// of SkRecordFillBounds(), which itself doesn't make total sense yet.
+DEF_TEST(RecordDraw_BBH, r) {
+    TestBBH bbh;
+
+    SkRecord record;
+
+    SkRecorder recorder(&record, W, H);
+    recorder.save();
+        recorder.clipRect(SkRect::MakeWH(400, 500));
+        recorder.scale(2, 2);
+        recorder.drawRect(SkRect::MakeWH(320, 240), SkPaint());
+    recorder.restore();
+
+    SkRecordFillBounds(record, &bbh);
+
+    REPORTER_ASSERT(r, bbh.entries.count() == 5);
+    for (int i = 0; i < bbh.entries.count(); i++) {
+        REPORTER_ASSERT(r, bbh.entries[i].data == (uintptr_t)i);
+
+        REPORTER_ASSERT(r, bbh.entries[i].bounds == SkIRect::MakeWH(400, 500));
+    }
+}