Merge "Improve multi-window render clipping logic" into nyc-dev
am: c79c324

* commit 'c79c3246c9a3e0d2aa34afd18fddc95a6aff0f30':
  Improve multi-window render clipping logic

Change-Id: I4be0f6cefba71f7928fec559481fe389d9de800b
diff --git a/core/java/com/android/internal/policy/BackdropFrameRenderer.java b/core/java/com/android/internal/policy/BackdropFrameRenderer.java
index 738aaca..b1598e7 100644
--- a/core/java/com/android/internal/policy/BackdropFrameRenderer.java
+++ b/core/java/com/android/internal/policy/BackdropFrameRenderer.java
@@ -270,15 +270,12 @@
             mLastXOffset = xOffset;
             mLastYOffset = yOffset;
 
-            // Only clip the content to the bounds if we are not fullscreen. In the other case, we
-            // actually need to draw outside these.
-            if (mResizeMode == RESIZE_MODE_FREEFORM) {
-                mRenderer.setContentDrawBounds(
-                        mLastXOffset,
-                        mLastYOffset,
-                        mLastXOffset + mLastContentWidth,
-                        mLastYOffset + mLastCaptionHeight + mLastContentHeight);
-            }
+            // Inform the renderer of the content's new bounds
+            mRenderer.setContentDrawBounds(
+                    mLastXOffset,
+                    mLastYOffset,
+                    mLastXOffset + mLastContentWidth,
+                    mLastYOffset + mLastCaptionHeight + mLastContentHeight);
 
             // If this was the first call and redrawLocked got already called prior
             // to us, we should re-issue a redrawLocked now.
