OSDN Git Service

Add z-reordering support to OpReorderer
authorChris Craik <ccraik@google.com>
Thu, 5 Nov 2015 19:08:52 +0000 (11:08 -0800)
committerChris Craik <ccraik@google.com>
Thu, 5 Nov 2015 23:45:59 +0000 (15:45 -0800)
Change-Id: I3fa969fe53cf648d145810f69fa7dada376c0b9a

libs/hwui/DisplayListOp.h
libs/hwui/OpReorderer.cpp
libs/hwui/OpReorderer.h
libs/hwui/RecordedOp.h
libs/hwui/RecordingCanvas.cpp
libs/hwui/RecordingCanvas.h
libs/hwui/RenderNode.h
libs/hwui/unit_tests/LayerUpdateQueueTests.cpp
libs/hwui/unit_tests/OpReordererTests.cpp
libs/hwui/unit_tests/RecordingCanvasTests.cpp
libs/hwui/unit_tests/TestUtils.h

index cb638a4..772aa72 100644 (file)
@@ -1398,6 +1398,7 @@ class DrawRenderNodeOp : public DrawBoundedOp {
     friend class RenderNode; // grant RenderNode access to info of child
     friend class DisplayList; // grant DisplayList access to info of child
     friend class DisplayListCanvas;
+    friend class TestUtils;
 public:
     DrawRenderNodeOp(RenderNode* renderNode, const mat4& transformFromParent, bool clipIsSimple)
             : DrawBoundedOp(0, 0, renderNode->getWidth(), renderNode->getHeight(), nullptr)
index c6b1609..68f80ea 100644 (file)
 
 #include "OpReorderer.h"
 
-#include "utils/PaintUtils.h"
-#include "RenderNode.h"
 #include "LayerUpdateQueue.h"
+#include "RenderNode.h"
+#include "utils/FatVector.h"
+#include "utils/PaintUtils.h"
 
-#include "SkCanvas.h"
-#include "utils/Trace.h"
+#include <SkCanvas.h>
+#include <utils/Trace.h>
+#include <utils/TypeHelpers.h>
 
 namespace android {
 namespace uirenderer {
@@ -375,6 +377,93 @@ void OpReorderer::deferNodePropsAndOps(RenderNode& node) {
     }
 }
 
+typedef key_value_pair_t<float, const RenderNodeOp*> ZRenderNodeOpPair;
+
+template <typename V>
+static void buildZSortedChildList(V* zTranslatedNodes,
+        const DisplayList& displayList, const DisplayList::Chunk& chunk) {
+    if (chunk.beginChildIndex == chunk.endChildIndex) return;
+
+    for (size_t i = chunk.beginChildIndex; i < chunk.endChildIndex; i++) {
+        RenderNodeOp* childOp = displayList.getChildren()[i];
+        RenderNode* child = childOp->renderNode;
+        float childZ = child->properties().getZ();
+
+        if (!MathUtils::isZero(childZ) && chunk.reorderChildren) {
+            zTranslatedNodes->push_back(ZRenderNodeOpPair(childZ, childOp));
+            childOp->skipInOrderDraw = true;
+        } else if (!child->properties().getProjectBackwards()) {
+            // regular, in order drawing DisplayList
+            childOp->skipInOrderDraw = false;
+        }
+    }
+
+    // Z sort any 3d children (stable-ness makes z compare fall back to standard drawing order)
+    std::stable_sort(zTranslatedNodes->begin(), zTranslatedNodes->end());
+}
+
+template <typename V>
+static size_t findNonNegativeIndex(const V& zTranslatedNodes) {
+    for (size_t i = 0; i < zTranslatedNodes.size(); i++) {
+        if (zTranslatedNodes[i].key >= 0.0f) return i;
+    }
+    return zTranslatedNodes.size();
+}
+
+template <typename V>
+void OpReorderer::defer3dChildren(ChildrenSelectMode mode, const V& zTranslatedNodes) {
+    const int size = zTranslatedNodes.size();
+    if (size == 0
+            || (mode == ChildrenSelectMode::Negative&& zTranslatedNodes[0].key > 0.0f)
+            || (mode == ChildrenSelectMode::Positive && zTranslatedNodes[size - 1].key < 0.0f)) {
+        // no 3d children to draw
+        return;
+    }
+
+    /**
+     * Draw shadows and (potential) casters mostly in order, but allow the shadows of casters
+     * with very similar Z heights to draw together.
+     *
+     * This way, if Views A & B have the same Z height and are both casting shadows, the shadows are
+     * underneath both, and neither's shadow is drawn on top of the other.
+     */
+    const size_t nonNegativeIndex = findNonNegativeIndex(zTranslatedNodes);
+    size_t drawIndex, shadowIndex, endIndex;
+    if (mode == ChildrenSelectMode::Negative) {
+        drawIndex = 0;
+        endIndex = nonNegativeIndex;
+        shadowIndex = endIndex; // draw no shadows
+    } else {
+        drawIndex = nonNegativeIndex;
+        endIndex = size;
+        shadowIndex = drawIndex; // potentially draw shadow for each pos Z child
+    }
+
+    float lastCasterZ = 0.0f;
+    while (shadowIndex < endIndex || drawIndex < endIndex) {
+        if (shadowIndex < endIndex) {
+            const RenderNodeOp* casterNodeOp = zTranslatedNodes[shadowIndex].value;
+            const float casterZ = zTranslatedNodes[shadowIndex].key;
+            // attempt to render the shadow if the caster about to be drawn is its caster,
+            // OR if its caster's Z value is similar to the previous potential caster
+            if (shadowIndex == drawIndex || casterZ - lastCasterZ < 0.1f) {
+                deferShadow(*casterNodeOp);
+
+                lastCasterZ = casterZ; // must do this even if current caster not casting a shadow
+                shadowIndex++;
+                continue;
+            }
+        }
+
+        const RenderNodeOp* childOp = zTranslatedNodes[drawIndex].value;
+        deferRenderNodeOp(*childOp);
+        drawIndex++;
+    }
+}
+
+void OpReorderer::deferShadow(const RenderNodeOp& casterNodeOp) {
+    // TODO
+}
 /**
  * Used to define a list of lambdas referencing private OpReorderer::onXXXXOp() methods.
  *
@@ -388,17 +477,20 @@ void OpReorderer::deferImpl(const DisplayList& displayList) {
         MAP_OPS(OP_RECEIVER)
     };
     for (const DisplayList::Chunk& chunk : displayList.getChunks()) {
+        FatVector<ZRenderNodeOpPair, 16> zTranslatedNodes;
+        buildZSortedChildList(&zTranslatedNodes, displayList, chunk);
+
+        defer3dChildren(ChildrenSelectMode::Negative, zTranslatedNodes);
         for (size_t opIndex = chunk.beginOpIndex; opIndex < chunk.endOpIndex; opIndex++) {
             const RecordedOp* op = displayList.getOps()[opIndex];
             receivers[op->opId](*this, *op);
         }
+        defer3dChildren(ChildrenSelectMode::Positive, zTranslatedNodes);
     }
 }
 
-void OpReorderer::onRenderNodeOp(const RenderNodeOp& op) {
-    if (op.renderNode->nothingToDraw()) {
-        return;
-    }
+void OpReorderer::deferRenderNodeOp(const RenderNodeOp& op) {
+    if (op.renderNode->nothingToDraw()) return;
     int count = mCanvasState.save(SkCanvas::kClip_SaveFlag | SkCanvas::kMatrix_SaveFlag);
 
     // apply state from RecordedOp
@@ -412,6 +504,12 @@ void OpReorderer::onRenderNodeOp(const RenderNodeOp& op) {
     mCanvasState.restoreToCount(count);
 }
 
+void OpReorderer::onRenderNodeOp(const RenderNodeOp& op) {
+    if (!op.skipInOrderDraw) {
+        deferRenderNodeOp(op);
+    }
+}
+
 static batchid_t tessellatedBatchId(const SkPaint& paint) {
     return paint.getPathEffect()
             ? OpBatchType::AlphaMaskTexture
index 77be402..936b6ed 100644 (file)
@@ -182,6 +182,10 @@ public:
     virtual GLuint getTargetFbo() const override { return 0; }
 
 private:
+    enum class ChildrenSelectMode {
+        Negative,
+        Positive
+    };
     void saveForLayer(uint32_t layerWidth, uint32_t layerHeight,
             const BeginLayerOp* beginLayerOp, RenderNode* renderNode);
     void restoreForLayer();
@@ -195,8 +199,15 @@ private:
     // should always be surrounded by a save/restore pair
     void deferNodePropsAndOps(RenderNode& node);
 
+    void deferShadow(const RenderNodeOp& casterOp);
+
     void deferImpl(const DisplayList& displayList);
 
+    template <typename V>
+    void defer3dChildren(ChildrenSelectMode mode, const V& zTranslatedNodes);
+
+    void deferRenderNodeOp(const RenderNodeOp& op);
+
     void replayBakedOpsImpl(void* arg, BakedOpDispatcher* receivers);
 
     /**
index 9ae868a..04af8e3 100644 (file)
@@ -93,6 +93,7 @@ struct RenderNodeOp : RecordedOp {
             : SUPER_PAINTLESS(RenderNodeOp)
             , renderNode(renderNode) {}
     RenderNode * renderNode; // not const, since drawing modifies it (somehow...)
+    bool skipInOrderDraw = false;
 };
 
 struct BitmapOp : RecordedOp {
index 7c460b1..e988555 100644 (file)
@@ -40,7 +40,7 @@ void RecordingCanvas::reset(int width, int height) {
 
     mState.initializeSaveStack(width, height, 0, 0, width, height, Vector3());
 
-    mDeferredBarrierType = kBarrier_InOrder;
+    mDeferredBarrierType = DeferredBarrierType::InOrder;
     mState.setDirtyClip(false);
     mRestoreSaveCount = -1;
 }
@@ -432,17 +432,17 @@ size_t RecordingCanvas::addOp(RecordedOp* op) {
     // TODO: validate if "addDrawOp" quickrejection logic is useful before adding
     int insertIndex = mDisplayList->ops.size();
     mDisplayList->ops.push_back(op);
-    if (mDeferredBarrierType != kBarrier_None) {
+    if (mDeferredBarrierType != DeferredBarrierType::None) {
         // op is first in new chunk
         mDisplayList->chunks.emplace_back();
         DisplayList::Chunk& newChunk = mDisplayList->chunks.back();
         newChunk.beginOpIndex = insertIndex;
         newChunk.endOpIndex = insertIndex + 1;
-        newChunk.reorderChildren = (mDeferredBarrierType == kBarrier_OutOfOrder);
+        newChunk.reorderChildren = (mDeferredBarrierType == DeferredBarrierType::OutOfOrder);
 
         int nextChildIndex = mDisplayList->children.size();
         newChunk.beginChildIndex = newChunk.endChildIndex = nextChildIndex;
-        mDeferredBarrierType = kBarrier_None;
+        mDeferredBarrierType = DeferredBarrierType::None;
     } else {
         // standard case - append to existing chunk
         mDisplayList->chunks.back().endOpIndex = insertIndex + 1;
index 8a56475..fc84c98 100644 (file)
@@ -39,6 +39,11 @@ class OpReceiver;
 struct RecordedOp;
 
 class RecordingCanvas: public Canvas, public CanvasStateClient {
+    enum class DeferredBarrierType {
+        None,
+        InOrder,
+        OutOfOrder,
+    };
 public:
     RecordingCanvas(size_t width, size_t height);
     virtual ~RecordingCanvas();
@@ -49,7 +54,10 @@ public:
 // ----------------------------------------------------------------------------
 // MISC HWUI OPERATIONS - TODO: CATEGORIZE
 // ----------------------------------------------------------------------------
-    void insertReorderBarrier(bool enableReorder) {}
+    void insertReorderBarrier(bool enableReorder) {
+        mDeferredBarrierType = enableReorder
+                ? DeferredBarrierType::OutOfOrder : DeferredBarrierType::InOrder;
+    }
     void drawRenderNode(RenderNode* renderNode);
 
 // ----------------------------------------------------------------------------
@@ -176,11 +184,6 @@ public:
     virtual bool drawTextAbsolutePos() const override { return false; }
 
 private:
-    enum DeferredBarrierType {
-        kBarrier_None,
-        kBarrier_InOrder,
-        kBarrier_OutOfOrder,
-    };
 
     void drawBitmap(const SkBitmap* bitmap, const SkPaint* paint);
     void drawSimpleRects(const float* rects, int vertexCount, const SkPaint* paint);
@@ -290,7 +293,7 @@ private:
     CanvasState mState;
     std::unique_ptr<SkiaCanvasProxy> mSkiaCanvasProxy;
     ResourceCache& mResourceCache;
-    DeferredBarrierType mDeferredBarrierType = kBarrier_None;
+    DeferredBarrierType mDeferredBarrierType = DeferredBarrierType::None;
     DisplayList* mDisplayList = nullptr;
     bool mHighContrastText = false;
     SkAutoTUnref<SkDrawFilter> mDrawFilter;
index 2c25751..bae5ebe 100644 (file)
@@ -201,7 +201,6 @@ public:
                 || properties().getScaleY() == 0;
     }
 
-    // Only call if RenderNode has DisplayList...
     const DisplayList* getDisplayList() const {
         return mDisplayList;
     }
index 9d625bc..ef205ec 100644 (file)
@@ -32,7 +32,7 @@ TEST(LayerUpdateQueue, construct) {
 // sync node properties, so properties() reflects correct width and height
 static sp<RenderNode> createSyncedNode(uint32_t width, uint32_t height) {
     sp<RenderNode> node = TestUtils::createNode(0, 0, width, height);
-    TestUtils::syncNodePropertiesAndDisplayList(node);
+    TestUtils::syncHierarchyPropertiesAndDisplayList(node);
     return node;
 }
 
index 50f210f..f67c24a 100644 (file)
@@ -198,8 +198,7 @@ TEST(OpReorderer, renderNode) {
         canvas.restore();
     });
 
-    TestUtils::syncNodePropertiesAndDisplayList(child);
-    TestUtils::syncNodePropertiesAndDisplayList(parent);
+    TestUtils::syncHierarchyPropertiesAndDisplayList(parent);
 
     std::vector< sp<RenderNode> > nodes;
     nodes.push_back(parent.get());
@@ -225,7 +224,7 @@ TEST(OpReorderer, clipped) {
         SkBitmap bitmap = TestUtils::createSkBitmap(200, 200);
         canvas.drawBitmap(bitmap, 0, 0, nullptr);
     });
-    TestUtils::syncNodePropertiesAndDisplayList(node);
+    TestUtils::syncHierarchyPropertiesAndDisplayList(node);
     std::vector< sp<RenderNode> > nodes;
     nodes.push_back(node.get());
 
@@ -409,7 +408,7 @@ TEST(OpReorderer, hwLayerSimple) {
     OffscreenBuffer** bufferHandle = node->getLayerHandle();
     *bufferHandle = (OffscreenBuffer*) 0x0124;
 
-    TestUtils::syncNodePropertiesAndDisplayList(node);
+    TestUtils::syncHierarchyPropertiesAndDisplayList(node);
 
     std::vector< sp<RenderNode> > nodes;
     nodes.push_back(node.get());
@@ -483,7 +482,8 @@ public:
     }
 };
 TEST(OpReorderer, hwLayerComplex) {
-    sp<RenderNode> child = TestUtils::createNode<RecordingCanvas>(50, 50, 150, 150, [](RecordingCanvas& canvas) {
+    auto child = TestUtils::createNode<RecordingCanvas>(50, 50, 150, 150,
+            [](RecordingCanvas& canvas) {
         SkPaint paint;
         paint.setColor(SK_ColorWHITE);
         canvas.drawRect(0, 0, 100, 100, paint);
@@ -493,7 +493,8 @@ TEST(OpReorderer, hwLayerComplex) {
     *(child->getLayerHandle()) = (OffscreenBuffer*) 0x4567;
 
     RenderNode* childPtr = child.get();
-    sp<RenderNode> parent = TestUtils::createNode<RecordingCanvas>(0, 0, 200, 200, [childPtr](RecordingCanvas& canvas) {
+    auto parent = TestUtils::createNode<RecordingCanvas>(0, 0, 200, 200,
+            [childPtr](RecordingCanvas& canvas) {
         SkPaint paint;
         paint.setColor(SK_ColorDKGRAY);
         canvas.drawRect(0, 0, 200, 200, paint);
@@ -506,8 +507,7 @@ TEST(OpReorderer, hwLayerComplex) {
     parent->setPropertyFieldsDirty(RenderNode::GENERIC);
     *(parent->getLayerHandle()) = (OffscreenBuffer*) 0x0123;
 
-    TestUtils::syncNodePropertiesAndDisplayList(child);
-    TestUtils::syncNodePropertiesAndDisplayList(parent);
+    TestUtils::syncHierarchyPropertiesAndDisplayList(parent);
 
     std::vector< sp<RenderNode> > nodes;
     nodes.push_back(parent.get());
@@ -528,6 +528,55 @@ TEST(OpReorderer, hwLayerComplex) {
 }
 
 
+class ZReorderTestRenderer : public TestRendererBase {
+public:
+    void onRectOp(const RectOp& op, const BakedOpState& state) override {
+        int expectedOrder = SkColorGetB(op.paint->getColor()); // extract order from blue channel
+        EXPECT_EQ(expectedOrder, mIndex++) << "An op was drawn out of order";
+    }
+};
+static void drawOrderedRect(RecordingCanvas* canvas, uint8_t expectedDrawOrder) {
+    SkPaint paint;
+    paint.setColor(SkColorSetARGB(256, 0, 0, expectedDrawOrder)); // order put in blue channel
+    canvas->drawRect(0, 0, 100, 100, paint);
+}
+static void drawOrderedNode(RecordingCanvas* canvas, uint8_t expectedDrawOrder, float z) {
+    auto node = TestUtils::createNode<RecordingCanvas>(0, 0, 100, 100,
+            [expectedDrawOrder](RecordingCanvas& canvas) {
+        drawOrderedRect(&canvas, expectedDrawOrder);
+    });
+    node->mutateStagingProperties().setTranslationZ(z);
+    node->setPropertyFieldsDirty(RenderNode::TRANSLATION_Z);
+    canvas->drawRenderNode(node.get()); // canvas takes reference/sole ownership
+}
+TEST(OpReorderer, zReorder) {
+    auto parent = TestUtils::createNode<RecordingCanvas>(0, 0, 100, 100,
+            [](RecordingCanvas& canvas) {
+        drawOrderedNode(&canvas, 0, 10.0f); // in reorder=false at this point, so played inorder
+        drawOrderedRect(&canvas, 1);
+        canvas.insertReorderBarrier(true);
+        drawOrderedNode(&canvas, 6, 2.0f);
+        drawOrderedRect(&canvas, 3);
+        drawOrderedNode(&canvas, 4, 0.0f);
+        drawOrderedRect(&canvas, 5);
+        drawOrderedNode(&canvas, 2, -2.0f);
+        drawOrderedNode(&canvas, 7, 2.0f);
+        canvas.insertReorderBarrier(false);
+        drawOrderedRect(&canvas, 8);
+        drawOrderedNode(&canvas, 9, -10.0f); // in reorder=false at this point, so played inorder
+    });
+    TestUtils::syncHierarchyPropertiesAndDisplayList(parent);
+
+    std::vector< sp<RenderNode> > nodes;
+    nodes.push_back(parent.get());
+    OpReorderer reorderer(sEmptyLayerUpdateQueue, SkRect::MakeWH(100, 100), 100, 100, nodes);
+
+    ZReorderTestRenderer renderer;
+    reorderer.replayBakedOps<TestDispatcher>(renderer);
+    EXPECT_EQ(10, renderer.getIndex());
+};
+
+
 class PropertyTestRenderer : public TestRendererBase {
 public:
     PropertyTestRenderer(std::function<void(const RectOp&, const BakedOpState&)> callback)
@@ -548,7 +597,7 @@ static void testProperty(
         canvas.drawRect(0, 0, 100, 100, paint);
     });
     node->setPropertyFieldsDirty(propSetupCallback(node->mutateStagingProperties()));
-    TestUtils::syncNodePropertiesAndDisplayList(node);
+    TestUtils::syncHierarchyPropertiesAndDisplayList(node);
 
     std::vector< sp<RenderNode> > nodes;
     nodes.push_back(node.get());
@@ -642,5 +691,6 @@ TEST(OpReorderer, renderPropTransform) {
                 << "Op draw matrix must match expected combination of transformation properties";
     });
 }
+
 } // namespace uirenderer
 } // namespace android
index dcf1f64..83b37ab 100644 (file)
@@ -25,7 +25,7 @@ namespace uirenderer {
 
 static void playbackOps(const DisplayList& displayList,
         std::function<void(const RecordedOp&)> opReceiver) {
-    for (const DisplayList::Chunk& chunk : displayList.getChunks()) {
+    for (auto& chunk : displayList.getChunks()) {
         for (size_t opIndex = chunk.beginOpIndex; opIndex < chunk.endOpIndex; opIndex++) {
             RecordedOp* op = displayList.getOps()[opIndex];
             opReceiver(*op);
@@ -224,5 +224,26 @@ TEST(RecordingCanvas, saveLayerRotateClipped) {
     EXPECT_EQ(3, count);
 }
 
+TEST(RecordingCanvas, testReorderBarrier) {
+    auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) {
+        canvas.drawRect(0, 0, 400, 400, SkPaint());
+        canvas.insertReorderBarrier(true);
+        canvas.insertReorderBarrier(false);
+        canvas.insertReorderBarrier(false);
+        canvas.insertReorderBarrier(true);
+        canvas.drawRect(0, 0, 400, 400, SkPaint());
+        canvas.insertReorderBarrier(false);
+    });
+
+    auto chunks = dl->getChunks();
+    EXPECT_EQ(0u, chunks[0].beginOpIndex);
+    EXPECT_EQ(1u, chunks[0].endOpIndex);
+    EXPECT_FALSE(chunks[0].reorderChildren);
+
+    EXPECT_EQ(1u, chunks[1].beginOpIndex);
+    EXPECT_EQ(2u, chunks[1].endOpIndex);
+    EXPECT_TRUE(chunks[1].reorderChildren);
+}
+
 } // namespace uirenderer
 } // namespace android
index 0cf8040..28e0fd8 100644 (file)
@@ -17,6 +17,7 @@
 #define TEST_UTILS_H
 
 #include <DeviceInfo.h>
+#include <DisplayList.h>
 #include <Matrix.h>
 #include <Rect.h>
 #include <RenderNode.h>
 #include <renderthread/RenderThread.h>
 #include <Snapshot.h>
 
+#if HWUI_NEW_OPS
+#include <RecordedOp.h>
+#else
+#include <DisplayListOp.h>
+#endif
+
 #include <memory>
 
 namespace android {
@@ -117,9 +124,8 @@ public:
         return node;
     }
 
-    static void syncNodePropertiesAndDisplayList(sp<RenderNode>& node) {
-        node->syncProperties();
-        node->syncDisplayList();
+    static void syncHierarchyPropertiesAndDisplayList(sp<RenderNode>& node) {
+        syncHierarchyPropertiesAndDisplayListImpl(node.get());
     }
 
     typedef std::function<void(renderthread::RenderThread& thread)> RtCallback;
@@ -147,6 +153,18 @@ public:
         TestTask task(rtCallback);
         renderthread::RenderThread::getInstance().queueAndWait(&task);
     }
+private:
+    static void syncHierarchyPropertiesAndDisplayListImpl(RenderNode* node) {
+        node->syncProperties();
+        node->syncDisplayList();
+        auto displayList = node->getDisplayList();
+        if (displayList) {
+            for (auto&& childOp : displayList->getChildren()) {
+                syncHierarchyPropertiesAndDisplayListImpl(childOp->renderNode);
+            }
+        }
+    }
+
 }; // class TestUtils
 
 } /* namespace uirenderer */