MultiThreaded rendering of different renderNodes

This is adding the renderer side infrastructure to allow
rendering multiple render nodes with different threads.
This is a pre-step for decoupling a non client decor
resize reder from a content resize render.

Multiple render nodes can be added to be drawn, and to
prevent overdrawing, a content bounds area can be set

Bug: 22527834

Change-Id: Ie7271e20895bf38957e5a84aeefc883e282039ad
diff --git a/core/java/android/view/ThreadedRenderer.java b/core/java/android/view/ThreadedRenderer.java
index 420f7a1..dcef142 100644
--- a/core/java/android/view/ThreadedRenderer.java
+++ b/core/java/android/view/ThreadedRenderer.java
@@ -108,6 +108,11 @@
     private Choreographer mChoreographer;
     private boolean mRootNodeNeedsUpdate;
 
+    // In case of multi threaded render nodes, these bounds indicate the content bounds against
+    // which the backdrop needs to be cropped against.
+    private final Rect mCurrentContentBounds = new Rect();
+    private final Rect mStagedContentBounds = new Rect();
+
     ThreadedRenderer(Context context, boolean translucent) {
         final TypedArray a = context.obtainStyledAttributes(null, R.styleable.Lighting, 0, 0);
         mLightY = a.getDimension(R.styleable.Lighting_lightY, 0);
@@ -307,6 +312,47 @@
         Trace.traceEnd(Trace.TRACE_TAG_VIEW);
     }
 