diff --git a/libs/hwui/FrameBuilder.cpp b/libs/hwui/FrameBuilder.cpp
index 6fc74a5..502f027 100644
--- a/libs/hwui/FrameBuilder.cpp
+++ b/libs/hwui/FrameBuilder.cpp
@@ -31,18 +31,16 @@
 namespace android {
 namespace uirenderer {
 
-FrameBuilder::FrameBuilder(const LayerUpdateQueue& layers, const SkRect& clip,
+FrameBuilder::FrameBuilder(const SkRect& clip,
         uint32_t viewportWidth, uint32_t viewportHeight,
-        const std::vector< sp<RenderNode> >& nodes,
-        const LightGeometry& lightGeometry, const Rect &contentDrawBounds, Caches& caches)
-        : mCanvasState(*this)
+        const LightGeometry& lightGeometry, Caches& caches)
+        : mStdAllocator(mAllocator)
+        , mLayerBuilders(mStdAllocator)
+        , mLayerStack(mStdAllocator)
+        , mCanvasState(*this)
         , mCaches(caches)
         , mLightRadius(lightGeometry.radius)
-        , mDrawFbo0(!nodes.empty()) {
-    ATRACE_NAME("prepare drawing commands");
-
-    mLayerBuilders.reserve(layers.entries().size());
-    mLayerStack.reserve(layers.entries().size());
+        , mDrawFbo0(true) {
 
     // Prepare to defer Fbo0
     auto fbo0 = mAllocator.create<LayerBuilder>(viewportWidth, viewportHeight, Rect(clip));
@@ -51,7 +49,31 @@
     mCanvasState.initializeSaveStack(viewportWidth, viewportHeight,
             clip.fLeft, clip.fTop, clip.fRight, clip.fBottom,
             lightGeometry.center);
+}
 
+FrameBuilder::FrameBuilder(const LayerUpdateQueue& layers,
+        const LightGeometry& lightGeometry, Caches& caches)
+        : mStdAllocator(mAllocator)
+        , mLayerBuilders(mStdAllocator)
+        , mLayerStack(mStdAllocator)
+        , mCanvasState(*this)
+        , mCaches(caches)
+        , mLightRadius(lightGeometry.radius)
+        , mDrawFbo0(false) {
+    // TODO: remove, with each layer on its own save stack
+
+    // Prepare to defer Fbo0 (which will be empty)
+    auto fbo0 = mAllocator.create<LayerBuilder>(1, 1, Rect(1, 1));
+    mLayerBuilders.push_back(fbo0);
+    mLayerStack.push_back(0);
+    mCanvasState.initializeSaveStack(1, 1,
+            0, 0, 1, 1,
+            lightGeometry.center);
+
+    deferLayers(layers);
+}
+
+void FrameBuilder::deferLayers(const LayerUpdateQueue& layers) {
     // Render all layers to be updated, in order. Defer in reverse order, so that they'll be
     // updated in the order they're passed in (mLayerBuilders are issued to Renderer in reverse)
     for (int i = layers.entries().size() - 1; i >= 0; i--) {
@@ -76,10 +98,45 @@
             restoreForLayer();
         }
     }
+}
 
+void FrameBuilder::deferRenderNode(RenderNode& renderNode) {
+    renderNode.computeOrdering();
+
+    mCanvasState.save(SaveFlags::MatrixClip);
+    deferNodePropsAndOps(renderNode);
+    mCanvasState.restore();
+}
+
+void FrameBuilder::deferRenderNode(float tx, float ty, Rect clipRect, RenderNode& renderNode) {
+    renderNode.computeOrdering();
+
+    mCanvasState.save(SaveFlags::MatrixClip);
+    mCanvasState.translate(tx, ty);
+    mCanvasState.clipRect(clipRect.left, clipRect.top, clipRect.right, clipRect.bottom,
+            SkRegion::kIntersect_Op);
+    deferNodePropsAndOps(renderNode);
+    mCanvasState.restore();
+}
+
+static Rect nodeBounds(RenderNode& node) {
+    auto& props = node.properties();
+    return Rect(props.getLeft(), props.getTop(),
+            props.getRight(), props.getBottom());
+}
+
+void FrameBuilder::deferRenderNodeScene(const std::vector< sp<RenderNode> >& nodes,
+        const Rect& contentDrawBounds) {
+    if (nodes.size() < 1) return;
+    if (nodes.size() == 1) {
+        if (!nodes[0]->nothingToDraw()) {
+            deferRenderNode(*nodes[0]);
+        }
+        return;
+    }
     // It there are multiple render nodes, they are laid out as follows:
     // #0 - backdrop (content + caption)
-    // #1 - content (positioned at (0,0) and clipped to - its bounds mContentDrawBounds)
+    // #1 - content (local bounds are at (0,0), will be translated and clipped to backdrop)
     // #2 - additional overlay nodes
     // Usually the backdrop cannot be seen since it will be entirely covered by the content. While
     // resizing however it might become partially visible. The following render loop will crop the
@@ -88,45 +145,52 @@
     //
     // Additional nodes will be drawn on top with no particular clipping semantics.
 
-    // The bounds of the backdrop against which the content should be clipped.
-    Rect backdropBounds = contentDrawBounds;
     // Usually the contents bounds should be mContentDrawBounds - however - we will
     // move it towards the fixed edge to give it a more stable appearance (for the moment).
     // If there is no content bounds we ignore the layering as stated above and start with 2.
-    int layer = (contentDrawBounds.isEmpty() || nodes.size() == 1) ? 2 : 0;
 
-    for (const sp<RenderNode>& node : nodes) {
-        if (node->nothingToDraw()) continue;
-        node->computeOrdering();
-        int count = mCanvasState.save(SaveFlags::MatrixClip);
+    // Backdrop bounds in render target space
+    const Rect backdrop = nodeBounds(*nodes[0]);
 
-        if (layer == 0) {
-            const RenderProperties& properties = node->properties();
-            Rect targetBounds(properties.getLeft(), properties.getTop(),
-                              properties.getRight(), properties.getBottom());
-            // Move the content bounds towards the fixed corner of the backdrop.
-            const int x = targetBounds.left;
-            const int y = targetBounds.top;
-            // Remember the intersection of the target bounds and the intersection bounds against
-            // which we have to crop the content.
-            backdropBounds.set(x, y, x + backdropBounds.getWidth(), y + backdropBounds.getHeight());
-            backdropBounds.doIntersect(targetBounds);
-        } else if (layer == 1) {
-            // We shift and clip the content to match its final location in the window.
-            const float left = contentDrawBounds.left;
-            const float top = contentDrawBounds.top;
-            const float dx = backdropBounds.left - left;
-            const float dy = backdropBounds.top - top;
-            const float width = backdropBounds.getWidth();
-            const float height = backdropBounds.getHeight();
-            mCanvasState.translate(dx, dy);
-            // It gets cropped against the bounds of the backdrop to stay inside.
-            mCanvasState.clipRect(left, top, left + width, top + height, SkRegion::kIntersect_Op);
+    // Bounds that content will fill in render target space (note content node bounds may be bigger)
+    Rect content(contentDrawBounds.getWidth(), contentDrawBounds.getHeight());
+    content.translate(backdrop.left, backdrop.top);
+    if (!content.contains(backdrop) && !nodes[0]->nothingToDraw()) {
+        // Content doesn't entirely overlap backdrop, so fill around content (right/bottom)
+
+        // Note: in the future, if content doesn't snap to backdrop's left/top, this may need to
+        // also fill left/top. Currently, both 2up and freeform position content at the top/left of
+        // the backdrop, so this isn't necessary.
+        if (content.right < backdrop.right) {
+            // draw backdrop to right side of content
+            deferRenderNode(0, 0, Rect(content.right, backdrop.top,
+                    backdrop.right, backdrop.bottom), *nodes[0]);
         }
+        if (content.bottom < backdrop.bottom) {
+            // draw backdrop to bottom of content
+            // Note: bottom fill uses content left/right, to avoid overdrawing left/right fill
+            deferRenderNode(0, 0, Rect(content.left, content.bottom,
+                    content.right, backdrop.bottom), *nodes[0]);
+        }
+    }
 
-        deferNodePropsAndOps(*node);
-        mCanvasState.restoreToCount(count);
-        layer++;
+    if (!backdrop.isEmpty()) {
+        // content node translation to catch up with backdrop
+        float dx = contentDrawBounds.left - backdrop.left;
+        float dy = contentDrawBounds.top - backdrop.top;
+
+        Rect contentLocalClip = backdrop;
+        contentLocalClip.translate(dx, dy);
+        deferRenderNode(-dx, -dy, contentLocalClip, *nodes[1]);
+    } else {
+        deferRenderNode(*nodes[1]);
+    }
+
+    // remaining overlay nodes, simply defer
+    for (size_t index = 2; index < nodes.size(); index++) {
+        if (!nodes[index]->nothingToDraw()) {
+            deferRenderNode(*nodes[index]);
+        }
     }
 }
 
diff --git a/libs/hwui/FrameBuilder.h b/libs/hwui/FrameBuilder.h
index a6fd761..b915443 100644
--- a/libs/hwui/FrameBuilder.h
+++ b/libs/hwui/FrameBuilder.h
@@ -37,8 +37,8 @@
 class Rect;
 
 /**
- * Traverses all of the drawing commands from the layers and RenderNodes passed into it, preparing
- * them to be rendered.
+ * Processes, optimizes, and stores rendering commands from RenderNodes and
+ * LayerUpdateQueue, building content needed to render a frame.
  *
  * Resolves final drawing state for each operation (including clip, alpha and matrix), and then
  * reorder and merge each op as it is resolved for drawing efficiency. Each layer of content (either
@@ -60,21 +60,21 @@
         float radius;
     };
 
-    // TODO: remove
-    FrameBuilder(const LayerUpdateQueue& layers, const SkRect& clip,
+    FrameBuilder(const SkRect& clip,
             uint32_t viewportWidth, uint32_t viewportHeight,
-            const std::vector< sp<RenderNode> >& nodes,
-            const LightGeometry& lightGeometry,
-            Caches& caches)
-            : FrameBuilder(layers, clip, viewportWidth, viewportHeight,
-                    nodes, lightGeometry, Rect(), caches) {}
+            const LightGeometry& lightGeometry, Caches& caches);
 
-    FrameBuilder(const LayerUpdateQueue& layers, const SkRect& clip,
-            uint32_t viewportWidth, uint32_t viewportHeight,
-            const std::vector< sp<RenderNode> >& nodes,
-            const LightGeometry& lightGeometry,
-            const Rect &contentDrawBounds,
-            Caches& caches);
+    FrameBuilder(const LayerUpdateQueue& layerUpdateQueue,
+            const LightGeometry& lightGeometry, Caches& caches);
+
+    void deferLayers(const LayerUpdateQueue& layers);
+
+    void deferRenderNode(RenderNode& renderNode);
+
+    void deferRenderNode(float tx, float ty, Rect clipRect, RenderNode& renderNode);
+
+    void deferRenderNodeScene(const std::vector< sp<RenderNode> >& nodes,
+            const Rect& contentDrawBounds);
 
     virtual ~FrameBuilder() {}
 
@@ -223,8 +223,12 @@
     MAP_DEFERRABLE_OPS(X)
 #undef X
 
+    // contains single-frame objects, such as BakedOpStates, LayerBuilders, Batches
+    LinearAllocator mAllocator;
+    LinearStdAllocator<void*> mStdAllocator;
+
     // List of every deferred layer's render state. Replayed in reverse order to render a frame.
-    std::vector<LayerBuilder*> mLayerBuilders;
+    LsaVector<LayerBuilder*> mLayerBuilders;
 
     /*
      * Stack of indices within mLayerBuilders representing currently active layers. If drawing
@@ -238,7 +242,7 @@
      * won't be in mLayerStack. This is because it can be replayed, but can't have any more drawing
      * ops added to it.
     */
-    std::vector<size_t> mLayerStack;
+    LsaVector<size_t> mLayerStack;
 
     CanvasState mCanvasState;
 
@@ -246,9 +250,6 @@
 
     float mLightRadius;
 
-    // contains single-frame objects, such as BakedOpStates, LayerBuilders, Batches
-    LinearAllocator mAllocator;
-
     const bool mDrawFbo0;
 };
 
diff --git a/libs/hwui/LayerBuilder.cpp b/libs/hwui/LayerBuilder.cpp
index eea11bf..3000777 100644
--- a/libs/hwui/LayerBuilder.cpp
+++ b/libs/hwui/LayerBuilder.cpp
@@ -244,7 +244,8 @@
 
         if (CC_UNLIKELY(activeUnclippedSaveLayers.empty()
                 && bakedState->computedState.opaqueOverClippedBounds
-                && bakedState->computedState.clippedBounds.contains(repaintRect))) {
+                && bakedState->computedState.clippedBounds.contains(repaintRect)
+                && !Properties::debugOverdraw)) {
             // discard all deferred drawing ops, since new one will occlude them
             clear();
         }
diff --git a/libs/hwui/renderthread/CanvasContext.cpp b/libs/hwui/renderthread/CanvasContext.cpp
index 890d4a1..904501e 100644
--- a/libs/hwui/renderthread/CanvasContext.cpp
+++ b/libs/hwui/renderthread/CanvasContext.cpp
@@ -356,9 +356,13 @@
 
 #if HWUI_NEW_OPS
     auto& caches = Caches::getInstance();
-    FrameBuilder frameBuilder(mLayerUpdateQueue, dirty, frame.width(), frame.height(),
-            mRenderNodes, mLightGeometry, mContentDrawBounds, caches);
+    FrameBuilder frameBuilder(dirty, frame.width(), frame.height(), mLightGeometry, caches);
+
+    frameBuilder.deferLayers(mLayerUpdateQueue);
     mLayerUpdateQueue.clear();
+
+    frameBuilder.deferRenderNodeScene(mRenderNodes, mContentDrawBounds);
+
     BakedOpRenderer renderer(caches, mRenderThread.renderState(),
             mOpaque, mLightInfo);
     frameBuilder.replayBakedOps<BakedOpDispatcher>(renderer);
@@ -623,8 +627,7 @@
 #if HWUI_NEW_OPS
     static const std::vector< sp<RenderNode> > emptyNodeList;
     auto& caches = Caches::getInstance();
-    FrameBuilder frameBuilder(mLayerUpdateQueue, SkRect::MakeWH(1, 1), 1, 1,
-            emptyNodeList, mLightGeometry, mContentDrawBounds, caches);
+    FrameBuilder frameBuilder(mLayerUpdateQueue, mLightGeometry, caches);
     mLayerUpdateQueue.clear();
     BakedOpRenderer renderer(caches, mRenderThread.renderState(),
             mOpaque, mLightInfo);
diff --git a/libs/hwui/tests/common/TestUtils.h b/libs/hwui/tests/common/TestUtils.h
index 5f4ebc0..dbaefa4 100644
--- a/libs/hwui/tests/common/TestUtils.h
+++ b/libs/hwui/tests/common/TestUtils.h
@@ -171,11 +171,9 @@
         syncHierarchyPropertiesAndDisplayListImpl(node.get());
     }
 
-    static std::vector<sp<RenderNode>> createSyncedNodeList(sp<RenderNode>& node) {
-        TestUtils::syncHierarchyPropertiesAndDisplayList(node);
-        std::vector<sp<RenderNode>> vec;
-        vec.emplace_back(node);
-        return vec;
+    static sp<RenderNode>& getSyncedNode(sp<RenderNode>& node) {
+        syncHierarchyPropertiesAndDisplayList(node);
+        return node;
     }
 
     typedef std::function<void(renderthread::RenderThread& thread)> RtCallback;
diff --git a/libs/hwui/tests/microbench/FrameBuilderBench.cpp b/libs/hwui/tests/microbench/FrameBuilderBench.cpp
index 0aef620..84ef9c2 100644
--- a/libs/hwui/tests/microbench/FrameBuilderBench.cpp
+++ b/libs/hwui/tests/microbench/FrameBuilderBench.cpp
@@ -35,11 +35,10 @@
 using namespace android::uirenderer::renderthread;
 using namespace android::uirenderer::test;
 
-const LayerUpdateQueue sEmptyLayerUpdateQueue;
 const FrameBuilder::LightGeometry sLightGeometry = { {100, 100, 100}, 50};
 const BakedOpRenderer::LightInfo sLightInfo = { 128, 128 };
 
-static std::vector<sp<RenderNode>> createTestNodeList() {
+static sp<RenderNode> createTestNode() {
     auto node = TestUtils::createNode(0, 0, 200, 200,
             [](RenderProperties& props, RecordingCanvas& canvas) {
         SkBitmap bitmap = TestUtils::createSkBitmap(10, 10);
@@ -56,31 +55,33 @@
         canvas.restore();
     });
     TestUtils::syncHierarchyPropertiesAndDisplayList(node);
-    std::vector<sp<RenderNode>> vec;
-    vec.emplace_back(node);
-    return vec;
+    return node;
 }
 
 void BM_FrameBuilder_defer(benchmark::State& state) {
-    auto nodes = createTestNodeList();
-    while (state.KeepRunning()) {
-        FrameBuilder frameBuilder(sEmptyLayerUpdateQueue, SkRect::MakeWH(100, 200), 100, 200,
-                nodes, sLightGeometry, Caches::getInstance());
-        benchmark::DoNotOptimize(&frameBuilder);
-    }
+    TestUtils::runOnRenderThread([&state](RenderThread& thread) {
+        auto node = createTestNode();
+        while (state.KeepRunning()) {
+            FrameBuilder frameBuilder(SkRect::MakeWH(100, 200), 100, 200,
+                    sLightGeometry, Caches::getInstance());
+            frameBuilder.deferRenderNode(*node);
+            benchmark::DoNotOptimize(&frameBuilder);
+        }
+    });
 }
 BENCHMARK(BM_FrameBuilder_defer);
 
 void BM_FrameBuilder_deferAndRender(benchmark::State& state) {
     TestUtils::runOnRenderThread([&state](RenderThread& thread) {
-        auto nodes = createTestNodeList();
+        auto node = createTestNode();
 
         RenderState& renderState = thread.renderState();
         Caches& caches = Caches::getInstance();
 
         while (state.KeepRunning()) {
-            FrameBuilder frameBuilder(sEmptyLayerUpdateQueue, SkRect::MakeWH(100, 200), 100, 200,
-                    nodes, sLightGeometry, Caches::getInstance());
+            FrameBuilder frameBuilder(SkRect::MakeWH(100, 200), 100, 200,
+                    sLightGeometry, caches);
+            frameBuilder.deferRenderNode(*node);
 
             BakedOpRenderer renderer(caches, renderState, true, sLightInfo);
             frameBuilder.replayBakedOps<BakedOpDispatcher>(renderer);
@@ -90,7 +91,7 @@
 }
 BENCHMARK(BM_FrameBuilder_deferAndRender);
 
-static std::vector<sp<RenderNode>> getSyncedSceneNodes(const char* sceneName) {
+static sp<RenderNode> getSyncedSceneNode(const char* sceneName) {
     gDisplay = getBuiltInDisplay(); // switch to real display if present
 
     TestContext testContext;
@@ -103,9 +104,7 @@
     });
 
     TestUtils::syncHierarchyPropertiesAndDisplayList(rootNode);
-    std::vector<sp<RenderNode>> nodes;
-    nodes.emplace_back(rootNode);
-    return nodes;
+    return rootNode;
 }
 
 static auto SCENES = {
@@ -116,11 +115,12 @@
     TestUtils::runOnRenderThread([&state](RenderThread& thread) {
         const char* sceneName = *(SCENES.begin() + state.range_x());
         state.SetLabel(sceneName);
-        auto nodes = getSyncedSceneNodes(sceneName);
+        auto node = getSyncedSceneNode(sceneName);
         while (state.KeepRunning()) {
-            FrameBuilder frameBuilder(sEmptyLayerUpdateQueue,
-                    SkRect::MakeWH(gDisplay.w, gDisplay.h), gDisplay.w, gDisplay.h,
-                    nodes, sLightGeometry, Caches::getInstance());
+            FrameBuilder frameBuilder(SkRect::MakeWH(gDisplay.w, gDisplay.h),
+                    gDisplay.w, gDisplay.h,
+                    sLightGeometry, Caches::getInstance());
+            frameBuilder.deferRenderNode(*node);
             benchmark::DoNotOptimize(&frameBuilder);
         }
     });
@@ -131,15 +131,16 @@
     TestUtils::runOnRenderThread([&state](RenderThread& thread) {
         const char* sceneName = *(SCENES.begin() + state.range_x());
         state.SetLabel(sceneName);
-        auto nodes = getSyncedSceneNodes(sceneName);
+        auto node = getSyncedSceneNode(sceneName);
 
         RenderState& renderState = thread.renderState();
         Caches& caches = Caches::getInstance();
 
         while (state.KeepRunning()) {
-            FrameBuilder frameBuilder(sEmptyLayerUpdateQueue,
-                    SkRect::MakeWH(gDisplay.w, gDisplay.h), gDisplay.w, gDisplay.h,
-                    nodes, sLightGeometry, Caches::getInstance());
+            FrameBuilder frameBuilder(SkRect::MakeWH(gDisplay.w, gDisplay.h),
+                    gDisplay.w, gDisplay.h,
+                    sLightGeometry, Caches::getInstance());
+            frameBuilder.deferRenderNode(*node);
 
             BakedOpRenderer renderer(caches, renderState, true, sLightInfo);
             frameBuilder.replayBakedOps<BakedOpDispatcher>(renderer);
diff --git a/libs/hwui/tests/unit/FrameBuilderTests.cpp b/libs/hwui/tests/unit/FrameBuilderTests.cpp
index ebc1c80..0f16b15 100644
--- a/libs/hwui/tests/unit/FrameBuilderTests.cpp
+++ b/libs/hwui/tests/unit/FrameBuilderTests.cpp
@@ -29,11 +29,8 @@
 namespace android {
 namespace uirenderer {
 
-const LayerUpdateQueue sEmptyLayerUpdateQueue;
-const std::vector< sp<RenderNode> > sEmptyNodeList;
 const FrameBuilder::LightGeometry sLightGeometry = { {100, 100, 100}, 50};
 
-
 /**
  * Virtual class implemented by each test to redirect static operation / state transitions to
  * virtual methods.
@@ -136,8 +133,10 @@
         canvas.drawRect(0, 0, 100, 200, SkPaint());
         canvas.drawBitmap(bitmap, 10, 10, nullptr);
     });
-    FrameBuilder frameBuilder(sEmptyLayerUpdateQueue, SkRect::MakeWH(100, 200), 100, 200,
-            TestUtils::createSyncedNodeList(node), sLightGeometry, Caches::getInstance());
+    FrameBuilder frameBuilder(SkRect::MakeWH(100, 200), 100, 200,
+            sLightGeometry, Caches::getInstance());
+    frameBuilder.deferRenderNode(*TestUtils::getSyncedNode(node));
+
     SimpleTestRenderer renderer;
     frameBuilder.replayBakedOps<TestDispatcher>(renderer);
     EXPECT_EQ(4, renderer.getIndex()); // 2 ops + start + end
@@ -162,8 +161,10 @@
         strokedPaint.setStrokeWidth(10);
         canvas.drawPoint(50, 50, strokedPaint);
     });
-    FrameBuilder frameBuilder(sEmptyLayerUpdateQueue, SkRect::MakeWH(100, 200), 100, 200,
-            TestUtils::createSyncedNodeList(node), sLightGeometry, Caches::getInstance());
+    FrameBuilder frameBuilder(SkRect::MakeWH(100, 200), 100, 200,
+            sLightGeometry, Caches::getInstance());
+    frameBuilder.deferRenderNode(*TestUtils::getSyncedNode(node));
+
     SimpleStrokeTestRenderer renderer;
     frameBuilder.replayBakedOps<TestDispatcher>(renderer);
     EXPECT_EQ(1, renderer.getIndex());
@@ -177,8 +178,9 @@
         canvas.drawRect(0, 0, 400, 400, SkPaint());
         canvas.restore();
     });
-    FrameBuilder frameBuilder(sEmptyLayerUpdateQueue, SkRect::MakeWH(200, 200), 200, 200,
-            TestUtils::createSyncedNodeList(node), sLightGeometry, Caches::getInstance());
+    FrameBuilder frameBuilder(SkRect::MakeWH(200, 200), 200, 200,
+            sLightGeometry, Caches::getInstance());
+    frameBuilder.deferRenderNode(*TestUtils::getSyncedNode(node));
 
     FailRenderer renderer;
     frameBuilder.replayBakedOps<TestDispatcher>(renderer);
@@ -211,15 +213,111 @@
         }
         canvas.restore();
     });
+    FrameBuilder frameBuilder(SkRect::MakeWH(200, 200), 200, 200,
+            sLightGeometry, Caches::getInstance());
+    frameBuilder.deferRenderNode(*TestUtils::getSyncedNode(node));
 
-    FrameBuilder frameBuilder(sEmptyLayerUpdateQueue, SkRect::MakeWH(200, 200), 200, 200,
-            TestUtils::createSyncedNodeList(node), sLightGeometry, Caches::getInstance());
     SimpleBatchingTestRenderer renderer;
     frameBuilder.replayBakedOps<TestDispatcher>(renderer);
     EXPECT_EQ(2 * LOOPS, renderer.getIndex())
             << "Expect number of ops = 2 * loop count";
 }
 
+RENDERTHREAD_TEST(FrameBuilder, deferRenderNode_translateClip) {
+    class DeferRenderNodeTranslateClipTestRenderer : public TestRendererBase {
+    public:
+        void onRectOp(const RectOp& op, const BakedOpState& state) override {
+            EXPECT_EQ(0, mIndex++);
+            EXPECT_EQ(Rect(5, 10, 55, 60), state.computedState.clippedBounds);
+            EXPECT_EQ(OpClipSideFlags::Right | OpClipSideFlags::Bottom,
+                    state.computedState.clipSideFlags);
+        }
+    };
+
+    auto node = TestUtils::createNode(0, 0, 100, 100,
+            [](RenderProperties& props, RecordingCanvas& canvas) {
+        canvas.drawRect(0, 0, 100, 100, SkPaint());
+    });
+
+    FrameBuilder frameBuilder(SkRect::MakeWH(100, 100), 100, 100,
+            sLightGeometry, Caches::getInstance());
+    frameBuilder.deferRenderNode(5, 10, Rect(50, 50), // translate + clip node
+            *TestUtils::getSyncedNode(node));
+
+    DeferRenderNodeTranslateClipTestRenderer renderer;
+    frameBuilder.replayBakedOps<TestDispatcher>(renderer);
+    EXPECT_EQ(1, renderer.getIndex());
+}
+
+RENDERTHREAD_TEST(FrameBuilder, deferRenderNodeScene) {
+    class DeferRenderNodeSceneTestRenderer : public TestRendererBase {
+    public:
+        void onRectOp(const RectOp& op, const BakedOpState& state) override {
+            const Rect& clippedBounds = state.computedState.clippedBounds;
+            Matrix4 expected;
+            switch (mIndex++) {
+            case 0:
+                // background - left side
+                EXPECT_EQ(Rect(600, 100, 700, 500), clippedBounds);
+                expected.loadTranslate(100, 100, 0);
+                break;
+            case 1:
+                // background - top side
+                EXPECT_EQ(Rect(100, 400, 600, 500), clippedBounds);
+                expected.loadTranslate(100, 100, 0);
+                break;
+            case 2:
+                // content
+                EXPECT_EQ(Rect(100, 100, 700, 500), clippedBounds);
+                expected.loadTranslate(-50, -50, 0);
+                break;
+            case 3:
+                // overlay
+                EXPECT_EQ(Rect(0, 0, 800, 200), clippedBounds);
+                break;
+            default:
+                ADD_FAILURE() << "Too many rects observed";
+            }
+            EXPECT_EQ(expected, state.computedState.transform);
+        }
+    };
+
+    std::vector<sp<RenderNode>> nodes;
+    SkPaint transparentPaint;
+    transparentPaint.setAlpha(128);
+
+    // backdrop
+    nodes.push_back(TestUtils::createNode(100, 100, 700, 500, // 600x400
+            [&transparentPaint](RenderProperties& props, RecordingCanvas& canvas) {
+        canvas.drawRect(0, 0, 600, 400, transparentPaint);
+    }));
+
+    // content
+    Rect contentDrawBounds(150, 150, 650, 450); // 500x300
+    nodes.push_back(TestUtils::createNode(0, 0, 800, 600,
+            [&transparentPaint](RenderProperties& props, RecordingCanvas& canvas) {
+        canvas.drawRect(0, 0, 800, 600, transparentPaint);
+    }));
+
+    // overlay
+    nodes.push_back(TestUtils::createNode(0, 0, 800, 600,
+            [&transparentPaint](RenderProperties& props, RecordingCanvas& canvas) {
+        canvas.drawRect(0, 0, 800, 200, transparentPaint);
+    }));
+
+    for (auto& node : nodes) {
+        TestUtils::syncHierarchyPropertiesAndDisplayList(node);
+    }
+
+    FrameBuilder frameBuilder(SkRect::MakeWH(800, 600), 800, 600,
+            sLightGeometry, Caches::getInstance());
+    frameBuilder.deferRenderNodeScene(nodes, contentDrawBounds);
+
+    DeferRenderNodeSceneTestRenderer renderer;
+    frameBuilder.replayBakedOps<TestDispatcher>(renderer);
+    EXPECT_EQ(4, renderer.getIndex());
+}
+
 RENDERTHREAD_TEST(FrameBuilder, empty_noFbo0) {
     class EmptyNoFbo0TestRenderer : public TestRendererBase {
     public:
@@ -231,9 +329,9 @@
         }
     };
 
-    // Pass empty node list, so no work is enqueued for Fbo0
-    FrameBuilder frameBuilder(sEmptyLayerUpdateQueue, SkRect::MakeWH(200, 200), 200, 200,
-            sEmptyNodeList, sLightGeometry, Caches::getInstance());
+    // Use layer update constructor, so no work is enqueued for Fbo0
+    LayerUpdateQueue emptyLayerUpdateQueue;
+    FrameBuilder frameBuilder(emptyLayerUpdateQueue, sLightGeometry, Caches::getInstance());
     EmptyNoFbo0TestRenderer renderer;
     frameBuilder.replayBakedOps<TestDispatcher>(renderer);
 }
@@ -252,11 +350,12 @@
             [](RenderProperties& props, RecordingCanvas& canvas) {
         // no drawn content
     });
-    auto syncedNodeList = TestUtils::createSyncedNodeList(node);
 
-    // Draw, but pass empty node list, so no work is done for primary frame
-    FrameBuilder frameBuilder(sEmptyLayerUpdateQueue, SkRect::MakeWH(200, 200), 200, 200,
-            syncedNodeList, sLightGeometry, Caches::getInstance());
+    // Draw, but pass node without draw content, so no work is done for primary frame
+    FrameBuilder frameBuilder(SkRect::MakeWH(200, 200), 200, 200,
+            sLightGeometry, Caches::getInstance());
+    frameBuilder.deferRenderNode(*TestUtils::getSyncedNode(node));
+
     EmptyWithFbo0TestRenderer renderer;
     frameBuilder.replayBakedOps<TestDispatcher>(renderer);
     EXPECT_EQ(2, renderer.getIndex()) << "No drawing content produced,"
@@ -281,9 +380,9 @@
 
     // Damage (and therefore clip) is same as last draw, subset of renderable area.
     // This means last op occludes other contents, and they'll be rejected to avoid overdraw.
-    SkRect damageRect = SkRect::MakeLTRB(10, 10, 190, 190);
-    FrameBuilder frameBuilder(sEmptyLayerUpdateQueue, damageRect, 200, 200,
-            TestUtils::createSyncedNodeList(node), sLightGeometry, Caches::getInstance());
+    FrameBuilder frameBuilder(SkRect::MakeLTRB(10, 10, 190, 190), 200, 200,
+            sLightGeometry, Caches::getInstance());
+    frameBuilder.deferRenderNode(*TestUtils::getSyncedNode(node));
 
     EXPECT_EQ(3u, node->getDisplayList()->getOps().size())
             << "Recording must not have rejected ops, in order for this test to be valid";
@@ -324,9 +423,9 @@
         canvas.drawBitmap(opaqueBitmap, 0, 0, nullptr);
         canvas.drawBitmap(transpBitmap, 0, 0, nullptr);
     });
-
-    FrameBuilder frameBuilder(sEmptyLayerUpdateQueue, SkRect::MakeWH(50, 50), 50, 50,
-            TestUtils::createSyncedNodeList(node), sLightGeometry, Caches::getInstance());
+    FrameBuilder frameBuilder(SkRect::MakeWH(50, 50), 50, 50,
+            sLightGeometry, Caches::getInstance());
+    frameBuilder.deferRenderNode(*TestUtils::getSyncedNode(node));
 
     EXPECT_EQ(5u, node->getDisplayList()->getOps().size())
             << "Recording must not have rejected ops, in order for this test to be valid";
@@ -369,8 +468,10 @@
         canvas.drawBitmap(bitmap, 40, 70, nullptr);
     });
 
-    FrameBuilder frameBuilder(sEmptyLayerUpdateQueue, SkRect::MakeWH(100, 100), 100, 100,
-            TestUtils::createSyncedNodeList(node), sLightGeometry, Caches::getInstance());
+    FrameBuilder frameBuilder(SkRect::MakeWH(100, 100), 100, 100,
+            sLightGeometry, Caches::getInstance());
+    frameBuilder.deferRenderNode(*TestUtils::getSyncedNode(node));
+
     ClippedMergingTestRenderer renderer;
     frameBuilder.replayBakedOps<TestDispatcher>(renderer);
     EXPECT_EQ(4, renderer.getIndex());
@@ -397,8 +498,10 @@
         TestUtils::drawUtf8ToCanvas(&canvas, "Test string1", paint, 100, 0); // will be top clipped
         TestUtils::drawUtf8ToCanvas(&canvas, "Test string1", paint, 100, 100); // not clipped
     });
-    FrameBuilder frameBuilder(sEmptyLayerUpdateQueue, SkRect::MakeWH(400, 400), 400, 400,
-            TestUtils::createSyncedNodeList(node), sLightGeometry, Caches::getInstance());
+    FrameBuilder frameBuilder(SkRect::MakeWH(400, 400), 400, 400,
+            sLightGeometry, Caches::getInstance());
+    frameBuilder.deferRenderNode(*TestUtils::getSyncedNode(node));
+
     TextMergingTestRenderer renderer;
     frameBuilder.replayBakedOps<TestDispatcher>(renderer);
     EXPECT_EQ(2, renderer.getIndex()) << "Expect 2 ops";
@@ -428,8 +531,11 @@
             TestUtils::drawUtf8ToCanvas(&canvas, "test text", textPaint, 10, 100 * (i + 1));
         }
     });
-    FrameBuilder frameBuilder(sEmptyLayerUpdateQueue, SkRect::MakeWH(200, 2000), 200, 2000,
-            TestUtils::createSyncedNodeList(node), sLightGeometry, Caches::getInstance());
+
+    FrameBuilder frameBuilder(SkRect::MakeWH(200, 2000), 200, 2000,
+            sLightGeometry, Caches::getInstance());
+    frameBuilder.deferRenderNode(*TestUtils::getSyncedNode(node));
+
     TextStrikethroughTestRenderer renderer;
     frameBuilder.replayBakedOps<TestDispatcher>(renderer);
     EXPECT_EQ(2 * LOOPS, renderer.getIndex())
@@ -485,8 +591,9 @@
             TestUtils::drawUtf8ToCanvas(&canvas, "Test string1", paint, 100, 100);
         }
     });
-    FrameBuilder frameBuilder(sEmptyLayerUpdateQueue, SkRect::MakeWH(400, 400), 400, 400,
-            TestUtils::createSyncedNodeList(node), sLightGeometry, Caches::getInstance());
+    FrameBuilder frameBuilder(SkRect::MakeWH(400, 400), 400, 400,
+            sLightGeometry, Caches::getInstance());
+    frameBuilder.deferRenderNode(*TestUtils::getSyncedNode(node));
     TextStyleTestRenderer renderer;
     frameBuilder.replayBakedOps<TestDispatcher>(renderer);
     EXPECT_EQ(3, renderer.getIndex()) << "Expect 3 ops";
@@ -516,8 +623,11 @@
         canvas.drawLayer(layerUpdater.get());
         canvas.restore();
     });
-    FrameBuilder frameBuilder(sEmptyLayerUpdateQueue, SkRect::MakeWH(200, 200), 200, 200,
-            TestUtils::createSyncedNodeList(node), sLightGeometry, Caches::getInstance());
+
+    FrameBuilder frameBuilder(SkRect::MakeWH(200, 200), 200, 200,
+            sLightGeometry, Caches::getInstance());
+    frameBuilder.deferRenderNode(*TestUtils::getSyncedNode(node));
+
     TextureLayerClipLocalMatrixTestRenderer renderer;
     frameBuilder.replayBakedOps<TestDispatcher>(renderer);
     EXPECT_EQ(1, renderer.getIndex());
@@ -546,8 +656,10 @@
         canvas.restore();
     });
 
-    FrameBuilder frameBuilder(sEmptyLayerUpdateQueue, SkRect::MakeWH(200, 200), 200, 200,
-            TestUtils::createSyncedNodeList(node), sLightGeometry, Caches::getInstance());
+    FrameBuilder frameBuilder(SkRect::MakeWH(200, 200), 200, 200,
+            sLightGeometry, Caches::getInstance());
+    frameBuilder.deferRenderNode(*TestUtils::getSyncedNode(node));
+
     TextureLayerCombineMatricesTestRenderer renderer;
     frameBuilder.replayBakedOps<TestDispatcher>(renderer);
     EXPECT_EQ(1, renderer.getIndex());
@@ -562,8 +674,11 @@
             [&layerUpdater](RenderProperties& props, RecordingCanvas& canvas) {
         canvas.drawLayer(layerUpdater.get());
     });
-    FrameBuilder frameBuilder(sEmptyLayerUpdateQueue, SkRect::MakeWH(200, 200), 200, 200,
-            TestUtils::createSyncedNodeList(node), sLightGeometry, Caches::getInstance());
+
+    FrameBuilder frameBuilder(SkRect::MakeWH(200, 200), 200, 200,
+            sLightGeometry, Caches::getInstance());
+    frameBuilder.deferRenderNode(*TestUtils::getSyncedNode(node));
+
     FailRenderer renderer;
     frameBuilder.replayBakedOps<TestDispatcher>(renderer);
 }
@@ -584,9 +699,10 @@
         canvas.callDrawGLFunction(&noopFunctor, nullptr);
     });
 
-    FrameBuilder frameBuilder(sEmptyLayerUpdateQueue, SkRect::MakeWH(200, 200), 200, 200,
-            TestUtils::createSyncedNodeList(scrolledFunctorView),
+    FrameBuilder frameBuilder(SkRect::MakeWH(200, 200), 200, 200,
             sLightGeometry, Caches::getInstance());
+    frameBuilder.deferRenderNode(*TestUtils::getSyncedNode(scrolledFunctorView));
+
     FunctorTestRenderer renderer;
     frameBuilder.replayBakedOps<TestDispatcher>(renderer);
     EXPECT_EQ(1, renderer.getIndex()) << "Functor should not be rejected";
@@ -608,9 +724,10 @@
         canvas.drawColor(SK_ColorWHITE, SkXfermode::Mode::kSrcOver_Mode);
     });
 
-    FrameBuilder frameBuilder(sEmptyLayerUpdateQueue, SkRect::MakeWH(200, 200), 200, 200,
-            TestUtils::createSyncedNodeList(unclippedColorView),
+    FrameBuilder frameBuilder(SkRect::MakeWH(200, 200), 200, 200,
             sLightGeometry, Caches::getInstance());
+    frameBuilder.deferRenderNode(*TestUtils::getSyncedNode(unclippedColorView));
+
     ColorTestRenderer renderer;
     frameBuilder.replayBakedOps<TestDispatcher>(renderer);
     EXPECT_EQ(1, renderer.getIndex()) << "ColorOp should not be rejected";
@@ -654,8 +771,10 @@
         canvas.restore();
     });
 
-    FrameBuilder frameBuilder(sEmptyLayerUpdateQueue, SkRect::MakeWH(200, 200), 200, 200,
-            TestUtils::createSyncedNodeList(parent), sLightGeometry, Caches::getInstance());
+    FrameBuilder frameBuilder(SkRect::MakeWH(200, 200), 200, 200,
+            sLightGeometry, Caches::getInstance());
+    frameBuilder.deferRenderNode(*TestUtils::getSyncedNode(parent));
+
     RenderNodeTestRenderer renderer;
     frameBuilder.replayBakedOps<TestDispatcher>(renderer);
     EXPECT_EQ(2, renderer.getIndex());
@@ -678,9 +797,11 @@
         canvas.drawBitmap(bitmap, 0, 0, nullptr);
     });
 
-    FrameBuilder frameBuilder(sEmptyLayerUpdateQueue,
-            SkRect::MakeLTRB(10, 20, 30, 40), // clip to small area, should see in receiver
-            200, 200, TestUtils::createSyncedNodeList(node), sLightGeometry, Caches::getInstance());
+    // clip to small area, should see in receiver
+    FrameBuilder frameBuilder(SkRect::MakeLTRB(10, 20, 30, 40), 200, 200,
+            sLightGeometry, Caches::getInstance());
+    frameBuilder.deferRenderNode(*TestUtils::getSyncedNode(node));
+
     ClippedTestRenderer renderer;
     frameBuilder.replayBakedOps<TestDispatcher>(renderer);
 }
@@ -725,8 +846,11 @@
         canvas.drawRect(10, 10, 190, 190, SkPaint());
         canvas.restore();
     });
-    FrameBuilder frameBuilder(sEmptyLayerUpdateQueue, SkRect::MakeWH(200, 200), 200, 200,
-            TestUtils::createSyncedNodeList(node), sLightGeometry, Caches::getInstance());
+
+    FrameBuilder frameBuilder(SkRect::MakeWH(200, 200), 200, 200,
+            sLightGeometry, Caches::getInstance());
+    frameBuilder.deferRenderNode(*TestUtils::getSyncedNode(node));
+
     SaveLayerSimpleTestRenderer renderer;
     frameBuilder.replayBakedOps<TestDispatcher>(renderer);
     EXPECT_EQ(5, renderer.getIndex());
@@ -806,8 +930,10 @@
         canvas.restore();
     });
 
-    FrameBuilder frameBuilder(sEmptyLayerUpdateQueue, SkRect::MakeWH(800, 800), 800, 800,
-            TestUtils::createSyncedNodeList(node), sLightGeometry, Caches::getInstance());
+    FrameBuilder frameBuilder(SkRect::MakeWH(800, 800), 800, 800,
+            sLightGeometry, Caches::getInstance());
+    frameBuilder.deferRenderNode(*TestUtils::getSyncedNode(node));
+
     SaveLayerNestedTestRenderer renderer;
     frameBuilder.replayBakedOps<TestDispatcher>(renderer);
     EXPECT_EQ(12, renderer.getIndex());
@@ -826,8 +952,10 @@
         canvas.restore();
         canvas.restore();
     });
-    FrameBuilder frameBuilder(sEmptyLayerUpdateQueue, SkRect::MakeWH(200, 200), 200, 200,
-            TestUtils::createSyncedNodeList(node), sLightGeometry, Caches::getInstance());
+
+    FrameBuilder frameBuilder(SkRect::MakeWH(200, 200), 200, 200,
+            sLightGeometry, Caches::getInstance());
+    frameBuilder.deferRenderNode(*TestUtils::getSyncedNode(node));
 
     FailRenderer renderer;
     // should see no ops, even within the layer, since the layer should be rejected
@@ -869,8 +997,11 @@
         canvas.drawRect(0, 0, 200, 200, SkPaint());
         canvas.restore();
     });
-    FrameBuilder frameBuilder(sEmptyLayerUpdateQueue, SkRect::MakeWH(200, 200), 200, 200,
-            TestUtils::createSyncedNodeList(node), sLightGeometry, Caches::getInstance());
+
+    FrameBuilder frameBuilder(SkRect::MakeWH(200, 200), 200, 200,
+            sLightGeometry, Caches::getInstance());
+    frameBuilder.deferRenderNode(*TestUtils::getSyncedNode(node));
+
     SaveLayerUnclippedSimpleTestRenderer renderer;
     frameBuilder.replayBakedOps<TestDispatcher>(renderer);
     EXPECT_EQ(4, renderer.getIndex());
@@ -923,8 +1054,11 @@
         canvas.drawRect(0, 0, 100, 100, SkPaint());
         canvas.restoreToCount(restoreTo);
     });
-    FrameBuilder frameBuilder(sEmptyLayerUpdateQueue, SkRect::MakeWH(200, 200), 200, 200,
-            TestUtils::createSyncedNodeList(node), sLightGeometry, Caches::getInstance());
+
+    FrameBuilder frameBuilder(SkRect::MakeWH(200, 200), 200, 200,
+            sLightGeometry, Caches::getInstance());
+    frameBuilder.deferRenderNode(*TestUtils::getSyncedNode(node));
+
     SaveLayerUnclippedMergedClearsTestRenderer renderer;
     frameBuilder.replayBakedOps<TestDispatcher>(renderer);
     EXPECT_EQ(10, renderer.getIndex())
@@ -964,8 +1098,10 @@
     });
 
     // draw with partial screen dirty, and assert we see that rect later
-    FrameBuilder frameBuilder(sEmptyLayerUpdateQueue, SkRect::MakeLTRB(50, 50, 150, 150), 200, 200,
-            TestUtils::createSyncedNodeList(node), sLightGeometry, Caches::getInstance());
+    FrameBuilder frameBuilder(SkRect::MakeLTRB(50, 50, 150, 150), 200, 200,
+            sLightGeometry, Caches::getInstance());
+    frameBuilder.deferRenderNode(*TestUtils::getSyncedNode(node));
+
     SaveLayerUnclippedClearClipTestRenderer renderer;
     frameBuilder.replayBakedOps<TestDispatcher>(renderer);
     EXPECT_EQ(4, renderer.getIndex());
@@ -981,8 +1117,10 @@
     });
 
     // draw with partial screen dirty that doesn't intersect with savelayer
-    FrameBuilder frameBuilder(sEmptyLayerUpdateQueue, SkRect::MakeWH(100, 100), 200, 200,
-            TestUtils::createSyncedNodeList(node), sLightGeometry, Caches::getInstance());
+    FrameBuilder frameBuilder(SkRect::MakeWH(100, 100), 200, 200,
+            sLightGeometry, Caches::getInstance());
+    frameBuilder.deferRenderNode(*TestUtils::getSyncedNode(node));
+
     FailRenderer renderer;
     frameBuilder.replayBakedOps<TestDispatcher>(renderer);
 }
@@ -1046,8 +1184,11 @@
         canvas.restore();
         canvas.restore();
     });
-    FrameBuilder frameBuilder(sEmptyLayerUpdateQueue, SkRect::MakeWH(600, 600), 600, 600,
-            TestUtils::createSyncedNodeList(node), sLightGeometry, Caches::getInstance());
+
+    FrameBuilder frameBuilder(SkRect::MakeWH(600, 600), 600, 600,
+            sLightGeometry, Caches::getInstance());
+    frameBuilder.deferRenderNode(*TestUtils::getSyncedNode(node));
+
     SaveLayerUnclippedComplexTestRenderer renderer;
     frameBuilder.replayBakedOps<TestDispatcher>(renderer);
     EXPECT_EQ(13, renderer.getIndex());
@@ -1098,14 +1239,17 @@
     OffscreenBuffer layer(renderThread.renderState(), Caches::getInstance(), 100, 100);
     *layerHandle = &layer;
 
-    auto syncedNodeList = TestUtils::createSyncedNodeList(node);
+    auto syncedNode = TestUtils::getSyncedNode(node);
 
     // only enqueue partial damage
     LayerUpdateQueue layerUpdateQueue; // Note: enqueue damage post-sync, so bounds are valid
     layerUpdateQueue.enqueueLayerWithDamage(node.get(), Rect(25, 25, 75, 75));
 
-    FrameBuilder frameBuilder(layerUpdateQueue, SkRect::MakeWH(200, 200), 200, 200,
-            syncedNodeList, sLightGeometry, Caches::getInstance());
+    FrameBuilder frameBuilder(SkRect::MakeWH(200, 200), 200, 200,
+            sLightGeometry, Caches::getInstance());
+    frameBuilder.deferLayers(layerUpdateQueue);
+    frameBuilder.deferRenderNode(*syncedNode);
+
     HwLayerSimpleTestRenderer renderer;
     frameBuilder.replayBakedOps<TestDispatcher>(renderer);
     EXPECT_EQ(6, renderer.getIndex());
@@ -1202,14 +1346,17 @@
     OffscreenBuffer parentLayer(renderThread.renderState(), Caches::getInstance(), 200, 200);
     *(parent->getLayerHandle()) = &parentLayer;
 
-    auto syncedList = TestUtils::createSyncedNodeList(parent);
+    auto syncedNode = TestUtils::getSyncedNode(parent);
 
     LayerUpdateQueue layerUpdateQueue; // Note: enqueue damage post-sync, so bounds are valid
     layerUpdateQueue.enqueueLayerWithDamage(child.get(), Rect(100, 100));
     layerUpdateQueue.enqueueLayerWithDamage(parent.get(), Rect(200, 200));
 
-    FrameBuilder frameBuilder(layerUpdateQueue, SkRect::MakeWH(200, 200), 200, 200,
-            syncedList, sLightGeometry, Caches::getInstance());
+    FrameBuilder frameBuilder(SkRect::MakeWH(200, 200), 200, 200,
+            sLightGeometry, Caches::getInstance());
+    frameBuilder.deferLayers(layerUpdateQueue);
+    frameBuilder.deferRenderNode(*syncedNode);
+
     HwLayerComplexTestRenderer renderer;
     frameBuilder.replayBakedOps<TestDispatcher>(renderer);
     EXPECT_EQ(14, renderer.getIndex());
@@ -1260,15 +1407,14 @@
     OffscreenBuffer layer(renderThread.renderState(), Caches::getInstance(), 100, 100);
     *layerHandle = &layer;
 
-    auto syncedNodeList = TestUtils::createSyncedNodeList(node);
+    TestUtils::syncHierarchyPropertiesAndDisplayList(node);
 
     // only enqueue partial damage
     LayerUpdateQueue layerUpdateQueue; // Note: enqueue damage post-sync, so bounds are valid
     layerUpdateQueue.enqueueLayerWithDamage(node.get(), Rect(25, 25, 75, 75));
 
     // Draw, but pass empty node list, so no work is done for primary frame
-    FrameBuilder frameBuilder(layerUpdateQueue, SkRect::MakeWH(1, 1), 1, 1,
-            sEmptyNodeList, sLightGeometry, Caches::getInstance());
+    FrameBuilder frameBuilder(layerUpdateQueue, sLightGeometry, Caches::getInstance());
     BuildLayerTestRenderer renderer;
     frameBuilder.replayBakedOps<TestDispatcher>(renderer);
     EXPECT_EQ(3, renderer.getIndex());
@@ -1315,8 +1461,10 @@
         drawOrderedRect(&canvas, 8);
         drawOrderedNode(&canvas, 9, -10.0f); // in reorder=false at this point, so played inorder
     });
-    FrameBuilder frameBuilder(sEmptyLayerUpdateQueue, SkRect::MakeWH(100, 100), 100, 100,
-            TestUtils::createSyncedNodeList(parent), sLightGeometry, Caches::getInstance());
+    FrameBuilder frameBuilder(SkRect::MakeWH(100, 100), 100, 100,
+            sLightGeometry, Caches::getInstance());
+    frameBuilder.deferRenderNode(*TestUtils::getSyncedNode(parent));
+
     ZReorderTestRenderer renderer;
     frameBuilder.replayBakedOps<TestDispatcher>(renderer);
     EXPECT_EQ(10, renderer.getIndex());
@@ -1406,8 +1554,10 @@
         canvas.restore();
     });
 
-    FrameBuilder frameBuilder(sEmptyLayerUpdateQueue, SkRect::MakeWH(100, 100), 100, 100,
-            TestUtils::createSyncedNodeList(parent), sLightGeometry, Caches::getInstance());
+    FrameBuilder frameBuilder(SkRect::MakeWH(100, 100), 100, 100,
+            sLightGeometry, Caches::getInstance());
+    frameBuilder.deferRenderNode(*TestUtils::getSyncedNode(parent));
+
     ProjectionReorderTestRenderer renderer;
     frameBuilder.replayBakedOps<TestDispatcher>(renderer);
     EXPECT_EQ(3, renderer.getIndex());
@@ -1486,11 +1636,16 @@
     layer.setWindowTransform(windowTransform);
     *layerHandle = &layer;
 
-    auto syncedList = TestUtils::createSyncedNodeList(parent);
+    auto syncedNode = TestUtils::getSyncedNode(parent);
+
     LayerUpdateQueue layerUpdateQueue; // Note: enqueue damage post-sync, so bounds are valid
     layerUpdateQueue.enqueueLayerWithDamage(child.get(), Rect(200, 200));
-    FrameBuilder frameBuilder(layerUpdateQueue, SkRect::MakeWH(400, 400), 400, 400,
-            syncedList, sLightGeometry, Caches::getInstance());
+
+    FrameBuilder frameBuilder(SkRect::MakeWH(400, 400), 400, 400,
+            sLightGeometry, Caches::getInstance());
+    frameBuilder.deferLayers(layerUpdateQueue);
+    frameBuilder.deferRenderNode(*syncedNode);
+
     ProjectionHwLayerTestRenderer renderer;
     frameBuilder.replayBakedOps<TestDispatcher>(renderer);
     EXPECT_EQ(6, renderer.getIndex());
@@ -1545,8 +1700,10 @@
         canvas.drawRenderNode(child.get());
     });
 
-    FrameBuilder frameBuilder(sEmptyLayerUpdateQueue, SkRect::MakeWH(400, 400), 400, 400,
-            TestUtils::createSyncedNodeList(parent), sLightGeometry, Caches::getInstance());
+    FrameBuilder frameBuilder(SkRect::MakeWH(400, 400), 400, 400,
+            sLightGeometry, Caches::getInstance());
+    frameBuilder.deferRenderNode(*TestUtils::getSyncedNode(parent));
+
     ProjectionChildScrollTestRenderer renderer;
     frameBuilder.replayBakedOps<TestDispatcher>(renderer);
     EXPECT_EQ(2, renderer.getIndex());
@@ -1588,8 +1745,10 @@
         canvas.drawRenderNode(createWhiteRectShadowCaster(5.0f).get());
     });
 
-    FrameBuilder frameBuilder(sEmptyLayerUpdateQueue, SkRect::MakeWH(200, 200), 200, 200,
-            TestUtils::createSyncedNodeList(parent), sLightGeometry, Caches::getInstance());
+    FrameBuilder frameBuilder(SkRect::MakeWH(200, 200), 200, 200,
+            sLightGeometry, Caches::getInstance());
+    frameBuilder.deferRenderNode(*TestUtils::getSyncedNode(parent));
+
     ShadowTestRenderer renderer;
     frameBuilder.replayBakedOps<TestDispatcher>(renderer);
     EXPECT_EQ(2, renderer.getIndex());
@@ -1632,9 +1791,10 @@
         canvas.restoreToCount(count);
     });
 
-    FrameBuilder frameBuilder(sEmptyLayerUpdateQueue, SkRect::MakeWH(200, 200), 200, 200,
-            TestUtils::createSyncedNodeList(parent),
+    FrameBuilder frameBuilder(SkRect::MakeWH(200, 200), 200, 200,
             (FrameBuilder::LightGeometry) {{ 100, 100, 100 }, 50}, Caches::getInstance());
+    frameBuilder.deferRenderNode(*TestUtils::getSyncedNode(parent));
+
     ShadowSaveLayerTestRenderer renderer;
     frameBuilder.replayBakedOps<TestDispatcher>(renderer);
     EXPECT_EQ(6, renderer.getIndex());
@@ -1681,12 +1841,15 @@
     layer.setWindowTransform(windowTransform);
     *layerHandle = &layer;
 
-    auto syncedList = TestUtils::createSyncedNodeList(parent);
+    auto syncedNode = TestUtils::getSyncedNode(parent);
     LayerUpdateQueue layerUpdateQueue; // Note: enqueue damage post-sync, so bounds are valid
     layerUpdateQueue.enqueueLayerWithDamage(parent.get(), Rect(100, 100));
-    FrameBuilder frameBuilder(layerUpdateQueue, SkRect::MakeWH(200, 200), 200, 200,
-            syncedList,
+
+    FrameBuilder frameBuilder(SkRect::MakeWH(200, 200), 200, 200,
             (FrameBuilder::LightGeometry) {{ 100, 100, 100 }, 30}, Caches::getInstance());
+    frameBuilder.deferLayers(layerUpdateQueue);
+    frameBuilder.deferRenderNode(*syncedNode);
+
     ShadowHwLayerTestRenderer renderer;
     frameBuilder.replayBakedOps<TestDispatcher>(renderer);
     EXPECT_EQ(5, renderer.getIndex());
@@ -1713,10 +1876,10 @@
         canvas.drawRenderNode(createWhiteRectShadowCaster(5.0f).get());
         canvas.drawRenderNode(createWhiteRectShadowCaster(5.0001f).get());
     });
-
-    FrameBuilder frameBuilder(sEmptyLayerUpdateQueue, SkRect::MakeWH(200, 200), 200, 200,
-            TestUtils::createSyncedNodeList(parent),
+    FrameBuilder frameBuilder(SkRect::MakeWH(200, 200), 200, 200,
             (FrameBuilder::LightGeometry) {{ 100, 100, 100 }, 50}, Caches::getInstance());
+    frameBuilder.deferRenderNode(*TestUtils::getSyncedNode(parent));
+
     ShadowLayeringTestRenderer renderer;
     frameBuilder.replayBakedOps<TestDispatcher>(renderer);
     EXPECT_EQ(4, renderer.getIndex());
@@ -1743,9 +1906,10 @@
         canvas.drawRenderNode(createWhiteRectShadowCaster(5.0f).get());
     });
 
-    FrameBuilder frameBuilder(sEmptyLayerUpdateQueue, SkRect::MakeWH(100, 100), 100, 100,
-            TestUtils::createSyncedNodeList(parent),
+    FrameBuilder frameBuilder(SkRect::MakeWH(100, 100), 100, 100,
             (FrameBuilder::LightGeometry) {{ 100, 100, 100 }, 50}, Caches::getInstance());
+    frameBuilder.deferRenderNode(*TestUtils::getSyncedNode(parent));
+
     ShadowClippingTestRenderer renderer;
     frameBuilder.replayBakedOps<TestDispatcher>(renderer);
     EXPECT_EQ(2, renderer.getIndex());
@@ -1772,8 +1936,10 @@
         canvas.drawRect(0, 0, 100, 100, paint);
     });
 
-    FrameBuilder frameBuilder(sEmptyLayerUpdateQueue, SkRect::MakeWH(100, 100), 200, 200,
-            TestUtils::createSyncedNodeList(node), sLightGeometry, Caches::getInstance());
+    FrameBuilder frameBuilder(SkRect::MakeWH(100, 100), 200, 200,
+            sLightGeometry, Caches::getInstance());
+    frameBuilder.deferRenderNode(*TestUtils::getSyncedNode(node));
+
     PropertyTestRenderer renderer(opValidateCallback);
     frameBuilder.replayBakedOps<TestDispatcher>(renderer);
     EXPECT_EQ(1, renderer.getIndex()) << "Should have seen one op";
@@ -1915,10 +2081,12 @@
         paint.setColor(SK_ColorWHITE);
         canvas.drawRect(0, 0, 10000, 10000, paint);
     });
-    auto nodes = TestUtils::createSyncedNodeList(node); // sync before querying height
+    auto syncedNode = TestUtils::getSyncedNode(node); // sync before querying height
 
-    FrameBuilder frameBuilder(sEmptyLayerUpdateQueue, SkRect::MakeWH(200, 200), 200, 200,
-            nodes, sLightGeometry, Caches::getInstance());
+    FrameBuilder frameBuilder(SkRect::MakeWH(200, 200), 200, 200,
+                sLightGeometry, Caches::getInstance());
+    frameBuilder.deferRenderNode(*syncedNode);
+
     SaveLayerAlphaClipTestRenderer renderer(outObservedData);
     frameBuilder.replayBakedOps<TestDispatcher>(renderer);
 
@@ -1991,8 +2159,10 @@
         canvas.drawColor(SK_ColorWHITE, SkXfermode::Mode::kSrcOver_Mode);
     });
 
-    FrameBuilder frameBuilder(sEmptyLayerUpdateQueue, SkRect::MakeLTRB(10, 10, 40, 40), 50, 50,
-            TestUtils::createSyncedNodeList(node), sLightGeometry, Caches::getInstance());
+    FrameBuilder frameBuilder(SkRect::MakeLTRB(10, 10, 40, 40), 50, 50,
+            sLightGeometry, Caches::getInstance());
+    frameBuilder.deferRenderNode(*TestUtils::getSyncedNode(node));
+
     ClipReplaceTestRenderer renderer;
     frameBuilder.replayBakedOps<TestDispatcher>(renderer);
     EXPECT_EQ(1, renderer.getIndex());
diff --git a/libs/hwui/tests/unit/LeakCheckTests.cpp b/libs/hwui/tests/unit/LeakCheckTests.cpp
index e2fc376..6148b33 100644
--- a/libs/hwui/tests/unit/LeakCheckTests.cpp
+++ b/libs/hwui/tests/unit/LeakCheckTests.cpp
@@ -26,7 +26,6 @@
 using namespace android;
 using namespace android::uirenderer;
 
-const LayerUpdateQueue sEmptyLayerUpdateQueue;
 const FrameBuilder::LightGeometry sLightGeometery = { {100, 100, 100}, 50};
 const BakedOpRenderer::LightInfo sLightInfo = { 128, 128 };
 
@@ -43,8 +42,9 @@
     RenderState& renderState = renderThread.renderState();
     Caches& caches = Caches::getInstance();
 
-    FrameBuilder frameBuilder(sEmptyLayerUpdateQueue, SkRect::MakeWH(100, 100), 100, 100,
-            TestUtils::createSyncedNodeList(node), sLightGeometery, Caches::getInstance());
+    FrameBuilder frameBuilder(SkRect::MakeWH(100, 100), 100, 100,
+            sLightGeometery, Caches::getInstance());
+    frameBuilder.deferRenderNode(*TestUtils::getSyncedNode(node));
     BakedOpRenderer renderer(caches, renderState, true, sLightInfo);
     frameBuilder.replayBakedOps<BakedOpDispatcher>(renderer);
 }
@@ -59,8 +59,9 @@
     RenderState& renderState = renderThread.renderState();
     Caches& caches = Caches::getInstance();
 
-    FrameBuilder frameBuilder(sEmptyLayerUpdateQueue, SkRect::MakeWH(200, 200), 200, 200,
-            TestUtils::createSyncedNodeList(node), sLightGeometery, Caches::getInstance());
+    FrameBuilder frameBuilder(SkRect::MakeWH(200, 200), 200, 200,
+            sLightGeometery, Caches::getInstance());
+    frameBuilder.deferRenderNode(*TestUtils::getSyncedNode(node));
     BakedOpRenderer renderer(caches, renderState, true, sLightInfo);
     frameBuilder.replayBakedOps<BakedOpDispatcher>(renderer);
 }