Fix DA bugs

 * Now aware of transform of DrawDisplayListOp
 * Supports projection

 Bug: 15539677
 Bug: 15506680

Change-Id: Ic16f482cd48c3add12e49eca529281be12b93491
diff --git a/libs/hwui/DamageAccumulator.cpp b/libs/hwui/DamageAccumulator.cpp
index 8aa8c92..898e81a 100644
--- a/libs/hwui/DamageAccumulator.cpp
+++ b/libs/hwui/DamageAccumulator.cpp
@@ -26,8 +26,23 @@
 namespace android {
 namespace uirenderer {
 
+NullDamageAccumulator NullDamageAccumulator::sInstance;
+
+NullDamageAccumulator* NullDamageAccumulator::instance() {
+    return &sInstance;
+}
+
+enum TransformType {
+    TransformRenderNode,
+    TransformMatrix4,
+};
+
 struct DirtyStack {
-    const RenderNode* node;
+    TransformType type;
+    union {
+        const RenderNode* renderNode;
+        const Matrix4* matrix4;
+    };
     // When this frame is pop'd, this rect is mapped through the above transform
     // and applied to the previous (aka parent) frame
     SkRect pendingDirty;
@@ -42,7 +57,7 @@
     mHead->prev = mHead;
 }
 
-void DamageAccumulator::pushNode(const RenderNode* node) {
+void DamageAccumulator::pushCommon() {
     if (!mHead->next) {
         DirtyStack* nextFrame = (DirtyStack*) mAllocator.alloc(sizeof(DirtyStack));
         nextFrame->next = 0;
@@ -50,42 +65,120 @@
         mHead->next = nextFrame;
     }
     mHead = mHead->next;
-    mHead->node = node;
     mHead->pendingDirty.setEmpty();
 }
 
-void DamageAccumulator::popNode() {
+void DamageAccumulator::pushTransform(const RenderNode* transform) {
+    pushCommon();
+    mHead->type = TransformRenderNode;
+    mHead->renderNode = transform;
+}
+
+void DamageAccumulator::pushTransform(const Matrix4* transform) {
+    pushCommon();
+    mHead->type = TransformMatrix4;
+    mHead->matrix4 = transform;
+}
+
+void DamageAccumulator::popTransform() {
     LOG_ALWAYS_FATAL_IF(mHead->prev == mHead, "Cannot pop the root frame!");
     DirtyStack* dirtyFrame = mHead;
     mHead = mHead->prev;
-    if (!dirtyFrame->pendingDirty.isEmpty()) {
-        SkRect mappedDirty;
-        const RenderProperties& props = dirtyFrame->node->properties();
-        const SkMatrix* transform = props.getTransformMatrix();
-        if (transform && !transform->isIdentity()) {
-            transform->mapRect(&mappedDirty, dirtyFrame->pendingDirty);
-        } else {
-            mappedDirty = dirtyFrame->pendingDirty;
+    if (dirtyFrame->type == TransformRenderNode) {
+        applyRenderNodeTransform(dirtyFrame);
+    } else {
+        applyMatrix4Transform(dirtyFrame);
+    }
+}
+
+static inline void mapRect(const Matrix4* matrix, const SkRect& in, SkRect* out) {
+    if (in.isEmpty()) return;
+    Rect temp(in);
+    matrix->mapRect(temp);
+    out->join(RECT_ARGS(temp));
+}
+
+void DamageAccumulator::applyMatrix4Transform(DirtyStack* frame) {
+    mapRect(frame->matrix4, frame->pendingDirty, &mHead->pendingDirty);
+}
+
+static inline void mapRect(const RenderProperties& props, const SkRect& in, SkRect* out) {
+    if (in.isEmpty()) return;
+    const SkMatrix* transform = props.getTransformMatrix();
+    SkRect temp(in);
+    if (transform && !transform->isIdentity()) {
+        transform->mapRect(&temp);
+    }
+    temp.offset(props.getLeft(), props.getTop());
+    out->join(temp);
+}
+
+static DirtyStack* findParentRenderNode(DirtyStack* frame) {
+    while (frame->prev != frame) {
+        frame = frame->prev;
+        if (frame->type == TransformRenderNode) {
+            return frame;
         }
-        if (CC_LIKELY(mHead->node)) {
-            const RenderProperties& parentProps = mHead->node->properties();
-            mappedDirty.offset(props.getLeft() - parentProps.getScrollX(),
-                    props.getTop() - parentProps.getScrollY());
-            if (props.getClipToBounds()) {
-                if (!mappedDirty.intersect(0, 0, parentProps.getWidth(), parentProps.getHeight())) {
-                    mappedDirty.setEmpty();
-                }
+    }
+    return NULL;
+}
+
+static DirtyStack* findProjectionReceiver(DirtyStack* frame) {
+    if (frame) {
+        while (frame->prev != frame) {
+            frame = frame->prev;
+            if (frame->type == TransformRenderNode
+                    && frame->renderNode->hasProjectionReceiver()) {
+                return frame;
             }
-            if (CC_UNLIKELY(!MathUtils::isZero(props.getTranslationZ()))) {
-                // TODO: Can we better bound the shadow damage area? For now
-                // match the old damageShadowReceiver() path and just dirty
-                // the entire parent bounds
-                mappedDirty.join(0, 0, parentProps.getWidth(), parentProps.getHeight());
-            }
-        } else {
-            mappedDirty.offset(props.getLeft(), props.getTop());
         }
-        dirty(mappedDirty.fLeft, mappedDirty.fTop, mappedDirty.fRight, mappedDirty.fBottom);
+    }
+    return NULL;
+}
+
+static void applyTransforms(DirtyStack* frame, DirtyStack* end) {
+    SkRect* rect = &frame->pendingDirty;
+    while (frame != end) {
+        if (frame->type == TransformRenderNode) {
+            mapRect(frame->renderNode->properties(), *rect, rect);
+        } else {
+            mapRect(frame->matrix4, *rect, rect);
+        }
+        frame = frame->prev;
+    }
+}
+
+void DamageAccumulator::applyRenderNodeTransform(DirtyStack* frame) {
+    if (frame->pendingDirty.isEmpty()) {
+        return;
+    }
+
+    const RenderProperties& props = frame->renderNode->properties();
+
+    // Perform clipping
+    if (props.getClipToBounds() && !frame->pendingDirty.isEmpty()) {
+        if (!frame->pendingDirty.intersect(0, 0, props.getWidth(), props.getHeight())) {
+            frame->pendingDirty.setEmpty();
+        }
+    }
+
+    // apply all transforms
+    mapRect(props, frame->pendingDirty, &mHead->pendingDirty);
+
+    // project backwards if necessary
+    if (props.getProjectBackwards() && !frame->pendingDirty.isEmpty()) {
+        // First, find our parent RenderNode:
+        DirtyStack* parentNode = findParentRenderNode(frame);
+        // Find our parent's projection receiver, which is what we project onto
+        DirtyStack* projectionReceiver = findProjectionReceiver(parentNode);
+        if (projectionReceiver) {
+            applyTransforms(frame, projectionReceiver);
+            projectionReceiver->pendingDirty.join(frame->pendingDirty);
+        } else {
+            ALOGW("Failed to find projection receiver? Dropping on the floor...");
+        }
+
+        frame->pendingDirty.setEmpty();
     }
 }