GlopBuilder, and test app refactor

Change-Id: I2cd299ccf178007fd5f83bab6c3448f03aec7843
diff --git a/libs/hwui/Android.common.mk b/libs/hwui/Android.common.mk
index de9ef06..2e1364e 100644
--- a/libs/hwui/Android.common.mk
+++ b/libs/hwui/Android.common.mk
@@ -34,9 +34,9 @@
     CanvasState.cpp \
     ClipArea.cpp \
     DamageAccumulator.cpp \
-    DisplayList.cpp \
     DeferredDisplayList.cpp \
     DeferredLayerUpdater.cpp \
+    DisplayList.cpp \
     DisplayListRenderer.cpp \
     Dither.cpp \
     DrawProfiler.cpp \
@@ -44,6 +44,7 @@
     FboCache.cpp \
     FontRenderer.cpp \
     GammaFontRenderer.cpp \
+    GlopBuilder.cpp \
     GradientCache.cpp \
     Image.cpp \
     Interpolator.cpp \
@@ -69,9 +70,9 @@
     Snapshot.cpp \
     SpotShadow.cpp \
     TessellationCache.cpp \
+    TextDropShadowCache.cpp \
     Texture.cpp \
-    TextureCache.cpp \
-    TextDropShadowCache.cpp
+    TextureCache.cpp
 
 intermediates := $(call intermediates-dir-for,STATIC_LIBRARIES,libRS,TARGET,)
 
diff --git a/libs/hwui/Glop.h b/libs/hwui/Glop.h
index 730d9df..bbeb19e 100644
--- a/libs/hwui/Glop.h
+++ b/libs/hwui/Glop.h
@@ -23,10 +23,13 @@
 
 #include <GLES2/gl2.h>
 #include <GLES2/gl2ext.h>
