Glop ColorFilter & VertexBuffer support, initial enable

Enables Glop rendering for supported Rects and VertexBuffers
Also removes unused Query object

Change-Id: Ibe227bc362685a153159f75077664f0947764e06
diff --git a/libs/hwui/AmbientShadow.cpp b/libs/hwui/AmbientShadow.cpp
index 6ec42c2..0a210d6 100644
--- a/libs/hwui/AmbientShadow.cpp
+++ b/libs/hwui/AmbientShadow.cpp
@@ -179,7 +179,7 @@
 void AmbientShadow::createAmbientShadow(bool isCasterOpaque,
         const Vector3* casterVertices, int casterVertexCount, const Vector3& centroid3d,
         float heightFactor, float geomFactor, VertexBuffer& shadowVertexBuffer) {
-    shadowVertexBuffer.setMode(VertexBuffer::kIndices);
+    shadowVertexBuffer.setMeshFeatureFlags(VertexBuffer::kAlpha | VertexBuffer::kIndices);
 
     // In order to computer the outer vertices in one loop, we need pre-compute
     // the normal by the vertex (n - 1) to vertex 0, and the spike and alpha value
diff --git a/libs/hwui/Caches.cpp b/libs/hwui/Caches.cpp
index af1b1cd..f4fc068 100644
--- a/libs/hwui/Caches.cpp
+++ b/libs/hwui/Caches.cpp
@@ -48,10 +48,11 @@
 ///////////////////////////////////////////////////////////////////////////////
 
 Caches::Caches(RenderState& renderState)
-        : patchCache(renderState)
+        : gradientCache(mExtensions)
+        , patchCache(renderState)
+        , programCache(mExtensions)
         , dither(*this)
         , mRenderState(&renderState)
-        , mExtensions(Extensions::getInstance())
         , mInitialized(false) {
     INIT_LOGD("Creating OpenGL renderer caches");
     init();
@@ -187,9 +188,9 @@
         INIT_LOGD("  Draw reorder enabled");
     }
 
-    return (prevDebugLayersUpdates != debugLayersUpdates) ||
-            (prevDebugOverdraw != debugOverdraw) ||
-            (prevDebugStencilClip != debugStencilClip);
+    return (prevDebugLayersUpdates != debugLayersUpdates)
+            || (prevDebugOverdraw != debugOverdraw)
+            || (prevDebugStencilClip != debugStencilClip);
 }
 
 void Caches::terminate() {
diff --git a/libs/hwui/Caches.h b/libs/hwui/Caches.h
index 16e2058..18bb5e6 100644
--- a/libs/hwui/Caches.h
+++ b/libs/hwui/Caches.h
@@ -178,14 +178,18 @@
         kStencilShowRegion
     };
     StencilClipDebug debugStencilClip;
-
+private:
+    // Declared before gradientCache and programCache which need this to initialize.
+    // TODO: cleanup / move elsewhere
+    Extensions mExtensions;
+public:
     TextureCache textureCache;
     LayerCache layerCache;
     RenderBufferCache renderBufferCache;
     GradientCache gradientCache;
-    ProgramCache programCache;
-    PathCache pathCache;
     PatchCache patchCache;
+    PathCache pathCache;
+    ProgramCache programCache;
     TessellationCache tessellationCache;
     TextDropShadowCache dropShadowCache;
     FboCache fboCache;
@@ -220,6 +224,7 @@
     void setProgram(const ProgramDescription& description);
     void setProgram(Program* program);
 
+    Extensions& extensions() { return mExtensions; }
     Program& program() { return *mProgram; }
     PixelBufferState& pixelBufferState() { return *mPixelBufferState; }
     TextureState& textureState() { return *mTextureState; }
@@ -248,7 +253,6 @@
     }
 
     RenderState* mRenderState;
-    Extensions& mExtensions;
 
     // Used to render layers
     std::unique_ptr<TextureVertex[]> mRegionMesh;
