Add rounded rects and circles support to OpenGLRenderer.

Change-Id: I6cedf2b495d58de7c0437096809fa9e4518a1b8c
diff --git a/core/java/android/view/GLES20Canvas.java b/core/java/android/view/GLES20Canvas.java
index 99b686e..d82f051 100644
--- a/core/java/android/view/GLES20Canvas.java
+++ b/core/java/android/view/GLES20Canvas.java
@@ -452,11 +452,14 @@
     @Override
     public int saveLayer(float left, float top, float right, float bottom, Paint paint,
             int saveFlags) {
-        boolean hasColorFilter = paint != null && setupColorFilter(paint);
-        final int nativePaint = paint == null ? 0 : paint.mNativePaint;
-        int count = nSaveLayer(mRenderer, left, top, right, bottom, nativePaint, saveFlags);
-        if (hasColorFilter) nResetModifiers(mRenderer);
-        return count;
+        if (left < right && top < bottom) {
+            boolean hasColorFilter = paint != null && setupColorFilter(paint);
+            final int nativePaint = paint == null ? 0 : paint.mNativePaint;
+            int count = nSaveLayer(mRenderer, left, top, right, bottom, nativePaint, saveFlags);
+            if (hasColorFilter) nResetModifiers(mRenderer);
+            return count;
+        }
+        return save(saveFlags);
     }
 
     private native int nSaveLayer(int renderer, float left, float top, float right, float bottom,
@@ -471,7 +474,10 @@
     @Override
     public int saveLayerAlpha(float left, float top, float right, float bottom, int alpha,
             int saveFlags) {
-        return nSaveLayerAlpha(mRenderer, left, top, right, bottom, alpha, saveFlags);
+        if (left < right && top < bottom) {
+            return nSaveLayerAlpha(mRenderer, left, top, right, bottom, alpha, saveFlags);
+        }
+        return save(saveFlags);
     }
 
     private native int nSaveLayerAlpha(int renderer, float left, float top, float right,
@@ -629,9 +635,13 @@
 
     @Override
     public void drawCircle(float cx, float cy, float radius, Paint paint) {
-        throw new UnsupportedOperationException();
+        boolean hasModifier = setupModifiers(paint);
+        nDrawCircle(mRenderer, cx, cy, radius, paint.mNativePaint);
+        if (hasModifier) nResetModifiers(mRenderer);        
     }
 
+    private native void nDrawCircle(int renderer, float cx, float cy, float radius, int paint);
+
     @Override
     public void drawColor(int color) {
         drawColor(color, PorterDuff.Mode.SRC_OVER);
@@ -767,9 +777,15 @@
 
     @Override
     public void drawRoundRect(RectF rect, float rx, float ry, Paint paint) {
-        // TODO: Implement
+        boolean hasModifier = setupModifiers(paint);
+        nDrawRoundRect(mRenderer, rect.left, rect.top, rect.right, rect.bottom,
+                rx, ry, paint.mNativePaint);
+        if (hasModifier) nResetModifiers(mRenderer);        
     }
 
+    private native void nDrawRoundRect(int renderer, float left, float top,
+            float right, float bottom, float rx, float y, int paint);
+
     @Override
     public void drawText(char[] text, int index, int count, float x, float y, Paint paint) {
         if ((index | count | (index + count) | (text.length - index - count)) < 0) {
diff --git a/core/jni/android_view_GLES20Canvas.cpp b/core/jni/android_view_GLES20Canvas.cpp
index ac491ea..bfd2b58e 100644
--- a/core/jni/android_view_GLES20Canvas.cpp
+++ b/core/jni/android_view_GLES20Canvas.cpp
@@ -299,6 +299,17 @@
     renderer->drawRect(left, top, right, bottom, paint);
 }
 
+static void android_view_GLES20Canvas_drawRoundRect(JNIEnv* env, jobject canvas,
+        OpenGLRenderer* renderer, jfloat left, jfloat top, jfloat right, jfloat bottom,
+        jfloat rx, jfloat ry, SkPaint* paint) {
+    renderer->drawRoundRect(left, top, right, bottom, rx, ry, paint);
+}
+
+static void android_view_GLES20Canvas_drawCircle(JNIEnv* env, jobject canvas,
+        OpenGLRenderer* renderer, jfloat x, jfloat y, jfloat radius, SkPaint* paint) {
+    renderer->drawCircle(x, y, radius, paint);
+}
+
 static void android_view_GLES20Canvas_drawRects(JNIEnv* env, jobject canvas,
         OpenGLRenderer* renderer, SkRegion* region, SkPaint* paint) {
     SkRegion::Iterator it(*region);
@@ -570,6 +581,8 @@
     { "nDrawColor",         "(III)V",          (void*) android_view_GLES20Canvas_drawColor },
     { "nDrawRect",          "(IFFFFI)V",       (void*) android_view_GLES20Canvas_drawRect },
     { "nDrawRects",         "(III)V",          (void*) android_view_GLES20Canvas_drawRects },
+    { "nDrawRoundRect",     "(IFFFFFFI)V",     (void*) android_view_GLES20Canvas_drawRoundRect },
+    { "nDrawCircle",        "(IFFFI)V",        (void*) android_view_GLES20Canvas_drawCircle },
     { "nDrawPath",          "(III)V",          (void*) android_view_GLES20Canvas_drawPath },
     { "nDrawLines",         "(I[FIII)V",       (void*) android_view_GLES20Canvas_drawLines },
 
diff --git a/libs/hwui/Android.mk b/libs/hwui/Android.mk
index c49be93..38e0848 100644
--- a/libs/hwui/Android.mk
+++ b/libs/hwui/Android.mk
@@ -23,6 +23,7 @@
 		Program.cpp \
 		ProgramCache.cpp \
 		ResourceCache.cpp \
+		ShapeCache.cpp \
 		SkiaColorFilter.cpp \
 		SkiaShader.cpp \
 		TextureCache.cpp \
diff --git a/libs/hwui/Caches.cpp b/libs/hwui/Caches.cpp
index fde4f96..bffab95 100644
--- a/libs/hwui/Caches.cpp
+++ b/libs/hwui/Caches.cpp
@@ -73,6 +73,10 @@
     LOGD("  LayerCache           %8d / %8d", layerCache.getSize(), layerCache.getMaxSize());
     LOGD("  GradientCache        %8d / %8d", gradientCache.getSize(), gradientCache.getMaxSize());
     LOGD("  PathCache            %8d / %8d", pathCache.getSize(), pathCache.getMaxSize());
+    LOGD("  CircleShapeCache     %8d / %8d",
+            circleShapeCache.getSize(), circleShapeCache.getMaxSize());
+    LOGD("  RoundRectShapeCache  %8d / %8d",
+            roundRectShapeCache.getSize(), roundRectShapeCache.getMaxSize());
     LOGD("  TextDropShadowCache  %8d / %8d", dropShadowCache.getSize(),
             dropShadowCache.getMaxSize());
     for (uint32_t i = 0; i < fontRenderer.getFontRendererCount(); i++) {
diff --git a/libs/hwui/Caches.h b/libs/hwui/Caches.h
index a11b6bc..c03c347 100644
--- a/libs/hwui/Caches.h
+++ b/libs/hwui/Caches.h
@@ -32,6 +32,7 @@
 #include "PatchCache.h"
 #include "ProgramCache.h"
 #include "PathCache.h"
+#include "ShapeCache.h"
 #include "TextDropShadowCache.h"
 #include "FboCache.h"
 #include "ResourceCache.h"
@@ -159,6 +160,8 @@
     GradientCache gradientCache;
     ProgramCache programCache;
     PathCache pathCache;
+    RoundRectShapeCache roundRectShapeCache;
+    CircleShapeCache circleShapeCache;
     PatchCache patchCache;
     TextDropShadowCache dropShadowCache;
     FboCache fboCache;
diff --git a/libs/hwui/Debug.h b/libs/hwui/Debug.h
index 71ec760..014519e 100644
--- a/libs/hwui/Debug.h
+++ b/libs/hwui/Debug.h
@@ -45,6 +45,9 @@
 // Turn on to display debug info about paths
 #define DEBUG_PATHS 0
 
+// Turn on to display debug info about shapes
+#define DEBUG_SHAPES 0
+
 // Turn on to display debug info about textures
 #define DEBUG_TEXTURES 0
 
diff --git a/libs/hwui/DisplayListRenderer.cpp b/libs/hwui/DisplayListRenderer.cpp
index ade85e5..a74a95f 100644
--- a/libs/hwui/DisplayListRenderer.cpp
+++ b/libs/hwui/DisplayListRenderer.cpp
@@ -103,6 +103,8 @@
     "DrawPatch",
     "DrawColor",
     "DrawRect",
+    "DrawRoundRect",
+    "DrawCircle",
     "DrawPath",
     "DrawLines",
     "DrawText",
@@ -332,6 +334,15 @@
                 renderer.drawRect(getFloat(), getFloat(), getFloat(), getFloat(), getPaint());
             }
             break;
+            case DrawRoundRect: {
+                renderer.drawRoundRect(getFloat(), getFloat(), getFloat(), getFloat(),
+                        getFloat(), getFloat(), getPaint());
+            }
+            break;
+            case DrawCircle: {
+                renderer.drawCircle(getFloat(), getFloat(), getFloat(), getPaint());
+            }
+            break;
             case DrawPath: {
                 renderer.drawPath(getPath(), getPaint());
             }
@@ -601,6 +612,21 @@
     addPaint(paint);
 }
 
+void DisplayListRenderer::drawRoundRect(float left, float top, float right, float bottom,
+            float rx, float ry, SkPaint* paint) {
+    addOp(DisplayList::DrawRoundRect);
+    addBounds(left, top, right, bottom);
+    addPoint(rx, ry);
+    addPaint(paint);
+}
+
+void DisplayListRenderer::drawCircle(float x, float y, float radius, SkPaint* paint) {
+    addOp(DisplayList::DrawCircle);
+    addPoint(x, y);
+    addFloat(radius);
+    addPaint(paint);
+}
+
 void DisplayListRenderer::drawPath(SkPath* path, SkPaint* paint) {
     addOp(DisplayList::DrawPath);
     addPath(path);
diff --git a/libs/hwui/DisplayListRenderer.h b/libs/hwui/DisplayListRenderer.h
index 05864ec..4b727f6 100644
--- a/libs/hwui/DisplayListRenderer.h
+++ b/libs/hwui/DisplayListRenderer.h
@@ -110,6 +110,8 @@
         DrawPatch,
         DrawColor,
         DrawRect,
+        DrawRoundRect,
+        DrawCircle,
         DrawPath,
         DrawLines,
         DrawText,
@@ -270,6 +272,9 @@
             float left, float top, float right, float bottom, SkPaint* paint);
     void drawColor(int color, SkXfermode::Mode mode);
     void drawRect(float left, float top, float right, float bottom, SkPaint* paint);
+    void drawRoundRect(float left, float top, float right, float bottom,
+            float rx, float ry, SkPaint* paint);
+    void drawCircle(float x, float y, float radius, SkPaint* paint);
     void drawPath(SkPath* path, SkPaint* paint);
     void drawLines(float* points, int count, SkPaint* paint);
     void drawText(const char* text, int bytesCount, int count, float x, float y, SkPaint* paint);
diff --git a/libs/hwui/OpenGLRenderer.cpp b/libs/hwui/OpenGLRenderer.cpp
index 2c50ac3..91ac166 100644
--- a/libs/hwui/OpenGLRenderer.cpp
+++ b/libs/hwui/OpenGLRenderer.cpp
@@ -1303,6 +1303,38 @@
     drawColorRect(clip.left, clip.top, clip.right, clip.bottom, color, mode, true);
 }
 
+void OpenGLRenderer::drawRoundRect(float left, float top, float right, float bottom,
+        float rx, float ry, SkPaint* paint) {
+    if (mSnapshot->isIgnored()) return;
+
+    glActiveTexture(gTextureUnits[0]);
+
+    const PathTexture* texture = mCaches.roundRectShapeCache.getRoundRect(
+            right - left, bottom - top, rx, ry, paint);
+    if (!texture) return;
+    const AutoTexture autoCleanup(texture);
+
+    const float x = left + texture->left - texture->offset;
+    const float y = top + texture->top - texture->offset;
+
+    drawPathTexture(texture, x, y, paint);
+}
+
+void OpenGLRenderer::drawCircle(float x, float y, float radius, SkPaint* paint) {
+    if (mSnapshot->isIgnored()) return;
+
+    glActiveTexture(gTextureUnits[0]);
+
+    const PathTexture* texture = mCaches.circleShapeCache.getCircle(radius, paint);
+    if (!texture) return;
+    const AutoTexture autoCleanup(texture);
+
+    const float left = (x - radius) + texture->left - texture->offset;
+    const float top = (y - radius) + texture->top - texture->offset;
+
+    drawPathTexture(texture, left, top, paint);
+}
+
 void OpenGLRenderer::drawRect(float left, float top, float right, float bottom, SkPaint* p) {
     if (quickReject(left, top, right, bottom)) {
         return;
@@ -1451,8 +1483,7 @@
 void OpenGLRenderer::drawPath(SkPath* path, SkPaint* paint) {
     if (mSnapshot->isIgnored()) return;
 
-    GLuint textureUnit = 0;
-    glActiveTexture(gTextureUnits[textureUnit]);
+    glActiveTexture(gTextureUnits[0]);
 
     const PathTexture* texture = mCaches.pathCache.get(path, paint);
     if (!texture) return;
@@ -1461,31 +1492,7 @@
     const float x = texture->left - texture->offset;
     const float y = texture->top - texture->offset;
 
-    if (quickReject(x, y, x + texture->width, y + texture->height)) {
-        return;
-    }
-
-    int alpha;
-    SkXfermode::Mode mode;
-    getAlphaAndMode(paint, &alpha, &mode);
-
-    setupDraw();
-    setupDrawWithTexture(true);
-    setupDrawAlpha8Color(paint->getColor(), alpha);
-    setupDrawColorFilter();
-    setupDrawShader();
-    setupDrawBlending(true, mode);
-    setupDrawProgram();
-    setupDrawModelView(x, y, x + texture->width, y + texture->height);
-    setupDrawTexture(texture->id);
-    setupDrawPureColorUniforms();
-    setupDrawColorFilterUniforms();
-    setupDrawShaderUniforms();
-    setupDrawMesh(NULL, (GLvoid*) gMeshTextureOffset);
-
-    glDrawArrays(GL_TRIANGLE_STRIP, 0, gMeshCount);
-
-    finishDrawTexture();
+    drawPathTexture(texture, x, y, paint);
 }
 
 void OpenGLRenderer::drawLayer(Layer* layer, float x, float y, SkPaint* paint) {
@@ -1581,6 +1588,35 @@
 // Drawing implementation
 ///////////////////////////////////////////////////////////////////////////////
 
+void OpenGLRenderer::drawPathTexture(const PathTexture* texture,
+        float x, float y, SkPaint* paint) {
+    if (quickReject(x, y, x + texture->width, y + texture->height)) {
+        return;
+    }
+
+    int alpha;
+    SkXfermode::Mode mode;
+    getAlphaAndMode(paint, &alpha, &mode);
+
+    setupDraw();
+    setupDrawWithTexture(true);
+    setupDrawAlpha8Color(paint->getColor(), alpha);
+    setupDrawColorFilter();
+    setupDrawShader();
+    setupDrawBlending(true, mode);
+    setupDrawProgram();
+    setupDrawModelView(x, y, x + texture->width, y + texture->height);
+    setupDrawTexture(texture->id);
+    setupDrawPureColorUniforms();
+    setupDrawColorFilterUniforms();
+    setupDrawShaderUniforms();
+    setupDrawMesh(NULL, (GLvoid*) gMeshTextureOffset);
+
+    glDrawArrays(GL_TRIANGLE_STRIP, 0, gMeshCount);
+
+    finishDrawTexture();
+}
+
 // Same values used by Skia
 #define kStdStrikeThru_Offset   (-6.0f / 21.0f)
 #define kStdUnderline_Offset    (1.0f / 9.0f)
diff --git a/libs/hwui/OpenGLRenderer.h b/libs/hwui/OpenGLRenderer.h
index 4150ddc..a43660b 100644
--- a/libs/hwui/OpenGLRenderer.h
+++ b/libs/hwui/OpenGLRenderer.h
@@ -107,6 +107,9 @@
             float left, float top, float right, float bottom, SkPaint* paint);
     virtual void drawColor(int color, SkXfermode::Mode mode);
     virtual void drawRect(float left, float top, float right, float bottom, SkPaint* paint);
+    virtual void drawRoundRect(float left, float top, float right, float bottom,
+            float rx, float ry, SkPaint* paint);
+    virtual void drawCircle(float x, float y, float radius, SkPaint* paint);
     virtual void drawPath(SkPath* path, SkPaint* paint);
     virtual void drawLines(float* points, int count, SkPaint* paint);
     virtual void drawText(const char* text, int bytesCount, int count, float x, float y,
@@ -343,6 +346,8 @@
     void drawTextDecorations(const char* text, int bytesCount, float length,
             float x, float y, SkPaint* paint);
 
+    void drawPathTexture(const PathTexture* texture, float x, float y, SkPaint* paint);
+
     /**
      * Resets the texture coordinates stored in mMeshVertices. Setting the values
      * back to default is achieved by calling:
diff --git a/libs/hwui/Properties.h b/libs/hwui/Properties.h
index 6a0d7ea..2f230b5 100644
--- a/libs/hwui/Properties.h
+++ b/libs/hwui/Properties.h
@@ -48,6 +48,7 @@
 #define PROPERTY_LAYER_CACHE_SIZE "ro.hwui.layer_cache_size"
 #define PROPERTY_GRADIENT_CACHE_SIZE "ro.hwui.gradient_cache_size"
 #define PROPERTY_PATH_CACHE_SIZE "ro.hwui.path_cache_size"
+#define PROPERTY_SHAPE_CACHE_SIZE "ro.hwui.shape_cache_size"
 #define PROPERTY_DROP_SHADOW_CACHE_SIZE "ro.hwui.drop_shadow_cache_size"
 #define PROPERTY_FBO_CACHE_SIZE "ro.hwui.fbo_cache_size"
 
@@ -66,6 +67,7 @@
 #define DEFAULT_TEXTURE_CACHE_SIZE 20.0f
 #define DEFAULT_LAYER_CACHE_SIZE 8.0f
 #define DEFAULT_PATH_CACHE_SIZE 4.0f
+#define DEFAULT_SHAPE_CACHE_SIZE 1.0f
 #define DEFAULT_PATCH_CACHE_SIZE 512
 #define DEFAULT_GRADIENT_CACHE_SIZE 0.5f
 #define DEFAULT_DROP_SHADOW_CACHE_SIZE 2.0f
diff --git a/libs/hwui/ShapeCache.cpp b/libs/hwui/ShapeCache.cpp
new file mode 100644
index 0000000..ffa18e3
--- /dev/null
+++ b/libs/hwui/ShapeCache.cpp
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2011 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.
+ */
+
+#define LOG_TAG "OpenGLRenderer"
+
+#include "ShapeCache.h"
+
+namespace android {
+namespace uirenderer {
+
+RoundRectShapeCache::RoundRectShapeCache(): ShapeCache<RoundRectShapeCacheEntry>() {
+}
+
+PathTexture* RoundRectShapeCache::getRoundRect(float width, float height,
+        float rx, float ry, SkPaint* paint) {
+    RoundRectShapeCacheEntry entry(width, height, rx, ry, paint);
+    PathTexture* texture = get(entry);
+
+    if (!texture) {
+        SkPath path;
+        SkRect r;
+        r.set(0.0f, 0.0f, width, height);
+        path.addRoundRect(r, rx, ry, SkPath::kCW_Direction);
+
+        texture = addTexture(entry, &path, paint);
+    }
+
+    return texture;
+}
+
+CircleShapeCache::CircleShapeCache(): ShapeCache<CircleShapeCacheEntry>() {
+}
+
+PathTexture* CircleShapeCache::getCircle(float radius, SkPaint* paint) {
+    CircleShapeCacheEntry entry(radius, paint);
+    PathTexture* texture = get(entry);
+
+    if (!texture) {
+        SkPath path;
+        path.addCircle(radius, radius, radius, SkPath::kCW_Direction);
+
+        texture = addTexture(entry, &path, paint);
+    }
+
+    return texture;
+}
+
+}; // namespace uirenderer
+}; // namespace android
diff --git a/libs/hwui/ShapeCache.h b/libs/hwui/ShapeCache.h
new file mode 100644
index 0000000..910d01d
--- /dev/null
+++ b/libs/hwui/ShapeCache.h
@@ -0,0 +1,464 @@
+/*
+ * Copyright (C) 2011 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_SHAPE_CACHE_H
+#define ANDROID_HWUI_SHAPE_CACHE_H
+
+#include <GLES2/gl2.h>
+
+#include <SkCanvas.h>
+#include <SkRect.h>
+
+#include "PathCache.h"
+#include "Properties.h"
+
+namespace android {
+namespace uirenderer {
+
+///////////////////////////////////////////////////////////////////////////////
+// Defines
+///////////////////////////////////////////////////////////////////////////////
+
+// Debug
+#if DEBUG_SHAPES
+    #define SHAPE_LOGD(...) LOGD(__VA_ARGS__)
+#else
+    #define SHAPE_LOGD(...)
+#endif
+
+///////////////////////////////////////////////////////////////////////////////
+// Classes
+///////////////////////////////////////////////////////////////////////////////
+
+/**
+ * Describe a shape in the shape cache.
+ */
+struct ShapeCacheEntry {
+    enum ShapeType {
+        kShapeNone,
+        kShapeRoundRect,
+        kShapeCircle,
+        kShapeOval,
+        kShapeArc
+    };
+
+    ShapeCacheEntry() {
+        shapeType = kShapeNone;
+        join = SkPaint::kDefault_Join;
+        cap = SkPaint::kDefault_Cap;
+        style = SkPaint::kFill_Style;
+        miter = 4.0f;
+        strokeWidth = 1.0f;
+    }
+
+    ShapeCacheEntry(const ShapeCacheEntry& entry):
+        shapeType(entry.shapeType), join(entry.join), cap(entry.cap),
+        style(entry.style), miter(entry.miter),
+        strokeWidth(entry.strokeWidth) {
+    }
+
+    ShapeCacheEntry(ShapeType type, SkPaint* paint) {
+        shapeType = type;
+        join = paint->getStrokeJoin();
+        cap = paint->getStrokeCap();
+        float v = paint->getStrokeMiter();
+        miter = *(uint32_t*) &v;
+        v = paint->getStrokeWidth();
+        strokeWidth = *(uint32_t*) &v;
+        style = paint->getStyle();
+    }
+
+    virtual ~ShapeCacheEntry() {
+    }
+
+    // shapeType must be checked in subclasses operator<
+    ShapeType shapeType;
+    SkPaint::Join join;
+    SkPaint::Cap cap;
+    SkPaint::Style style;
+    uint32_t miter;
+    uint32_t strokeWidth;
+
+    bool operator<(const ShapeCacheEntry& rhs) const {
+        LTE_INT(shapeType) {
+            LTE_INT(join) {
+                LTE_INT(cap) {
+                    LTE_INT(style) {
+                        LTE_INT(miter) {
+                            LTE_INT(strokeWidth) {
+                                return lessThan(rhs);
+                            }
+                        }
+                    }
+                }
+            }
+        }
+        return false;
+    }
+
+protected:
+    virtual bool lessThan(const ShapeCacheEntry& rhs) const {
+        return false;
+    }
+}; // struct ShapeCacheEntry
+
+
+struct RoundRectShapeCacheEntry: public ShapeCacheEntry {
+    RoundRectShapeCacheEntry(float width, float height, float rx, float ry, SkPaint* paint):
+            ShapeCacheEntry(ShapeCacheEntry::kShapeRoundRect, paint) {
+        mWidth = *(uint32_t*) &width;
+        mHeight = *(uint32_t*) &height;
+        mRx = *(uint32_t*) &rx;
+        mRy = *(uint32_t*) &ry;
+    }
+
+    RoundRectShapeCacheEntry(): ShapeCacheEntry() {
+        mWidth = 0;
+        mHeight = 0;
+        mRx = 0;
+        mRy = 0;
+    }
+
+    RoundRectShapeCacheEntry(const RoundRectShapeCacheEntry& entry):
+            ShapeCacheEntry(entry) {
+        mWidth = entry.mWidth;
+        mHeight = entry.mHeight;
+        mRx = entry.mRx;
+        mRy = entry.mRy;
+    }
+
+    bool lessThan(const ShapeCacheEntry& r) const {
+        const RoundRectShapeCacheEntry& rhs = (const RoundRectShapeCacheEntry&) r;
+        LTE_INT(mWidth) {
+            LTE_INT(mHeight) {
+                LTE_INT(mRx) {
+                    LTE_INT(mRy) {
+                        return false;
+                    }
+                }
+            }
+        }
+        return false;
+    }
+
+private:
+    uint32_t mWidth;
+    uint32_t mHeight;
+    uint32_t mRx;
+    uint32_t mRy;
+}; // RoundRectShapeCacheEntry
+
+struct CircleShapeCacheEntry: public ShapeCacheEntry {
+    CircleShapeCacheEntry(float radius, SkPaint* paint):
+            ShapeCacheEntry(ShapeCacheEntry::kShapeCircle, paint) {
+        mRadius = *(uint32_t*) &radius;
+    }
+
+    CircleShapeCacheEntry(): ShapeCacheEntry() {
+        mRadius = 0;
+    }
+
+    CircleShapeCacheEntry(const CircleShapeCacheEntry& entry):
+            ShapeCacheEntry(entry) {
+        mRadius = entry.mRadius;
+    }
+
+    bool lessThan(const ShapeCacheEntry& r) const {
+        const CircleShapeCacheEntry& rhs = (const CircleShapeCacheEntry&) r;
+        LTE_INT(mRadius) {
+            return false;
+        }
+        return false;
+    }
+
+private:
+    uint32_t mRadius;
+}; // CircleShapeCacheEntry
+
+/**
+ * A simple LRU shape cache. The cache has a maximum size expressed in bytes.
+ * 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.
+ */
+template<typename Entry>
+class ShapeCache: public OnEntryRemoved<Entry, PathTexture*> {
+public:
+    ShapeCache();
+    ShapeCache(uint32_t maxByteSize);
+    ~ShapeCache();
+
+    /**
+     * Used as a callback when an entry is removed from the cache.
+     * Do not invoke directly.
+     */
+    void operator()(Entry& path, PathTexture*& texture);
+
+    /**
+     * Clears the cache. This causes all textures to be deleted.
+     */
+    void clear();
+
+    /**
+     * Sets the maximum size of the cache in bytes.
+     */
+    void setMaxSize(uint32_t maxSize);
+    /**
+     * Returns the maximum size of the cache in bytes.
+     */
+    uint32_t getMaxSize();
+    /**
+     * Returns the current size of the cache in bytes.
+     */
+    uint32_t getSize();
+
+protected:
+    PathTexture* addTexture(const Entry& entry, const SkPath *path, const SkPaint* paint);
+
+    PathTexture* get(Entry entry) {
+        return mCache.get(entry);
+    }
+
+private:
+    /**
+     * Generates the texture from a bitmap into the specified texture structure.
+     */
+    void generateTexture(SkBitmap& bitmap, Texture* texture);
+
+    void removeTexture(PathTexture* texture);
+
+    void init();
+
+    GenerationCache<Entry, PathTexture*> mCache;
+    uint32_t mSize;
+    uint32_t mMaxSize;
+    GLuint mMaxTextureSize;
+
+    bool mDebugEnabled;
+}; // class ShapeCache
+
+class RoundRectShapeCache: public ShapeCache<RoundRectShapeCacheEntry> {
+public:
+    RoundRectShapeCache();
+
+    PathTexture* getRoundRect(float width, float height, float rx, float ry, SkPaint* paint);
+}; // class RoundRectShapeCache
+
+class CircleShapeCache: public ShapeCache<CircleShapeCacheEntry> {
+public:
+    CircleShapeCache();
+
+    PathTexture* getCircle(float radius, SkPaint* paint);
+}; // class RoundRectShapeCache
+
+
+///////////////////////////////////////////////////////////////////////////////
+// Constructors/destructor
+///////////////////////////////////////////////////////////////////////////////
+
+template<class Entry>
+ShapeCache<Entry>::ShapeCache():
+        mCache(GenerationCache<ShapeCacheEntry, PathTexture*>::kUnlimitedCapacity),
+        mSize(0), mMaxSize(MB(DEFAULT_SHAPE_CACHE_SIZE)) {
+    char property[PROPERTY_VALUE_MAX];
+    if (property_get(PROPERTY_SHAPE_CACHE_SIZE, property, NULL) > 0) {
+        LOGD("  Setting shape cache size to %sMB", property);
+        setMaxSize(MB(atof(property)));
+    } else {
+        LOGD("  Using default shape cache size of %.2fMB", DEFAULT_SHAPE_CACHE_SIZE);
+    }
+    init();
+}
+
+template<class Entry>
+ShapeCache<Entry>::ShapeCache(uint32_t maxByteSize):
+        mCache(GenerationCache<ShapeCacheEntry, PathTexture*>::kUnlimitedCapacity),
+        mSize(0), mMaxSize(maxByteSize) {
+    init();
+}
+
+template<class Entry>
+ShapeCache<Entry>::~ShapeCache() {
+    mCache.clear();
+}
+
+template<class Entry>
+void ShapeCache<Entry>::init() {
+    mCache.setOnEntryRemovedListener(this);
+
+    GLint maxTextureSize;
+    glGetIntegerv(GL_MAX_TEXTURE_SIZE, &maxTextureSize);
+    mMaxTextureSize = maxTextureSize;
+
+    mDebugEnabled = readDebugLevel() & kDebugCaches;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// Size management
+///////////////////////////////////////////////////////////////////////////////
+
+template<class Entry>
+uint32_t ShapeCache<Entry>::getSize() {
+    return mSize;
+}
+
+template<class Entry>
+uint32_t ShapeCache<Entry>::getMaxSize() {
+    return mMaxSize;
+}
+
+template<class Entry>
+void ShapeCache<Entry>::setMaxSize(uint32_t maxSize) {
+    mMaxSize = maxSize;
+    while (mSize > mMaxSize) {
+        mCache.removeOldest();
+    }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// Callbacks
+///////////////////////////////////////////////////////////////////////////////
+
+template<class Entry>
+void ShapeCache<Entry>::operator()(Entry& path, PathTexture*& texture) {
+    removeTexture(texture);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// Caching
+///////////////////////////////////////////////////////////////////////////////
+
+template<class Entry>
+void ShapeCache<Entry>::removeTexture(PathTexture* texture) {
+    if (texture) {
+        const uint32_t size = texture->width * texture->height;
+        mSize -= size;
+
+        SHAPE_LOGD("ShapeCache::callback: delete path: name, size, mSize = %d, %d, %d",
+                texture->id, size, mSize);
+        if (mDebugEnabled) {
+            LOGD("Path deleted, size = %d", size);
+        }
+
+        glDeleteTextures(1, &texture->id);
+        delete texture;
+    }
+}
+
+template<class Entry>
+PathTexture* ShapeCache<Entry>::addTexture(const Entry& entry, const SkPath *path,
+        const SkPaint* paint) {
+    const SkRect& bounds = path->getBounds();
+
+    const float pathWidth = fmax(bounds.width(), 1.0f);
+    const float pathHeight = fmax(bounds.height(), 1.0f);
+
+    if (pathWidth > mMaxTextureSize || pathHeight > mMaxTextureSize) {
+        LOGW("Shape too large to be rendered into a texture");
+        return NULL;
+    }
+
+    const float offset = paint->getStrokeWidth() * 1.5f;
+    const uint32_t width = uint32_t(pathWidth + offset * 2.0 + 0.5);
+    const uint32_t height = uint32_t(pathHeight + offset * 2.0 + 0.5);
+
+    const uint32_t size = width * height;
+    // Don't even try to cache a bitmap that's bigger than the cache
+    if (size < mMaxSize) {
+        while (mSize + size > mMaxSize) {
+            mCache.removeOldest();
+        }
+    }
+
+    PathTexture* texture = new PathTexture;
+    texture->left = bounds.fLeft;
+    texture->top = bounds.fTop;
+    texture->offset = offset;
+    texture->width = width;
+    texture->height = height;
+    texture->generation = path->getGenerationID();
+
+    SkBitmap bitmap;
+    bitmap.setConfig(SkBitmap::kA8_Config, width, height);
+    bitmap.allocPixels();
+    bitmap.eraseColor(0);
+
+    SkPaint pathPaint(*paint);
+
+    // Make sure the paint is opaque, color, alpha, filter, etc.
+    // will be applied later when compositing the alpha8 texture
+    pathPaint.setColor(0xff000000);
+    pathPaint.setAlpha(255);
+    pathPaint.setColorFilter(NULL);
+    pathPaint.setMaskFilter(NULL);
+    pathPaint.setShader(NULL);
+    SkXfermode* mode = SkXfermode::Create(SkXfermode::kSrc_Mode);
+    pathPaint.setXfermode(mode)->safeUnref();
+
+    SkCanvas canvas(bitmap);
+    canvas.translate(-bounds.fLeft + offset, -bounds.fTop + offset);
+    canvas.drawPath(*path, pathPaint);
+
+    generateTexture(bitmap, texture);
+
+    if (size < mMaxSize) {
+        mSize += size;
+        SHAPE_LOGD("ShapeCache::get: create path: name, size, mSize = %d, %d, %d",
+                texture->id, size, mSize);
+        if (mDebugEnabled) {
+            LOGD("Shape created, size = %d", size);
+        }
+        mCache.put(entry, texture);
+    } else {
+        texture->cleanup = true;
+    }
+
+    return texture;
+}
+
+template<class Entry>
+void ShapeCache<Entry>::clear() {
+    mCache.clear();
+}
+
+template<class Entry>
+void ShapeCache<Entry>::generateTexture(SkBitmap& bitmap, Texture* texture) {
+    SkAutoLockPixels alp(bitmap);
+    if (!bitmap.readyToDraw()) {
+        LOGE("Cannot generate texture from bitmap");
+        return;
+    }
+
+    glGenTextures(1, &texture->id);
+
+    glBindTexture(GL_TEXTURE_2D, texture->id);
+    // Textures are Alpha8
+    glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
+
+    texture->blend = true;
+    glTexImage2D(GL_TEXTURE_2D, 0, GL_ALPHA, texture->width, texture->height, 0,
+            GL_ALPHA, GL_UNSIGNED_BYTE, bitmap.getPixels());
+
+    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+
+    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+}
+
+}; // namespace uirenderer
+}; // namespace android
+
+#endif // ANDROID_HWUI_SHAPE_CACHE_H
diff --git a/tests/HwAccelerationTest/AndroidManifest.xml b/tests/HwAccelerationTest/AndroidManifest.xml
index 7a61b3c..1d67964 100644
--- a/tests/HwAccelerationTest/AndroidManifest.xml
+++ b/tests/HwAccelerationTest/AndroidManifest.xml
@@ -25,6 +25,15 @@
         android:hardwareAccelerated="true">
 
         <activity
+                android:name="ShapesActivity"
+                android:label="_Shapes">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+        
+        <activity
                 android:name="SimplePatchActivity"
                 android:label="_SimplePatch"
                 android:theme="@android:style/Theme.Translucent.NoTitleBar">
diff --git a/tests/HwAccelerationTest/src/com/android/test/hwui/ShapesActivity.java b/tests/HwAccelerationTest/src/com/android/test/hwui/ShapesActivity.java
new file mode 100644
index 0000000..536a669
--- /dev/null
+++ b/tests/HwAccelerationTest/src/com/android/test/hwui/ShapesActivity.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2010 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.
+ */
+
+package com.android.test.hwui;
+
+import android.app.Activity;
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.RectF;
+import android.os.Bundle;
+import android.view.View;
+
+@SuppressWarnings({"UnusedDeclaration"})
+public class ShapesActivity extends Activity {
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        setContentView(new ShapesView(this));
+    }
+
+    static class ShapesView extends View {
+        private Paint mNormalPaint;
+        private Paint mStrokePaint;
+        private Paint mFillPaint;
+        private RectF mRect;
+
+        ShapesView(Context c) {
+            super(c);
+
+            mRect = new RectF(0.0f, 0.0f, 160.0f, 90.0f);
+
+            mNormalPaint = new Paint();
+            mNormalPaint.setAntiAlias(true);
+            mNormalPaint.setColor(0xff0000ff);
+            mNormalPaint.setStrokeWidth(6.0f);
+            mNormalPaint.setStyle(Paint.Style.FILL_AND_STROKE);
+
+            mStrokePaint = new Paint();
+            mStrokePaint.setAntiAlias(true);
+            mStrokePaint.setColor(0xff0000ff);
+            mStrokePaint.setStrokeWidth(6.0f);
+            mStrokePaint.setStyle(Paint.Style.STROKE);
+            
+            mFillPaint = new Paint();
+            mFillPaint.setAntiAlias(true);
+            mFillPaint.setColor(0xff0000ff);
+            mFillPaint.setStyle(Paint.Style.FILL);
+        }
+
+        @Override
+        protected void onDraw(Canvas canvas) {
+            super.onDraw(canvas);
+
+            canvas.save();
+            canvas.translate(50.0f, 50.0f);
+            canvas.drawRoundRect(mRect, 6.0f, 6.0f, mNormalPaint);
+
+            canvas.translate(0.0f, 110.0f);
+            canvas.drawRoundRect(mRect, 6.0f, 6.0f, mStrokePaint);
+
+            canvas.translate(0.0f, 110.0f);
+            canvas.drawRoundRect(mRect, 6.0f, 6.0f, mFillPaint);
+            canvas.restore();
+
+            canvas.save();
+            canvas.translate(250.0f, 50.0f);
+            canvas.drawCircle(80.0f, 45.0f, 45.0f, mNormalPaint);
+
+            canvas.translate(0.0f, 110.0f);
+            canvas.drawCircle(80.0f, 45.0f, 45.0f, mStrokePaint);
+
+            canvas.translate(0.0f, 110.0f);
+            canvas.drawCircle(80.0f, 45.0f, 45.0f, mFillPaint);
+            canvas.restore();
+        }
+    }
+}