+#include <SkXfermode.h>
 
 namespace android {
 namespace uirenderer {
 
+class Program;
+
 /*
  * Enumerates optional vertex attributes
  *
@@ -53,18 +56,16 @@
     Rect bounds;
 
     struct Mesh {
-        VertexAttribFlags vertexFlags = static_cast<VertexAttribFlags>(0);
+        VertexAttribFlags vertexFlags;
         GLuint primitiveMode; // GL_TRIANGLES and GL_TRIANGLE_STRIP supported
-        GLuint vertexBufferObject = 0;
-        GLuint indexBufferObject = 0;
+        GLuint vertexBufferObject;
+        GLuint indexBufferObject;
         int vertexCount;
         GLsizei stride;
     } mesh;
 
     struct Fill {
         Program* program;
-        GLuint shaderId;
-        GLuint textureId;
 
         struct Color {
             float a, r, g, b;
@@ -89,10 +90,8 @@
     } transform;
 
     struct Blend {
-        static const SkXfermode::Mode kDisable =
-                static_cast<SkXfermode::Mode>(SkXfermode::kLastMode + 1);
-        SkXfermode::Mode mode;
-        bool swapSrcDst;
+        GLenum src;
+        GLenum dst;
     } blend;
 
     /**
diff --git a/libs/hwui/GlopBuilder.cpp b/libs/hwui/GlopBuilder.cpp
new file mode 100644
index 0000000..dafe087
--- /dev/null
+++ b/libs/hwui/GlopBuilder.cpp
@@ -0,0 +1,133 @@
+/*
+ * Copyright (C) 2015 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.
+ */
+#include "GlopBuilder.h"
+
+#include "Caches.h"
+#include "Glop.h"
+#include "Matrix.h"
+#include "Texture.h"
+#include "renderstate/MeshState.h"
+#include "renderstate/RenderState.h"
+#include "utils/PaintUtils.h"
+
+#include <GLES2/gl2.h>
+#include <SkPaint.h>
+
+namespace android {
+namespace uirenderer {
+
+GlopBuilder::GlopBuilder(RenderState& renderState, Caches& caches, Glop* outGlop)
+        : mRenderState(renderState)
+        , mCaches(caches)
+        , mOutGlop(outGlop){
+}
+
+GlopBuilder& GlopBuilder::setMeshUnitQuad() {
+    mOutGlop->mesh.vertexFlags = static_cast<VertexAttribFlags>(0);
+    mOutGlop->mesh.primitiveMode = GL_TRIANGLE_STRIP;
+    mOutGlop->mesh.vertexBufferObject = mRenderState.meshState().getUnitQuadVBO();
+    mOutGlop->mesh.indexBufferObject = 0;
+    mOutGlop->mesh.vertexCount = 4;
+    mOutGlop->mesh.stride = kTextureVertexStride;
+    return *this;
+}
+
+GlopBuilder& GlopBuilder::setTransformAndRect(ModelViewMode mode,
+        const Matrix4& ortho, const Matrix4& transform,
+        float left, float top, float right, float bottom, bool offset) {
+    mOutGlop->transform.ortho.load(ortho);
+
+    mOutGlop->transform.modelView.loadTranslate(left, top, 0.0f);
+    if (mode == kModelViewMode_TranslateAndScale) {
+        mOutGlop->transform.modelView.scale(right - left, bottom - top, 1.0f);
+    }
+
+    mOutGlop->transform.canvas.load(transform);
+
+    mOutGlop->transform.offset = offset;
+
+    mOutGlop->bounds.set(left, top, right, bottom);
+    mOutGlop->transform.canvas.mapRect(mOutGlop->bounds);
+    return *this;
+}
+
+GlopBuilder& GlopBuilder::setPaint(const SkPaint* paint, float alphaScale) {
+    // TODO: support null paint
+    const SkShader* shader = paint->getShader();
+    const SkColorFilter* colorFilter = paint->getColorFilter();
+
+    SkXfermode::Mode mode = PaintUtils::getXfermode(paint->getXfermode());
+    if (mode != SkXfermode::kClear_Mode) {
+        int color = paint->getColor();
+        float alpha = (SkColorGetA(color) / 255.0f) * alphaScale;
+        if (shader) {
+            // shader discards color channels
+            color |= 0x00FFFFFF;
+        }
+        mOutGlop->fill.color = {
+                alpha,
+                alpha * SkColorGetR(color),
+                alpha * SkColorGetG(color),
+                alpha * SkColorGetB(color)
+        };
+    } else {
+        mOutGlop->fill.color = { 1, 0, 0, 0 };
+    }
+    const bool SWAP_SRC_DST = false;
+    const bool HAS_FRAMEBUFFER_FETCH = false; //mExtensions.hasFramebufferFetch();
+
+    mOutGlop->blend = {GL_ZERO, GL_ZERO};
+    if (mOutGlop->fill.color.a < 1.0f
+            || (shader && !shader->isOpaque())
+            || PaintUtils::isBlendedColorFilter(colorFilter)
+            || mode != SkXfermode::kSrcOver_Mode) {
+        if (CC_LIKELY(mode <= SkXfermode::kScreen_Mode)) {
+            Blend::getFactors(mode, SWAP_SRC_DST,
+                    &mOutGlop->blend.src, &mOutGlop->blend.dst);
+        } else {
+            // These blend modes are not supported by OpenGL directly and have
+            // to be implemented using shaders. Since the shader will perform
+            // the blending, don't enable GL blending off here
+            // If the blend mode cannot be implemented using shaders, fall
+            // back to the default SrcOver blend mode instead
+            if (CC_UNLIKELY(HAS_FRAMEBUFFER_FETCH)) {
+                mDescription.framebufferMode = mode;
+                mDescription.swapSrcDst = SWAP_SRC_DST;
+                // blending in shader, don't enable
+            } else {
+                // unsupported
+                Blend::getFactors(SkXfermode::kSrcOver_Mode, SWAP_SRC_DST,
+                        &mOutGlop->blend.src, &mOutGlop->blend.dst);
+            }
+        }
+    }
+
+    return *this;
+}
+
+GlopBuilder& GlopBuilder::setTexture(Texture* texture) {
+    LOG_ALWAYS_FATAL("not yet supported");
+    return *this;
+}
+
+void GlopBuilder::build() {
+    mDescription.modulate = mOutGlop->fill.color.a < 1.0f;
+    mOutGlop->fill.program = mCaches.programCache.get(mDescription);
+}
+
+} /* namespace uirenderer */
+} /* namespace android */
+
diff --git a/libs/hwui/GlopBuilder.h b/libs/hwui/GlopBuilder.h
new file mode 100644
index 0000000..d243d76
--- /dev/null
+++ b/libs/hwui/GlopBuilder.h
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2015 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.
+ */
+#ifndef RENDERSTATE_GLOPBUILDER_H
+#define RENDERSTATE_GLOPBUILDER_H
+
+#include "OpenGLRenderer.h"
+#include "Program.h"
+#include "utils/Macros.h"
+
+class SkPaint;
+
+namespace android {
+namespace uirenderer {
+
+class Caches;
+struct Glop;
+class RenderState;
+class Texture;
+class Matrix4;
+
+class GlopBuilder {
+    PREVENT_COPY_AND_ASSIGN(GlopBuilder);
+public:
+    GlopBuilder(RenderState& renderState, Caches& caches, Glop* outGlop);
+    GlopBuilder& setMeshUnitQuad();
+    GlopBuilder& setTransformAndRect(ModelViewMode mode,
+            const Matrix4& ortho, const Matrix4& transform,
+            float left, float top, float right, float bottom, bool offset);
+    GlopBuilder& setPaint(const SkPaint* paint, float alphaScale);
+    GlopBuilder& setTexture(Texture* texture);
+    void build();
+private:
+    ProgramDescription mDescription;
+    RenderState& mRenderState;
+    Caches& mCaches;
+    Glop* mOutGlop;
+};
+
+} /* namespace uirenderer */
+} /* namespace android */
+
+#endif // RENDERSTATE_GLOPBUILDER_H
diff --git a/libs/hwui/OpenGLRenderer.cpp b/libs/hwui/OpenGLRenderer.cpp
index ab6f0ce..b10aea3 100644
--- a/libs/hwui/OpenGLRenderer.cpp
+++ b/libs/hwui/OpenGLRenderer.cpp
@@ -20,10 +20,13 @@
 #include "DisplayListRenderer.h"
 #include "Fence.h"
 #include "GammaFontRenderer.h"
+#include "Glop.h"
+#include "GlopBuilder.h"
 #include "Patch.h"
 #include "PathTessellator.h"
 #include "Properties.h"
 #include "RenderNode.h"
+#include "renderstate/MeshState.h"
 #include "renderstate/RenderState.h"
 #include "ShadowTessellator.h"
 #include "SkiaShader.h"
@@ -101,7 +104,7 @@
     memset(&mDrawModifiers, 0, sizeof(mDrawModifiers));
     mDrawModifiers.mOverrideLayerAlpha = 1.0f;
 
-    memcpy(mMeshVertices, kMeshVertices, sizeof(kMeshVertices));
+    memcpy(mMeshVertices, kUnitQuadVertices, sizeof(kUnitQuadVertices));
 }
 
 OpenGLRenderer::~OpenGLRenderer() {
@@ -1704,9 +1707,9 @@
     // When the blending mode is kClear_Mode, we need to use a modulate color
     // argb=1,0,0,0
     accountForClear(mode);
-    blend |= (mColorSet && mColorA < 1.0f) ||
-            (getShader(paint) && !getShader(paint)->isOpaque()) ||
-            PaintUtils::isBlendedColorFilter(getColorFilter(paint));
+    blend |= (mColorSet && mColorA < 1.0f)
+            || (getShader(paint) && !getShader(paint)->isOpaque())
+            || PaintUtils::isBlendedColorFilter(getColorFilter(paint));
     chooseBlending(blend, mode, mDescription, swapSrcDst);
 }
 
diff --git a/libs/hwui/RenderNode.cpp b/libs/hwui/RenderNode.cpp
index a0a5a1c..659ef6c 100644
--- a/libs/hwui/RenderNode.cpp
+++ b/libs/hwui/RenderNode.cpp
@@ -836,7 +836,6 @@
  */
 template <class T>
 void RenderNode::issueOperations(OpenGLRenderer& renderer, T& handler) {
-    const int level = handler.level();
     if (mDisplayListData->isEmpty()) {
         DISPLAY_LIST_LOGD("%*sEmpty display list (%p, %s)", level * 2, "", this, getName());
         return;
@@ -860,7 +859,7 @@
 #if DEBUG_DISPLAY_LIST
     const Rect& clipRect = renderer.getLocalClipBounds();
     DISPLAY_LIST_LOGD("%*sStart display list (%p, %s), localClipBounds: %.0f, %.0f, %.0f, %.0f",
-            level * 2, "", this, getName(),
+            handler.level() * 2, "", this, getName(),
             clipRect.left, clipRect.top, clipRect.right, clipRect.bottom);
 #endif
 
@@ -900,7 +899,7 @@
                 for (size_t opIndex = chunk.beginOpIndex; opIndex < chunk.endOpIndex; opIndex++) {
                     DisplayListOp *op = mDisplayListData->displayListOps[opIndex];
 #if DEBUG_DISPLAY_LIST
-                    op->output(level + 1);
+                    op->output(handler.level() + 1);
 #endif
                     handler(op, saveCountOffset, properties().getClipToBounds());
 
diff --git a/libs/hwui/ResourceCache.h b/libs/hwui/ResourceCache.h
index c6483ac3..4333792 100644
--- a/libs/hwui/ResourceCache.h
+++ b/libs/hwui/ResourceCache.h
@@ -81,7 +81,7 @@
     uint32_t mPixelRefStableID;
 
     friend class ResourceCache;
-    friend class android::key_value_pair_t<BitmapKey, SkBitmap*>;
+    friend struct android::key_value_pair_t<BitmapKey, SkBitmap*>;
 };
 
 class ANDROID_API ResourceCache: public Singleton<ResourceCache> {
diff --git a/libs/hwui/Snapshot.cpp b/libs/hwui/Snapshot.cpp
index 49fb4ba..597d95c 100644
--- a/libs/hwui/Snapshot.cpp
+++ b/libs/hwui/Snapshot.cpp
@@ -203,8 +203,8 @@
     ALOGD("Snapshot %p, flags %x, prev %p, height %d, ignored %d, hasComplexClip %d",
             this, flags, previous.get(), getViewportHeight(), isIgnored(), !mClipArea->isSimple());
     const Rect& clipRect(mClipArea->getClipRect());
-    ALOGD("  ClipRect (at %p) %.1f %.1f %.1f %.1f",
-            clipRect, clipRect.left, clipRect.top, clipRect.right, clipRect.bottom);
+    ALOGD("  ClipRect %.1f %.1f %.1f %.1f",
+            clipRect.left, clipRect.top, clipRect.right, clipRect.bottom);
     ALOGD("  Transform (at %p):", transform);
     transform->dump();
 }
diff --git a/libs/hwui/renderstate/Blend.cpp b/libs/hwui/renderstate/Blend.cpp
index 3e7b721..93088e4 100644
--- a/libs/hwui/renderstate/Blend.cpp
+++ b/libs/hwui/renderstate/Blend.cpp
@@ -79,21 +79,10 @@
 }
 
 void Blend::enable(SkXfermode::Mode mode, bool swapSrcDst) {
-    // enable
-    if (!mEnabled) {
-        glEnable(GL_BLEND);
-        mEnabled = true;
-    }
-
-    // select blend mode
-    GLenum sourceMode = swapSrcDst ? kBlendsSwap[mode].src : kBlends[mode].src;
-    GLenum destMode = swapSrcDst ? kBlendsSwap[mode].dst : kBlends[mode].dst;
-
-    if (sourceMode != mSrcMode || destMode != mSrcMode) {
-        glBlendFunc(sourceMode, destMode);
-        mSrcMode = sourceMode;
-        mDstMode = destMode;
-    }
+    GLenum srcMode;
+    GLenum dstMode;
+    getFactors(mode, swapSrcDst, &srcMode, &dstMode);
+    setFactors(srcMode, dstMode);
 }
 
 void Blend::disable() {
@@ -116,6 +105,28 @@
     }
 }
 
+void Blend::getFactors(SkXfermode::Mode mode, bool swapSrcDst, GLenum* outSrc, GLenum* outDst) {
+    *outSrc = swapSrcDst ? kBlendsSwap[mode].src : kBlends[mode].src;
+    *outDst = swapSrcDst ? kBlendsSwap[mode].dst : kBlends[mode].dst;
+}
+
+void Blend::setFactors(GLenum srcMode, GLenum dstMode) {
+    if (srcMode == GL_ZERO && dstMode == GL_ZERO) {
+        disable();
+    } else {
+        if (!mEnabled) {
+            glEnable(GL_BLEND);
+            mEnabled = true;
+        }
+
+        if (srcMode != mSrcMode || dstMode != mSrcMode) {
+            glBlendFunc(srcMode, dstMode);
+            mSrcMode = srcMode;
+            mDstMode = dstMode;
+        }
+    }
+}
+
 } /* namespace uirenderer */
 } /* namespace android */
 
