Support projection of DisplayLists onto ancestors.

For now, ancestor views signal the acceptance of projections with a
save(0x20)/restore pair.

During the order traversal, each view with the save(0x20) code will
collect descendent views with mProjectToContainedVolume (which still
needs to be renamed) so that they can be drawn out of order later.

- *Temporary* sample code added to HwAccelerationTest.

- Note that a projected displaylist must not be clipped.

Change-Id: I45c493e845961535b958d59c53e8aff3f8891d9f
diff --git a/libs/hwui/DisplayList.cpp b/libs/hwui/DisplayList.cpp
index 4e811ec..d3d2613 100644
--- a/libs/hwui/DisplayList.cpp
+++ b/libs/hwui/DisplayList.cpp
@@ -472,6 +472,7 @@
         matrix.multiply(anim);
     }
     if (mMatrixFlags != 0) {
+        updateMatrix();
         if (mMatrixFlags == TRANSLATION) {
             matrix.translate(mTranslationX, mTranslationY, mTranslationZ);
         } else {
@@ -499,29 +500,42 @@
 
     for (unsigned int i = 0; i < mDisplayListData->children.size(); i++) {
         DrawDisplayListOp* childOp = mDisplayListData->children[i];
-        childOp->mDisplayList->computeOrderingImpl(childOp, &m3dNodes, &mat4::identity());
+        childOp->mDisplayList->computeOrderingImpl(childOp,
+                &m3dNodes, &mat4::identity(),
+                &mProjectedNodes, &mat4::identity());
     }
 }
 
 void DisplayList::computeOrderingImpl(
         DrawDisplayListOp* opState,
         Vector<ZDrawDisplayListOpPair>* compositedChildrenOf3dRoot,
-        const mat4* transformFrom3dRoot) {
+        const mat4* transformFrom3dRoot,
+        Vector<DrawDisplayListOp*>* compositedChildrenOfProjectionSurface,
+        const mat4* transformFromProjectionSurface) {
 
     // TODO: should avoid this calculation in most cases
-    opState->mTransformFrom3dRoot.load(*transformFrom3dRoot);
-    opState->mTransformFrom3dRoot.multiply(opState->mTransformFromParent);
+    // TODO: just calculate single matrix, down to all leaf composited elements
+    Matrix4 localTransformFrom3dRoot(*transformFrom3dRoot);
+    localTransformFrom3dRoot.multiply(opState->mTransformFromParent);
+    Matrix4 localTransformFromProjectionSurface(*transformFromProjectionSurface);
+    localTransformFromProjectionSurface.multiply(opState->mTransformFromParent);
 
-    if (mTranslationZ != 0.0f) { // TODO: other signals, such as custom 4x4 matrix
-        // composited layer, flag for out of order draw...
+    if (mTranslationZ != 0.0f) { // TODO: other signals for 3d compositing, such as custom matrix4
+        // composited 3d layer, flag for out of order draw and save matrix...
         opState->mSkipInOrderDraw = true;
+        opState->mTransformFromCompositingAncestor.load(localTransformFrom3dRoot);
 
         // ... and insert into current 3d root, keyed with pivot z for later sorting
         Vector3 pivot(mPivotX, mPivotY, 0.0f);
-        mat4 totalTransform(opState->mTransformFrom3dRoot);
+        mat4 totalTransform(localTransformFrom3dRoot);
         applyViewPropertyTransforms(totalTransform);
         totalTransform.mapPoint3d(pivot);
         compositedChildrenOf3dRoot->add(ZDrawDisplayListOpPair(pivot.z, opState));
+    } else if (mProjectToContainedVolume) {
+        // composited projectee, flag for out of order draw, save matrix, and store in proj surface
+        opState->mSkipInOrderDraw = true;
+        opState->mTransformFromCompositingAncestor.load(localTransformFromProjectionSurface);
+        compositedChildrenOfProjectionSurface->add(opState);
     } else {
         // standard in order draw
         opState->mSkipInOrderDraw = false;
@@ -529,18 +543,30 @@
 
     m3dNodes.clear();
     if (mIsContainedVolume) {
-        // create a new 3d space for children by separating their ordering
+        // create a new 3d space for descendents by collecting them
         compositedChildrenOf3dRoot = &m3dNodes;
         transformFrom3dRoot = &mat4::identity();
     } else {
-        transformFrom3dRoot = &(opState->mTransformFrom3dRoot);
+        applyViewPropertyTransforms(localTransformFrom3dRoot);
+        transformFrom3dRoot = &localTransformFrom3dRoot;
+    }
+
+    mProjectedNodes.clear();
+    if (mDisplayListData != NULL && mDisplayListData->projectionIndex >= 0) {
+        // create a new projection surface for descendents by collecting them
+        compositedChildrenOfProjectionSurface = &mProjectedNodes;
+        transformFromProjectionSurface = &mat4::identity();
+    } else {
+        applyViewPropertyTransforms(localTransformFromProjectionSurface);
+        transformFromProjectionSurface = &localTransformFromProjectionSurface;
     }
 
     if (mDisplayListData != NULL && mDisplayListData->children.size() > 0) {
         for (unsigned int i = 0; i < mDisplayListData->children.size(); i++) {
             DrawDisplayListOp* childOp = mDisplayListData->children[i];
             childOp->mDisplayList->computeOrderingImpl(childOp,
-                    compositedChildrenOf3dRoot, transformFrom3dRoot);
+                    compositedChildrenOf3dRoot, transformFrom3dRoot,
+                    compositedChildrenOfProjectionSurface, transformFromProjectionSurface);
         }
     }
 }
@@ -598,19 +624,19 @@
     if (m3dNodes.size() == 0 ||
             (mode == kNegativeZChildren && m3dNodes[0].key > 0.0f) ||
             (mode == kPositiveZChildren && m3dNodes[m3dNodes.size() - 1].key < 0.0f)) {
-        // nothing to draw
+        // no 3d children to draw
         return;
     }
 
     LinearAllocator& alloc = handler.allocator();
-    ClipRectOp* op = new (alloc) ClipRectOp(0, 0, mWidth, mHeight,
+    ClipRectOp* clipOp = new (alloc) ClipRectOp(0, 0, mWidth, mHeight,
             SkRegion::kIntersect_Op); // clip to 3d root bounds for now
-    handler(op, PROPERTY_SAVECOUNT, mClipToBounds);
+    handler(clipOp, PROPERTY_SAVECOUNT, mClipToBounds);
     int rootRestoreTo = renderer.save(SkCanvas::kMatrix_SaveFlag | SkCanvas::kClip_SaveFlag);
 
     for (size_t i = 0; i < m3dNodes.size(); i++) {
         const float zValue = m3dNodes[i].key;
-        DrawDisplayListOp* op = m3dNodes[i].value;
+        DrawDisplayListOp* childOp = m3dNodes[i].value;
 
         if (mode == kPositiveZChildren && zValue < 0.0f) continue;
         if (mode == kNegativeZChildren && zValue > 0.0f) break;
@@ -622,19 +648,38 @@
              * -determine and pass background shape (and possibly drawable alpha)
              * -view must opt-in to shadows
              * -consider shadows for other content
+             * -inform shadow system of ancestor transform (for use in lighting)
              */
-            mat4 shadowMatrix(op->mTransformFrom3dRoot);
-            op->mDisplayList->applyViewPropertyTransforms(shadowMatrix);
+            mat4 shadowMatrix(childOp->mTransformFromCompositingAncestor);
+            childOp->mDisplayList->applyViewPropertyTransforms(shadowMatrix);
             DisplayListOp* shadowOp  = new (alloc) DrawShadowOp(shadowMatrix,
-                    op->mDisplayList->mAlpha,
-                    op->mDisplayList->getWidth(), op->mDisplayList->getHeight());
+                    childOp->mDisplayList->mAlpha,
+                    childOp->mDisplayList->getWidth(), childOp->mDisplayList->getHeight());
             handler(shadowOp, PROPERTY_SAVECOUNT, mClipToBounds);
         }
 
-        renderer.concatMatrix(op->mTransformFrom3dRoot);
-        op->mSkipInOrderDraw = false; // this is horrible, I'm so sorry everyone
-        handler(op, renderer.getSaveCount() - 1, mClipToBounds);
-        op->mSkipInOrderDraw = true;
+        renderer.concatMatrix(childOp->mTransformFromCompositingAncestor);
+        childOp->mSkipInOrderDraw = false; // this is horrible, I'm so sorry everyone
+        handler(childOp, renderer.getSaveCount() - 1, mClipToBounds);
+        childOp->mSkipInOrderDraw = true;
+    }
+    handler(new (alloc) RestoreToCountOp(rootRestoreTo), PROPERTY_SAVECOUNT, mClipToBounds);
+}
+
+template <class T>
+void DisplayList::iterateProjectedChildren(OpenGLRenderer& renderer, T& handler, const int level) {
+    LinearAllocator& alloc = handler.allocator();
+    ClipRectOp* clipOp = new (alloc) ClipRectOp(0, 0, mWidth, mHeight,
+            SkRegion::kReplace_Op); // clip to projection surface root bounds
+    handler(clipOp, PROPERTY_SAVECOUNT, mClipToBounds);
+    int rootRestoreTo = renderer.save(SkCanvas::kMatrix_SaveFlag | SkCanvas::kClip_SaveFlag);
+
+    for (size_t i = 0; i < mProjectedNodes.size(); i++) {
+        DrawDisplayListOp* childOp = mProjectedNodes[i];
+        renderer.concatMatrix(childOp->mTransformFromCompositingAncestor);
+        childOp->mSkipInOrderDraw = false; // this is horrible, I'm so sorry everyone
+        handler(childOp, renderer.getSaveCount() - 1, mClipToBounds);
+        childOp->mSkipInOrderDraw = true;
     }
     handler(new (alloc) RestoreToCountOp(rootRestoreTo), PROPERTY_SAVECOUNT, mClipToBounds);
 }