diff --git a/libs/hwui/Dither.cpp b/libs/hwui/Dither.cpp
index 359c193..1ba6511 100644
--- a/libs/hwui/Dither.cpp
+++ b/libs/hwui/Dither.cpp
@@ -32,8 +32,6 @@
 
 void Dither::bindDitherTexture() {
     if (!mInitialized) {
-        bool useFloatTexture = Extensions::getInstance().hasFloatTextures();
-
         glGenTextures(1, &mDitherTexture);
         mCaches.textureState().bindTexture(mDitherTexture);
 
@@ -43,7 +41,7 @@
         glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
         glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
 
-        if (useFloatTexture) {
+        if (mCaches.extensions().hasFloatTextures()) {
             // We use a R16F texture, let's remap the alpha channel to the
             // red channel to avoid changing the shader sampling code on GL ES 3.0+
             glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_A, GL_RED);
diff --git a/libs/hwui/Dither.h b/libs/hwui/Dither.h
index facd1ea..b589b80 100644
--- a/libs/hwui/Dither.h
+++ b/libs/hwui/Dither.h
@@ -23,6 +23,7 @@
 namespace uirenderer {
 
 class Caches;
+class Extensions;
 class Program;
 
 // Must be a power of two
diff --git a/libs/hwui/Extensions.cpp b/libs/hwui/Extensions.cpp
index 8352c3f..c68822b 100644
--- a/libs/hwui/Extensions.cpp
+++ b/libs/hwui/Extensions.cpp
@@ -16,18 +16,16 @@
 
 #define LOG_TAG "OpenGLRenderer"
 
-#include <GLES2/gl2.h>
-#include <GLES2/gl2ext.h>
+#include "Extensions.h"
+
+#include "Debug.h"
+#include "Properties.h"
 
 #include <EGL/egl.h>
 #include <EGL/eglext.h>
-
+#include <GLES2/gl2ext.h>
 #include <utils/Log.h>
 
-#include "Debug.h"
-#include "Extensions.h"
-#include "Properties.h"
-
 namespace android {
 
 using namespace uirenderer;
@@ -50,7 +48,7 @@
 // Constructors
 ///////////////////////////////////////////////////////////////////////////////
 
-Extensions::Extensions(): Singleton<Extensions>() {
+Extensions::Extensions() {
     // Query GL extensions
     findExtensions((const char*) glGetString(GL_EXTENSIONS), mGlExtensionList);
     mHasNPot = hasGlExtension("GL_OES_texture_npot");
@@ -93,9 +91,6 @@
     }
 }
 
-Extensions::~Extensions() {
-}
-
 ///////////////////////////////////////////////////////////////////////////////
 // Methods
 ///////////////////////////////////////////////////////////////////////////////
diff --git a/libs/hwui/Extensions.h b/libs/hwui/Extensions.h
index 25d4c5e..731001a 100644
--- a/libs/hwui/Extensions.h
+++ b/libs/hwui/Extensions.h
@@ -23,6 +23,8 @@
 #include <utils/SortedVector.h>
 #include <utils/String8.h>
 
+#include <GLES2/gl2.h>
+
 namespace android {
 namespace uirenderer {
 
@@ -30,8 +32,10 @@
 // Classes
 ///////////////////////////////////////////////////////////////////////////////
 
-class ANDROID_API Extensions: public Singleton<Extensions> {
+class ANDROID_API Extensions {
 public:
+    Extensions();
+
     inline bool hasNPot() const { return mHasNPot; }
     inline bool hasFramebufferFetch() const { return mHasFramebufferFetch; }
     inline bool hasDiscardFramebuffer() const { return mHasDiscardFramebuffer; }
@@ -55,13 +59,8 @@
     void dump() const;
 
 private:
-    Extensions();
-    ~Extensions();
-
     void findExtensions(const char* extensions, SortedVector<String8>& list) const;
 
-    friend class Singleton<Extensions>;
-
     SortedVector<String8> mGlExtensionList;
     SortedVector<String8> mEglExtensionList;
 
diff --git a/libs/hwui/Glop.h b/libs/hwui/Glop.h
index bbeb19e..9150869 100644
--- a/libs/hwui/Glop.h
+++ b/libs/hwui/Glop.h
@@ -38,11 +38,13 @@
  */
 enum VertexAttribFlags {
     // NOTE: position attribute always enabled
+    kNone_Attrib = 0,
     kTextureCoord_Attrib = 1 << 0,
     kColor_Attrib = 1 << 1,
     kAlpha_Attrib = 1 << 2,
 };
 
+
 /**
  * Structure containing all data required to issue a single OpenGL draw
  *
@@ -53,40 +55,53 @@
  */
 // TODO: PREVENT_COPY_AND_ASSIGN(...) or similar
 struct Glop {
+    struct FloatColor {
+        float a, r, g, b;
+    };
+
     Rect bounds;
 
+    /*
+     * Stores mesh - vertex and index data.
+     *
+     * buffer objects and void*s are mutually exclusive
+     * indices are optional
+     */
     struct Mesh {
         VertexAttribFlags vertexFlags;
         GLuint primitiveMode; // GL_TRIANGLES and GL_TRIANGLE_STRIP supported
         GLuint vertexBufferObject;
         GLuint indexBufferObject;
+        const void* vertices;
+        const void* indices;
         int vertexCount;
         GLsizei stride;
     } mesh;
 
     struct Fill {
         Program* program;
-
-        struct Color {
-            float a, r, g, b;
-        } color;
+        FloatColor color;
 
         /* TODO
         union shader {
             //...
         }; TODO
-        union filter {
-            //color
-            //matrix + vector
-        };
         */
+        ProgramDescription::ColorFilterMode filterMode;
+        union Filter {
+            struct Matrix {
+                float matrix[16];
+                float vector[4];
+            } matrix;
+            FloatColor color;
+        } filter;
     } fill;
 
     struct Transform {
         Matrix4 ortho; // TODO: out of op, since this is static per FBO
         Matrix4 modelView;
         Matrix4 canvas;
-        bool offset;
+        bool fudgingOffset;
     } transform;
 
     struct Blend {
diff --git a/libs/hwui/GlopBuilder.cpp b/libs/hwui/GlopBuilder.cpp
index dafe087..e22af40 100644
--- a/libs/hwui/GlopBuilder.cpp
+++ b/libs/hwui/GlopBuilder.cpp
@@ -18,10 +18,12 @@
 #include "Caches.h"
 #include "Glop.h"
 #include "Matrix.h"
-#include "Texture.h"
 #include "renderstate/MeshState.h"
 #include "renderstate/RenderState.h"
+#include "SkiaShader.h"
+#include "Texture.h"
 #include "utils/PaintUtils.h"
+#include "VertexBuffer.h"
 
 #include <GLES2/gl2.h>
 #include <SkPaint.h>
@@ -29,42 +31,82 @@
 namespace android {
 namespace uirenderer {
 
+#define TRIGGER_STAGE(stageFlag) \
+    LOG_ALWAYS_FATAL_IF(stageFlag & mStageFlags, "Stage %d cannot be run twice"); \
+    mStageFlags = static_cast<StageFlags>(mStageFlags | stageFlag)
+
 GlopBuilder::GlopBuilder(RenderState& renderState, Caches& caches, Glop* outGlop)
         : mRenderState(renderState)
         , mCaches(caches)
         , mOutGlop(outGlop){
+    mStageFlags = kInitialStage;
+}
+
+GlopBuilder& GlopBuilder::setMeshVertexBuffer(const VertexBuffer& vertexBuffer, bool shadowInterp) {
+    TRIGGER_STAGE(kMeshStage);
+
+    const VertexBuffer::MeshFeatureFlags flags = vertexBuffer.getMeshFeatureFlags();
+
+    bool alphaVertex = flags & VertexBuffer::kAlpha;
+    bool indices = flags & VertexBuffer::kIndices;
+    mOutGlop->mesh.vertexFlags = alphaVertex ? kAlpha_Attrib : kNone_Attrib;
+    mOutGlop->mesh.primitiveMode = GL_TRIANGLE_STRIP;
+    mOutGlop->mesh.vertexBufferObject = 0;
+    mOutGlop->mesh.vertices = vertexBuffer.getBuffer();
+    mOutGlop->mesh.indexBufferObject = 0;
+    mOutGlop->mesh.indices = vertexBuffer.getIndices();
+    mOutGlop->mesh.vertexCount = indices
+            ? vertexBuffer.getIndexCount() : vertexBuffer.getVertexCount();
+    mOutGlop->mesh.stride = alphaVertex ? kAlphaVertexStride : kVertexStride;
+
+    mDescription.hasVertexAlpha = alphaVertex;
+    mDescription.useShadowAlphaInterp = shadowInterp;
+    return *this;
 }
 
 GlopBuilder& GlopBuilder::setMeshUnitQuad() {
-    mOutGlop->mesh.vertexFlags = static_cast<VertexAttribFlags>(0);
+    TRIGGER_STAGE(kMeshStage);
+
+    mOutGlop->mesh.vertexFlags = kNone_Attrib;
     mOutGlop->mesh.primitiveMode = GL_TRIANGLE_STRIP;
     mOutGlop->mesh.vertexBufferObject = mRenderState.meshState().getUnitQuadVBO();
+    mOutGlop->mesh.vertices = nullptr;
     mOutGlop->mesh.indexBufferObject = 0;
+    mOutGlop->mesh.indices = nullptr;
     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) {
+GlopBuilder& GlopBuilder::setTransform(const Matrix4& ortho,
+        const Matrix4& transform, bool fudgingOffset) {
+    TRIGGER_STAGE(kTransformStage);
+
     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.fudgingOffset = fudgingOffset;
+    return *this;
+}
 
-    mOutGlop->transform.offset = offset;
+GlopBuilder& GlopBuilder::setModelViewMapUnitToRect(const Rect destination) {
+    TRIGGER_STAGE(kModelViewStage);
+    mOutGlop->transform.modelView.loadTranslate(destination.left, destination.top, 0.0f);
+    mOutGlop->transform.modelView.scale(destination.getWidth(), destination.getHeight(), 1.0f);
+    mOutGlop->bounds = destination;
+    return *this;
+}
 
-    mOutGlop->bounds.set(left, top, right, bottom);
-    mOutGlop->transform.canvas.mapRect(mOutGlop->bounds);
+GlopBuilder& GlopBuilder::setModelViewOffsetRect(float offsetX, float offsetY, const Rect source) {
+    TRIGGER_STAGE(kModelViewStage);
+    mOutGlop->transform.modelView.loadTranslate(offsetX, offsetY, 0.0f);
+    mOutGlop->bounds = source;
+    mOutGlop->bounds.translate(offsetX, offsetY);
     return *this;
 }
 
 GlopBuilder& GlopBuilder::setPaint(const SkPaint* paint, float alphaScale) {
+    TRIGGER_STAGE(kFillStage);
+
     // TODO: support null paint
     const SkShader* shader = paint->getShader();
     const SkColorFilter* colorFilter = paint->getColorFilter();
@@ -73,25 +115,25 @@
     if (mode != SkXfermode::kClear_Mode) {
         int color = paint->getColor();
         float alpha = (SkColorGetA(color) / 255.0f) * alphaScale;
-        if (shader) {
-            // shader discards color channels
-            color |= 0x00FFFFFF;
+        if (!shader) {
+            float colorScale = alpha / 255.0f;
+            mOutGlop->fill.color = {
+                    alpha,
+                    colorScale * SkColorGetR(color),
+                    colorScale * SkColorGetG(color),
+                    colorScale * SkColorGetB(color)
+            };
+        } else {
+            mOutGlop->fill.color = { alpha, 1, 1, 1 };
         }
-        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};
+    mOutGlop->blend = { GL_ZERO, GL_ZERO };
     if (mOutGlop->fill.color.a < 1.0f
-            || (shader && !shader->isOpaque())
+            || PaintUtils::isBlendedShader(shader)
             || PaintUtils::isBlendedColorFilter(colorFilter)
             || mode != SkXfermode::kSrcOver_Mode) {
         if (CC_LIKELY(mode <= SkXfermode::kScreen_Mode)) {
@@ -103,7 +145,7 @@
             // 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)) {
+            if (CC_UNLIKELY(mCaches.extensions().hasFramebufferFetch())) {
                 mDescription.framebufferMode = mode;
                 mDescription.swapSrcDst = SWAP_SRC_DST;
                 // blending in shader, don't enable
@@ -115,17 +157,58 @@
         }
     }
 
-    return *this;
-}
+    if (shader) {
+        SkiaShader::describe(&mCaches, mDescription, mCaches.extensions(), *shader);
+        // TODO: store shader data
+        LOG_ALWAYS_FATAL("shaders not yet supported");
+    }
 
-GlopBuilder& GlopBuilder::setTexture(Texture* texture) {
-    LOG_ALWAYS_FATAL("not yet supported");
+    if (colorFilter) {
+        SkColor color;
+        SkXfermode::Mode mode;
+        SkScalar srcColorMatrix[20];
+        if (colorFilter->asColorMode(&color, &mode)) {
+            mOutGlop->fill.filterMode = mDescription.colorOp = ProgramDescription::kColorBlend;
+            mDescription.colorMode = mode;
+
+            const float alpha = SkColorGetA(color) / 255.0f;
+            float colorScale = alpha / 255.0f;
+            mOutGlop->fill.filter.color = {
+                    alpha,
+                    colorScale * SkColorGetR(color),
+                    colorScale * SkColorGetG(color),
+                    colorScale * SkColorGetB(color),
+            };
+        } else if (colorFilter->asColorMatrix(srcColorMatrix)) {
+            mOutGlop->fill.filterMode = mDescription.colorOp = ProgramDescription::kColorMatrix;
+
+            float* colorMatrix = mOutGlop->fill.filter.matrix.matrix;
+            memcpy(colorMatrix, srcColorMatrix, 4 * sizeof(float));
+            memcpy(&colorMatrix[4], &srcColorMatrix[5], 4 * sizeof(float));
+            memcpy(&colorMatrix[8], &srcColorMatrix[10], 4 * sizeof(float));
+            memcpy(&colorMatrix[12], &srcColorMatrix[15], 4 * sizeof(float));
+
+            // Skia uses the range [0..255] for the addition vector, but we need
+            // the [0..1] range to apply the vector in GLSL
+            float* colorVector = mOutGlop->fill.filter.matrix.vector;
+            colorVector[0] = srcColorMatrix[4] / 255.0f;
+            colorVector[1] = srcColorMatrix[9] / 255.0f;
+            colorVector[2] = srcColorMatrix[14] / 255.0f;
+            colorVector[3] = srcColorMatrix[19] / 255.0f;
+        }
+    } else {
+        mOutGlop->fill.filterMode = ProgramDescription::kColorNone;
+    }
+
     return *this;
 }
 
 void GlopBuilder::build() {
+    LOG_ALWAYS_FATAL_IF(mStageFlags != kAllStages, "glop not fully prepared!");
+
     mDescription.modulate = mOutGlop->fill.color.a < 1.0f;
     mOutGlop->fill.program = mCaches.programCache.get(mDescription);
+    mOutGlop->transform.canvas.mapRect(mOutGlop->bounds);
 }
 
 } /* namespace uirenderer */
diff --git a/libs/hwui/GlopBuilder.h b/libs/hwui/GlopBuilder.h
index d243d76..c7464cd 100644
--- a/libs/hwui/GlopBuilder.h
+++ b/libs/hwui/GlopBuilder.h
@@ -27,22 +27,35 @@
 
 class Caches;
 struct Glop;
+class Matrix4;
 class RenderState;
 class Texture;
-class Matrix4;
+class VertexBuffer;
 
 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& setMeshVertexBuffer(const VertexBuffer& vertexBuffer, bool shadowInterp);
+
+    GlopBuilder& setTransform(const Matrix4& ortho, const Matrix4& transform, bool fudgingOffset);
+
+    GlopBuilder& setModelViewMapUnitToRect(const Rect destination);
+    GlopBuilder& setModelViewOffsetRect(float offsetX, float offsetY, const Rect source);
+
     GlopBuilder& setPaint(const SkPaint* paint, float alphaScale);
-    GlopBuilder& setTexture(Texture* texture);
     void build();
 private:
+    enum StageFlags {
+        kInitialStage = 0,
+        kMeshStage = 1 << 0,
+        kTransformStage = 1 << 1,
+        kModelViewStage = 1 << 2,
+        kFillStage = 1 << 3,
+        kAllStages = kMeshStage | kTransformStage | kModelViewStage | kFillStage,
+    } mStageFlags;
+
     ProgramDescription mDescription;
     RenderState& mRenderState;
     Caches& mCaches;
diff --git a/libs/hwui/GradientCache.cpp b/libs/hwui/GradientCache.cpp
index 416b0b3..fb4c785 100644
--- a/libs/hwui/GradientCache.cpp
+++ b/libs/hwui/GradientCache.cpp
@@ -62,9 +62,12 @@
 // Constructors/destructor
 ///////////////////////////////////////////////////////////////////////////////
 
-GradientCache::GradientCache():
-        mCache(LruCache<GradientCacheEntry, Texture*>::kUnlimitedCapacity),
-        mSize(0), mMaxSize(MB(DEFAULT_GRADIENT_CACHE_SIZE)) {
+GradientCache::GradientCache(Extensions& extensions)
+        : mCache(LruCache<GradientCacheEntry, Texture*>::kUnlimitedCapacity)
+        , mSize(0)
+        , mMaxSize(MB(DEFAULT_GRADIENT_CACHE_SIZE))
+        , mUseFloatTexture(extensions.hasFloatTextures())
+        , mHasNpot(extensions.hasNPot()){
     char property[PROPERTY_VALUE_MAX];
     if (property_get(PROPERTY_GRADIENT_CACHE_SIZE, property, nullptr) > 0) {
         INIT_LOGD("  Setting gradient cache size to %sMB", property);
@@ -76,16 +79,6 @@
     glGetIntegerv(GL_MAX_TEXTURE_SIZE, &mMaxTextureSize);
 
     mCache.setOnEntryRemovedListener(this);
-
-    const Extensions& extensions = Extensions::getInstance();
-    mUseFloatTexture = extensions.hasFloatTextures();
-    mHasNpot = extensions.hasNPot();
-}
-
-GradientCache::GradientCache(uint32_t maxByteSize):
-        mCache(LruCache<GradientCacheEntry, Texture*>::kUnlimitedCapacity),
-        mSize(0), mMaxSize(maxByteSize) {
-    mCache.setOnEntryRemovedListener(this);
 }
 
 GradientCache::~GradientCache() {
diff --git a/libs/hwui/GradientCache.h b/libs/hwui/GradientCache.h
index 1714e6d..08319ea 100644
--- a/libs/hwui/GradientCache.h
+++ b/libs/hwui/GradientCache.h
@@ -104,8 +104,7 @@
  */
 class GradientCache: public OnEntryRemoved<GradientCacheEntry, Texture*> {
 public:
-    GradientCache();
-    GradientCache(uint32_t maxByteSize);
+    GradientCache(Extensions& extensions);
     ~GradientCache();
 
     /**
diff --git a/libs/hwui/LayerRenderer.cpp b/libs/hwui/LayerRenderer.cpp
index d2f9a94..b4b14e8 100644
--- a/libs/hwui/LayerRenderer.cpp
+++ b/libs/hwui/LayerRenderer.cpp
@@ -337,7 +337,7 @@
     if (fbo) {
         // If possible, discard any enqueud operations on deferred
         // rendering architectures
-        if (Extensions::getInstance().hasDiscardFramebuffer()) {
+        if (Caches::getInstance().extensions().hasDiscardFramebuffer()) {
             GLuint previousFbo = renderState.getFramebuffer();
             if (fbo != previousFbo) {
                 renderState.bindFramebuffer(fbo);
diff --git a/libs/hwui/OpenGLRenderer.cpp b/libs/hwui/OpenGLRenderer.cpp
index b10aea3..c4622f6 100644
--- a/libs/hwui/OpenGLRenderer.cpp
+++ b/libs/hwui/OpenGLRenderer.cpp
@@ -89,7 +89,6 @@
 OpenGLRenderer::OpenGLRenderer(RenderState& renderState)
         : mState(*this)
         , mCaches(Caches::getInstance())
-        , mExtensions(Extensions::getInstance())
         , mRenderState(renderState)
         , mFrameStarted(false)
         , mScissorOptimizationDisabled(false)
@@ -195,7 +194,7 @@
     // If we know that we are going to redraw the entire framebuffer,
     // perform a discard to let the driver know we don't need to preserve
     // the back buffer for this frame.
-    if (mExtensions.hasDiscardFramebuffer() &&
+    if (mCaches.extensions().hasDiscardFramebuffer() &&
             left <= 0.0f && top <= 0.0f && right >= mState.getWidth() && bottom >= mState.getHeight()) {
         const bool isFbo = onGetTargetFbo() == 0;
         const GLenum attachments[] = {
@@ -905,7 +904,7 @@
     setupDrawTextureTransformUniforms(layer->getTexTransform());
     setupDrawMesh(&mMeshVertices[0].x, &mMeshVertices[0].u);
 
-    glDrawArrays(GL_TRIANGLE_STRIP, 0, kMeshCount);
+    glDrawArrays(GL_TRIANGLE_STRIP, 0, kUnitQuadCount);
 }
 
 void OpenGLRenderer::composeLayerRect(Layer* layer, const Rect& rect, bool swap) {
@@ -947,7 +946,7 @@
         drawTextureMesh(x, y, x + rect.getWidth(), y + rect.getHeight(),
                 layer->getTexture(), &layerPaint, blend,
                 &mMeshVertices[0].x, &mMeshVertices[0].u,
-                GL_TRIANGLE_STRIP, kMeshCount, swap, swap || simpleTransform);
+                GL_TRIANGLE_STRIP, kUnitQuadCount, swap, swap || simpleTransform);
 
         resetDrawTextureTexCoords(0.0f, 0.0f, 1.0f, 1.0f);
     }
@@ -1387,7 +1386,8 @@
         endTiling();
 
         RenderBuffer* buffer = mCaches.renderBufferCache.get(
-                Stencil::getSmallestStencilFormat(), layer->getWidth(), layer->getHeight());
+                Stencil::getSmallestStencilFormat(),
+                layer->getWidth(), layer->getHeight());
         layer->setStencilRenderBuffer(buffer);
 
         startTiling(layer->clipRect, layer->layer.getHeight());
@@ -1573,6 +1573,18 @@
 #endif
 }
 
+void OpenGLRenderer::renderGlop(const Glop& glop) {
+    if (mState.getDirtyClip()) {
+        if (mRenderState.scissor().isEnabled()) {
+            setScissorFromClip();
+        }
+
+        setStencilFromClip();
+    }
+    mRenderState.render(glop);
+    dirtyLayer(glop.bounds.left, glop.bounds.top, glop.bounds.right, glop.bounds.bottom);
+}
+
 ///////////////////////////////////////////////////////////////////////////////
 // Drawing commands
 ///////////////////////////////////////////////////////////////////////////////
@@ -1601,9 +1613,9 @@
 
     // Enable debug highlight when what we're about to draw is tested against
     // the stencil buffer and if stencil highlight debugging is on
-    mDescription.hasDebugHighlight = !mCaches.debugOverdraw &&
-            mCaches.debugStencilClip == Caches::kStencilShowHighlight &&
-            mRenderState.stencil().isTestEnabled();
+    mDescription.hasDebugHighlight = !mCaches.debugOverdraw
+            && mCaches.debugStencilClip == Caches::kStencilShowHighlight
+            && mRenderState.stencil().isTestEnabled();
 }
 
 void OpenGLRenderer::setupDrawWithTexture(bool isAlpha8) {
@@ -1663,7 +1675,7 @@
 
 void OpenGLRenderer::setupDrawShader(const SkShader* shader) {
     if (shader != nullptr) {
-        SkiaShader::describe(&mCaches, mDescription, mExtensions, *shader);
+        SkiaShader::describe(&mCaches, mDescription, mCaches.extensions(), *shader);
     }
 }
 
@@ -1784,7 +1796,8 @@
         mModelViewMatrix.load(modelViewWithoutTransform);
     }
 
-    SkiaShader::setupProgram(&mCaches, mModelViewMatrix, &mTextureUnit, mExtensions, *shader);
+    SkiaShader::setupProgram(&mCaches, mModelViewMatrix, &mTextureUnit,
+            mCaches.extensions(), *shader);
 }
 
 void OpenGLRenderer::setupDrawColorFilterUniforms(const SkColorFilter* filter) {
@@ -1978,7 +1991,7 @@
     // bitmaps get packed in the atlas
     drawAlpha8TextureMesh(x, y, x + texture->width, y + texture->height, texture->id,
             paint, (GLvoid*) nullptr, (GLvoid*) kMeshTextureOffset,
-            GL_TRIANGLE_STRIP, kMeshCount, ignoreTransform);
+            GL_TRIANGLE_STRIP, kUnitQuadCount, ignoreTransform);
 }
 
 /**
@@ -2132,7 +2145,7 @@
     setupDrawBlending(paint, true);
     setupDrawProgram();
     setupDrawDirtyRegionsDisabled();
-    setupDrawModelView(kModelViewMode_TranslateAndScale, false, 0.0f, 0.0f, 1.0f, 1.0f);
+    setupDrawModelView(kModelViewMode_Translate, false, 0, 0, 0, 0);
     setupDrawTexture(texture->id);
     setupDrawPureColorUniforms();
     setupDrawColorFilterUniforms(getColorFilter(paint));
@@ -2218,12 +2231,12 @@
         drawAlpha8TextureMesh(dstLeft, dstTop, dstRight, dstBottom,
                 texture->id, paint,
                 &mMeshVertices[0].x, &mMeshVertices[0].u,
-                GL_TRIANGLE_STRIP, kMeshCount, ignoreTransform);
+                GL_TRIANGLE_STRIP, kUnitQuadCount, ignoreTransform);
     } else {
         drawTextureMesh(dstLeft, dstTop, dstRight, dstBottom,
                 texture->id, paint, texture->blend,
                 &mMeshVertices[0].x, &mMeshVertices[0].u,
-                GL_TRIANGLE_STRIP, kMeshCount, false, ignoreTransform);
+                GL_TRIANGLE_STRIP, kUnitQuadCount, false, ignoreTransform);
     }
 
     if (CC_UNLIKELY(useScaleTransform)) {
@@ -2333,17 +2346,33 @@
         return;
     }
 
+    if (!paint->getShader() && !currentSnapshot()->roundRectClipState) {
+        Glop glop;
+        GlopBuilder aBuilder(mRenderState, mCaches, &glop);
+        bool fudgeOffset = displayFlags & kVertexBuffer_Offset;
+        bool shadowInterp = displayFlags & kVertexBuffer_ShadowInterp;
+        aBuilder.setMeshVertexBuffer(vertexBuffer, shadowInterp)
+                .setTransform(currentSnapshot()->getOrthoMatrix(), *currentTransform(), fudgeOffset)
+                .setModelViewOffsetRect(translateX, translateY, vertexBuffer.getBounds())
+                .setPaint(paint, currentSnapshot()->alpha)
+                .build();
+        renderGlop(glop);
+        return;
+    }
+
+
+    const VertexBuffer::MeshFeatureFlags meshFeatureFlags = vertexBuffer.getMeshFeatureFlags();
     Rect bounds(vertexBuffer.getBounds());
     bounds.translate(translateX, translateY);
     dirtyLayer(bounds.left, bounds.top, bounds.right, bounds.bottom, *currentTransform());
 
     int color = paint->getColor();
-    bool isAA = paint->isAntiAlias();
+    bool isAA = meshFeatureFlags & VertexBuffer::kAlpha;
 
     setupDraw();
     setupDrawNoTexture();
     if (isAA) setupDrawVertexAlpha((displayFlags & kVertexBuffer_ShadowInterp));
-    setupDrawColor(color, ((color >> 24) & 0xFF) * writableSnapshot()->alpha);
+    setupDrawColor(color, ((color >> 24) & 0xFF) * currentSnapshot()->alpha);
     setupDrawColorFilter(getColorFilter(paint));
     setupDrawShader(getShader(paint));
     setupDrawBlending(paint, isAA);
@@ -2369,22 +2398,13 @@
         glVertexAttribPointer(alphaSlot, 1, GL_FLOAT, GL_FALSE, kAlphaVertexStride, alphaCoords);
     }
 
-    const VertexBuffer::Mode mode = vertexBuffer.getMode();
-    if (mode == VertexBuffer::kStandard) {
-        mRenderState.meshState().unbindIndicesBuffer();
-        glDrawArrays(GL_TRIANGLE_STRIP, 0, vertexBuffer.getVertexCount());
-    } else if (mode == VertexBuffer::kOnePolyRingShadow) {
-        mRenderState.meshState().bindShadowIndicesBuffer();
-        glDrawElements(GL_TRIANGLE_STRIP, ONE_POLY_RING_SHADOW_INDEX_COUNT,
-                GL_UNSIGNED_SHORT, nullptr);
-    } else if (mode == VertexBuffer::kTwoPolyRingShadow) {
-        mRenderState.meshState().bindShadowIndicesBuffer();
-        glDrawElements(GL_TRIANGLE_STRIP, TWO_POLY_RING_SHADOW_INDEX_COUNT,
-                GL_UNSIGNED_SHORT, nullptr);
-    } else if (mode == VertexBuffer::kIndices) {
+    if (meshFeatureFlags & VertexBuffer::kIndices) {
         mRenderState.meshState().unbindIndicesBuffer();
         glDrawElements(GL_TRIANGLE_STRIP, vertexBuffer.getIndexCount(),
                 GL_UNSIGNED_SHORT, vertexBuffer.getIndices());
+    } else {
+        mRenderState.meshState().unbindIndicesBuffer();
+        glDrawArrays(GL_TRIANGLE_STRIP, 0, vertexBuffer.getVertexCount());
     }
 
     if (isAA) {
@@ -2666,7 +2686,7 @@
     setupDrawShaderUniforms(getShader(paint));
     setupDrawMesh(nullptr, (GLvoid*) kMeshTextureOffset);
 
-    glDrawArrays(GL_TRIANGLE_STRIP, 0, kMeshCount);
+    glDrawArrays(GL_TRIANGLE_STRIP, 0, kUnitQuadCount);
 }
 
 bool OpenGLRenderer::canSkipText(const SkPaint* paint) const {
@@ -3100,7 +3120,7 @@
     setupDrawShaderUniforms(getShader(paint));
     setupDrawMesh(nullptr, (GLvoid*) kMeshTextureOffset);
 
-    glDrawArrays(GL_TRIANGLE_STRIP, 0, kMeshCount);
+    glDrawArrays(GL_TRIANGLE_STRIP, 0, kUnitQuadCount);
 }
 
 // Same values used by Skia
@@ -3259,6 +3279,20 @@
 
 void OpenGLRenderer::drawColorRect(float left, float top, float right, float bottom,
         const SkPaint* paint, bool ignoreTransform) {
+
+    if (!paint->getShader() && !currentSnapshot()->roundRectClipState) {
+        const Matrix4& transform = ignoreTransform ? Matrix4::identity() : *currentTransform();
+        Glop glop;
+        GlopBuilder aBuilder(mRenderState, mCaches, &glop);
+        aBuilder.setMeshUnitQuad()
+                .setTransform(currentSnapshot()->getOrthoMatrix(), transform, false)
+                .setModelViewMapUnitToRect(Rect(left, top, right, bottom))
+                .setPaint(paint, currentSnapshot()->alpha)
+                .build();
+        renderGlop(glop);
+        return;
+    }
+
     int color = paint->getColor();
     // If a shader is set, preserve only the alpha
     if (getShader(paint)) {
@@ -3279,7 +3313,7 @@
     setupDrawColorFilterUniforms(getColorFilter(paint));
     setupDrawSimpleMesh();
 
-    glDrawArrays(GL_TRIANGLE_STRIP, 0, kMeshCount);
+    glDrawArrays(GL_TRIANGLE_STRIP, 0, kUnitQuadCount);
 }
 
 void OpenGLRenderer::drawTextureRect(float left, float top, float right, float bottom,
@@ -3306,11 +3340,11 @@
         texture->setFilter(GL_NEAREST, true);
         drawTextureMesh(x, y, x + texture->width, y + texture->height, texture->id,
                 paint, texture->blend, vertices, texCoords,
-                GL_TRIANGLE_STRIP, kMeshCount, false, true);
+                GL_TRIANGLE_STRIP, kUnitQuadCount, false, true);
     } else {
         texture->setFilter(getFilter(paint), true);
         drawTextureMesh(left, top, right, bottom, texture->id, paint,
-                texture->blend, vertices, texCoords, GL_TRIANGLE_STRIP, kMeshCount);
+                texture->blend, vertices, texCoords, GL_TRIANGLE_STRIP, kUnitQuadCount);
     }
 
     if (texture->uvMapper) {
@@ -3405,7 +3439,7 @@
 void OpenGLRenderer::chooseBlending(bool blend, SkXfermode::Mode mode,
         ProgramDescription& description, bool swapSrcDst) {
 
-    if (writableSnapshot()->roundRectClipState != nullptr /*&& !mSkipOutlineClip*/) {
+    if (currentSnapshot()->roundRectClipState != nullptr /*&& !mSkipOutlineClip*/) {
         blend = true;
         mDescription.hasRoundRectClip = true;
     }
@@ -3420,7 +3454,7 @@
         // If the blend mode cannot be implemented using shaders, fall
         // back to the default SrcOver blend mode instead
         if (CC_UNLIKELY(mode > SkXfermode::kScreen_Mode)) {
-            if (CC_UNLIKELY(mExtensions.hasFramebufferFetch())) {
+            if (CC_UNLIKELY(mCaches.extensions().hasFramebufferFetch())) {
                 description.framebufferMode = mode;
                 description.swapSrcDst = swapSrcDst;
 
diff --git a/libs/hwui/OpenGLRenderer.h b/libs/hwui/OpenGLRenderer.h
index f0de089..f097041 100755
--- a/libs/hwui/OpenGLRenderer.h
+++ b/libs/hwui/OpenGLRenderer.h
@@ -57,6 +57,7 @@
 namespace uirenderer {
 
 class DeferredDisplayState;
+struct Glop;
 class RenderState;
 class RenderNode;
 class TextSetupFunctor;
@@ -529,10 +530,11 @@
 
     CanvasState mState;
     Caches& mCaches;
-    Extensions& mExtensions; // TODO: move to RenderState
     RenderState& mRenderState;
 
 private:
+    void renderGlop(const Glop& glop);
+
     /**
      * Discards the content of the framebuffer if supported by the driver.
      * This method should be called at the beginning of a frame to optimize
diff --git a/libs/hwui/PathTessellator.cpp b/libs/hwui/PathTessellator.cpp
index ceec4fc..3d8a749 100644
--- a/libs/hwui/PathTessellator.cpp
+++ b/libs/hwui/PathTessellator.cpp
@@ -783,6 +783,7 @@
     Rect bounds(path.getBounds());
     paintInfo.expandBoundsForStroke(&bounds);
     vertexBuffer.setBounds(bounds);
+    vertexBuffer.setMeshFeatureFlags(paintInfo.isAA ? VertexBuffer::kAlpha : VertexBuffer::kNone);
 }
 
 template <class TYPE>
@@ -840,6 +841,7 @@
     // expand bounds from vertex coords to pixel data
     paintInfo.expandBoundsForStroke(&bounds);
     vertexBuffer.setBounds(bounds);
+    vertexBuffer.setMeshFeatureFlags(paintInfo.isAA ? VertexBuffer::kAlpha : VertexBuffer::kNone);
 }
 
 void PathTessellator::tessellateLines(const float* points, int count, const SkPaint* paint,
@@ -890,6 +892,7 @@
     // expand bounds from vertex coords to pixel data
     paintInfo.expandBoundsForStroke(&bounds);
     vertexBuffer.setBounds(bounds);
+    vertexBuffer.setMeshFeatureFlags(paintInfo.isAA ? VertexBuffer::kAlpha : VertexBuffer::kNone);
 }
 
 ///////////////////////////////////////////////////////////////////////////////
diff --git a/libs/hwui/Program.h b/libs/hwui/Program.h
index b637450..01231b5 100644
--- a/libs/hwui/Program.h
+++ b/libs/hwui/Program.h
@@ -102,7 +102,7 @@
  * A ProgramDescription must be used in conjunction with a ProgramCache.
  */
 struct ProgramDescription {
-    enum ColorModifier {
+    enum ColorFilterMode {
         kColorNone = 0,
         kColorMatrix,
         kColorBlend
@@ -148,7 +148,7 @@
     GLenum bitmapWrapT;
 
     // Color operations
-    ColorModifier colorOp;
+    ColorFilterMode colorOp;
     SkXfermode::Mode colorMode;
 
     // Framebuffer blending (requires Extensions.hasFramebufferFetch())
diff --git a/libs/hwui/ProgramCache.cpp b/libs/hwui/ProgramCache.cpp
index 3bbd520..8c6a91cc 100644
--- a/libs/hwui/ProgramCache.cpp
+++ b/libs/hwui/ProgramCache.cpp
@@ -404,7 +404,8 @@
 // Constructors/destructors
 ///////////////////////////////////////////////////////////////////////////////
 
-ProgramCache::ProgramCache(): mHasES3(Extensions::getInstance().getMajorGlVersion() >= 3) {
+ProgramCache::ProgramCache(Extensions& extensions)
+        : mHasES3(extensions.getMajorGlVersion() >= 3) {
 }
 
 ProgramCache::~ProgramCache() {
diff --git a/libs/hwui/ProgramCache.h b/libs/hwui/ProgramCache.h
index 30fa0df..1dadda1 100644
--- a/libs/hwui/ProgramCache.h
+++ b/libs/hwui/ProgramCache.h
@@ -40,7 +40,7 @@
  */
 class ProgramCache {
 public:
-    ProgramCache();
+    ProgramCache(Extensions& extensions);
     ~ProgramCache();
 
     Program* get(const ProgramDescription& description);
diff --git a/libs/hwui/Query.h b/libs/hwui/Query.h
deleted file mode 100644
index e25b16b..0000000
--- a/libs/hwui/Query.h
+++ /dev/null
@@ -1,152 +0,0 @@
-/*
- * Copyright (C) 2013 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 ANDROID_HWUI_QUERY_H
-#define ANDROID_HWUI_QUERY_H
-
-#include <GLES3/gl3.h>
-
-#include "Extensions.h"
-
-namespace android {
-namespace uirenderer {
-
-/**
- * A Query instance can be used to perform occlusion queries. If the device
- * does not support occlusion queries, the result of a query will always be
- * 0 and the result will always be marked available.
- *
- * To run an occlusion query successfully, you must start end end the query:
- *
- * Query query;
- * query.begin();
- * // execute OpenGL calls
- * query.end();
- * GLuint result = query.getResult();
- */
-class Query {
-public:
-    /**
-     * Possible query targets.
-     */
-    enum Target {
-        /**
-         * Indicates if any sample passed the depth & stencil tests.
-         */
-        kTargetSamples = GL_ANY_SAMPLES_PASSED,
-        /**
-         * Indicates if any sample passed the depth & stencil tests.
-         * The implementation may choose to use a less precise version
-         * of the test, potentially resulting in false positives.
-         */
-        kTargetConservativeSamples = GL_ANY_SAMPLES_PASSED_CONSERVATIVE,
-    };
-
-    /**
-     * Creates a new query with the specified target. The default
-     * target is kTargetSamples (of GL_ANY_SAMPLES_PASSED in OpenGL.)
-     */
-    Query(Target target = kTargetSamples): mActive(false), mTarget(target),
-            mCanQuery(Extensions::getInstance().hasOcclusionQueries()),
-            mQuery(0) {
-    }
-
-    ~Query() {
-        if (mQuery) {
-            glDeleteQueries(1, &mQuery);
-        }
-    }
-
-    /**
-     * Begins the query. If the query has already begun or if the device
-     * does not support occlusion queries, calling this method as no effect.
-     * After calling this method successfully, the query is marked active.
-     */
-    void begin() {
-        if (!mActive && mCanQuery) {
-            if (!mQuery) {
-                glGenQueries(1, &mQuery);
-            }
-
-            glBeginQuery(mTarget, mQuery);
-            mActive = true;
-        }
-    }
-
-    /**
-     * Ends the query. If the query has already begun or if the device
-     * does not support occlusion queries, calling this method as no effect.
-     * After calling this method successfully, the query is marked inactive.
-     */
-    void end() {
-        if (mQuery && mActive) {
-            glEndQuery(mTarget);
-            mActive = false;
-        }
-    }
-
-    /**
-     * Returns true if the query is active, false otherwise.
-     */
-    bool isActive() {
-        return mActive;
-    }
-
-    /**
-     * Returns true if the result of the query is available,
-     * false otherwise. Calling getResult() before the result
-     * is available may result in the calling thread being blocked.
-     * If the device does not support queries, this method always
-     * returns true.
-     */
-    bool isResultAvailable() {
-        if (!mQuery) return true;
-
-        GLuint result;
-        glGetQueryObjectuiv(mQuery, GL_QUERY_RESULT_AVAILABLE, &result);
-        return result == GL_TRUE;
-    }
-
-    /**
-     * Returns the result of the query. If the device does not
-     * support queries this method will return 0.
-     *
-     * Calling this method implicitely calls end() if the query
-     * is currently active.
-     */
-    GLuint getResult() {
-        if (!mQuery) return 0;
-
-        end();
-
-        GLuint result;
-        glGetQueryObjectuiv(mQuery, GL_QUERY_RESULT, &result);
-        return result;
-    }
-
-
-private:
-    bool mActive;
-    GLenum mTarget;
-    bool mCanQuery;
-    GLuint mQuery;
-
-}; // class Query
-
-}; // namespace uirenderer
-}; // namespace android
-
-#endif // ANDROID_HWUI_QUERY_H
diff --git a/libs/hwui/ShadowTessellator.cpp b/libs/hwui/ShadowTessellator.cpp
index f8917e3..ca7f48d 100644
--- a/libs/hwui/ShadowTessellator.cpp
+++ b/libs/hwui/ShadowTessellator.cpp
@@ -113,33 +113,6 @@
 #endif
 }
 
-void ShadowTessellator::generateShadowIndices(uint16_t* shadowIndices) {
-    int currentIndex = 0;
-    const int rays = SHADOW_RAY_COUNT;
-    // For the penumbra area.
-    for (int layer = 0; layer < 2; layer ++) {
-        int baseIndex = layer * rays;
-        for (int i = 0; i < rays; i++) {
-            shadowIndices[currentIndex++] = i + baseIndex;
-            shadowIndices[currentIndex++] = rays + i + baseIndex;
-        }
-        // To close the loop, back to the ray 0.
-        shadowIndices[currentIndex++] = 0 + baseIndex;
-         // Note this is the same as the first index of next layer loop.
-        shadowIndices[currentIndex++] = rays + baseIndex;
-    }
-
-#if DEBUG_SHADOW
-    if (currentIndex != MAX_SHADOW_INDEX_COUNT) {
-        ALOGW("vertex index count is wrong. current %d, expected %d",
-                currentIndex, MAX_SHADOW_INDEX_COUNT);
-    }
-    for (int i = 0; i < MAX_SHADOW_INDEX_COUNT; i++) {
-        ALOGD("vertex index is (%d, %d)", i, shadowIndices[i]);
-    }
-#endif
-}
-
 /**
  * Calculate the centroid of a 2d polygon.
  *
diff --git a/libs/hwui/ShadowTessellator.h b/libs/hwui/ShadowTessellator.h
index 16ad91d..c04d8ef 100644
--- a/libs/hwui/ShadowTessellator.h
+++ b/libs/hwui/ShadowTessellator.h
@@ -78,8 +78,6 @@
             const mat4& receiverTransform, const Vector3& lightCenter, int lightRadius,
             const Rect& casterBounds, const Rect& localClip, VertexBuffer& shadowVertexBuffer);
 
-    static void generateShadowIndices(uint16_t*  shadowIndices);
-
     static Vector2 centroid2d(const Vector2* poly, int polyLength);
 
     static bool isClockwise(const Vector2* polygon, int len);
diff --git a/libs/hwui/SpotShadow.cpp b/libs/hwui/SpotShadow.cpp
index a03192a..b3b06d6 100644
--- a/libs/hwui/SpotShadow.cpp
+++ b/libs/hwui/SpotShadow.cpp
@@ -1031,7 +1031,7 @@
     ShadowTessellator::checkOverflow(vertexBufferIndex, totalVertexCount, "Spot Vertex Buffer");
     ShadowTessellator::checkOverflow(indexBufferIndex, totalIndexCount, "Spot Index Buffer");
 
-    shadowTriangleStrip.setMode(VertexBuffer::kIndices);
+    shadowTriangleStrip.setMeshFeatureFlags(VertexBuffer::kAlpha | VertexBuffer::kIndices);
     shadowTriangleStrip.computeBounds<AlphaVertex>();
 }
 
diff --git a/libs/hwui/TextureCache.cpp b/libs/hwui/TextureCache.cpp
index fe8fb5b..f4f8e44 100644
--- a/libs/hwui/TextureCache.cpp
+++ b/libs/hwui/TextureCache.cpp
@@ -38,10 +38,12 @@
 // Constructors/destructor
 ///////////////////////////////////////////////////////////////////////////////
 
-TextureCache::TextureCache():
-        mCache(LruCache<uint32_t, Texture*>::kUnlimitedCapacity),
-        mSize(0), mMaxSize(MB(DEFAULT_TEXTURE_CACHE_SIZE)),
-        mFlushRate(DEFAULT_TEXTURE_CACHE_FLUSH_RATE), mAssetAtlas(nullptr) {
+TextureCache::TextureCache()
+        : mCache(LruCache<uint32_t, Texture*>::kUnlimitedCapacity)
+        , mSize(0)
+        , mMaxSize(MB(DEFAULT_TEXTURE_CACHE_SIZE))
+        , mFlushRate(DEFAULT_TEXTURE_CACHE_FLUSH_RATE)
+        , mAssetAtlas(nullptr) {
     char property[PROPERTY_VALUE_MAX];
     if (property_get(PROPERTY_TEXTURE_CACHE_SIZE, property, nullptr) > 0) {
         INIT_LOGD("  Setting texture cache size to %sMB", property);
@@ -59,20 +61,6 @@
                 DEFAULT_TEXTURE_CACHE_FLUSH_RATE * 100.0f);
     }
 
-    init();
-}
-
-TextureCache::TextureCache(uint32_t maxByteSize):
-        mCache(LruCache<uint32_t, Texture*>::kUnlimitedCapacity),
-        mSize(0), mMaxSize(maxByteSize), mAssetAtlas(nullptr) {
-    init();
-}
-
-TextureCache::~TextureCache() {
-    mCache.clear();
-}
-
-void TextureCache::init() {
     mCache.setOnEntryRemovedListener(this);
 
     glGetIntegerv(GL_MAX_TEXTURE_SIZE, &mMaxTextureSize);
@@ -81,6 +69,10 @@
     mDebugEnabled = readDebugLevel() & kDebugCaches;
 }
 
+TextureCache::~TextureCache() {
+    mCache.clear();
+}
+
 ///////////////////////////////////////////////////////////////////////////////
 // Size management
 ///////////////////////////////////////////////////////////////////////////////
@@ -280,7 +272,7 @@
 
     // We could also enable mipmapping if both bitmap dimensions are powers
     // of 2 but we'd have to deal with size changes. Let's keep this simple
-    const bool canMipMap = Extensions::getInstance().hasNPot();
+    const bool canMipMap = Caches::getInstance().extensions().hasNPot();
 
     // If the texture had mipmap enabled but not anymore,
     // force a glTexImage2D to discard the mipmap levels
@@ -355,7 +347,8 @@
 void TextureCache::uploadToTexture(bool resize, GLenum format, GLsizei stride, GLsizei bpp,
         GLsizei width, GLsizei height, GLenum type, const GLvoid * data) {
     glPixelStorei(GL_UNPACK_ALIGNMENT, bpp);
-    const bool useStride = stride != width && Extensions::getInstance().hasUnpackRowLength();
+    const bool useStride = stride != width
+            && Caches::getInstance().extensions().hasUnpackRowLength();
     if ((stride == width) || useStride) {
         if (useStride) {
             glPixelStorei(GL_UNPACK_ROW_LENGTH, stride);
diff --git a/libs/hwui/TextureCache.h b/libs/hwui/TextureCache.h
index b97d92d..a2c6380 100644
--- a/libs/hwui/TextureCache.h
+++ b/libs/hwui/TextureCache.h
@@ -52,10 +52,9 @@
  * Any texture added to the cache causing the cache to grow beyond the maximum
  * allowed size will also cause the oldest texture to be kicked out.
  */
-class TextureCache: public OnEntryRemoved<uint32_t, Texture*> {
+class TextureCache : public OnEntryRemoved<uint32_t, Texture*> {
 public:
     TextureCache();
-    TextureCache(uint32_t maxByteSize);
     ~TextureCache();
 
     /**
@@ -146,8 +145,6 @@
     void uploadToTexture(bool resize, GLenum format, GLsizei stride, GLsizei bpp,
             GLsizei width, GLsizei height, GLenum type, const GLvoid * data);
 
-    void init();
-
     LruCache<uint32_t, Texture*> mCache;
 
     uint32_t mSize;
diff --git a/libs/hwui/VertexBuffer.h b/libs/hwui/VertexBuffer.h
index d81dd42..9be4d84 100644
--- a/libs/hwui/VertexBuffer.h
+++ b/libs/hwui/VertexBuffer.h
@@ -24,11 +24,10 @@
 
 class VertexBuffer {
 public:
-    enum Mode {
-        kStandard = 0,
-        kOnePolyRingShadow = 1,
-        kTwoPolyRingShadow = 2,
-        kIndices = 3
+    enum MeshFeatureFlags {
+        kNone = 0,
+        kAlpha = 1 << 0,
+        kIndices = 1 << 1,
     };
 
     VertexBuffer()
@@ -39,7 +38,7 @@
             , mAllocatedVertexCount(0)
             , mAllocatedIndexCount(0)
             , mByteCount(0)
-            , mMode(kStandard)
+            , mMeshFeatureFlags(kNone)
             , mReallocBuffer(nullptr)
             , mCleanupMethod(nullptr)
             , mCleanupIndexMethod(nullptr)
@@ -135,10 +134,12 @@
     void updateVertexCount(unsigned int newCount)  {
         mVertexCount = MathUtils::min(newCount, mAllocatedVertexCount);
     }
-    Mode getMode() const { return mMode; }
+    MeshFeatureFlags getMeshFeatureFlags() const { return mMeshFeatureFlags; }
+    void setMeshFeatureFlags(int flags) {
+        mMeshFeatureFlags = static_cast<MeshFeatureFlags>(flags);
+    }
 
     void setBounds(Rect bounds) { mBounds = bounds; }
-    void setMode(Mode mode) { mMode = mode; }
 
     template <class TYPE>
     void createDegenerateSeparators(int allocSize) {
@@ -166,7 +167,7 @@
     unsigned int mAllocatedIndexCount;
     unsigned int mByteCount;
 
-    Mode mMode;
+    MeshFeatureFlags mMeshFeatureFlags;
 
     void* mReallocBuffer; // used for multi-allocation
 
diff --git a/libs/hwui/font/CacheTexture.cpp b/libs/hwui/font/CacheTexture.cpp
index 53fa0dc..9314126 100644
--- a/libs/hwui/font/CacheTexture.cpp
+++ b/libs/hwui/font/CacheTexture.cpp
@@ -120,7 +120,7 @@
     // OpenGL ES 3.0+ lets us specify the row length for unpack operations such
     // as glTexSubImage2D(). This allows us to upload a sub-rectangle of a texture.
     // With OpenGL ES 2.0 we have to upload entire stripes instead.
-    mHasUnpackRowLength = Extensions::getInstance().hasUnpackRowLength();
+    mHasUnpackRowLength = mCaches.extensions().hasUnpackRowLength();
 }
 
 CacheTexture::~CacheTexture() {
diff --git a/libs/hwui/renderstate/Blend.cpp b/libs/hwui/renderstate/Blend.cpp
index 93088e4..c751dba 100644
--- a/libs/hwui/renderstate/Blend.cpp
+++ b/libs/hwui/renderstate/Blend.cpp
@@ -127,6 +127,10 @@
     }
 }
 
+void Blend::dump() {
+    ALOGD("Blend: enabled %d, func src %d, dst %d", mEnabled, mSrcMode, mDstMode);
+}
+
 } /* namespace uirenderer */
 } /* namespace android */
 
diff --git a/libs/hwui/renderstate/Blend.h b/libs/hwui/renderstate/Blend.h
index 31d7dde..6d0c115c 100644
--- a/libs/hwui/renderstate/Blend.h
+++ b/libs/hwui/renderstate/Blend.h
@@ -35,6 +35,8 @@
 
     static void getFactors(SkXfermode::Mode mode, bool swapSrcDst, GLenum* outSrc, GLenum* outDst);
     void setFactors(GLenum src, GLenum dst);
+
+    void dump();
 private:
     Blend();
     void invalidate();
diff --git a/libs/hwui/renderstate/MeshState.cpp b/libs/hwui/renderstate/MeshState.cpp
index 50c09c8..ce6030d 100644
--- a/libs/hwui/renderstate/MeshState.cpp
+++ b/libs/hwui/renderstate/MeshState.cpp
@@ -23,22 +23,20 @@
 namespace uirenderer {
 
 MeshState::MeshState()
-        : mCurrentPositionPointer(this)
+        : mCurrentIndicesBuffer(0)
+        , mCurrentPixelBuffer(0)
+        , mCurrentPositionPointer(this)
         , mCurrentPositionStride(0)
         , mCurrentTexCoordsPointer(this)
         , mCurrentTexCoordsStride(0)
-        , mTexCoordsArrayEnabled(false) {
+        , mTexCoordsArrayEnabled(false)
+        , mQuadListIndices(0) {
 
     glGenBuffers(1, &mUnitQuadBuffer);
     glBindBuffer(GL_ARRAY_BUFFER, mUnitQuadBuffer);
     glBufferData(GL_ARRAY_BUFFER, sizeof(kUnitQuadVertices), kUnitQuadVertices, GL_STATIC_DRAW);
 
     mCurrentBuffer = mUnitQuadBuffer;
-    mCurrentIndicesBuffer = 0;
-    mCurrentPixelBuffer = 0;
-
-    mQuadListIndices = 0;
-    mShadowStripsIndices = 0;
 
     // position attribute always enabled
     glEnableVertexAttribArray(Program::kBindingPosition);
@@ -50,9 +48,10 @@
 
     glDeleteBuffers(1, &mQuadListIndices);
     mQuadListIndices = 0;
+}
 
-    glDeleteBuffers(1, &mShadowStripsIndices);
-    mShadowStripsIndices = 0;
+void MeshState::dump() {
+    ALOGD("MeshState vertices: unitQuad %d, current %d", mUnitQuadBuffer, mCurrentBuffer);
 }
 
 ///////////////////////////////////////////////////////////////////////////////
@@ -65,18 +64,17 @@
 
 bool MeshState::bindMeshBuffer(GLuint buffer) {
     if (!buffer) buffer = mUnitQuadBuffer;
-    if (mCurrentBuffer != buffer) {
-        glBindBuffer(GL_ARRAY_BUFFER, buffer);
-        mCurrentBuffer = buffer;
-        return true;
-    }
-    return false;
+    return bindMeshBufferInternal(buffer);
 }
 
 bool MeshState::unbindMeshBuffer() {
-    if (mCurrentBuffer) {
-        glBindBuffer(GL_ARRAY_BUFFER, 0);
-        mCurrentBuffer = 0;
+    return bindMeshBufferInternal(0);
+}
+
+bool MeshState::bindMeshBufferInternal(GLuint buffer) {
+    if (mCurrentBuffer != buffer) {
+        glBindBuffer(GL_ARRAY_BUFFER, buffer);
+        mCurrentBuffer = buffer;
         return true;
     }
     return false;
@@ -163,20 +161,6 @@
     return bindIndicesBufferInternal(mQuadListIndices);
 }
 
-bool MeshState::bindShadowIndicesBuffer() {
-    if (!mShadowStripsIndices) {
-        std::unique_ptr<uint16_t[]> shadowIndices(new uint16_t[MAX_SHADOW_INDEX_COUNT]);
-        ShadowTessellator::generateShadowIndices(shadowIndices.get());
-        glGenBuffers(1, &mShadowStripsIndices);
-        bool force = bindIndicesBufferInternal(mShadowStripsIndices);
-        glBufferData(GL_ELEMENT_ARRAY_BUFFER, MAX_SHADOW_INDEX_COUNT * sizeof(uint16_t),
-            shadowIndices.get(), GL_STATIC_DRAW);
-        return force;
-    }
-
-    return bindIndicesBufferInternal(mShadowStripsIndices);
-}
-
 bool MeshState::unbindIndicesBuffer() {
     if (mCurrentIndicesBuffer) {
         glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
diff --git a/libs/hwui/renderstate/MeshState.h b/libs/hwui/renderstate/MeshState.h
index 5cb1143..afc6267 100644
--- a/libs/hwui/renderstate/MeshState.h
+++ b/libs/hwui/renderstate/MeshState.h
@@ -47,7 +47,7 @@
 const GLsizei kVertexAlphaOffset = 2 * sizeof(float);
 const GLsizei kVertexAAWidthOffset = 2 * sizeof(float);
 const GLsizei kVertexAALengthOffset = 3 * sizeof(float);
-const GLsizei kMeshCount = 4;
+const GLsizei kUnitQuadCount = 4;
 
 class MeshState {
 private:
@@ -55,6 +55,7 @@
 
 public:
     ~MeshState();
+    void dump();
     ///////////////////////////////////////////////////////////////////////////////
     // Buffer objects
     ///////////////////////////////////////////////////////////////////////////////
@@ -107,7 +108,6 @@
      * gMaxNumberOfQuads quads.
      */
     bool bindQuadIndicesBuffer();
-    bool bindShadowIndicesBuffer();
     bool unbindIndicesBuffer();
 
     ///////////////////////////////////////////////////////////////////////////////
@@ -116,9 +116,9 @@
     GLuint getUnitQuadVBO() { return mUnitQuadBuffer; }
 private:
     MeshState();
+    bool bindMeshBufferInternal(const GLuint buffer);
     bool bindIndicesBufferInternal(const GLuint buffer);
 
-    // VBO to draw with
     GLuint mUnitQuadBuffer;
 
     GLuint mCurrentBuffer;
@@ -134,7 +134,6 @@
 
     // Global index buffer
     GLuint mQuadListIndices;
-    GLuint mShadowStripsIndices;
 };
 
 } /* namespace uirenderer */
diff --git a/libs/hwui/renderstate/RenderState.cpp b/libs/hwui/renderstate/RenderState.cpp
index d3f6277..a819d9a 100644
--- a/libs/hwui/renderstate/RenderState.cpp
+++ b/libs/hwui/renderstate/RenderState.cpp
@@ -17,6 +17,7 @@
 
 #include "renderthread/CanvasContext.h"
 #include "renderthread/EglManager.h"
+#include "utils/GLUtils.h"
 
 namespace android {
 namespace uirenderer {
@@ -213,18 +214,41 @@
     const Glop::Mesh& mesh = glop.mesh;
     const Glop::Fill& shader = glop.fill;
 
+    // --------------------------------------------
     // ---------- Shader + uniform setup ----------
+    // --------------------------------------------
     mCaches->setProgram(shader.program);
 
-    Glop::Fill::Color color = shader.color;
+    Glop::FloatColor color = shader.color;
     shader.program->setColor(color.r, color.g, color.b, color.a);
 
     shader.program->set(glop.transform.ortho,
             glop.transform.modelView,
             glop.transform.canvas,
-            glop.transform.offset);
+            glop.transform.fudgingOffset);
 
+    if (glop.fill.filterMode == ProgramDescription::kColorBlend) {
+        const Glop::FloatColor& color = glop.fill.filter.color;
+        glUniform4f(mCaches->program().getUniform("colorBlend"),
+                color.r, color.g, color.b, color.a);
+    } else if (glop.fill.filterMode == ProgramDescription::kColorMatrix) {
+        glUniformMatrix4fv(mCaches->program().getUniform("colorMatrix"), 1, GL_FALSE,
+                glop.fill.filter.matrix.matrix);
+        glUniform4fv(mCaches->program().getUniform("colorMatrixVector"), 1,
+                glop.fill.filter.matrix.vector);
+    }
+
+    // --------------------------------
     // ---------- Mesh setup ----------
+    // --------------------------------
+    // vertices
+    bool force = meshState().bindMeshBufferInternal(mesh.vertexBufferObject)
+            || (mesh.vertices != nullptr);
+    meshState().bindPositionVertexPointer(force, mesh.vertices, mesh.stride);
+
+    // indices
+    meshState().bindIndicesBufferInternal(mesh.indexBufferObject);
+
     if (glop.mesh.vertexFlags & kTextureCoord_Attrib) {
         // TODO: support textures
         LOG_ALWAYS_FATAL("textures not yet supported");
@@ -232,40 +256,42 @@
         meshState().disableTexCoordsVertexArray();
     }
     if (glop.mesh.vertexFlags & kColor_Attrib) {
-        LOG_ALWAYS_FATAL("color attribute not yet supported");
+        LOG_ALWAYS_FATAL("color vertex attribute not yet supported");
         // TODO: enable color, disable when done
     }
+    int alphaSlot = -1;
     if (glop.mesh.vertexFlags & kAlpha_Attrib) {
-        LOG_ALWAYS_FATAL("alpha attribute not yet supported");
-        // TODO: enable alpha attribute, disable when done
+        const void* alphaCoords = ((const GLbyte*) glop.mesh.vertices) + kVertexAlphaOffset;
+        alphaSlot = shader.program->getAttrib("vtxAlpha");
+        glEnableVertexAttribArray(alphaSlot);
+        glVertexAttribPointer(alphaSlot, 1, GL_FLOAT, GL_FALSE, kAlphaVertexStride, alphaCoords);
     }
 
-    /**
-    * Hard-coded vertex assumptions:
-     *     - required
-     *     - xy floats
-     *     - 0 offset
-     *     - in VBO
-     */
-    bool force = meshState().bindMeshBuffer(mesh.vertexBufferObject);
-    meshState().bindPositionVertexPointer(force, nullptr, mesh.stride);
-
-    /**
-     * Hard-coded index assumptions:
-     *     - optional
-     *     - 0 offset
-     *     - in IBO
-     */
-    meshState().bindIndicesBufferInternal(mesh.indexBufferObject);
-
+    // ------------------------------------
     // ---------- GL state setup ----------
+    // ------------------------------------
     blend().setFactors(glop.blend.src, glop.blend.dst);
 
-    if (mesh.indexBufferObject) {
-        glDrawElements(glop.mesh.primitiveMode, glop.mesh.vertexCount, GL_UNSIGNED_BYTE, nullptr);
+    // ------------------------------------
+    // ---------- GL state setup ----------
+    // ------------------------------------
+    if (mesh.indexBufferObject || mesh.indices) {
+        glDrawElements(glop.mesh.primitiveMode, glop.mesh.vertexCount,
+                GL_UNSIGNED_SHORT, mesh.indices);
     } else {
-        glDrawArrays(GL_TRIANGLE_STRIP, 0, glop.mesh.vertexCount);
+        glDrawArrays(glop.mesh.primitiveMode, 0, glop.mesh.vertexCount);
     }
+
+    if (glop.mesh.vertexFlags & kAlpha_Attrib) {
+        glDisableVertexAttribArray(alphaSlot);
+    }
+}
+
+void RenderState::dump() {
+    blend().dump();
+    meshState().dump();
+    scissor().dump();
+    stencil().dump();
 }
 
 } /* namespace uirenderer */
diff --git a/libs/hwui/renderstate/RenderState.h b/libs/hwui/renderstate/RenderState.h
index 2e28ff6..4fd792c 100644
--- a/libs/hwui/renderstate/RenderState.h
+++ b/libs/hwui/renderstate/RenderState.h
@@ -91,6 +91,8 @@
     MeshState& meshState() { return *mMeshState; }
     Scissor& scissor() { return *mScissor; }
     Stencil& stencil() { return *mStencil; }
+
+    void dump();
 private:
     friend class renderthread::RenderThread;
     friend class Caches;
@@ -99,9 +101,6 @@
     void resumeFromFunctorInvoke();
     void assertOnGLThread();
 
-    void setupVertexAttributes(const Glop& glop);
-    void tearDownVertexAttributes(const Glop& glop);
-
     RenderState(renderthread::RenderThread& thread);
     ~RenderState();
 
diff --git a/libs/hwui/renderstate/Scissor.cpp b/libs/hwui/renderstate/Scissor.cpp
index 66c31a2..95dcd18 100644
--- a/libs/hwui/renderstate/Scissor.cpp
+++ b/libs/hwui/renderstate/Scissor.cpp
@@ -15,6 +15,8 @@
  */
 #include "renderstate/Scissor.h"
 
+#include <utils/Log.h>
+
 namespace android {
 namespace uirenderer {
 
@@ -79,6 +81,11 @@
     reset();
 }
 
+void Scissor::dump() {
+    ALOGD("Scissor: enabled %d, %d %d %d %d",
+            mEnabled, mScissorX, mScissorY, mScissorWidth, mScissorHeight);
+}
+
 } /* namespace uirenderer */
 } /* namespace android */
 
diff --git a/libs/hwui/renderstate/Scissor.h b/libs/hwui/renderstate/Scissor.h
index cc8b3dd..b37ec58 100644
--- a/libs/hwui/renderstate/Scissor.h
+++ b/libs/hwui/renderstate/Scissor.h
@@ -29,6 +29,7 @@
     bool set(GLint x, GLint y, GLint width, GLint height);
     void reset();
     bool isEnabled() { return mEnabled; }
+    void dump();
 private:
     Scissor();
     void invalidate();
diff --git a/libs/hwui/renderstate/Stencil.cpp b/libs/hwui/renderstate/Stencil.cpp
index acbed14..cedb233 100644
--- a/libs/hwui/renderstate/Stencil.cpp
+++ b/libs/hwui/renderstate/Stencil.cpp
@@ -14,10 +14,12 @@
  * limitations under the License.
  */
 
+#include "renderstate/Stencil.h"
+
+#include "Caches.h"
 #include "Debug.h"
 #include "Extensions.h"
 #include "Properties.h"
-#include "renderstate/Stencil.h"
 
 #include <GLES2/gl2ext.h>
 
@@ -42,7 +44,7 @@
 
 GLenum Stencil::getSmallestStencilFormat() {
 #if !DEBUG_STENCIL
-    const Extensions& extensions = Extensions::getInstance();
+    const Extensions& extensions = Caches::getInstance().extensions();
     if (extensions.has1BitStencil()) {
         return GL_STENCIL_INDEX1_OES;
     } else if (extensions.has4BitStencil()) {
@@ -123,5 +125,9 @@
     }
 }
 
+void Stencil::dump() {
+    ALOGD("Stencil: state %d", mState);
+}
+
 }; // namespace uirenderer
 }; // namespace android
diff --git a/libs/hwui/renderstate/Stencil.h b/libs/hwui/renderstate/Stencil.h
index 20bb955..a88beae 100644
--- a/libs/hwui/renderstate/Stencil.h
+++ b/libs/hwui/renderstate/Stencil.h
@@ -98,6 +98,8 @@
         return mState == kTest;
     }
 
+    void dump();
+
 private:
     void enable();
 
diff --git a/libs/hwui/tests/main.cpp b/libs/hwui/tests/main.cpp
index 1e7ba23..5f445f4 100644
--- a/libs/hwui/tests/main.cpp
+++ b/libs/hwui/tests/main.cpp
@@ -201,6 +201,45 @@
     }
 };
 
+class OvalAnimation : 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);
+
+        SkPaint paint;
+        paint.setAntiAlias(true);
+        paint.setColor(0xFF00FFFF);
+        renderer->drawOval(0, 0, width, height, 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;
@@ -212,6 +251,7 @@
 std::map<const char*, testProc, cstr_cmp> gTestMap {
     {"shadowgrid", TreeContentAnimation::run<ShadowGridAnimation>},
     {"rectgrid", TreeContentAnimation::run<RectGridAnimation> },
+    {"oval", TreeContentAnimation::run<OvalAnimation> },
 };
 
 int main(int argc, char* argv[]) {
diff --git a/libs/hwui/utils/PaintUtils.h b/libs/hwui/utils/PaintUtils.h
index 2091705..679e2bf 100644
--- a/libs/hwui/utils/PaintUtils.h
+++ b/libs/hwui/utils/PaintUtils.h
@@ -52,6 +52,13 @@
                 && getXfermode(paint.getXfermode()) == SkXfermode::kSrcOver_Mode;
     }
 
+    static bool isBlendedShader(const SkShader* shader) {
+        if (shader == nullptr) {
+            return false;
+        }
+        return !shader->isOpaque();
+    }
+
     static bool isBlendedColorFilter(const SkColorFilter* filter) {
         if (filter == nullptr) {
             return false;