diff --git a/libs/hwui/renderstate/Blend.h b/libs/hwui/renderstate/Blend.h
index b82b477..31d7dde 100644
--- a/libs/hwui/renderstate/Blend.h
+++ b/libs/hwui/renderstate/Blend.h
@@ -32,6 +32,9 @@
     void enable(SkXfermode::Mode mode, bool swapSrcDst);
     void disable();
     void syncEnabled();
+
+    static void getFactors(SkXfermode::Mode mode, bool swapSrcDst, GLenum* outSrc, GLenum* outDst);
+    void setFactors(GLenum src, GLenum dst);
 private:
     Blend();
     void invalidate();
diff --git a/libs/hwui/renderstate/MeshState.cpp b/libs/hwui/renderstate/MeshState.cpp
index 022faf7d..50c09c8 100644
--- a/libs/hwui/renderstate/MeshState.cpp
+++ b/libs/hwui/renderstate/MeshState.cpp
@@ -29,11 +29,11 @@
         , mCurrentTexCoordsStride(0)
         , mTexCoordsArrayEnabled(false) {
 
-    glGenBuffers(1, &meshBuffer);
-    glBindBuffer(GL_ARRAY_BUFFER, meshBuffer);
-    glBufferData(GL_ARRAY_BUFFER, sizeof(kMeshVertices), kMeshVertices, GL_STATIC_DRAW);
+    glGenBuffers(1, &mUnitQuadBuffer);
+    glBindBuffer(GL_ARRAY_BUFFER, mUnitQuadBuffer);
+    glBufferData(GL_ARRAY_BUFFER, sizeof(kUnitQuadVertices), kUnitQuadVertices, GL_STATIC_DRAW);
 
-    mCurrentBuffer = meshBuffer;
+    mCurrentBuffer = mUnitQuadBuffer;
     mCurrentIndicesBuffer = 0;
     mCurrentPixelBuffer = 0;
 
@@ -45,7 +45,7 @@
 }
 
 MeshState::~MeshState() {
-    glDeleteBuffers(1, &meshBuffer);
+    glDeleteBuffers(1, &mUnitQuadBuffer);
     mCurrentBuffer = 0;
 
     glDeleteBuffers(1, &mQuadListIndices);
@@ -60,11 +60,11 @@
 ///////////////////////////////////////////////////////////////////////////////
 
 bool MeshState::bindMeshBuffer() {
-    return bindMeshBuffer(meshBuffer);
+    return bindMeshBuffer(mUnitQuadBuffer);
 }
 
 bool MeshState::bindMeshBuffer(GLuint buffer) {
-    if (!buffer) buffer = meshBuffer;
+    if (!buffer) buffer = mUnitQuadBuffer;
     if (mCurrentBuffer != buffer) {
         glBindBuffer(GL_ARRAY_BUFFER, buffer);
         mCurrentBuffer = buffer;
diff --git a/libs/hwui/renderstate/MeshState.h b/libs/hwui/renderstate/MeshState.h
index 9b1021d..5cb1143 100644
--- a/libs/hwui/renderstate/MeshState.h
+++ b/libs/hwui/renderstate/MeshState.h
@@ -32,7 +32,7 @@
 
 // This array is never used directly but used as a memcpy source in the
 // OpenGLRenderer constructor
-const TextureVertex kMeshVertices[] = {
+const TextureVertex kUnitQuadVertices[] = {
         { 0, 0, 0, 0 },
         { 1, 0, 1, 0 },
         { 0, 1, 0, 1 },
@@ -110,12 +110,16 @@
     bool bindShadowIndicesBuffer();
     bool unbindIndicesBuffer();
 
+    ///////////////////////////////////////////////////////////////////////////////
+    // Getters - for use in Glop building
+    ///////////////////////////////////////////////////////////////////////////////
+    GLuint getUnitQuadVBO() { return mUnitQuadBuffer; }
 private:
     MeshState();
     bool bindIndicesBufferInternal(const GLuint buffer);
 
     // VBO to draw with
-    GLuint meshBuffer;
+    GLuint mUnitQuadBuffer;
 
     GLuint mCurrentBuffer;
     GLuint mCurrentIndicesBuffer;
diff --git a/libs/hwui/renderstate/RenderState.cpp b/libs/hwui/renderstate/RenderState.cpp
index 8eda7c9..d3f6277 100644
--- a/libs/hwui/renderstate/RenderState.cpp
+++ b/libs/hwui/renderstate/RenderState.cpp
@@ -217,7 +217,7 @@
     mCaches->setProgram(shader.program);
 
     Glop::Fill::Color color = shader.color;
-    shader.program->setColor(color.a, color.r, color.g, color.b);
+    shader.program->setColor(color.r, color.g, color.b, color.a);
 
     shader.program->set(glop.transform.ortho,
             glop.transform.modelView,
@@ -259,14 +259,13 @@
     meshState().bindIndicesBufferInternal(mesh.indexBufferObject);
 
     // ---------- GL state setup ----------
+    blend().setFactors(glop.blend.src, glop.blend.dst);
 
-    if (glop.blend.mode != Glop::Blend::kDisable) {
-        blend().enable(glop.blend.mode, glop.blend.swapSrcDst);
+    if (mesh.indexBufferObject) {
+        glDrawElements(glop.mesh.primitiveMode, glop.mesh.vertexCount, GL_UNSIGNED_BYTE, nullptr);
     } else {
-        blend().disable();
+        glDrawArrays(GL_TRIANGLE_STRIP, 0, glop.mesh.vertexCount);
     }
-
-    glDrawElements(glop.mesh.primitiveMode, glop.mesh.vertexCount, GL_UNSIGNED_BYTE, nullptr);
 }
 
 } /* namespace uirenderer */
diff --git a/libs/hwui/tests/main.cpp b/libs/hwui/tests/main.cpp
index a12dac78..1e7ba23 100644
--- a/libs/hwui/tests/main.cpp
+++ b/libs/hwui/tests/main.cpp
@@ -42,7 +42,8 @@
 
 static DisplayListRenderer* startRecording(RenderNode* node) {
     DisplayListRenderer* renderer = new DisplayListRenderer();
-    renderer->setViewport(node->getWidth(), node->getHeight());
+    renderer->setViewport(node->stagingProperties().getWidth(),
+            node->stagingProperties().getHeight());
     renderer->prepare();
     return renderer;
 }
@@ -53,80 +54,174 @@
     delete renderer;
 }
 
-sp<RenderNode> createCard(int x, int y, int width, int height) {
-    sp<RenderNode> node = new RenderNode();
-    node->mutateStagingProperties().setLeftTopRightBottom(x, y, x + width, y + height);
-    node->mutateStagingProperties().setElevation(dp(16));
-    node->mutateStagingProperties().mutableOutline().setRoundRect(0, 0, width, height, dp(10), 1);
-    node->mutateStagingProperties().mutableOutline().setShouldClip(true);
-    node->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y | RenderNode::Z);
+class TreeContentAnimation {
+public:
+    virtual ~TreeContentAnimation() {}
+    virtual int getFrameCount() { return 150; }
+    virtual void createContent(int width, int height, DisplayListRenderer* renderer) = 0;
+    virtual void doFrame(int frameNr) = 0;
 
-    DisplayListRenderer* renderer = startRecording(node.get());
-    renderer->drawColor(0xFFEEEEEE, SkXfermode::kSrcOver_Mode);
-    endRecording(renderer, node.get());
+    template <class T>
+    static void run() {
+        T animation;
 
-    return node;
-}
+        TestContext testContext;
 
-int main(int argc, char* argv[]) {
-    TestContext testContext;
+        // create the native surface
+        const int width = gDisplay.w;
+        const int height = gDisplay.h;
+        sp<Surface> surface = testContext.surface();
 
-    // create the native surface
-    const int width = gDisplay.w;
-    const int height = gDisplay.h;
-    sp<Surface> surface = testContext.surface();
+        RenderNode* rootNode = new RenderNode();
+        rootNode->incStrong(nullptr);
+        rootNode->mutateStagingProperties().setLeftTopRightBottom(0, 0, width, height);
+        rootNode->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y);
+        rootNode->mutateStagingProperties().setClipToBounds(false);
+        rootNode->setPropertyFieldsDirty(RenderNode::GENERIC);
 
-    RenderNode* rootNode = new RenderNode();
-    rootNode->incStrong(nullptr);
-    rootNode->mutateStagingProperties().setLeftTopRightBottom(0, 0, width, height);
-    rootNode->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y);
-    rootNode->mutateStagingProperties().setClipToBounds(false);
-    rootNode->setPropertyFieldsDirty(RenderNode::GENERIC);
+        ContextFactory factory;
+        std::unique_ptr<RenderProxy> proxy(new RenderProxy(false, rootNode, &factory));
+        proxy->loadSystemProperties();
+        proxy->initialize(surface);
+        float lightX = width / 2.0;
+        proxy->setup(width, height, (Vector3){lightX, dp(-200.0f), dp(800.0f)},
+                dp(800.0f), 255 * 0.075, 255 * 0.15);
 
-    ContextFactory factory;
-    std::unique_ptr<RenderProxy> proxy(new RenderProxy(false, rootNode, &factory));
-    proxy->loadSystemProperties();
-    proxy->initialize(surface);
-    float lightX = width / 2.0;
-    proxy->setup(width, height, (Vector3){lightX, dp(-200.0f), dp(800.0f)},
-            dp(800.0f), 255 * 0.075, 255 * 0.15);
+        android::uirenderer::Rect DUMMY;
 
-    android::uirenderer::Rect DUMMY;
+        std::vector< sp<RenderNode> > cards;
 
-    std::vector< sp<RenderNode> > cards;
+        DisplayListRenderer* renderer = startRecording(rootNode);
+        animation.createContent(width, height, renderer);
+        endRecording(renderer, rootNode);
 
-    DisplayListRenderer* renderer = startRecording(rootNode);
-    renderer->drawColor(0xFFFFFFFF, SkXfermode::kSrcOver_Mode);
-    renderer->insertReorderBarrier(true);
+        for (int i = 0; i < 150; i++) {
+            testContext.waitForVsync();
 
-    for (int x = dp(16); x < (width - dp(116)); x += dp(116)) {
-        for (int y = dp(16); y < (height - dp(116)); y += dp(116)) {
-            sp<RenderNode> card = createCard(x, y, dp(100), dp(100));
-            renderer->drawRenderNode(card.get(), DUMMY, 0);
-            cards.push_back(card);
+            ATRACE_NAME("UI-Draw Frame");
+            animation.doFrame(i);
+            nsecs_t frameTimeNs = systemTime(CLOCK_MONOTONIC);
+            proxy->syncAndDrawFrame(frameTimeNs, 0, gDisplay.density);
         }
+
+        sleep(5);
+
+        rootNode->decStrong(nullptr);
     }
+};
 
-    renderer->insertReorderBarrier(false);
-    endRecording(renderer, rootNode);
+class ShadowGridAnimation : public TreeContentAnimation {
+public:
+    std::vector< sp<RenderNode> > cards;
+    void createContent(int width, int height, DisplayListRenderer* renderer) override {
+        android::uirenderer::Rect DUMMY;
 
-    for (int i = 0; i < 150; i++) {
-        testContext.waitForVsync();
+        renderer->drawColor(0xFFFFFFFF, SkXfermode::kSrcOver_Mode);
+        renderer->insertReorderBarrier(true);
 
-        ATRACE_NAME("UI-Draw Frame");
+        for (int x = dp(16); x < (width - dp(116)); x += dp(116)) {
+            for (int y = dp(16); y < (height - dp(116)); y += dp(116)) {
+                sp<RenderNode> card = createCard(x, y, dp(100), dp(100));
+                renderer->drawRenderNode(card.get(), DUMMY, 0);
+                cards.push_back(card);
+            }
+        }
+
+        renderer->insertReorderBarrier(false);
+    }
+    void doFrame(int frameNr) override {
         for (size_t ci = 0; ci < cards.size(); ci++) {
-            cards[ci]->mutateStagingProperties().setTranslationX(i);
-            cards[ci]->mutateStagingProperties().setTranslationY(i);
+            cards[ci]->mutateStagingProperties().setTranslationX(frameNr);
+            cards[ci]->mutateStagingProperties().setTranslationY(frameNr);
             cards[ci]->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y);
         }
-        nsecs_t frameTimeNs = systemTime(CLOCK_MONOTONIC);
-        proxy->syncAndDrawFrame(frameTimeNs, 0, gDisplay.density);
     }
+private:
+    sp<RenderNode> createCard(int x, int y, int width, int height) {
+        sp<RenderNode> node = new RenderNode();
+        node->mutateStagingProperties().setLeftTopRightBottom(x, y, x + width, y + height);
+        node->mutateStagingProperties().setElevation(dp(16));
+        node->mutateStagingProperties().mutableOutline().setRoundRect(0, 0, width, height, dp(10), 1);
+        node->mutateStagingProperties().mutableOutline().setShouldClip(true);
+        node->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y | RenderNode::Z);
 
-    sleep(5);
+        DisplayListRenderer* renderer = startRecording(node.get());
+        renderer->drawColor(0xFFEEEEEE, SkXfermode::kSrcOver_Mode);
+        endRecording(renderer, node.get());
+        return node;
+    }
+};
 
-    rootNode->decStrong(nullptr);
+class RectGridAnimation : public TreeContentAnimation {
+public:
+    sp<RenderNode> card;
+    void createContent(int width, int height, DisplayListRenderer* renderer) override {
+        android::uirenderer::Rect DUMMY;
 
+        renderer->drawColor(0xFFFFFFFF, SkXfermode::kSrcOver_Mode);
+        renderer->insertReorderBarrier(true);
+
+        card = createCard(40, 40, 200, 200);
+        renderer->drawRenderNode(card.get(), DUMMY, 0);
+
+        renderer->insertReorderBarrier(false);
+    }
+    void doFrame(int frameNr) override {
+        card->mutateStagingProperties().setTranslationX(frameNr);
+        card->mutateStagingProperties().setTranslationY(frameNr);
+        card->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y);
+    }
+private:
+    sp<RenderNode> createCard(int x, int y, int width, int height) {
+        sp<RenderNode> node = new RenderNode();
+        node->mutateStagingProperties().setLeftTopRightBottom(x, y, x + width, y + height);
+        node->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y);
+
+        DisplayListRenderer* renderer = startRecording(node.get());
+        renderer->drawColor(0xFFFF00FF, SkXfermode::kSrcOver_Mode);
+
+        float rects[width * height];
+        int index = 0;
+        for (int xOffset = 0; xOffset < width; xOffset+=2) {
+            for (int yOffset = 0; yOffset < height; yOffset+=2) {
+                rects[index++] = xOffset;
+                rects[index++] = yOffset;
+                rects[index++] = xOffset + 1;
+                rects[index++] = yOffset + 1;
+            }
+        }
+        int count = width * height;
+
+        SkPaint paint;
+        paint.setColor(0xff00ffff);
+        renderer->drawRects(rects, count, &paint);
+
+        endRecording(renderer, node.get());
+        return node;
+    }
+};
+
+struct cstr_cmp {
+    bool operator()(const char *a, const char *b) const {
+        return std::strcmp(a, b) < 0;
+    }
+};
+
+typedef void (*testProc)();
+
+std::map<const char*, testProc, cstr_cmp> gTestMap {
+    {"shadowgrid", TreeContentAnimation::run<ShadowGridAnimation>},
+    {"rectgrid", TreeContentAnimation::run<RectGridAnimation> },
+};
+
+int main(int argc, char* argv[]) {
+    const char* testName = argc > 1 ? argv[1] : "shadowgrid";
+    testProc proc = gTestMap[testName];
+    if(!proc) {
+        printf("Error: couldn't find test %s\n", testName);
+        return 1;
+    }
+    proc();
     printf("Success!\n");
     return 0;
 }
diff --git a/libs/hwui/utils/PaintUtils.h b/libs/hwui/utils/PaintUtils.h
index fa0ae03..2091705 100644
--- a/libs/hwui/utils/PaintUtils.h
+++ b/libs/hwui/utils/PaintUtils.h
@@ -16,6 +16,9 @@
 #ifndef PAINT_UTILS_H
 #define PAINT_UTILS_H
 
+#include <SkColorFilter.h>
+#include <SkXfermode.h>
+
 namespace android {
 namespace uirenderer {