+    /**
+     * Adds a rendernode to the renderer which can be drawn and changed asynchronously to the
+     * rendernode of the UI thread.
+     * @param node The node to add.
+     * @param placeFront If true, the render node will be placed in front of the content node,
+     *                   otherwise behind the content node.
+     */
+    public void addRenderNode(RenderNode node, boolean placeFront) {
+        nAddRenderNode(mNativeProxy, node.mNativeRenderNode, placeFront);
+    }
+
+    /**
+     * Only especially added render nodes can be removed.
+     * @param node The node which was added via addRenderNode which should get removed again.
+     */
+    public void removeRenderNode(RenderNode node) {
+        nRemoveRenderNode(mNativeProxy, node.mNativeRenderNode);
+    }
+
+    /**
+     * Draws a particular render node. If the node is not the content node, only the additional
+     * nodes will get drawn and the content remains untouched.
+     * @param node The node to be drawn.
+     */
+    public void drawRenderNode(RenderNode node) {
+        nDrawRenderNode(mNativeProxy, node.mNativeRenderNode);
+    }
+
+    /**
+     * To avoid unnecessary overdrawing of the main content all additionally passed render nodes
+     * will be prevented to overdraw this area. It will be synchronized with the draw call.
+     * This should be updated in the content view's draw call.
+     * @param left The left side of the protected bounds.
+     * @param top The top side of the protected bounds.
+     * @param right The right side of the protected bounds.
+     * @param bottom The bottom side of the protected bounds.
+     */
+    public void setContentOverdrawProtectionBounds(int left, int top, int right, int bottom) {
+        mStagedContentBounds.set(left, top, right, bottom);
+    }
+
     @Override
     void invalidateRoot() {
         mRootNodeNeedsUpdate = true;
@@ -320,6 +366,14 @@
         choreographer.mFrameInfo.markDrawStart();
 
         updateRootDisplayList(view, callbacks);
+        // The main content view was updating the content bounds and we transfer them to the
+        // renderer.
+        if (!mCurrentContentBounds.equals(mStagedContentBounds)) {
+            mCurrentContentBounds.set(mStagedContentBounds);
+            nSetContentOverdrawProtectionBounds(mNativeProxy, mCurrentContentBounds.left,
+                mCurrentContentBounds.top, mCurrentContentBounds.right,
+            mCurrentContentBounds.bottom);
+        }
 
         attachInfo.mIgnoreDirtyState = false;
 
@@ -541,4 +595,11 @@
     private static native void nDumpProfileInfo(long nativeProxy, FileDescriptor fd,
             @DumpFlags int dumpFlags);
     private static native void nDumpProfileData(byte[] data, FileDescriptor fd);
+
+    private static native void nAddRenderNode(long nativeProxy, long rootRenderNode,
+             boolean placeFront);
+    private static native void nRemoveRenderNode(long nativeProxy, long rootRenderNode);
+    private static native void nDrawRenderNode(long nativeProxy, long rootRenderNode);
+    private static native void nSetContentOverdrawProtectionBounds(long nativeProxy, int left,
+             int top, int right, int bottom);
 }
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 98c0494..24d0bfe 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -7373,6 +7373,23 @@
     }
 
     /**
+     * Compute the view's coordinate within the surface.
+     *
+     * <p>Computes the coordinates of this view in its surface. The argument
+     * must be an array of two integers. After the method returns, the array
+     * contains the x and y location in that order.</p>
+     * @hide
+     * @param location an array of two integers in which to hold the coordinates
+     */
+    public void getLocationInSurface(@Size(2) int[] location) {
+        getLocationInWindow(location);
+        if (mAttachInfo != null && mAttachInfo.mViewRootImpl != null) {
+            location[0] += mAttachInfo.mViewRootImpl.mWindowAttributes.surfaceInsets.left;
+            location[1] += mAttachInfo.mViewRootImpl.mWindowAttributes.surfaceInsets.top;
+        }
+    }
+
+    /**
      * Provide original WindowInsets that are dispatched to the view hierarchy. The insets are
      * only available if the view is attached.
      *
diff --git a/core/jni/android_view_ThreadedRenderer.cpp b/core/jni/android_view_ThreadedRenderer.cpp
index 6c3676b..9a2703b 100644
--- a/core/jni/android_view_ThreadedRenderer.cpp
+++ b/core/jni/android_view_ThreadedRenderer.cpp
@@ -440,6 +440,32 @@
     }
 }
 
+static void android_view_ThreadedRenderer_addRenderNode(JNIEnv* env, jobject clazz,
+        jlong proxyPtr, jlong renderNodePtr, jboolean placeFront) {
+    RenderProxy* proxy = reinterpret_cast<RenderProxy*>(proxyPtr);
+    RenderNode* renderNode = reinterpret_cast<RenderNode*>(renderNodePtr);
+    proxy->addRenderNode(renderNode, placeFront);
+}
+
+static void android_view_ThreadedRenderer_removeRenderNode(JNIEnv* env, jobject clazz,
+        jlong proxyPtr, jlong renderNodePtr) {
+    RenderProxy* proxy = reinterpret_cast<RenderProxy*>(proxyPtr);
+    RenderNode* renderNode = reinterpret_cast<RenderNode*>(renderNodePtr);
+    proxy->removeRenderNode(renderNode);
+}
+
+static void android_view_ThreadedRendererd_drawRenderNode(JNIEnv* env, jobject clazz,
+        jlong proxyPtr, jlong renderNodePtr) {
+    RenderProxy* proxy = reinterpret_cast<RenderProxy*>(proxyPtr);
+    RenderNode* renderNode = reinterpret_cast<RenderNode*>(renderNodePtr);
+    proxy->drawRenderNode(renderNode);
+}
+
+static void android_view_ThreadedRenderer_setContentOverdrawProtectionBounds(JNIEnv* env,
+        jobject clazz, jlong proxyPtr, jint left, jint top, jint right, jint bottom) {
+    RenderProxy* proxy = reinterpret_cast<RenderProxy*>(proxyPtr);
+    proxy->setContentOverdrawProtectionBounds(left, top, right, bottom);
+}
 
 // ----------------------------------------------------------------------------
 // Shaders
@@ -447,7 +473,6 @@
 
 static void android_view_ThreadedRenderer_setupShadersDiskCache(JNIEnv* env, jobject clazz,
         jstring diskCachePath) {
-
     const char* cacheArray = env->GetStringUTFChars(diskCachePath, NULL);
     egl_cache_t::get()->setCacheFilename(cacheArray);
     env->ReleaseStringUTFChars(diskCachePath, cacheArray);
@@ -494,6 +519,11 @@
     { "nDumpProfileData", "([BLjava/io/FileDescriptor;)V", (void*) android_view_ThreadedRenderer_dumpProfileData },
     { "setupShadersDiskCache", "(Ljava/lang/String;)V",
                 (void*) android_view_ThreadedRenderer_setupShadersDiskCache },
+    { "nAddRenderNode", "(JJZ)V", (void*) android_view_ThreadedRenderer_addRenderNode},
+    { "nRemoveRenderNode", "(JJ)V", (void*) android_view_ThreadedRenderer_removeRenderNode},
+    { "nDrawRenderNode", "(JJ)V", (void*) android_view_ThreadedRendererd_drawRenderNode},
+    { "nSetContentOverdrawProtectionBounds", "(JIIII)V",
+                (void*)android_view_ThreadedRenderer_setContentOverdrawProtectionBounds},
 };
 
 int register_android_view_ThreadedRenderer(JNIEnv* env) {
diff --git a/libs/hwui/TreeInfo.h b/libs/hwui/TreeInfo.h
index ed853f7..98e6146 100644
--- a/libs/hwui/TreeInfo.h
+++ b/libs/hwui/TreeInfo.h
@@ -77,7 +77,7 @@
         , canvasContext(clone.canvasContext)
     {}
 
-    const TraversalMode mode;
+    TraversalMode mode;
     // TODO: Remove this? Currently this is used to signal to stop preparing
     // textures if we run out of cache space.
     bool prepareTextures;
diff --git a/libs/hwui/renderthread/CanvasContext.cpp b/libs/hwui/renderthread/CanvasContext.cpp
index b74b508..9dc5b45 100644
--- a/libs/hwui/renderthread/CanvasContext.cpp
+++ b/libs/hwui/renderthread/CanvasContext.cpp
@@ -60,9 +60,10 @@
         , mEglManager(thread.eglManager())
         , mOpaque(!translucent)
         , mAnimationContext(contextFactory->createAnimationContext(mRenderThread.timeLord()))
-        , mRootRenderNode(rootRenderNode)
         , mJankTracker(thread.timeLord().frameIntervalNanos())
-        , mProfiler(mFrames) {
+        , mProfiler(mFrames)
+        , mContentOverdrawProtectionBounds(0, 0, 0, 0) {
+    mRenderNodes.emplace_back(rootRenderNode);
     mRenderThread.renderState().registerCanvasContext(this);
     mProfiler.setDensity(mRenderThread.mainDisplayInfo().density);
 }
@@ -172,7 +173,8 @@
     return info && ((*info)[FrameInfoIndex::Flags] & FrameInfoFlags::SkippedFrame);
 }
 
-void CanvasContext::prepareTree(TreeInfo& info, int64_t* uiFrameInfo, int64_t syncQueued) {
+void CanvasContext::prepareTree(TreeInfo& info, int64_t* uiFrameInfo,
+        int64_t syncQueued, RenderNode* target) {
     mRenderThread.removeFrameCallback(this);
 
     // If the previous frame was dropped we don't need to hold onto it, so
@@ -189,7 +191,13 @@
     info.canvasContext = this;
 
     mAnimationContext->startFrame(info.mode);
-    mRootRenderNode->prepareTree(info);
+    for (const sp<RenderNode>& node : mRenderNodes) {
+        // Only the primary target node will be drawn full - all other nodes would get drawn in
+        // real time mode. In case of a window, the primary node is the window content and the other
+        // node(s) are non client / filler nodes.
+        info.mode = (node.get() == target ? TreeInfo::MODE_FULL : TreeInfo::MODE_RT_ONLY);
+        node->prepareTree(info);
+    }
     mAnimationContext->runRemainingAnimations(info);
 
     freePrefetechedLayers();
@@ -299,7 +307,95 @@
             dirty.fLeft, dirty.fTop, dirty.fRight, dirty.fBottom, mOpaque);
 
     Rect outBounds;
-    mCanvas->drawRenderNode(mRootRenderNode.get(), outBounds);
+    // It there are multiple render nodes, they are as follows:
+    // #0 - backdrop
+    // #1 - content (with - and clipped to - bounds mContentOverdrawProtectionBounds)
+    // #2 - frame
+    // 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
+    // backdrop against the content and draw the remaining part of it. It will then crop the content
+    // against the backdrop (since that indicates a shrinking of the window) and then the frame
+    // around everything.
+    // The bounds of the backdrop against which the content should be clipped.
+    Rect backdropBounds = mContentOverdrawProtectionBounds;
+    // If there is no content bounds we ignore the layering as stated above and start with 2.
+    int layer = mContentOverdrawProtectionBounds.isEmpty() ? 2 : 0;
+    // Draw all render nodes. Note that
+    for (const sp<RenderNode>& node : mRenderNodes) {
+        if (layer == 0) { // Backdrop.
+            // Draw the backdrop clipped to the inverse content bounds.
+            const RenderProperties& properties = node->properties();
+            Rect targetBounds(properties.getLeft(), properties.getTop(),
+                              properties.getRight(), properties.getBottom());
+            // Remember the intersection of the target bounds and the intersection bounds against
+            // which we have to crop the content.
+            backdropBounds.intersect(targetBounds);
+            // Check if we have to draw something on the left side ...
+            if (targetBounds.left < mContentOverdrawProtectionBounds.left) {
+                mCanvas->save(SkCanvas::kClip_SaveFlag);
+                if (mCanvas->clipRect(targetBounds.left, targetBounds.top,
+                                      mContentOverdrawProtectionBounds.left, targetBounds.bottom,
+                                      SkRegion::kIntersect_Op)) {
+                    mCanvas->drawRenderNode(node.get(), outBounds);
+                }
+                // Reduce the target area by the area we have just painted.
+                targetBounds.left = std::min(mContentOverdrawProtectionBounds.left,
+                                             targetBounds.right);
+                mCanvas->restore();
+            }
+            // ... or on the right side ...
+            if (targetBounds.right > mContentOverdrawProtectionBounds.right &&
+                !targetBounds.isEmpty()) {
+                mCanvas->save(SkCanvas::kClip_SaveFlag);
+                if (mCanvas->clipRect(mContentOverdrawProtectionBounds.right, targetBounds.top,
+                                      targetBounds.right, targetBounds.bottom,
+                                      SkRegion::kIntersect_Op)) {
+                    mCanvas->drawRenderNode(node.get(), outBounds);
+                }
+                // Reduce the target area by the area we have just painted.
+                targetBounds.right = std::max(targetBounds.left,
+                                              mContentOverdrawProtectionBounds.right);
+                mCanvas->restore();
+            }
+            // ... or at the top ...
+            if (targetBounds.top < mContentOverdrawProtectionBounds.top &&
+                !targetBounds.isEmpty()) {
+                mCanvas->save(SkCanvas::kClip_SaveFlag);
+                if (mCanvas->clipRect(targetBounds.left, targetBounds.top, targetBounds.right,
+                                      mContentOverdrawProtectionBounds.top,
+                                      SkRegion::kIntersect_Op)) {
+                    mCanvas->drawRenderNode(node.get(), outBounds);
+                }
+                // Reduce the target area by the area we have just painted.
+                targetBounds.top = std::min(mContentOverdrawProtectionBounds.top,
+                                            targetBounds.bottom);
+                mCanvas->restore();
+            }
+            // ... or at the bottom.
+            if (targetBounds.bottom > mContentOverdrawProtectionBounds.bottom &&
+                !targetBounds.isEmpty()) {
+                mCanvas->save(SkCanvas::kClip_SaveFlag);
+                if (mCanvas->clipRect(targetBounds.left,
+                                      mContentOverdrawProtectionBounds.bottom, targetBounds.right,
+                                      targetBounds.bottom, SkRegion::kIntersect_Op)) {
+                    mCanvas->drawRenderNode(node.get(), outBounds);
+                }
+                mCanvas->restore();
+            }
+        } else if (layer == 1) { // Content
+            // It gets cropped against the bounds of the backdrop to stay inside.
+            mCanvas->save(SkCanvas::kClip_SaveFlag);
+            if (mCanvas->clipRect(backdropBounds.left, backdropBounds.top,
+                                  backdropBounds.right, backdropBounds.bottom,
+                                  SkRegion::kIntersect_Op)) {
+                mCanvas->drawRenderNode(node.get(), outBounds);
+            }
+            mCanvas->restore();
+        } else { // draw the rest on top at will!
+            mCanvas->drawRenderNode(node.get(), outBounds);
+        }
+        layer++;
+    }
 
     profiler().draw(mCanvas);
 
@@ -343,7 +439,10 @@
     if (CC_UNLIKELY(!mCanvas || mEglSurface == EGL_NO_SURFACE)) {
         return;
     }
+    prepareAndDraw(nullptr);
+}
 
+void CanvasContext::prepareAndDraw(RenderNode* node) {
     ATRACE_CALL();
 
     int64_t frameInfo[UI_THREAD_FRAME_INFO_SIZE];
@@ -353,7 +452,7 @@
                 mRenderThread.timeLord().latestVsync());
 
     TreeInfo info(TreeInfo::MODE_RT_ONLY, mRenderThread.renderState());
-    prepareTree(info, frameInfo, systemTime(CLOCK_MONOTONIC));
+    prepareTree(info, frameInfo, systemTime(CLOCK_MONOTONIC), node);
     if (info.out.canDrawThisFrame) {
         draw();
     }
@@ -423,7 +522,9 @@
     stopDrawing();
     if (mEglManager.hasEglContext()) {
         freePrefetechedLayers();
-        mRootRenderNode->destroyHardwareResources();
+        for (const sp<RenderNode>& node : mRenderNodes) {
+            node->destroyHardwareResources();
+        }
         Caches& caches = Caches::getInstance();
         // Make sure to release all the textures we were owning as there won't
         // be another draw
diff --git a/libs/hwui/renderthread/CanvasContext.h b/libs/hwui/renderthread/CanvasContext.h
index 6a79320..1c3845c 100644
--- a/libs/hwui/renderthread/CanvasContext.h
+++ b/libs/hwui/renderthread/CanvasContext.h
@@ -34,6 +34,7 @@
 
 #include <set>
 #include <string>
+#include <vector>
 
 namespace android {
 namespace uirenderer {
@@ -77,12 +78,14 @@
     void setOpaque(bool opaque);
     void makeCurrent();
     void processLayerUpdate(DeferredLayerUpdater* layerUpdater);
-    void prepareTree(TreeInfo& info, int64_t* uiFrameInfo, int64_t syncQueued);
+    void prepareTree(TreeInfo& info, int64_t* uiFrameInfo,
+            int64_t syncQueued, RenderNode* target);
     void draw();
     void destroy();
 
     // IFrameCallback, Chroreographer-driven frame callback entry point
     virtual void doFrame() override;
+    void prepareAndDraw(RenderNode* node);
 
     void buildLayer(RenderNode* node);
     bool copyLayerInto(DeferredLayerUpdater* layer, SkBitmap* bitmap);
@@ -113,6 +116,20 @@
 
     void serializeDisplayListTree();
 
+    void addRenderNode(RenderNode* node, bool placeFront) {
+        int pos = placeFront ? 0 : static_cast<int>(mRenderNodes.size());
+        mRenderNodes.emplace( mRenderNodes.begin() + pos, node);
+    }
+
+    void removeRenderNode(RenderNode* node) {
+        mRenderNodes.erase(std::remove(mRenderNodes.begin(), mRenderNodes.end(), node),
+                mRenderNodes.end());
+    }
+
+    void setContentOverdrawProtectionBounds(int left, int top, int right, int bottom) {
+        mContentOverdrawProtectionBounds.set(left, top, right, bottom);
+    }
+
 private:
     friend class RegisterFrameCallbackTask;
     // TODO: Replace with something better for layer & other GL object
@@ -138,7 +155,7 @@
     DamageAccumulator mDamageAccumulator;
     std::unique_ptr<AnimationContext> mAnimationContext;
 
-    const sp<RenderNode> mRootRenderNode;
+    std::vector< sp<RenderNode> > mRenderNodes;
 
     FrameInfo* mCurrentFrameInfo = nullptr;
     // Ring buffer large enough for 2 seconds worth of frames
@@ -148,6 +165,9 @@
     FrameInfoVisualizer mProfiler;
 
     std::set<RenderNode*> mPrefetechedLayers;
+
+    // Stores the bounds of the main content.
+    Rect mContentOverdrawProtectionBounds;
 };
 
 } /* namespace renderthread */