@@ -686,6 +731,7 @@
 
         DisplayListLogBuffer& logBuffer = DisplayListLogBuffer::getInstance();
         const int saveCountOffset = renderer.getSaveCount() - 1;
+        const int projectionIndex = mDisplayListData->projectionIndex;
         for (unsigned int i = 0; i < mDisplayListData->displayListOps.size(); i++) {
             DisplayListOp *op = mDisplayListData->displayListOps[i];
 
@@ -695,6 +741,10 @@
 
             logBuffer.writeCommand(level, op->name());
             handler(op, saveCountOffset, mClipToBounds);
+
+            if (CC_UNLIKELY(i == projectionIndex && mProjectedNodes.size() > 0)) {
+                iterateProjectedChildren(renderer, handler, level);
+            }
         }
 
         // for 3d root, draw children with positive z values
diff --git a/libs/hwui/DisplayList.h b/libs/hwui/DisplayList.h
index d3113a3..5399185 100644
--- a/libs/hwui/DisplayList.h
+++ b/libs/hwui/DisplayList.h
@@ -112,6 +112,7 @@
  */
 class DisplayListData : public LightRefBase<DisplayListData> {
 public:
+    DisplayListData() : projectionIndex(-1) {}
     // allocator into which all ops were allocated
     LinearAllocator allocator;
 
@@ -120,6 +121,10 @@
 
     // list of children display lists for quick, non-drawing traversal
     Vector<DrawDisplayListOp*> children;
+
+    // index of DisplayListOp restore, after which projected descendents should be drawn
+    int projectionIndex;
+    Matrix4 projectionTransform;
 };
 
 /**
@@ -522,8 +527,10 @@
     void applyViewPropertyTransforms(mat4& matrix);
 
     void computeOrderingImpl(DrawDisplayListOp* opState,
-            Vector<ZDrawDisplayListOpPair>* compositedChildrenOf3dRoot,
-            const mat4* transformFromRoot);
+        Vector<ZDrawDisplayListOpPair>* compositedChildrenOf3dRoot,
+        const mat4* transformFrom3dRoot,
+        Vector<DrawDisplayListOp*>* compositedChildrenOfProjectionSurface,
+        const mat4* transformFromProjectionSurface);
 
     template <class T>
     inline void setViewProperties(OpenGLRenderer& renderer, T& handler, const int level);
@@ -533,6 +540,9 @@
         T& handler, const int level);
 
     template <class T>
+    inline void iterateProjectedChildren(OpenGLRenderer& renderer, T& handler, const int level);
+
+    template <class T>
     inline void iterate(OpenGLRenderer& renderer, T& handler, const int level);
 
     void init();
@@ -610,6 +620,9 @@
 
     // for 3d roots, contains a z sorted list of all children items
     Vector<ZDrawDisplayListOpPair> m3dNodes;
+
+    // for projection surfaces, contains a list of all children items
+    Vector<DrawDisplayListOp*> mProjectedNodes;
 }; // class DisplayList
 
 }; // namespace uirenderer
diff --git a/libs/hwui/DisplayListOp.h b/libs/hwui/DisplayListOp.h
index c375bfa..e3d4e2d 100644
--- a/libs/hwui/DisplayListOp.h
+++ b/libs/hwui/DisplayListOp.h
@@ -1568,14 +1568,14 @@
     const mat4 mTransformFromParent;
 
     /**
-     * Holds the transformation between the 3d root ViewGroup and this DisplayList drawing
-     * instance. Represents any translations / transformations done within the drawing of the 3d
-     * root ViewGroup's draw, before the draw of the View represented by this DisplayList draw
-     * instance.
+     * Holds the transformation between the 3d root OR projection surface ViewGroup and this
+     * DisplayList drawing instance. Represents any translations / transformations done within the
+     * drawing of the compositing ancestor ViewGroup's draw, before the draw of the View represented
+     * by this DisplayList draw instance.
      *
      * Note: doesn't include any transformation recorded within the DisplayList and its properties.
      */
