Optimize state changes

Change-Id: Iae59bc8dfd6427d0967472462cc1994987092827
diff --git a/libs/hwui/Caches.cpp b/libs/hwui/Caches.cpp
index f293cba..4da576d 100644
--- a/libs/hwui/Caches.cpp
+++ b/libs/hwui/Caches.cpp
@@ -73,6 +73,9 @@
     glBufferData(GL_ARRAY_BUFFER, sizeof(gMeshVertices), gMeshVertices, GL_STATIC_DRAW);
 
     mCurrentBuffer = meshBuffer;
+    mCurrentPositionPointer = this;
+    mCurrentTexCoordsPointer = this;
+
     mRegionMesh = NULL;
 
     blend = false;
@@ -218,22 +221,49 @@
 // VBO
 ///////////////////////////////////////////////////////////////////////////////
 
-void Caches::bindMeshBuffer() {
-    bindMeshBuffer(meshBuffer);
+bool Caches::bindMeshBuffer() {
+    return bindMeshBuffer(meshBuffer);
 }
 
-void Caches::bindMeshBuffer(const GLuint buffer) {
+bool Caches::bindMeshBuffer(const GLuint buffer) {
     if (mCurrentBuffer != buffer) {
         glBindBuffer(GL_ARRAY_BUFFER, buffer);
         mCurrentBuffer = buffer;
+        return true;
     }
+    return false;
 }
 
-void Caches::unbindMeshBuffer() {
+bool Caches::unbindMeshBuffer() {
     if (mCurrentBuffer) {
         glBindBuffer(GL_ARRAY_BUFFER, 0);
         mCurrentBuffer = 0;
+        return true;
     }
+    return false;
+}
+
+void Caches::bindPositionVertexPointer(bool force, GLuint slot, GLvoid* vertices, GLsizei stride) {
+    if (force || vertices != mCurrentPositionPointer) {
+        glVertexAttribPointer(slot, 2, GL_FLOAT, GL_FALSE, stride, vertices);
+        mCurrentPositionPointer = vertices;
+    }
+}
+
+void Caches::bindTexCoordsVertexPointer(bool force, GLuint slot, GLvoid* vertices) {
+    if (force || vertices != mCurrentTexCoordsPointer) {
+        glVertexAttribPointer(slot, 2, GL_FLOAT, GL_FALSE, gMeshStride, vertices);
+        mCurrentTexCoordsPointer = vertices;
+    }
+}
+
+void Caches::resetVertexPointers() {
+    mCurrentPositionPointer = this;
+    mCurrentTexCoordsPointer = this;
+}
+
+void Caches::resetTexCoordsVertexPointer() {
+    mCurrentTexCoordsPointer = this;
 }
 
 TextureVertex* Caches::getRegionMesh() {
diff --git a/libs/hwui/Caches.h b/libs/hwui/Caches.h
index 5e58a9e..7ca198a 100644
--- a/libs/hwui/Caches.h
+++ b/libs/hwui/Caches.h
@@ -91,15 +91,6 @@
 
     CacheLogger mLogger;
 
-    GLuint mCurrentBuffer;
-
-    // Used to render layers
-    TextureVertex* mRegionMesh;
-    GLuint mRegionMeshIndices;
-
-    mutable Mutex mGarbageLock;
-    Vector<Layer*> mLayerGarbage;
-
 public:
     enum FlushMode {
         kFlushMode_Layers = 0,
@@ -147,17 +138,36 @@
     /**
      * Binds the VBO used to render simple textured quads.
      */
-    void bindMeshBuffer();
+    bool bindMeshBuffer();
 
     /**
      * Binds the specified VBO if needed.
      */
-    void bindMeshBuffer(const GLuint buffer);
+    bool bindMeshBuffer(const GLuint buffer);
 
     /**
      * Unbinds the VBO used to render simple textured quads.
      */
-    void unbindMeshBuffer();
+    bool unbindMeshBuffer();
+
+    /**
+     * Binds an attrib to the specified float vertex pointer.
+     * Assumes a stride of gMeshStride and a size of 2.
+     */
+    void bindPositionVertexPointer(bool force, GLuint slot, GLvoid* vertices,
+            GLsizei stride = gMeshStride);
+
+    /**
+     * Binds an attrib to the specified float vertex pointer.
+     * Assumes a stride of gMeshStride and a size of 2.
+     */
+    void bindTexCoordsVertexPointer(bool force, GLuint slot, GLvoid* vertices);
+
+    /**
+     * Resets the vertex pointers.
+     */
+    void resetVertexPointers();
+    void resetTexCoordsVertexPointer();
 
     /**
      * Returns the mesh used to draw regions. Calling this method will
@@ -203,6 +213,17 @@
     ResourceCache resourceCache;
 
 private:
+    GLuint mCurrentBuffer;
+    void* mCurrentPositionPointer;
+    void* mCurrentTexCoordsPointer;
+
+    // Used to render layers
+    TextureVertex* mRegionMesh;
+    GLuint mRegionMeshIndices;
+
+    mutable Mutex mGarbageLock;
+    Vector<Layer*> mLayerGarbage;
+
     DebugLevel mDebugLevel;
     bool mInitialized;
 }; // class Caches
diff --git a/libs/hwui/FontRenderer.cpp b/libs/hwui/FontRenderer.cpp
index 6e80d15..f04ea6f 100644
--- a/libs/hwui/FontRenderer.cpp
+++ b/libs/hwui/FontRenderer.cpp
@@ -325,8 +325,6 @@
     mTextTexture = NULL;
 
     mIndexBufferID = 0;
-    mPositionAttrSlot = -1;
-    mTexcoordAttrSlot = -1;
 
     mCacheWidth = DEFAULT_TEXT_CACHE_WIDTH;
     mCacheHeight = DEFAULT_TEXT_CACHE_HEIGHT;
@@ -599,12 +597,6 @@
 void FontRenderer::issueDrawCommand() {
     checkTextureUpdate();
 
-    float* vtx = mTextMeshPtr;
-    float* tex = vtx + 2;
-
-    glVertexAttribPointer(mPositionAttrSlot, 2, GL_FLOAT, GL_FALSE, 16, vtx);
-    glVertexAttribPointer(mTexcoordAttrSlot, 2, GL_FLOAT, GL_FALSE, 16, tex);
-
     glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, mIndexBufferID);
     glDrawElements(GL_TRIANGLES, mCurrentQuadIndex * 6, GL_UNSIGNED_SHORT, NULL);
 
@@ -760,11 +752,6 @@
         return false;
     }
 
-    if (mPositionAttrSlot < 0 || mTexcoordAttrSlot < 0) {
-        LOGE("Font renderer unable to draw, attribute slots undefined");
-        return false;
-    }
-
     mDrawn = false;
     mBounds = bounds;
     mClip = clip;
diff --git a/libs/hwui/FontRenderer.h b/libs/hwui/FontRenderer.h
index 71695a8..f945873 100644
--- a/libs/hwui/FontRenderer.h
+++ b/libs/hwui/FontRenderer.h
@@ -178,9 +178,13 @@
         mGammaTable = gammaTable;
     }
 
-    void setAttributeBindingSlots(int positionSlot, int texCoordSlot) {
-        mPositionAttrSlot = positionSlot;
-        mTexcoordAttrSlot = texCoordSlot;
+    inline float* getMeshBuffer() {
+        checkInit();
+        return mTextMeshPtr;
+    }
+
+    inline int getMeshTexCoordsOffset() const {
+        return 2;
     }
 
     void setFont(SkPaint* paint, uint32_t fontId, float fontSize);
@@ -309,9 +313,6 @@
 
     uint32_t mIndexBufferID;
 
-    int32_t mPositionAttrSlot;
-    int32_t mTexcoordAttrSlot;
-
     const Rect* mClip;
     Rect* mBounds;
     bool mDrawn;
diff --git a/libs/hwui/OpenGLRenderer.cpp b/libs/hwui/OpenGLRenderer.cpp
index 6e2f1c3..fcac053 100644
--- a/libs/hwui/OpenGLRenderer.cpp
+++ b/libs/hwui/OpenGLRenderer.cpp
@@ -199,13 +199,13 @@
         }
     }
     mCaches.unbindMeshBuffer();
+    mCaches.resetVertexPointers();
 }
 
 void OpenGLRenderer::resume() {
     sp<Snapshot> snapshot = (mSnapshot != NULL) ? mSnapshot : mFirstSnapshot;
 
     glViewport(0, 0, snapshot->viewport.getWidth(), snapshot->viewport.getHeight());
-
     glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
 
     glEnable(GL_SCISSOR_TEST);
@@ -1201,16 +1201,15 @@
 }
 
 void OpenGLRenderer::setupDrawSimpleMesh() {
-    mCaches.bindMeshBuffer();
-    glVertexAttribPointer(mCaches.currentProgram->position, 2, GL_FLOAT, GL_FALSE,
-            gMeshStride, 0);
+    bool force = mCaches.bindMeshBuffer();
+    mCaches.bindPositionVertexPointer(force, mCaches.currentProgram->position, 0);
 }
 
 void OpenGLRenderer::setupDrawTexture(GLuint texture) {
     bindTexture(texture);
     glUniform1i(mCaches.currentProgram->getUniform("sampler"), mTextureUnit++);
 
-    mTexCoordsSlot = mCaches.currentProgram->getAttrib("texCoords");
+    mTexCoordsSlot = mCaches.currentProgram->texCoords;
     glEnableVertexAttribArray(mTexCoordsSlot);
 }
 
@@ -1218,7 +1217,7 @@
     bindExternalTexture(texture);
     glUniform1i(mCaches.currentProgram->getUniform("sampler"), mTextureUnit++);
 
-    mTexCoordsSlot = mCaches.currentProgram->getAttrib("texCoords");
+    mTexCoordsSlot = mCaches.currentProgram->texCoords;
     glEnableVertexAttribArray(mTexCoordsSlot);
 }
 
@@ -1232,23 +1231,23 @@
 }
 
 void OpenGLRenderer::setupDrawMesh(GLvoid* vertices, GLvoid* texCoords, GLuint vbo) {
+    bool force = false;
     if (!vertices) {
-        mCaches.bindMeshBuffer(vbo == 0 ? mCaches.meshBuffer : vbo);
+        force = mCaches.bindMeshBuffer(vbo == 0 ? mCaches.meshBuffer : vbo);
     } else {
-        mCaches.unbindMeshBuffer();
+        force = mCaches.unbindMeshBuffer();
     }
 
-    glVertexAttribPointer(mCaches.currentProgram->position, 2, GL_FLOAT, GL_FALSE,
-            gMeshStride, vertices);
+    mCaches.bindPositionVertexPointer(force, mCaches.currentProgram->position, vertices);
     if (mTexCoordsSlot >= 0) {
-        glVertexAttribPointer(mTexCoordsSlot, 2, GL_FLOAT, GL_FALSE, gMeshStride, texCoords);
+        mCaches.bindTexCoordsVertexPointer(force, mTexCoordsSlot, texCoords);
     }
 }
 
 void OpenGLRenderer::setupDrawVertices(GLvoid* vertices) {
-    mCaches.unbindMeshBuffer();
-    glVertexAttribPointer(mCaches.currentProgram->position, 2, GL_FLOAT, GL_FALSE,
-            gVertexStride, vertices);
+    bool force = mCaches.unbindMeshBuffer();
+    mCaches.bindPositionVertexPointer(force, mCaches.currentProgram->position,
+            vertices, gVertexStride);
 }
 
 /**
@@ -1264,10 +1263,10 @@
  */
 void OpenGLRenderer::setupDrawAALine(GLvoid* vertices, GLvoid* widthCoords,
         GLvoid* lengthCoords, float boundaryWidthProportion) {
-    mCaches.unbindMeshBuffer();
-
-    glVertexAttribPointer(mCaches.currentProgram->position, 2, GL_FLOAT, GL_FALSE,
-            gAAVertexStride, vertices);
+    bool force = mCaches.unbindMeshBuffer();
+    mCaches.bindPositionVertexPointer(force, mCaches.currentProgram->position,
+            vertices, gAAVertexStride);
+    mCaches.resetTexCoordsVertexPointer();
 
     int widthSlot = mCaches.currentProgram->getAttrib("vtxWidth");
     glEnableVertexAttribArray(widthSlot);
@@ -2186,12 +2185,14 @@
 #else
     bool hasActiveLayer = false;
 #endif
-    mCaches.unbindMeshBuffer();
 
-    // Tell font renderer the locations of position and texture coord
-    // attributes so it can bind its data properly
-    int positionSlot = mCaches.currentProgram->position;
-    fontRenderer.setAttributeBindingSlots(positionSlot, mTexCoordsSlot);
+    float* buffer = fontRenderer.getMeshBuffer();
+    int offset = fontRenderer.getMeshTexCoordsOffset();
+
+    bool force = mCaches.unbindMeshBuffer();
+    mCaches.bindPositionVertexPointer(force, mCaches.currentProgram->position, buffer);
+    mCaches.bindTexCoordsVertexPointer(force, mTexCoordsSlot, buffer + offset);
+
     if (fontRenderer.renderText(paint, clip, text, 0, bytesCount, count, x, y,
             hasActiveLayer ? &bounds : NULL)) {
 #if RENDER_LAYERS_AS_REGIONS
@@ -2205,7 +2206,7 @@
     }
 
     glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
-    glDisableVertexAttribArray(mCaches.currentProgram->getAttrib("texCoords"));
+    glDisableVertexAttribArray(mCaches.currentProgram->texCoords);
 
     drawTextDecorations(text, bytesCount, length, oldX, oldY, paint);
 }
diff --git a/libs/hwui/Patch.cpp b/libs/hwui/Patch.cpp
index 47a2c99..27f530c 100644
--- a/libs/hwui/Patch.cpp
+++ b/libs/hwui/Patch.cpp
@@ -197,7 +197,8 @@
     }
 
     if (verticesCount > 0) {
-        Caches::getInstance().bindMeshBuffer(meshBuffer);
+        Caches& caches = Caches::getInstance();
+        caches.bindMeshBuffer(meshBuffer);
         if (!mUploaded) {
             glBufferData(GL_ARRAY_BUFFER, sizeof(TextureVertex) * verticesCount,
                     mVertices, GL_DYNAMIC_DRAW);
@@ -206,6 +207,7 @@
             glBufferSubData(GL_ARRAY_BUFFER, 0,
                     sizeof(TextureVertex) * verticesCount, mVertices);
         }
+        caches.resetVertexPointers();
     }
 
     PATCH_LOGD("    patch: new vertices count = %d", verticesCount);
diff --git a/libs/hwui/Program.cpp b/libs/hwui/Program.cpp
index cb2a5fd..cbea843 100644
--- a/libs/hwui/Program.cpp
+++ b/libs/hwui/Program.cpp
@@ -26,7 +26,7 @@
 ///////////////////////////////////////////////////////////////////////////////
 
 // TODO: Program instance should be created from a factory method
-Program::Program(const char* vertex, const char* fragment) {
+Program::Program(const ProgramDescription& description, const char* vertex, const char* fragment) {
     mInitialized = false;
     mHasColorUniform = false;
     mUse = false;
@@ -43,6 +43,12 @@
             glAttachShader(mProgramId, mFragmentShader);
 
             position = bindAttrib("position", kBindingPosition);
+            if (description.hasTexture || description.hasExternalTexture) {
+                texCoords = bindAttrib("texCoords", kBindingTexCoords);
+            } else {
+                texCoords = -1;
+            }
+
             glLinkProgram(mProgramId);
 
             GLint status;
diff --git a/libs/hwui/Program.h b/libs/hwui/Program.h
index 9e59621..559c717 100644
--- a/libs/hwui/Program.h
+++ b/libs/hwui/Program.h
@@ -17,16 +17,264 @@
 #ifndef ANDROID_HWUI_PROGRAM_H
 #define ANDROID_HWUI_PROGRAM_H
 
+#include <utils/KeyedVector.h>
+
 #include <GLES2/gl2.h>
 #include <GLES2/gl2ext.h>
 
-#include <utils/KeyedVector.h>
+#include <SkXfermode.h>
 
 #include "Matrix.h"
+#include "Properties.h"
 
 namespace android {
 namespace uirenderer {
 
+///////////////////////////////////////////////////////////////////////////////
+// Defines
+///////////////////////////////////////////////////////////////////////////////
+
+// Debug
+#if DEBUG_PROGRAMS
+    #define PROGRAM_LOGD(...) LOGD(__VA_ARGS__)
+#else
+    #define PROGRAM_LOGD(...)
+#endif
+
+#define COLOR_COMPONENT_THRESHOLD (1.0f - (0.5f / PANEL_BIT_DEPTH))
+#define COLOR_COMPONENT_INV_THRESHOLD (0.5f / PANEL_BIT_DEPTH)
+
+#define PROGRAM_KEY_TEXTURE 0x1
+#define PROGRAM_KEY_A8_TEXTURE 0x2
+#define PROGRAM_KEY_BITMAP 0x4
+#define PROGRAM_KEY_GRADIENT 0x8
+#define PROGRAM_KEY_BITMAP_FIRST 0x10
+#define PROGRAM_KEY_COLOR_MATRIX 0x20
+#define PROGRAM_KEY_COLOR_LIGHTING 0x40
+#define PROGRAM_KEY_COLOR_BLEND 0x80
+#define PROGRAM_KEY_BITMAP_NPOT 0x100
+#define PROGRAM_KEY_SWAP_SRC_DST 0x2000
+
+#define PROGRAM_KEY_BITMAP_WRAPS_MASK 0x600
+#define PROGRAM_KEY_BITMAP_WRAPT_MASK 0x1800
+
+// Encode the xfermodes on 6 bits
+#define PROGRAM_MAX_XFERMODE 0x1f
+#define PROGRAM_XFERMODE_SHADER_SHIFT 26
+#define PROGRAM_XFERMODE_COLOR_OP_SHIFT 20
+#define PROGRAM_XFERMODE_FRAMEBUFFER_SHIFT 14
+
+#define PROGRAM_BITMAP_WRAPS_SHIFT 9
+#define PROGRAM_BITMAP_WRAPT_SHIFT 11
+
+#define PROGRAM_GRADIENT_TYPE_SHIFT 33
+#define PROGRAM_MODULATE_SHIFT 35
+
+#define PROGRAM_IS_POINT_SHIFT 36
+
+#define PROGRAM_HAS_AA_SHIFT 37
+
+#define PROGRAM_HAS_EXTERNAL_TEXTURE_SHIFT 38
+#define PROGRAM_HAS_TEXTURE_TRANSFORM_SHIFT 39
+
+///////////////////////////////////////////////////////////////////////////////
+// Types
+///////////////////////////////////////////////////////////////////////////////
+
+typedef uint64_t programid;
+
+///////////////////////////////////////////////////////////////////////////////
+// Program description
+///////////////////////////////////////////////////////////////////////////////
+
+/**
+ * Describe the features required for a given program. The features
+ * determine the generation of both the vertex and fragment shaders.
+ * A ProgramDescription must be used in conjunction with a ProgramCache.
+ */
+struct ProgramDescription {
+    enum ColorModifier {
+        kColorNone,
+        kColorMatrix,
+        kColorLighting,
+        kColorBlend
+    };
+
+    enum Gradient {
+        kGradientLinear,
+        kGradientCircular,
+        kGradientSweep
+    };
+
+    ProgramDescription() {
+        reset();
+    }
+
+    // Texturing
+    bool hasTexture;
+    bool hasAlpha8Texture;
+    bool hasExternalTexture;
+    bool hasTextureTransform;
+
+    // Modulate, this should only be set when setColor() return true
+    bool modulate;
+
+    // Shaders
+    bool hasBitmap;
+    bool isBitmapNpot;
+
+    bool isAA;
+
+    bool hasGradient;
+    Gradient gradientType;
+
+    SkXfermode::Mode shadersMode;
+
+    bool isBitmapFirst;
+    GLenum bitmapWrapS;
+    GLenum bitmapWrapT;
+
+    // Color operations
+    ColorModifier colorOp;
+    SkXfermode::Mode colorMode;
+
+    // Framebuffer blending (requires Extensions.hasFramebufferFetch())
+    // Ignored for all values < SkXfermode::kPlus_Mode
+    SkXfermode::Mode framebufferMode;
+    bool swapSrcDst;
+
+    bool isPoint;
+    float pointSize;
+
+    /**
+     * Resets this description. All fields are reset back to the default
+     * values they hold after building a new instance.
+     */
+    void reset() {
+        hasTexture = false;
+        hasAlpha8Texture = false;
+        hasExternalTexture = false;
+        hasTextureTransform = false;
+
+        isAA = false;
+
+        modulate = false;
+
+        hasBitmap = false;
+        isBitmapNpot = false;
+
+        hasGradient = false;
+        gradientType = kGradientLinear;
+
+        shadersMode = SkXfermode::kClear_Mode;
+
+        isBitmapFirst = false;
+        bitmapWrapS = GL_CLAMP_TO_EDGE;
+        bitmapWrapT = GL_CLAMP_TO_EDGE;
+
+        colorOp = kColorNone;
+        colorMode = SkXfermode::kClear_Mode;
+
+        framebufferMode = SkXfermode::kClear_Mode;
+        swapSrcDst = false;
+
+        isPoint = false;
+        pointSize = 0.0f;
+    }
+
+    /**
+     * Indicates, for a given color, whether color modulation is required in
+     * the fragment shader. When this method returns true, the program should
+     * be provided with a modulation color.
+     */
+    bool setColor(const float r, const float g, const float b, const float a) {
+        modulate = a < COLOR_COMPONENT_THRESHOLD || r < COLOR_COMPONENT_THRESHOLD ||
+                g < COLOR_COMPONENT_THRESHOLD || b < COLOR_COMPONENT_THRESHOLD;
+        return modulate;
+    }
+
+    /**
+     * Indicates, for a given color, whether color modulation is required in
+     * the fragment shader. When this method returns true, the program should
+     * be provided with a modulation color.
+     */
+    bool setAlpha8Color(const float r, const float g, const float b, const float a) {
+        modulate = a < COLOR_COMPONENT_THRESHOLD || r > COLOR_COMPONENT_INV_THRESHOLD ||
+                g > COLOR_COMPONENT_INV_THRESHOLD || b > COLOR_COMPONENT_INV_THRESHOLD;
+        return modulate;
+    }
+
+    /**
+     * Computes the unique key identifying this program.
+     */
+    programid key() const {
+        programid key = 0;
+        if (hasTexture) key |= PROGRAM_KEY_TEXTURE;
+        if (hasAlpha8Texture) key |= PROGRAM_KEY_A8_TEXTURE;
+        if (hasBitmap) {
+            key |= PROGRAM_KEY_BITMAP;
+            if (isBitmapNpot) {
+                key |= PROGRAM_KEY_BITMAP_NPOT;
+                key |= getEnumForWrap(bitmapWrapS) << PROGRAM_BITMAP_WRAPS_SHIFT;
+                key |= getEnumForWrap(bitmapWrapT) << PROGRAM_BITMAP_WRAPT_SHIFT;
+            }
+        }
+        if (hasGradient) key |= PROGRAM_KEY_GRADIENT;
+        key |= programid(gradientType) << PROGRAM_GRADIENT_TYPE_SHIFT;
+        if (isBitmapFirst) key |= PROGRAM_KEY_BITMAP_FIRST;
+        if (hasBitmap && hasGradient) {
+            key |= (shadersMode & PROGRAM_MAX_XFERMODE) << PROGRAM_XFERMODE_SHADER_SHIFT;
+        }
+        switch (colorOp) {
+            case kColorMatrix:
+                key |= PROGRAM_KEY_COLOR_MATRIX;
+                break;
+            case kColorLighting:
+                key |= PROGRAM_KEY_COLOR_LIGHTING;
+                break;
+            case kColorBlend:
+                key |= PROGRAM_KEY_COLOR_BLEND;
+                key |= (colorMode & PROGRAM_MAX_XFERMODE) << PROGRAM_XFERMODE_COLOR_OP_SHIFT;
+                break;
+            case kColorNone:
+                break;
+        }
+        key |= (framebufferMode & PROGRAM_MAX_XFERMODE) << PROGRAM_XFERMODE_FRAMEBUFFER_SHIFT;
+        if (swapSrcDst) key |= PROGRAM_KEY_SWAP_SRC_DST;
+        if (modulate) key |= programid(0x1) << PROGRAM_MODULATE_SHIFT;
+        if (isPoint) key |= programid(0x1) << PROGRAM_IS_POINT_SHIFT;
+        if (isAA) key |= programid(0x1) << PROGRAM_HAS_AA_SHIFT;
+        if (hasExternalTexture) key |= programid(0x1) << PROGRAM_HAS_EXTERNAL_TEXTURE_SHIFT;
+        if (hasTextureTransform) key |= programid(0x1) << PROGRAM_HAS_TEXTURE_TRANSFORM_SHIFT;
+        return key;
+    }
+
+    /**
+     * Logs the specified message followed by the key identifying this program.
+     */
+    void log(const char* message) const {
+#if DEBUG_PROGRAMS
+        programid k = key();
+        PROGRAM_LOGD("%s (key = 0x%.8x%.8x)", message, uint32_t(k >> 32),
+                uint32_t(k & 0xffffffff));
+#endif
+    }
+
+private:
+    inline uint32_t getEnumForWrap(GLenum wrap) const {
+        switch (wrap) {
+            case GL_CLAMP_TO_EDGE:
+                return 0;
+            case GL_REPEAT:
+                return 1;
+            case GL_MIRRORED_REPEAT:
+                return 2;
+        }
+        return 0;
+    }
+
+}; // struct ProgramDescription
+
 /**
  * A program holds a vertex and a fragment shader. It offers several utility
  * methods to query attributes and uniforms.
@@ -42,7 +290,7 @@
      * Creates a new program with the specified vertex and fragment
      * shaders sources.
      */
-    Program(const char* vertex, const char* fragment);
+    Program(const ProgramDescription& description, const char* vertex, const char* fragment);
     virtual ~Program();
 
     /**
@@ -99,6 +347,11 @@
     int position;
 
     /**
+     * Name of the texCoords attribute if it exists, -1 otherwise.
+     */
+    int texCoords;
+
+    /**
      * Name of the transform uniform.
      */
     int transform;
diff --git a/libs/hwui/ProgramCache.cpp b/libs/hwui/ProgramCache.cpp
index c2383f4..a7f1277 100644
--- a/libs/hwui/ProgramCache.cpp
+++ b/libs/hwui/ProgramCache.cpp
@@ -388,7 +388,7 @@
     String8 vertexShader = generateVertexShader(description);
     String8 fragmentShader = generateFragmentShader(description);
 
-    Program* program = new Program(vertexShader.string(), fragmentShader.string());
+    Program* program = new Program(description, vertexShader.string(), fragmentShader.string());
     return program;
 }
 
diff --git a/libs/hwui/ProgramCache.h b/libs/hwui/ProgramCache.h
index 441db8c..e3ed79e 100644
--- a/libs/hwui/ProgramCache.h
+++ b/libs/hwui/ProgramCache.h
@@ -23,8 +23,6 @@
 
 #include <GLES2/gl2.h>
 
-#include <SkXfermode.h>
-
 #include "Debug.h"
 #include "Program.h"
 #include "Properties.h"
@@ -43,241 +41,11 @@
     #define PROGRAM_LOGD(...)
 #endif
 
-#define COLOR_COMPONENT_THRESHOLD (1.0f - (0.5f / PANEL_BIT_DEPTH))
-#define COLOR_COMPONENT_INV_THRESHOLD (0.5f / PANEL_BIT_DEPTH)
-
-#define PROGRAM_KEY_TEXTURE 0x1
-#define PROGRAM_KEY_A8_TEXTURE 0x2
-#define PROGRAM_KEY_BITMAP 0x4
-#define PROGRAM_KEY_GRADIENT 0x8
-#define PROGRAM_KEY_BITMAP_FIRST 0x10
-#define PROGRAM_KEY_COLOR_MATRIX 0x20
-#define PROGRAM_KEY_COLOR_LIGHTING 0x40
-#define PROGRAM_KEY_COLOR_BLEND 0x80
-#define PROGRAM_KEY_BITMAP_NPOT 0x100
-#define PROGRAM_KEY_SWAP_SRC_DST 0x2000
-
-#define PROGRAM_KEY_BITMAP_WRAPS_MASK 0x600
-#define PROGRAM_KEY_BITMAP_WRAPT_MASK 0x1800
-
-// Encode the xfermodes on 6 bits
-#define PROGRAM_MAX_XFERMODE 0x1f
-#define PROGRAM_XFERMODE_SHADER_SHIFT 26
-#define PROGRAM_XFERMODE_COLOR_OP_SHIFT 20
-#define PROGRAM_XFERMODE_FRAMEBUFFER_SHIFT 14
-
-#define PROGRAM_BITMAP_WRAPS_SHIFT 9
-#define PROGRAM_BITMAP_WRAPT_SHIFT 11
-
-#define PROGRAM_GRADIENT_TYPE_SHIFT 33
-#define PROGRAM_MODULATE_SHIFT 35
-
-#define PROGRAM_IS_POINT_SHIFT 36
-
-#define PROGRAM_HAS_AA_SHIFT 37
-
-#define PROGRAM_HAS_EXTERNAL_TEXTURE_SHIFT 38
-#define PROGRAM_HAS_TEXTURE_TRANSFORM_SHIFT 39
-
-///////////////////////////////////////////////////////////////////////////////
-// Types
-///////////////////////////////////////////////////////////////////////////////
-
-typedef uint64_t programid;
-
 ///////////////////////////////////////////////////////////////////////////////
 // Cache
 ///////////////////////////////////////////////////////////////////////////////
 
 /**
- * Describe the features required for a given program. The features
- * determine the generation of both the vertex and fragment shaders.
- * A ProgramDescription must be used in conjunction with a ProgramCache.
- */
-struct ProgramDescription {
-    enum ColorModifier {
-        kColorNone,
-        kColorMatrix,
-        kColorLighting,
-        kColorBlend
-    };
-
-    enum Gradient {
-        kGradientLinear,
-        kGradientCircular,
-        kGradientSweep
-    };
-
-    ProgramDescription() {
-        reset();
-    }
-
-    // Texturing
-    bool hasTexture;
-    bool hasAlpha8Texture;
-    bool hasExternalTexture;
-    bool hasTextureTransform;
-
-    // Modulate, this should only be set when setColor() return true
-    bool modulate;
-
-    // Shaders
-    bool hasBitmap;
-    bool isBitmapNpot;
-
-    bool isAA;
-
-    bool hasGradient;
-    Gradient gradientType;
-
-    SkXfermode::Mode shadersMode;
-
-    bool isBitmapFirst;
-    GLenum bitmapWrapS;
-    GLenum bitmapWrapT;
-
-    // Color operations
-    ColorModifier colorOp;
-    SkXfermode::Mode colorMode;
-
-    // Framebuffer blending (requires Extensions.hasFramebufferFetch())
-    // Ignored for all values < SkXfermode::kPlus_Mode
-    SkXfermode::Mode framebufferMode;
-    bool swapSrcDst;
-
-    bool isPoint;
-    float pointSize;
-
-    /**
-     * Resets this description. All fields are reset back to the default
-     * values they hold after building a new instance.
-     */
-    void reset() {
-        hasTexture = false;
-        hasAlpha8Texture = false;
-        hasExternalTexture = false;
-        hasTextureTransform = false;
-
-        isAA = false;
-
-        modulate = false;
-
-        hasBitmap = false;
-        isBitmapNpot = false;
-
-        hasGradient = false;
-        gradientType = kGradientLinear;
-
-        shadersMode = SkXfermode::kClear_Mode;
-
-        isBitmapFirst = false;
-        bitmapWrapS = GL_CLAMP_TO_EDGE;
-        bitmapWrapT = GL_CLAMP_TO_EDGE;
-
-        colorOp = kColorNone;
-        colorMode = SkXfermode::kClear_Mode;
-
-        framebufferMode = SkXfermode::kClear_Mode;
-        swapSrcDst = false;
-
-        isPoint = false;
-        pointSize = 0.0f;
-    }
-
-    /**
-     * Indicates, for a given color, whether color modulation is required in
-     * the fragment shader. When this method returns true, the program should
-     * be provided with a modulation color.
-     */
-    bool setColor(const float r, const float g, const float b, const float a) {
-        modulate = a < COLOR_COMPONENT_THRESHOLD || r < COLOR_COMPONENT_THRESHOLD ||
-                g < COLOR_COMPONENT_THRESHOLD || b < COLOR_COMPONENT_THRESHOLD;
-        return modulate;
-    }
-
-    /**
-     * Indicates, for a given color, whether color modulation is required in
-     * the fragment shader. When this method returns true, the program should
-     * be provided with a modulation color.
-     */
-    bool setAlpha8Color(const float r, const float g, const float b, const float a) {
-        modulate = a < COLOR_COMPONENT_THRESHOLD || r > COLOR_COMPONENT_INV_THRESHOLD ||
-                g > COLOR_COMPONENT_INV_THRESHOLD || b > COLOR_COMPONENT_INV_THRESHOLD;
-        return modulate;
-    }
-
-    /**
-     * Computes the unique key identifying this program.
-     */
-    programid key() const {
-        programid key = 0;
-        if (hasTexture) key |= PROGRAM_KEY_TEXTURE;
-        if (hasAlpha8Texture) key |= PROGRAM_KEY_A8_TEXTURE;
-        if (hasBitmap) {
-            key |= PROGRAM_KEY_BITMAP;
-            if (isBitmapNpot) {
-                key |= PROGRAM_KEY_BITMAP_NPOT;
-                key |= getEnumForWrap(bitmapWrapS) << PROGRAM_BITMAP_WRAPS_SHIFT;
-                key |= getEnumForWrap(bitmapWrapT) << PROGRAM_BITMAP_WRAPT_SHIFT;
-            }
-        }
-        if (hasGradient) key |= PROGRAM_KEY_GRADIENT;
-        key |= programid(gradientType) << PROGRAM_GRADIENT_TYPE_SHIFT;
-        if (isBitmapFirst) key |= PROGRAM_KEY_BITMAP_FIRST;
-        if (hasBitmap && hasGradient) {
-            key |= (shadersMode & PROGRAM_MAX_XFERMODE) << PROGRAM_XFERMODE_SHADER_SHIFT;
-        }
-        switch (colorOp) {
-            case kColorMatrix:
-                key |= PROGRAM_KEY_COLOR_MATRIX;
-                break;
-            case kColorLighting:
-                key |= PROGRAM_KEY_COLOR_LIGHTING;
-                break;
-            case kColorBlend:
-                key |= PROGRAM_KEY_COLOR_BLEND;
-                key |= (colorMode & PROGRAM_MAX_XFERMODE) << PROGRAM_XFERMODE_COLOR_OP_SHIFT;
-                break;
-            case kColorNone:
-                break;
-        }
-        key |= (framebufferMode & PROGRAM_MAX_XFERMODE) << PROGRAM_XFERMODE_FRAMEBUFFER_SHIFT;
-        if (swapSrcDst) key |= PROGRAM_KEY_SWAP_SRC_DST;
-        if (modulate) key |= programid(0x1) << PROGRAM_MODULATE_SHIFT;
-        if (isPoint) key |= programid(0x1) << PROGRAM_IS_POINT_SHIFT;
-        if (isAA) key |= programid(0x1) << PROGRAM_HAS_AA_SHIFT;
-        if (hasExternalTexture) key |= programid(0x1) << PROGRAM_HAS_EXTERNAL_TEXTURE_SHIFT;
-        if (hasTextureTransform) key |= programid(0x1) << PROGRAM_HAS_TEXTURE_TRANSFORM_SHIFT;
-        return key;
-    }
-
-    /**
-     * Logs the specified message followed by the key identifying this program.
-     */
-    void log(const char* message) const {
-#if DEBUG_PROGRAMS
-        programid k = key();
-        PROGRAM_LOGD("%s (key = 0x%.8x%.8x)", message, uint32_t(k >> 32),
-                uint32_t(k & 0xffffffff));
-#endif
-    }
-
-private:
-    inline uint32_t getEnumForWrap(GLenum wrap) const {
-        switch (wrap) {
-            case GL_CLAMP_TO_EDGE:
-                return 0;
-            case GL_REPEAT:
-                return 1;
-            case GL_MIRRORED_REPEAT:
-                return 2;
-        }
-        return 0;
-    }
-
-}; // struct ProgramDescription
-
-/**
  * Generates and caches program. Programs are generated based on
  * ProgramDescriptions.
  */