diff --git a/libs/hwui/renderthread/DrawFrameTask.cpp b/libs/hwui/renderthread/DrawFrameTask.cpp
index 198906c..a47c9ec 100644
--- a/libs/hwui/renderthread/DrawFrameTask.cpp
+++ b/libs/hwui/renderthread/DrawFrameTask.cpp
@@ -38,9 +38,11 @@
 DrawFrameTask::~DrawFrameTask() {
 }
 
-void DrawFrameTask::setContext(RenderThread* thread, CanvasContext* context) {
+void DrawFrameTask::setContext(RenderThread* thread, CanvasContext* context,
+        RenderNode* targetNode) {
     mRenderThread = thread;
     mContext = context;
+    mTargetNode = targetNode;
 }
 
 void DrawFrameTask::pushLayerUpdate(DeferredLayerUpdater* layer) {
@@ -118,7 +120,7 @@
         mContext->processLayerUpdate(mLayers[i].get());
     }
     mLayers.clear();
-    mContext->prepareTree(info, mFrameInfo, mSyncQueued);
+    mContext->prepareTree(info, mFrameInfo, mSyncQueued, mTargetNode);
 
     // This is after the prepareTree so that any pending operations
     // (RenderNode tree state, prefetched layers, etc...) will be flushed.
diff --git a/libs/hwui/renderthread/DrawFrameTask.h b/libs/hwui/renderthread/DrawFrameTask.h
index ebefcba..68ee897 100644
--- a/libs/hwui/renderthread/DrawFrameTask.h
+++ b/libs/hwui/renderthread/DrawFrameTask.h
@@ -57,7 +57,7 @@
     DrawFrameTask();
     virtual ~DrawFrameTask();
 
-    void setContext(RenderThread* thread, CanvasContext* context);
+    void setContext(RenderThread* thread, CanvasContext* context, RenderNode* targetNode);
 
     void pushLayerUpdate(DeferredLayerUpdater* layer);
     void removeLayerUpdate(DeferredLayerUpdater* layer);
@@ -78,6 +78,7 @@
 
     RenderThread* mRenderThread;
     CanvasContext* mContext;
+    RenderNode* mTargetNode = nullptr;
 
     /*********************************************
      *  Single frame data
diff --git a/libs/hwui/renderthread/RenderProxy.cpp b/libs/hwui/renderthread/RenderProxy.cpp
index b838811..f43a769 100644
--- a/libs/hwui/renderthread/RenderProxy.cpp
+++ b/libs/hwui/renderthread/RenderProxy.cpp
@@ -74,7 +74,7 @@
     args->thread = &mRenderThread;
     args->contextFactory = contextFactory;
     mContext = (CanvasContext*) postAndWait(task);
-    mDrawFrameTask.setContext(&mRenderThread, mContext);
+    mDrawFrameTask.setContext(&mRenderThread, mContext, rootRenderNode);
 }
 
 RenderProxy::~RenderProxy() {
@@ -91,7 +91,7 @@
         SETUP_TASK(destroyContext);
         args->context = mContext;
         mContext = nullptr;
-        mDrawFrameTask.setContext(nullptr, nullptr);
+        mDrawFrameTask.setContext(nullptr, nullptr, nullptr);
         // This is also a fence as we need to be certain that there are no
         // outstanding mDrawFrame tasks posted before it is destroyed
         postAndWait(task);
@@ -461,7 +461,8 @@
     staticPostAndWait(task);
 }
 
-CREATE_BRIDGE4(setTextureAtlas, RenderThread* thread, GraphicBuffer* buffer, int64_t* map, size_t size) {
+CREATE_BRIDGE4(setTextureAtlas, RenderThread* thread, GraphicBuffer* buffer, int64_t* map,
+               size_t size) {
     CanvasContext::setTextureAtlas(*args->thread, args->buffer, args->map, args->size);
     args->buffer->decStrong(nullptr);
     return nullptr;
@@ -490,6 +491,61 @@
     post(task);
 }
 
+CREATE_BRIDGE3(addRenderNode, CanvasContext* context, RenderNode* node, bool placeFront) {
+    args->context->addRenderNode(args->node, args->placeFront);
+    return nullptr;
+}
+
+void RenderProxy::addRenderNode(RenderNode* node, bool placeFront) {
+    SETUP_TASK(addRenderNode);
+    args->context = mContext;
+    args->node = node;
+    args->placeFront = placeFront;
+    post(task);
+}
+
+CREATE_BRIDGE2(removeRenderNode, CanvasContext* context, RenderNode* node) {
+    args->context->removeRenderNode(args->node);
+    return nullptr;
+}
+
+void RenderProxy::removeRenderNode(RenderNode* node) {
+    SETUP_TASK(removeRenderNode);
+    args->context = mContext;
+    args->node = node;
+    post(task);
+}
+
+CREATE_BRIDGE2(drawRenderNode, CanvasContext* context, RenderNode* node) {
+    args->context->prepareAndDraw(args->node);
+    return nullptr;
+}
+
+void RenderProxy::drawRenderNode(RenderNode* node) {
+    SETUP_TASK(drawRenderNode);
+    args->context = mContext;
+    args->node = node;
+    // Be pseudo-thread-safe and don't use any member variables
+    staticPostAndWait(task);
+}
+
+CREATE_BRIDGE5(setContentOverdrawProtectionBounds, CanvasContext* context, int left, int top,
+        int right, int bottom) {
+    args->context->setContentOverdrawProtectionBounds(args->left, args->top, args->right,
+                                                      args->bottom);
+    return nullptr;
+}
+
+void RenderProxy::setContentOverdrawProtectionBounds(int left, int top, int right, int bottom) {
+    SETUP_TASK(setContentOverdrawProtectionBounds);
+    args->context = mContext;
+    args->left = left;
+    args->top = top;
+    args->right = right;
+    args->bottom = bottom;
+    staticPostAndWait(task);
+}
+
 CREATE_BRIDGE1(serializeDisplayListTree, CanvasContext* context) {
     args->context->serializeDisplayListTree();
     return nullptr;
diff --git a/libs/hwui/renderthread/RenderProxy.h b/libs/hwui/renderthread/RenderProxy.h
index e7356db..046f24a 100644
--- a/libs/hwui/renderthread/RenderProxy.h
+++ b/libs/hwui/renderthread/RenderProxy.h
@@ -106,6 +106,11 @@
 
     ANDROID_API void serializeDisplayListTree();
 
+    ANDROID_API void addRenderNode(RenderNode* node, bool placeFront);
+    ANDROID_API void removeRenderNode(RenderNode* node);
+    ANDROID_API void drawRenderNode(RenderNode* node);
+    ANDROID_API void setContentOverdrawProtectionBounds(int left, int top, int right, int bottom);
+
 private:
     RenderThread& mRenderThread;
     CanvasContext* mContext;
diff --git a/tests/HwAccelerationTest/AndroidManifest.xml b/tests/HwAccelerationTest/AndroidManifest.xml
index 10cf5c1..b028ce6 100644
--- a/tests/HwAccelerationTest/AndroidManifest.xml
+++ b/tests/HwAccelerationTest/AndroidManifest.xml
@@ -944,5 +944,14 @@
             </intent-filter>
         </activity>
 
+        <activity
+                android:name="MultiProducerActivity"
+                android:label="Threads/Multiple Producers">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="com.android.test.hwui.TEST" />
+            </intent-filter>
+        </activity>
+
     </application>
 </manifest>
diff --git a/tests/HwAccelerationTest/src/com/android/test/hwui/MultiProducerActivity.java b/tests/HwAccelerationTest/src/com/android/test/hwui/MultiProducerActivity.java
new file mode 100644
index 0000000..b458d9b
--- /dev/null
+++ b/tests/HwAccelerationTest/src/com/android/test/hwui/MultiProducerActivity.java
@@ -0,0 +1,347 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.test.hwui;
+
+import android.app.Activity;
+import android.graphics.Canvas;
+import android.graphics.ColorFilter;
+import android.graphics.Paint;
+import android.graphics.PixelFormat;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.os.Bundle;
+import android.view.DisplayListCanvas;
+import android.view.HardwareRenderer;
+import android.view.RenderNode;
+import android.view.ThreadedRenderer;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.widget.AbsoluteLayout;
+import android.widget.AbsoluteLayout.LayoutParams;
+
+public class MultiProducerActivity extends Activity implements OnClickListener {
+    private static final int DURATION = 800;
+    private View mBackgroundTarget = null;
+    private View mFrameTarget = null;
+    private View mContent = null;
+    // The width & height of our "output drawing".
+    private final int WIDTH = 900;
+    private final int HEIGHT = 600;
+    // A border width around the drawing.
+    private static final int BORDER_WIDTH = 20;
+    // The Gap between the content and the frame which should get filled on the right and bottom
+    // side by the backdrop.
+    final int CONTENT_GAP = 100;
+
+    // For debug purposes - disable drawing of frame / background.
+    private final boolean USE_FRAME = true;
+    private final boolean USE_BACK = true;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        // To make things simple - we do a quick and dirty absolute layout.
+        final AbsoluteLayout layout = new AbsoluteLayout(this);
+
+        // Create the outer frame
+        if (USE_FRAME) {
+            mFrameTarget = new View(this);
+            LayoutParams frameLP = new LayoutParams(WIDTH, HEIGHT, 0, 0);
+            layout.addView(mFrameTarget, frameLP);
+        }
+
+        // Create the background which fills the gap between content and frame.
+        if (USE_BACK) {
+            mBackgroundTarget = new View(this);
+            LayoutParams backgroundLP = new LayoutParams(
+                    WIDTH - 2 * BORDER_WIDTH, HEIGHT - 2 * BORDER_WIDTH,
+                    BORDER_WIDTH, BORDER_WIDTH);
+            layout.addView(mBackgroundTarget, backgroundLP);
+        }
+
+        // Create the content
+        // Note: We reduce the size by CONTENT_GAP pixels on right and bottom, so that they get
+        // drawn by the backdrop.
+        mContent = new View(this);
+        mContent.setBackground(new ColorPulse(0xFFF44336, 0xFF9C27B0, null));
+        mContent.setOnClickListener(this);
+        LayoutParams contentLP = new LayoutParams(WIDTH - 2 * BORDER_WIDTH - CONTENT_GAP,
+                HEIGHT - 2 * BORDER_WIDTH - CONTENT_GAP, BORDER_WIDTH, BORDER_WIDTH);
+        layout.addView(mContent, contentLP);
+
+        setContentView(layout);
+    }
+
+    @Override
+    protected void onStart() {
+        super.onStart();
+        View view = mBackgroundTarget != null ? mBackgroundTarget : mFrameTarget;
+        if (view != null) {
+            view.post(mSetup);
+        }
+    }
+
+    @Override
+    protected void onStop() {
+        super.onStop();
+        View view = mBackgroundTarget != null ? mBackgroundTarget : mFrameTarget;
+        if (view != null) {
+            view.removeCallbacks(mSetup);
+        }
+        if (mBgRenderer != null) {
+            mBgRenderer.destroy();
+            mBgRenderer = null;
+        }
+    }
+
+    @Override
+    public void onClick(View view) {
+        sBlockThread.run();
+    }
+
+    private Runnable mSetup = new Runnable() {
+        @Override
+        public void run() {
+            View view = mBackgroundTarget != null ? mBackgroundTarget : mFrameTarget;
+            if (view == null) {
+                view.postDelayed(mSetup, 50);
+            }
+            HardwareRenderer renderer = view.getHardwareRenderer();
+            if (renderer == null || view.getWidth() == 0) {
+                view.postDelayed(mSetup, 50);
+            }
+            ThreadedRenderer threaded = (ThreadedRenderer) renderer;
+
+            mBgRenderer = new FakeFrame(threaded,mFrameTarget, mBackgroundTarget);
+            mBgRenderer.start();
+        }
+    };
+
+    private FakeFrame mBgRenderer;
+    private class FakeFrame extends Thread {
+        ThreadedRenderer mRenderer;
+        volatile boolean mRunning = true;
+        View mTargetFrame;
+        View mTargetBack;
+        Drawable mFrameContent;
+        Drawable mBackContent;
+        // The Z value where to place this.
+        int mZFrame;
+        int mZBack;
+        String mRenderNodeName;
+
+        FakeFrame(ThreadedRenderer renderer, View targetFrame, View targetBack) {
+            mRenderer = renderer;
+            mTargetFrame = targetFrame;
+
+            mTargetBack = targetBack;
+            mFrameContent = new ColorPulse(0xFF101010, 0xFF707070, new Rect(0, 0, WIDTH, HEIGHT));
+            mBackContent = new ColorPulse(0xFF909090, 0xFFe0e0e0, null);
+        }
+
+        @Override
+        public void run() {
+            Rect currentFrameBounds = new Rect();
+            Rect currentBackBounds = new Rect();
+            Rect newBounds = new Rect();
+            int[] surfaceOrigin = new int[2];
+            RenderNode nodeFrame = null;
+            RenderNode nodeBack = null;
+
+            // Since we are overriding the window painting logic we need to at least fill the
+            // surface with some window content (otherwise the world will go black).
+            try {
+                Thread.sleep(200);
+            } catch (InterruptedException e) {
+            }
+
+            if (mTargetBack != null) {
+                nodeBack = RenderNode.create("FakeBackdrop", null);
+                nodeBack.setClipToBounds(true);
+                mRenderer.addRenderNode(nodeBack, true);
+            }
+
+            if (mTargetFrame != null) {
+                nodeFrame = RenderNode.create("FakeFrame", null);
+                nodeFrame.setClipToBounds(true);
+                mRenderer.addRenderNode(nodeFrame, false);
+            }
+
+            while (mRunning) {
+                // Get the surface position to draw to within our surface.
+                surfaceOrigin[0] = 0;
+                surfaceOrigin[1] = 0;
+                // This call should be done while the rendernode's displaylist is produced.
+                // For simplicity of this test we do this before we kick off the draw.
+                mContent.getLocationInSurface(surfaceOrigin);
+                mRenderer.setContentOverdrawProtectionBounds(surfaceOrigin[0], surfaceOrigin[1],
+                        surfaceOrigin[0] + mContent.getWidth(),
+                        surfaceOrigin[1] + mContent.getHeight());
+                // Determine new position for frame.
+                if (nodeFrame != null) {
+                    surfaceOrigin[0] = 0;
+                    surfaceOrigin[1] = 0;
+                    mTargetFrame.getLocationInSurface(surfaceOrigin);
+                    newBounds.set(surfaceOrigin[0], surfaceOrigin[1],
+                            surfaceOrigin[0] + mTargetFrame.getWidth(),
+                            surfaceOrigin[1] + mTargetFrame.getHeight());
+                    if (!currentFrameBounds.equals(newBounds)) {
+                        currentFrameBounds.set(newBounds);
+                        nodeFrame.setLeftTopRightBottom(currentFrameBounds.left,
+                                currentFrameBounds.top,
+                                currentFrameBounds.right, currentFrameBounds.bottom);
+                    }
+
+                    // Draw frame
+                    DisplayListCanvas canvas = nodeFrame.start(currentFrameBounds.width(),
+                            currentFrameBounds.height());
+                    mFrameContent.draw(canvas);
+                    nodeFrame.end(canvas);
+                }
+
+                // Determine new position for backdrop
+                if (nodeBack != null) {
+                    surfaceOrigin[0] = 0;
+                    surfaceOrigin[1] = 0;
+                    mTargetBack.getLocationInSurface(surfaceOrigin);
+                    newBounds.set(surfaceOrigin[0], surfaceOrigin[1],
+                            surfaceOrigin[0] + mTargetBack.getWidth(),
+                            surfaceOrigin[1] + mTargetBack.getHeight());
+                    if (!currentBackBounds.equals(newBounds)) {
+                        currentBackBounds.set(newBounds);
+                        nodeBack.setLeftTopRightBottom(currentBackBounds.left,
+                                currentBackBounds.top,
+                                currentBackBounds.right, currentBackBounds.bottom);
+                    }
+
+                    // Draw Backdrop
+                    DisplayListCanvas canvas = nodeBack.start(currentBackBounds.width(),
+                            currentBackBounds.height());
+                    mBackContent.draw(canvas);
+                    nodeBack.end(canvas);
+                }
+
+                // we need to only render one guy - the rest will happen automatically (I think).
+                if (nodeFrame != null) {
+                    mRenderer.drawRenderNode(nodeFrame);
+                }
+                if (nodeBack != null) {
+                    mRenderer.drawRenderNode(nodeBack);
+                }
+                try {
+                    Thread.sleep(5);
+                } catch (InterruptedException e) {}
+            }
+            if (nodeFrame != null) {
+                mRenderer.removeRenderNode(nodeFrame);
+            }
+            if (nodeBack != null) {
+                mRenderer.removeRenderNode(nodeBack);
+            }
+        }
+
+        public void destroy() {
+            mRunning = false;
+            try {
+                join();
+            } catch (InterruptedException e) {}
+        }
+    }
+
+    private final static Runnable sBlockThread = new Runnable() {
+        @Override
+        public void run() {
+            try {
+                Thread.sleep(DURATION);
+            } catch (InterruptedException e) {
+            }
+        }
+    };
+
+    static class ColorPulse extends Drawable {
+
+        private int mColorStart;
+        private int mColorEnd;
+        private int mStep;
+        private Rect mRect;
+        private Paint mPaint = new Paint();
+
+        public ColorPulse(int color1, int color2, Rect rect) {
+            mColorStart = color1;
+            mColorEnd = color2;
+            if (rect != null) {
+                mRect = new Rect(rect.left + BORDER_WIDTH / 2, rect.top + BORDER_WIDTH / 2,
+                                 rect.right - BORDER_WIDTH / 2, rect.bottom - BORDER_WIDTH / 2);
+            }
+        }
+
+        static int evaluate(float fraction, int startInt, int endInt) {
+            int startA = (startInt >> 24) & 0xff;
+            int startR = (startInt >> 16) & 0xff;
+            int startG = (startInt >> 8) & 0xff;
+            int startB = startInt & 0xff;
+
+            int endA = (endInt >> 24) & 0xff;
+            int endR = (endInt >> 16) & 0xff;
+            int endG = (endInt >> 8) & 0xff;
+            int endB = endInt & 0xff;
+
+            return (int)((startA + (int)(fraction * (endA - startA))) << 24) |
+                    (int)((startR + (int)(fraction * (endR - startR))) << 16) |
+                    (int)((startG + (int)(fraction * (endG - startG))) << 8) |
+                    (int)((startB + (int)(fraction * (endB - startB))));
+        }
+
+        @Override
+        public void draw(Canvas canvas) {
+            float frac = mStep / 50.0f;
+            int color = evaluate(frac, mColorStart, mColorEnd);
+            if (mRect != null && !mRect.isEmpty()) {
+                mPaint.setStyle(Paint.Style.STROKE);
+                mPaint.setStrokeWidth(BORDER_WIDTH);
+                mPaint.setColor(color);
+                canvas.drawRect(mRect, mPaint);
+            } else {
+                canvas.drawColor(color);
+            }
+
+            mStep++;
+            if (mStep >= 50) {
+                mStep = 0;
+                int tmp = mColorStart;
+                mColorStart = mColorEnd;
+                mColorEnd = tmp;
+            }
+            invalidateSelf();
+        }
+
+        @Override
+        public void setAlpha(int alpha) {
+        }
+
+        @Override
+        public void setColorFilter(ColorFilter colorFilter) {
+        }
+
+        @Override
+        public int getOpacity() {
+            return mRect == null || mRect.isEmpty() ? PixelFormat.OPAQUE : PixelFormat.TRANSLUCENT;
+        }
+
+    }
+}
+