-    mat4 mTransformFrom3dRoot;
+    mat4 mTransformFromCompositingAncestor;
     bool mSkipInOrderDraw;
 };
 
diff --git a/libs/hwui/DisplayListRenderer.cpp b/libs/hwui/DisplayListRenderer.cpp
index 051ccfe..c8a6c2d 100644
--- a/libs/hwui/DisplayListRenderer.cpp
+++ b/libs/hwui/DisplayListRenderer.cpp
@@ -173,6 +173,14 @@
     StatefulBaseRenderer::restoreToCount(saveCount);
 }
 
+void DisplayListRenderer::onSnapshotRestored(const Snapshot& removed, const Snapshot& restored) {
+    bool restoreProjection = removed.flags & Snapshot::kFlagProjectionTarget;
+    if (restoreProjection) {
+        mDisplayListData->projectionIndex = mDisplayListData->displayListOps.size() - 1;
+        mDisplayListData->projectionTransform.load(*currentTransform());
+    }
+}
+
 int DisplayListRenderer::saveLayer(float left, float top, float right, float bottom,
         int alpha, SkXfermode::Mode mode, int flags) {
     addStateOp(new (alloc()) SaveLayerOp(left, top, right, bottom, alpha, mode, flags));
diff --git a/libs/hwui/DisplayListRenderer.h b/libs/hwui/DisplayListRenderer.h
index f129c10..1360808 100644
--- a/libs/hwui/DisplayListRenderer.h
+++ b/libs/hwui/DisplayListRenderer.h
@@ -221,6 +221,8 @@
     uint32_t getFunctorCount() const {
         return mFunctorCount;
     }
+protected:
+    virtual void onSnapshotRestored(const Snapshot& removed, const Snapshot& restored);
 
 private:
     void insertRestoreToCount();
diff --git a/libs/hwui/Snapshot.cpp b/libs/hwui/Snapshot.cpp
index d26ee38..a6ec183 100644
--- a/libs/hwui/Snapshot.cpp
+++ b/libs/hwui/Snapshot.cpp
@@ -70,6 +70,10 @@
     } else {
         region = NULL;
     }
