Fully deferred displaylist replay

bug:8037003

A recursive drawDisplayList call is now entirely deferred before
playing back to the screen and issuing GL commands. This way, the
entire stream can be inspected, optimized, and batch work (such as
uploading textures) before issuing commands.

Additionally, this fixes an issue where operations draw could move
across restores corresponding to saveLayer(alpha). Those and other
similar cases (such as complex clipping, requiring the stencil) are
now treated as batching barriers, with the operations that change
renderer state in a way that's difficult to defer are just re-issued
at flush time.

Change-Id: Ie7348166662a5ad89fb9b1e87558334fb826b01e
diff --git a/libs/hwui/DeferredDisplayList.cpp b/libs/hwui/DeferredDisplayList.cpp
index a4e9950..2027fc8 100644
--- a/libs/hwui/DeferredDisplayList.cpp
+++ b/libs/hwui/DeferredDisplayList.cpp
@@ -32,15 +32,15 @@
 namespace android {
 namespace uirenderer {
 
+/////////////////////////////////////////////////////////////////////////////////
+// Operation Batches
+/////////////////////////////////////////////////////////////////////////////////
+
 class DrawOpBatch {
 public:
-    DrawOpBatch() {
-        mOps.clear();
-    }
+    DrawOpBatch() { mOps.clear(); }
 
-    ~DrawOpBatch() {
-        mOps.clear();
-    }
+    virtual ~DrawOpBatch() { mOps.clear(); }
 
     void add(DrawOp* op) {
         // NOTE: ignore empty bounds special case, since we don't merge across those ops
@@ -48,8 +48,9 @@
         mOps.add(op);
     }
 
-    bool intersects(Rect& rect) {
+    virtual bool intersects(Rect& rect) {
         if (!rect.intersects(mBounds)) return false;
+
         for (unsigned int i = 0; i < mOps.size(); i++) {
             if (rect.intersects(mOps[i]->state.mBounds)) {
 #if DEBUG_DEFER
@@ -64,27 +65,217 @@
         return false;
     }
 
-    Vector<DrawOp*> mOps;
+    virtual status_t replay(OpenGLRenderer& renderer, Rect& dirty) {
+        DEFER_LOGD("replaying draw batch %p", this);
+
+        status_t status = DrawGlInfo::kStatusDone;
+        DisplayListLogBuffer& logBuffer = DisplayListLogBuffer::getInstance();
+        for (unsigned int i = 0; i < mOps.size(); i++) {
+            DrawOp* op = mOps[i];
+
+            renderer.restoreDisplayState(op->state, kStateDeferFlag_Draw);
+
+#if DEBUG_DISPLAY_LIST_OPS_AS_EVENTS
+            renderer.eventMark(strlen(op->name()), op->name());
+#endif
+            status |= op->applyDraw(renderer, dirty, 0, op->state.mMultipliedAlpha);
+            logBuffer.writeCommand(0, op->name());
+        }
+        return status;
+    }
+
+    inline int count() const { return mOps.size(); }
 private:
+    Vector<DrawOp*> mOps;
     Rect mBounds;
 };
 
-void DeferredDisplayList::clear() {
+class StateOpBatch : public DrawOpBatch {
+public:
+    // creates a single operation batch
+    StateOpBatch(StateOp* op) : mOp(op) {}
+
+    bool intersects(Rect& rect) {
+        // if something checks for intersection, it's trying to go backwards across a state op,
+        // something not currently supported - state ops are always barriers
+        CRASH();
+        return false;
+    }
+
+    virtual status_t replay(OpenGLRenderer& renderer, Rect& dirty) {
+        DEFER_LOGD("replaying state op batch %p", this);
+        renderer.restoreDisplayState(mOp->state, 0);
+
+        // use invalid save count because it won't be used at flush time - RestoreToCountOp is the
+        // only one to use it, and we don't use that class at flush time, instead calling
+        // renderer.restoreToCount directly
+        int saveCount = -1;
+        mOp->applyState(renderer, saveCount);
+        return DrawGlInfo::kStatusDone;
+    }
+
+private:
+    StateOp* mOp;
+};
+
+class RestoreToCountBatch : public DrawOpBatch {
+public:
+    RestoreToCountBatch(int restoreCount) : mRestoreCount(restoreCount) {}
+
+    bool intersects(Rect& rect) {
+        // if something checks for intersection, it's trying to go backwards across a state op,
+        // something not currently supported - state ops are always barriers
+        CRASH();
+        return false;
+    }
+
+    virtual status_t replay(OpenGLRenderer& renderer, Rect& dirty) {
+        DEFER_LOGD("batch %p restoring to count %d", this, mRestoreCount);
+        renderer.restoreToCount(mRestoreCount);
+
+        return DrawGlInfo::kStatusDone;
+    }
+
+private:
+    /*
+     * The count used here represents the flush() time saveCount. This is as opposed to the
+     * DisplayList record time, or defer() time values (which are RestoreToCountOp's mCount, and
+     * (saveCount + mCount) respectively). Since the count is different from the original
+     * RestoreToCountOp, we don't store a pointer to the op, as elsewhere.
+     */
+    const int mRestoreCount;
+};
+
+/////////////////////////////////////////////////////////////////////////////////
+// DeferredDisplayList
+/////////////////////////////////////////////////////////////////////////////////
+
+void DeferredDisplayList::resetBatchingState() {
     for (int i = 0; i < kOpBatch_Count; i++) {
         mBatchIndices[i] = -1;
     }
+}
+
+void DeferredDisplayList::clear() {
+    resetBatchingState();
+    mComplexClipStackStart = -1;
+
     for (unsigned int i = 0; i < mBatches.size(); i++) {
         delete mBatches[i];
     }
     mBatches.clear();
+    mSaveStack.clear();
 }
 
-void DeferredDisplayList::add(DrawOp* op, bool disallowReorder) {
-    if (CC_UNLIKELY(disallowReorder)) {
-        if (!mBatches.isEmpty()) {
-            mBatches[0]->add(op);
-            return;
+/////////////////////////////////////////////////////////////////////////////////
+// Operation adding
+/////////////////////////////////////////////////////////////////////////////////
+
+int DeferredDisplayList::getStateOpDeferFlags() const {
+    // For both clipOp and save(Layer)Op, we don't want to save drawing info, and only want to save
+    // the clip if we aren't recording a complex clip (and can thus trust it to be a rect)
+    return recordingComplexClip() ? 0 : kStateDeferFlag_Clip;
+}
+
+int DeferredDisplayList::getDrawOpDeferFlags() const {
+    return kStateDeferFlag_Draw | getStateOpDeferFlags();
+}
+
+/**
+ * When an clipping operation occurs that could cause a complex clip, record the operation and all
+ * subsequent clipOps, save/restores (if the clip flag is set). During a flush, instead of loading
+ * the clip from deferred state, we play back all of the relevant state operations that generated
+ * the complex clip.
+ *
+ * Note that we don't need to record the associated restore operation, since operations at defer
+ * time record whether they should store the renderer's current clip
+ */
+void DeferredDisplayList::addClip(OpenGLRenderer& renderer, ClipOp* op) {
+    if (recordingComplexClip() || op->canCauseComplexClip() || !renderer.hasRectToRectTransform()) {
+        DEFER_LOGD("%p Received complex clip operation %p", this, op);
+
+        // NOTE: defer clip op before setting mComplexClipStackStart so previous clip is recorded
+        storeStateOpBarrier(renderer, op);
+
+        if (!recordingComplexClip()) {
+            mComplexClipStackStart = renderer.getSaveCount() - 1;
+            DEFER_LOGD("    Starting complex clip region, start is %d", mComplexClipStackStart);
         }
+    }
+}
+
+/**
+ * For now, we record save layer operations as barriers in the batch list, preventing drawing
+ * operations from reordering around the saveLayer and it's associated restore()
+ *
+ * In the future, we should send saveLayer commands (if they can be played out of order) and their
+ * contained drawing operations to a seperate list of batches, so that they may draw at the
+ * beginning of the frame. This would avoid targetting and removing an FBO in the middle of a frame.
+ *
+ * saveLayer operations should be pulled to the beginning of the frame if the canvas doesn't have a
+ * complex clip, and if the flags (kClip_SaveFlag & kClipToLayer_SaveFlag) are set.
+ */
+void DeferredDisplayList::addSaveLayer(OpenGLRenderer& renderer,
+        SaveLayerOp* op, int newSaveCount) {
+    DEFER_LOGD("%p adding saveLayerOp %p, flags %x, new count %d",
+            this, op, op->getFlags(), newSaveCount);
+
+    storeStateOpBarrier(renderer, op);
+    mSaveStack.push(newSaveCount);
+}
+
+/**
+ * Takes save op and it's return value - the new save count - and stores it into the stream as a
+ * barrier if it's needed to properly modify a complex clip
+ */
+void DeferredDisplayList::addSave(OpenGLRenderer& renderer, SaveOp* op, int newSaveCount) {
+    int saveFlags = op->getFlags();
+    DEFER_LOGD("%p adding saveOp %p, flags %x, new count %d", this, op, saveFlags, newSaveCount);
+
+    if (recordingComplexClip() && (saveFlags & SkCanvas::kClip_SaveFlag)) {
+        // store and replay the save operation, as it may be needed to correctly playback the clip
+        DEFER_LOGD("    adding save barrier with new save count %d", newSaveCount);
+        storeStateOpBarrier(renderer, op);
+        mSaveStack.push(newSaveCount);
+    }
+}
+
+/**
+ * saveLayer() commands must be associated with a restoreToCount batch that will clean up and draw
+ * the layer in the deferred list
+ *
+ * other save() commands which occur as children of a snapshot with complex clip will be deferred,
+ * and must be restored
+ *
+ * Either will act as a barrier to draw operation reordering, as we want to play back layer
+ * save/restore and complex canvas modifications (including save/restore) in order.
+ */
+void DeferredDisplayList::addRestoreToCount(OpenGLRenderer& renderer, int newSaveCount) {
+    DEFER_LOGD("%p addRestoreToCount %d", this, newSaveCount);
+
+    if (recordingComplexClip() && newSaveCount <= mComplexClipStackStart) {
+        mComplexClipStackStart = -1;
+        resetBatchingState();
+    }
+
+    if (mSaveStack.isEmpty() || newSaveCount > mSaveStack.top()) {
+        return;
+    }
+
+    while (!mSaveStack.isEmpty() && mSaveStack.top() >= newSaveCount) mSaveStack.pop();
+
+    storeRestoreToCountBarrier(mSaveStack.size() + 1);
+}
+
+void DeferredDisplayList::addDrawOp(OpenGLRenderer& renderer, DrawOp* op) {
+    if (renderer.storeDisplayState(op->state, getDrawOpDeferFlags())) {
+        return; // quick rejected
+    }
+
+    op->onDrawOpDeferred(renderer);
+
+    if (CC_UNLIKELY(renderer.getCaches().drawReorderDisabled)) {
+        // TODO: elegant way to reuse batches?
         DrawOpBatch* b = new DrawOpBatch();
         b->add(op);
         mBatches.add(b);
@@ -138,9 +329,41 @@
     targetBatch->add(op);
 }
 
-status_t DeferredDisplayList::flush(OpenGLRenderer& renderer, Rect& dirty, int32_t flags,
-        uint32_t level) {
-    ATRACE_CALL();
+void DeferredDisplayList::storeStateOpBarrier(OpenGLRenderer& renderer, StateOp* op) {
+    DEFER_LOGD("%p adding state op barrier at pos %d", this, mBatches.size());
+
+    renderer.storeDisplayState(op->state, getStateOpDeferFlags());
+    mBatches.add(new StateOpBatch(op));
+    resetBatchingState();
+}
+
+void DeferredDisplayList::storeRestoreToCountBarrier(int newSaveCount) {
+    DEFER_LOGD("%p adding restore to count %d barrier, pos %d",
+            this, newSaveCount, mBatches.size());
+
+    mBatches.add(new RestoreToCountBatch(newSaveCount));
+    resetBatchingState();
+}
+
+/////////////////////////////////////////////////////////////////////////////////
+// Replay / flush
+/////////////////////////////////////////////////////////////////////////////////
+
+static status_t replayBatchList(Vector<DrawOpBatch*>& batchList,
+        OpenGLRenderer& renderer, Rect& dirty) {
+    status_t status = DrawGlInfo::kStatusDone;
+
+    int opCount = 0;
+    for (unsigned int i = 0; i < batchList.size(); i++) {
+        status |= batchList[i]->replay(renderer, dirty);
+        opCount += batchList[i]->count();
+    }
+    DEFER_LOGD("--flushed, drew %d batches (total %d ops)", batchList.size(), opCount);
+    return status;
+}
+
+status_t DeferredDisplayList::flush(OpenGLRenderer& renderer, Rect& dirty) {
+    ATRACE_NAME("flush drawing commands");
     status_t status = DrawGlInfo::kStatusDone;
 
     if (isEmpty()) return status; // nothing to flush
@@ -148,29 +371,12 @@
     DEFER_LOGD("--flushing");
     renderer.eventMark("Flush");
 
-    DrawModifiers restoreDrawModifiers = renderer.getDrawModifiers();
-    int restoreTo = renderer.save(SkCanvas::kMatrix_SaveFlag | SkCanvas::kClip_SaveFlag);
-    int opCount = 0;
-    for (unsigned int i = 0; i < mBatches.size(); i++) {
-        DrawOpBatch* batch = mBatches[i];
-        for (unsigned int j = 0; j < batch->mOps.size(); j++) {
-            DrawOp* op = batch->mOps[j];
+    renderer.restoreToCount(1);
 
-            renderer.restoreDisplayState(op->state);
+    status |= replayBatchList(mBatches, renderer, dirty);
 
-#if DEBUG_DEFER
-            op->output(2);
-#endif
-            status |= op->applyDraw(renderer, dirty, level,
-                    op->state.mMultipliedAlpha >= 0, op->state.mMultipliedAlpha);
-            opCount++;
-        }
-    }
+    DEFER_LOGD("--flush complete, returning %x", status);
 
-    DEFER_LOGD("--flushed, drew %d batches (total %d ops)", mBatches.size(), opCount);
-
-    renderer.restoreToCount(restoreTo);
-    renderer.setDrawModifiers(restoreDrawModifiers);
     clear();
     return status;
 }