+
+    if (saveFlags & Snapshot::kFlagProjectionTarget) {
+        flags |= Snapshot::kFlagProjectionTarget;
+    }
 }
 
 ///////////////////////////////////////////////////////////////////////////////
diff --git a/libs/hwui/Snapshot.h b/libs/hwui/Snapshot.h
index cc6d0cd..d61d972 100644
--- a/libs/hwui/Snapshot.h
+++ b/libs/hwui/Snapshot.h
@@ -75,7 +75,13 @@
          * Indicates that this snapshot or an ancestor snapshot is
          * an FBO layer.
          */
-        kFlagFboTarget = 0x10
+        kFlagFboTarget = 0x10,
+        /**
+         * Indicates that the save/restore pair encapsulates a
+         * projection target, and that after the restore any projected
+         * descendents should be drawn.
+         */
+        kFlagProjectionTarget = 0x20
     };
 
     /**
diff --git a/tests/HwAccelerationTest/AndroidManifest.xml b/tests/HwAccelerationTest/AndroidManifest.xml
index 6f774f8..0ad3456 100644
--- a/tests/HwAccelerationTest/AndroidManifest.xml
+++ b/tests/HwAccelerationTest/AndroidManifest.xml
@@ -858,5 +858,14 @@
             </intent-filter>
         </activity>
 
+        <activity
+                android:name="ProjectionActivity"
+                android:label="Reordering/Projection">
+            <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/res/layout/projection.xml b/tests/HwAccelerationTest/res/layout/projection.xml
new file mode 100644
index 0000000..564201a
--- /dev/null
+++ b/tests/HwAccelerationTest/res/layout/projection.xml
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="utf-8"?>
+<view class="com.android.test.hwui.ProjectionActivity$ProjecteeLayout"
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:id="@+id/container"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:orientation="vertical"
+    tools:context="com.example.projection.ProjectionActivity"
+    tools:ignore="MergeRootFrame">
+    <TextView
+        android:layout_width="match_parent"
+        android:layout_height="100dp"
+        android:textSize="50sp"
+        android:text="TextView"/>
+    <FrameLayout
+        android:layout_width="match_parent"
+        android:layout_height="100dp"
+        android:clipChildren="false">
+        <view class="com.android.test.hwui.ProjectionActivity$ProjectedView"
+            android:id="@+id/projection"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"/>
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:textSize="50sp"
+            android:text="TextView"/>
+    </FrameLayout>
+
+    <TextView
+        android:layout_width="match_parent"
+        android:layout_height="100dp"
+        android:textSize="50sp"
+        android:text="TextView"/>
+</view>
\ No newline at end of file
diff --git a/tests/HwAccelerationTest/src/com/android/test/hwui/ProjectionActivity.java b/tests/HwAccelerationTest/src/com/android/test/hwui/ProjectionActivity.java
new file mode 100644
index 0000000..51a6803
--- /dev/null
+++ b/tests/HwAccelerationTest/src/com/android/test/hwui/ProjectionActivity.java
@@ -0,0 +1,110 @@
+package com.android.test.hwui;
+
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.RectF;
+import android.os.Bundle;
+
+import android.app.Activity;
+import android.util.AttributeSet;
+import android.view.DisplayList;
+import android.view.View;
+import android.widget.LinearLayout;
+
+public class ProjectionActivity extends Activity {
+    /**
+     * The content from this view should be projected in between the background of the
+     * ProjecteeLayout and its children, unclipped.
+     *
+     * This view doesn't clip to its bounds (because its parent has clipChildren=false) so that
+     * when it is projected onto the ProjecteeLayout, it draws outside its view bounds.
+     */
+    public static class ProjectedView extends View {
+        private final Paint mPaint = new Paint();
+        private final RectF mRectF = new RectF();
+
+        public ProjectedView(Context context) {
+            this(context, null);
+        }
+
+        public ProjectedView(Context context, AttributeSet attrs) {
+            this(context, attrs, 0);
+        }
+
+        public ProjectedView(Context context, AttributeSet attrs, int defStyleAttr) {
+            super(context, attrs, defStyleAttr);
+
+            setOnClickListener(new OnClickListener() {
+                boolean toggle = false;
+                @Override
+                public void onClick(View v) {
+                    toggle = !toggle;
+                    setProject(toggle);
+                }
+            });
+        }
+
+        private void setProject(boolean value) {
+            DisplayList displayList = getDisplayList();
+            if (displayList != null) {
+                displayList.setProjectToContainedVolume(value);
+            }
+            // NOTE: we can't invalidate ProjectedView for the redraw because:
+            // 1) the view won't preserve displayList properties that it doesn't know about
+            // 2) the damage rect won't be big enough
+
+            // instead, twiddle properties on the container, so that enough area of the screen is
+            // redrawn without rerecording any DisplayLists.
+            container.setTranslationX(100f);
+            container.setTranslationX(0.0f);
+        }
+
+        @Override
+        protected void onDraw(Canvas canvas) {
+            // TODO: set projection flag
+            final int w = getWidth();
+            final int h = getHeight();
+            mRectF.set(0, -h, w, 2 * h);
+            mPaint.setAntiAlias(true);
+            mPaint.setColor(0x5f00ff00);
+            canvas.drawOval(mRectF, mPaint);
+        }
+    }
+
+    public static class ProjecteeLayout extends LinearLayout {
+        private final Paint mPaint = new Paint();
+        private final RectF mRectF = new RectF();
+
+        public ProjecteeLayout(Context context) {
+            this(context, null);
+        }
+
+        public ProjecteeLayout(Context context, AttributeSet attrs) {
+            this(context, attrs, 0);
+        }
+
+        public ProjecteeLayout(Context context, AttributeSet attrs, int defStyle) {
+            super(context, attrs, defStyle);
+        }
+
+        @Override
+        protected void dispatchDraw(Canvas canvas) {
+            canvas.save(0x20); // secret save flag
+            mRectF.set(0, 0, getWidth(), getHeight());
+            mPaint.setColor(0x5f000000);
+            canvas.drawOval(mRectF, mPaint);
+            canvas.restore();
+            super.dispatchDraw(canvas);
+        }
+    }
+
+    static View container;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.projection);
+        container = findViewById(R.id.container);
+    }
+}