Pack preloaded framework assets in a texture atlas

When the Android runtime starts, the system preloads a series of assets
in the Zygote process. These assets are shared across all processes.
Unfortunately, each one of these assets is later uploaded in its own
OpenGL texture, once per process. This wastes memory and generates
unnecessary OpenGL state changes.

This CL introduces an asset server that provides an atlas to all processes.

Note: bitmaps used by skia shaders are *not* sampled from the atlas.
It's an uncommon use case and would require extra texture transforms
in the GL shaders.

WHAT IS THE ASSETS ATLAS

The "assets atlas" is a single, shareable graphic buffer that contains
all the system's preloaded bitmap drawables (this includes 9-patches.)
The atlas is made of two distinct objects: the graphic buffer that
contains the actual pixels and the map which indicates where each
preloaded bitmap can be found in the atlas (essentially a pair of
x and y coordinates.)

HOW IS THE ASSETS ATLAS GENERATED

Because we need to support a wide variety of devices and because it
is easy to change the list of preloaded drawables, the atlas is
generated at runtime, during the startup phase of the system process.

There are several steps that lead to the atlas generation:

1. If the device is booting for the first time, or if the device was
updated, we need to find the best atlas configuration. To do so,
the atlas service tries a number of width, height and algorithm
variations that allows us to pack as many assets as possible while
using as little memory as possible. Once a best configuration is found,
it gets written to disk in /data/system/framework_atlas

2. Given a best configuration (algorithm variant, dimensions and
number of bitmaps that can be packed in the atlas), the atlas service
packs all the preloaded bitmaps into a single graphic buffer object.

3. The packing is done using Skia in a temporary native bitmap. The
Skia bitmap is then copied into the graphic buffer using OpenGL ES
to benefit from texture swizzling.

HOW PROCESSES USE THE ATLAS

Whenever a process' hardware renderer initializes its EGL context,
it queries the atlas service for the graphic buffer and the map.

It is important to remember that both the context and the map will
be valid for the lifetime of the hardware renderer (if the system
process goes down, all apps get killed as well.)

Every time the hardware renderer needs to render a bitmap, it first
checks whether the bitmap can be found in the assets atlas. When
the bitmap is part of the atlas, texture coordinates are remapped
appropriately before rendering.

Change-Id: I8eaecf53e7f6a33d90da3d0047c5ceec89ea3af0
diff --git a/Android.mk b/Android.mk
index ae098b2..c6b3b1d 100644
--- a/Android.mk
+++ b/Android.mk
@@ -161,6 +161,7 @@
 	core/java/android/view/accessibility/IAccessibilityManager.aidl \
 	core/java/android/view/accessibility/IAccessibilityManagerClient.aidl \
 	core/java/android/view/IApplicationToken.aidl \
+	core/java/android/view/IAssetAtlas.aidl \
 	core/java/android/view/IMagnificationCallbacks.aidl \
 	core/java/android/view/IInputFilter.aidl \
 	core/java/android/view/IInputFilterHost.aidl \
diff --git a/core/java/android/content/res/Resources.java b/core/java/android/content/res/Resources.java
index 42f4faf..c7976c3 100644
--- a/core/java/android/content/res/Resources.java
+++ b/core/java/android/content/res/Resources.java
@@ -16,8 +16,6 @@
 
 package android.content.res;
 
-import android.os.Trace;
-import android.view.View;
 import com.android.internal.util.XmlUtils;
 
 import org.xmlpull.v1.XmlPullParser;
@@ -30,6 +28,7 @@
 import android.graphics.drawable.Drawable.ConstantState;
 import android.os.Build;
 import android.os.Bundle;
+import android.os.Trace;
 import android.util.AttributeSet;
 import android.util.DisplayMetrics;
 import android.util.Log;
@@ -1985,6 +1984,13 @@
         }
     }
 
+    /**
+     * @hide
+     */
+    public LongSparseArray<Drawable.ConstantState> getPreloadedDrawables() {
+        return sPreloadedDrawables[0];
+    }
+
     private boolean verifyPreloadConfig(int changingConfigurations, int allowVarying,
             int resourceId, String name) {
         // We allow preloading of resources even if they vary by font scale (which
diff --git a/core/java/android/view/GLES20Canvas.java b/core/java/android/view/GLES20Canvas.java
index 2ec9a7d..21a03d8 100644
--- a/core/java/android/view/GLES20Canvas.java
+++ b/core/java/android/view/GLES20Canvas.java
@@ -21,6 +21,7 @@
 import android.graphics.ColorFilter;
 import android.graphics.DrawFilter;
 import android.graphics.Matrix;
+import android.graphics.NinePatch;
 import android.graphics.Paint;
 import android.graphics.PaintFlagsDrawFilter;
 import android.graphics.Path;
@@ -314,21 +315,21 @@
      * 
      * @see #flushCaches(int) 
      */
-    public static final int FLUSH_CACHES_LAYERS = 0;
+    static final int FLUSH_CACHES_LAYERS = 0;
     
     /**
      * Must match Caches::FlushMode values
      * 
      * @see #flushCaches(int) 
      */
-    public static final int FLUSH_CACHES_MODERATE = 1;
+    static final int FLUSH_CACHES_MODERATE = 1;
 
     /**
      * Must match Caches::FlushMode values
      * 
      * @see #flushCaches(int) 
      */
-    public static final int FLUSH_CACHES_FULL = 2;
+    static final int FLUSH_CACHES_FULL = 2;
 
     /**
      * Flush caches to reclaim as much memory as possible. The amount of memory
@@ -338,10 +339,8 @@
      * {@link #FLUSH_CACHES_FULL}.
      * 
      * @param level Hint about the amount of memory to reclaim
-     * 
-     * @hide
      */
-    public static void flushCaches(int level) {
+    static void flushCaches(int level) {
         nFlushCaches(level);
     }
 
@@ -353,21 +352,28 @@
      * 
      * @hide
      */
-    public static void terminateCaches() {
+    static void terminateCaches() {
         nTerminateCaches();
     }
 
     private static native void nTerminateCaches();
 
-    /**
-     * @hide
-     */
-    public static void initCaches() {
-        nInitCaches();
+    static boolean initCaches() {
+        return nInitCaches();
     }
 
-    private static native void nInitCaches();
-    
+    private static native boolean nInitCaches();
+
+    ///////////////////////////////////////////////////////////////////////////
+    // Atlas
+    ///////////////////////////////////////////////////////////////////////////
+
+    static void initAtlas(GraphicBuffer buffer, int[] map) {
+        nInitAtlas(buffer, map, map.length);
+    }
+
+    private static native void nInitAtlas(GraphicBuffer buffer, int[] map, int count);
+
     ///////////////////////////////////////////////////////////////////////////
     // Display list
     ///////////////////////////////////////////////////////////////////////////
@@ -718,20 +724,21 @@
     }
 
     @Override
-    public void drawPatch(Bitmap bitmap, byte[] chunks, RectF dst, Paint paint) {
+    public void drawPatch(NinePatch patch, RectF dst, Paint paint) {
+        Bitmap bitmap = patch.getBitmap();
         if (bitmap.isRecycled()) throw new IllegalArgumentException("Cannot draw recycled bitmaps");
         // Shaders are ignored when drawing patches
         int modifier = paint != null ? setupColorFilter(paint) : MODIFIER_NONE;
         try {
             final int nativePaint = paint == null ? 0 : paint.mNativePaint;
-            nDrawPatch(mRenderer, bitmap.mNativeBitmap, bitmap.mBuffer, chunks,
+            nDrawPatch(mRenderer, bitmap.mNativeBitmap, patch.mChunk,
                     dst.left, dst.top, dst.right, dst.bottom, nativePaint);
         } finally {
             if (modifier != MODIFIER_NONE) nResetModifiers(mRenderer, modifier);
         }
     }
 
-    private static native void nDrawPatch(int renderer, int bitmap, byte[] buffer, byte[] chunks,
+    private static native void nDrawPatch(int renderer, int bitmap, byte[] chunks,
             float left, float top, float right, float bottom, int paint);
 
     @Override
@@ -741,14 +748,14 @@
         int modifiers = paint != null ? setupModifiers(bitmap, paint) : MODIFIER_NONE;
         try {
             final int nativePaint = paint == null ? 0 : paint.mNativePaint;
-            nDrawBitmap(mRenderer, bitmap.mNativeBitmap, bitmap.mBuffer, left, top, nativePaint);
+            nDrawBitmap(mRenderer, bitmap.mNativeBitmap, left, top, nativePaint);
         } finally {
             if (modifiers != MODIFIER_NONE) nResetModifiers(mRenderer, modifiers);
         }
     }
 
-    private static native void nDrawBitmap(
-            int renderer, int bitmap, byte[] buffer, float left, float top, int paint);
+    private static native void nDrawBitmap(int renderer, int bitmap,
+            float left, float top, int paint);
 
     @Override
     public void drawBitmap(Bitmap bitmap, Matrix matrix, Paint paint) {
@@ -757,15 +764,13 @@
         int modifiers = paint != null ? setupModifiers(bitmap, paint) : MODIFIER_NONE;
         try {
             final int nativePaint = paint == null ? 0 : paint.mNativePaint;
-            nDrawBitmap(mRenderer, bitmap.mNativeBitmap, bitmap.mBuffer,
-                    matrix.native_instance, nativePaint);
+            nDrawBitmap(mRenderer, bitmap.mNativeBitmap,  matrix.native_instance, nativePaint);
         } finally {
             if (modifiers != MODIFIER_NONE) nResetModifiers(mRenderer, modifiers);
         }
     }
 
-    private static native void nDrawBitmap(int renderer, int bitmap, byte[] buff,
-            int matrix, int paint);
+    private static native void nDrawBitmap(int renderer, int bitmap, int matrix, int paint);
 
     @Override
     public void drawBitmap(Bitmap bitmap, Rect src, Rect dst, Paint paint) {
@@ -787,7 +792,7 @@
                 bottom = src.bottom;
             }
 
-            nDrawBitmap(mRenderer, bitmap.mNativeBitmap, bitmap.mBuffer, left, top, right, bottom,
+            nDrawBitmap(mRenderer, bitmap.mNativeBitmap, left, top, right, bottom,
                     dst.left, dst.top, dst.right, dst.bottom, nativePaint);
         } finally {
             if (modifiers != MODIFIER_NONE) nResetModifiers(mRenderer, modifiers);
@@ -814,14 +819,14 @@
                 bottom = src.bottom;
             }
     
-            nDrawBitmap(mRenderer, bitmap.mNativeBitmap, bitmap.mBuffer, left, top, right, bottom,
+            nDrawBitmap(mRenderer, bitmap.mNativeBitmap, left, top, right, bottom,
                     dst.left, dst.top, dst.right, dst.bottom, nativePaint);
         } finally {
             if (modifiers != MODIFIER_NONE) nResetModifiers(mRenderer, modifiers);
         }
     }
 
-    private static native void nDrawBitmap(int renderer, int bitmap, byte[] buffer,
+    private static native void nDrawBitmap(int renderer, int bitmap,
             float srcLeft, float srcTop, float srcRight, float srcBottom,
             float left, float top, float right, float bottom, int paint);
 
@@ -891,14 +896,14 @@
         int modifiers = paint != null ? setupModifiers(bitmap, paint) : MODIFIER_NONE;
         try {
             final int nativePaint = paint == null ? 0 : paint.mNativePaint;        
-            nDrawBitmapMesh(mRenderer, bitmap.mNativeBitmap, bitmap.mBuffer, meshWidth, meshHeight,
+            nDrawBitmapMesh(mRenderer, bitmap.mNativeBitmap, meshWidth, meshHeight,
                     verts, vertOffset, colors, colorOffset, nativePaint);
         } finally {
             if (modifiers != MODIFIER_NONE) nResetModifiers(mRenderer, modifiers);
         }
     }
 
-    private static native void nDrawBitmapMesh(int renderer, int bitmap, byte[] buffer,
+    private static native void nDrawBitmapMesh(int renderer, int bitmap,
             int meshWidth, int meshHeight, float[] verts, int vertOffset,
             int[] colors, int colorOffset, int paint);
 
diff --git a/core/java/android/view/GLES20DisplayList.java b/core/java/android/view/GLES20DisplayList.java
index 3272504..d367267 100644
--- a/core/java/android/view/GLES20DisplayList.java
+++ b/core/java/android/view/GLES20DisplayList.java
@@ -18,6 +18,7 @@
 
 import android.graphics.Bitmap;
 import android.graphics.Matrix;
+import android.graphics.NinePatch;
 
 import java.util.ArrayList;
 
@@ -29,7 +30,8 @@
     // alive as long as the DisplayList is alive.  The Bitmap and DisplayList lists
     // are populated by the GLES20RecordingCanvas during appropriate drawing calls and are
     // cleared at the start of a new drawing frame or when the view is detached from the window.
-    final ArrayList<Bitmap> mBitmaps = new ArrayList<Bitmap>(5);
+    final ArrayList<Bitmap> mBitmaps = new ArrayList<Bitmap>(10);
+    final ArrayList<NinePatch> mNinePatches = new ArrayList<NinePatch>(10);
     final ArrayList<DisplayList> mChildDisplayLists = new ArrayList<DisplayList>();
 
     private GLES20RecordingCanvas mCanvas;
@@ -83,7 +85,12 @@
         }
         mValid = false;
 
+        clearReferences();
+    }
+
+    void clearReferences() {
         mBitmaps.clear();
+        mNinePatches.clear();
         mChildDisplayLists.clear();
     }
 
diff --git a/core/java/android/view/GLES20RecordingCanvas.java b/core/java/android/view/GLES20RecordingCanvas.java
index 7da2451..ec059d5 100644
--- a/core/java/android/view/GLES20RecordingCanvas.java
+++ b/core/java/android/view/GLES20RecordingCanvas.java
@@ -19,6 +19,7 @@
 import android.graphics.Bitmap;
 import android.graphics.BitmapShader;
 import android.graphics.Matrix;
+import android.graphics.NinePatch;
 import android.graphics.Paint;
 import android.graphics.Path;
 import android.graphics.Rect;
@@ -62,8 +63,7 @@
     }
 
     void start() {
-        mDisplayList.mBitmaps.clear();
-        mDisplayList.mChildDisplayLists.clear();
+        mDisplayList.clearReferences();
     }
 
     int end(int nativeDisplayList) {
@@ -80,9 +80,10 @@
     }
 
     @Override
-    public void drawPatch(Bitmap bitmap, byte[] chunks, RectF dst, Paint paint) {
-        super.drawPatch(bitmap, chunks, dst, paint);
-        mDisplayList.mBitmaps.add(bitmap);
+    public void drawPatch(NinePatch patch, RectF dst, Paint paint) {
+        super.drawPatch(patch, dst, paint);
+        mDisplayList.mBitmaps.add(patch.getBitmap());
+        mDisplayList.mNinePatches.add(patch);
         // Shaders in the Paint are ignored when drawing a Bitmap
     }
 
diff --git a/core/java/android/view/GraphicBuffer.aidl b/core/java/android/view/GraphicBuffer.aidl
new file mode 100644
index 0000000..6dc6bed
--- /dev/null
+++ b/core/java/android/view/GraphicBuffer.aidl
@@ -0,0 +1,19 @@
+/*
+ * 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.
+ */
+
+package android.view;
+
+parcelable GraphicBuffer;
diff --git a/core/java/android/view/GraphicBuffer.java b/core/java/android/view/GraphicBuffer.java
new file mode 100644
index 0000000..b4576f3
--- /dev/null
+++ b/core/java/android/view/GraphicBuffer.java
@@ -0,0 +1,229 @@
+/*
+ * 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.
+ */
+
+package android.view;
+
+import android.graphics.Canvas;
+import android.graphics.PixelFormat;
+import android.graphics.Rect;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * Simple wrapper for the native GraphicBuffer class.
+ *
+ * @hide
+ */
+@SuppressWarnings("UnusedDeclaration")
+public class GraphicBuffer implements Parcelable {
+    // Note: keep usage flags in sync with GraphicBuffer.h and gralloc.h
+    public static final int USAGE_SW_READ_NEVER = 0x0;
+    public static final int USAGE_SW_READ_RARELY = 0x2;
+    public static final int USAGE_SW_READ_OFTEN = 0x3;
+    public static final int USAGE_SW_READ_MASK = 0xF;
+
+    public static final int USAGE_SW_WRITE_NEVER = 0x0;
+    public static final int USAGE_SW_WRITE_RARELY = 0x20;
+    public static final int USAGE_SW_WRITE_OFTEN = 0x30;
+    public static final int USAGE_SW_WRITE_MASK = 0xF0;
+
+    public static final int USAGE_SOFTWARE_MASK = USAGE_SW_READ_MASK | USAGE_SW_WRITE_MASK;
+
+    public static final int USAGE_PROTECTED = 0x4000;
+
+    public static final int USAGE_HW_TEXTURE = 0x100;
+    public static final int USAGE_HW_RENDER = 0x200;
+    public static final int USAGE_HW_2D = 0x400;
+    public static final int USAGE_HW_COMPOSER = 0x800;
+    public static final int USAGE_HW_VIDEO_ENCODER = 0x10000;
+    public static final int USAGE_HW_MASK = 0x71F00;
+
+    private final int mWidth;
+    private final int mHeight;
+    private final int mFormat;
+    private final int mUsage;
+    // Note: do not rename, this field is used by native code
+    private final int mNativeObject;
+
+    // These two fields are only used by lock/unlockCanvas()
+    private Canvas mCanvas;
+    private int mSaveCount;
+
+    /**
+     * Creates new <code>GraphicBuffer</code> instance. This method will return null
+     * if the buffer cannot be created.
+     *
+     * @param width The width in pixels of the buffer
+     * @param height The height in pixels of the buffer
+     * @param format The format of each pixel as specified in {@link PixelFormat}
+     * @param usage Hint indicating how the buffer will be used
+     *
+     * @return A <code>GraphicBuffer</code> instance or null
+     */
+    public static GraphicBuffer create(int width, int height, int format, int usage) {
+        int nativeObject = nCreateGraphicBuffer(width, height, format, usage);
+        if (nativeObject != 0) {
+            return new GraphicBuffer(width, height, format, usage, nativeObject);
+        }
+        return null;
+    }
+
+    /**
+     * Private use only. See {@link #create(int, int, int, int)}.
+     */
+    private GraphicBuffer(int width, int height, int format, int usage, int nativeObject) {
+        mWidth = width;
+        mHeight = height;
+        mFormat = format;
+        mUsage = usage;
+        mNativeObject = nativeObject;
+    }
+
+    /**
+     * Returns the width of this buffer in pixels.
+     */
+    public int getWidth() {
+        return mWidth;
+    }
+
+    /**
+     * Returns the height of this buffer in pixels.
+     */
+    public int getHeight() {
+        return mHeight;
+    }
+
+    /**
+     * Returns the pixel format of this buffer. The pixel format must be one of
+     * the formats defined in {@link PixelFormat}.
+     */
+    public int getFormat() {
+        return mFormat;
+    }
+
+    /**
+     * Returns the usage hint set on this buffer.
+     */
+    public int getUsage() {
+        return mUsage;
+    }
+
+    /**
+     * <p>Start editing the pixels in the buffer. A null is returned if the buffer
+     * cannot be locked for editing.</p>
+     *
+     * <p>The content of the buffer is preserved between unlockCanvas()
+     * and lockCanvas().</p>
+     *
+     * @return A Canvas used to draw into the buffer, or null.
+     *
+     * @see #lockCanvas(android.graphics.Rect)
+     * @see #unlockCanvasAndPost(android.graphics.Canvas)
+     */
+    public Canvas lockCanvas() {
+        return lockCanvas(null);
+    }
+
+    /**
+     * Just like {@link #lockCanvas()} but allows specification of a dirty
+     * rectangle.
+     *
+     * @param dirty Area of the buffer that may be modified.
+
+     * @return A Canvas used to draw into the surface or null
+     *
+     * @see #lockCanvas()
+     * @see #unlockCanvasAndPost(android.graphics.Canvas)
+     */
+    public Canvas lockCanvas(Rect dirty) {
+        if (mCanvas == null) {
+            mCanvas = new Canvas();
+        }
+
+        if (nLockCanvas(mNativeObject, mCanvas, dirty)) {
+            mSaveCount = mCanvas.save();
+            return mCanvas;
+        }
+
+        return null;
+    }
+
+    /**
+     * Finish editing pixels in the buffer.
+     *
+     * @param canvas The Canvas previously returned by lockCanvas()
+     *
+     * @see #lockCanvas()
+     * @see #lockCanvas(android.graphics.Rect)
+     */
+    public void unlockCanvasAndPost(Canvas canvas) {
+        if (mCanvas != null && canvas == mCanvas) {
+            canvas.restoreToCount(mSaveCount);
+            mSaveCount = 0;
+
+            nUnlockCanvasAndPost(mNativeObject, mCanvas);
+        }
+    }
+
+    @Override
+    protected void finalize() throws Throwable {
+        try {
+            nDestroyGraphicBuffer(mNativeObject);
+        } finally {
+            super.finalize();
+        }
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeInt(mWidth);
+        dest.writeInt(mHeight);
+        dest.writeInt(mFormat);
+        dest.writeInt(mUsage);
+        nWriteGraphicBufferToParcel(mNativeObject, dest);
+    }
+
+    public static final Parcelable.Creator<GraphicBuffer> CREATOR =
+            new Parcelable.Creator<GraphicBuffer>() {
+        public GraphicBuffer createFromParcel(Parcel in) {
+            int width = in.readInt();
+            int height = in.readInt();
+            int format = in.readInt();
+            int usage = in.readInt();
+            int nativeObject = nReadGraphicBufferFromParcel(in);
+            if (nativeObject != 0) {
+                return new GraphicBuffer(width, height, format, usage, nativeObject);
+            }
+            return null;
+        }
+
+        public GraphicBuffer[] newArray(int size) {
+            return new GraphicBuffer[size];
+        }
+    };
+
+    private static native int nCreateGraphicBuffer(int width, int height, int format, int usage);
+    private static native void nDestroyGraphicBuffer(int nativeObject);
+    private static native void nWriteGraphicBufferToParcel(int nativeObject, Parcel dest);
+    private static native int nReadGraphicBufferFromParcel(Parcel in);
+    private static native boolean nLockCanvas(int nativeObject, Canvas canvas, Rect dirty);
+    private static native boolean nUnlockCanvasAndPost(int nativeObject, Canvas canvas);
+}
diff --git a/core/java/android/view/HardwareRenderer.java b/core/java/android/view/HardwareRenderer.java
index 8308459..0632e1d 100644
--- a/core/java/android/view/HardwareRenderer.java
+++ b/core/java/android/view/HardwareRenderer.java
@@ -24,7 +24,10 @@
 import android.opengl.GLUtils;
 import android.opengl.ManagedEGLContext;
 import android.os.Handler;
+import android.os.IBinder;
 import android.os.Looper;
+import android.os.RemoteException;
+import android.os.ServiceManager;
 import android.os.SystemClock;
 import android.os.SystemProperties;
 import android.os.Trace;
@@ -968,7 +971,7 @@
             if (fallback) {
                 // we'll try again if it was context lost
                 setRequested(false);
-                Log.w(LOG_TAG, "Mountain View, we've had a problem here. " 
+                Log.w(LOG_TAG, "Mountain View, we've had a problem here. "
                         + "Switching back to software rendering.");
             }
         }
@@ -976,7 +979,7 @@
         @Override
         boolean initialize(Surface surface) throws Surface.OutOfResourcesException {
             if (isRequested() && !isEnabled()) {
-                initializeEgl();
+                boolean contextCreated = initializeEgl();
                 mGl = createEglSurface(surface);
                 mDestroyed = false;
 
@@ -991,6 +994,10 @@
                             mCanvas.setName(mName);
                         }
                         setEnabled(true);
+
+                        if (contextCreated) {
+                            initAtlas();
+                        }
                     }
 
                     return mCanvas != null;
@@ -1010,7 +1017,7 @@
 
         abstract int[] getConfig(boolean dirtyRegions);
 
-        void initializeEgl() {
+        boolean initializeEgl() {
             synchronized (sEglLock) {
                 if (sEgl == null && sEglConfig == null) {
                     sEgl = (EGL10) EGLContext.getEGL();
@@ -1043,7 +1050,10 @@
             if (mEglContext == null) {
                 mEglContext = createContext(sEgl, sEglDisplay, sEglConfig);
                 sEglContextStorage.set(createManagedContext(mEglContext));
+                return true;
             }
+
+            return false;
         }
 
         private EGLConfig loadEglConfig() {
@@ -1181,6 +1191,7 @@
         }
 
         abstract void initCaches();
+        abstract void initAtlas();
 
         EGLContext createContext(EGL10 egl, EGLDisplay eglDisplay, EGLConfig eglConfig) {
             int[] attribs = { EGL14.EGL_CONTEXT_CLIENT_VERSION, mGlVersion, EGL_NONE };
@@ -1193,6 +1204,7 @@
                         "Could not create an EGL context. eglCreateContext failed with error: " +
                         GLUtils.getEGLErrorString(sEgl.eglGetError()));
             }
+
             return context;
         }
 
@@ -1788,7 +1800,27 @@
 
         @Override
         void initCaches() {
-            GLES20Canvas.initCaches();
+            if (GLES20Canvas.initCaches()) {
+                // Caches were (re)initialized, rebind atlas
+                initAtlas();
+            }
+        }
+
+        @Override
+        void initAtlas() {
+            IBinder binder = ServiceManager.getService("assetatlas");
+            IAssetAtlas atlas = IAssetAtlas.Stub.asInterface(binder);
+            try {
+                GraphicBuffer buffer = atlas.getBuffer();
+                if (buffer != null) {
+                    int[] map = atlas.getMap();
+                    if (map != null) {
+                        GLES20Canvas.initAtlas(buffer, map);
+                    }
+                }
+            } catch (RemoteException e) {
+                Log.w(LOG_TAG, "Could not acquire atlas", e);
+            }
         }
 
         @Override
diff --git a/core/java/android/view/IAssetAtlas.aidl b/core/java/android/view/IAssetAtlas.aidl
new file mode 100644
index 0000000..2595179
--- /dev/null
+++ b/core/java/android/view/IAssetAtlas.aidl
@@ -0,0 +1,47 @@
+/**
+ * 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.
+ */
+
+package android.view;
+
+import android.view.GraphicBuffer;
+
+/**
+ * Programming interface to the system assets atlas. This atlas, when
+ * present, holds preloaded drawable in a single, shareable graphics
+ * buffer. This allows multiple processes to share the same data to
+ * save up on memory.
+ *
+ * @hide
+ */
+interface IAssetAtlas {
+    /**
+     * Returns the atlas buffer (texture) or null if the atlas is
+     * not available yet.
+     */
+    GraphicBuffer getBuffer();
+
+    /**
+     * Returns the map of the bitmaps stored in the atlas or null
+     * if the atlas is not available yet.
+     *
+     * Each bitmap is represented by several entries in the array:
+     * int0: SkBitmap*, the native bitmap object
+     * int1: x position
+     * int2: y position
+     * int3: rotated, 1 if the bitmap must be rotated, 0 otherwise
+     */
+    int[] getMap();
+}
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index f34d390..47c40d2 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -23,7 +23,6 @@
 import android.content.ComponentCallbacks;
 import android.content.ComponentCallbacks2;
 import android.content.Context;
-import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
 import android.content.res.CompatibilityInfo;
 import android.content.res.Configuration;
@@ -108,8 +107,6 @@
     private static final boolean DEBUG_FPS = false;
     private static final boolean DEBUG_INPUT_PROCESSING = false || LOCAL_LOGV;
 
-    private static final boolean USE_RENDER_THREAD = false;
-
     /**
      * Set this system property to true to force the view hierarchy to render
      * at 60 Hz. This can be used to measure the potential framerate.
@@ -131,10 +128,6 @@
     static final ArrayList<ComponentCallbacks> sConfigCallbacks
             = new ArrayList<ComponentCallbacks>();
 
-    private static boolean sUseRenderThread = false;
-    private static boolean sRenderThreadQueried = false;
-    private static final Object[] sRenderThreadQueryLock = new Object[0];
-
     final Context mContext;
     final IWindowSession mWindowSession;
     final Display mDisplay;
@@ -375,35 +368,6 @@
         loadSystemProperties();
     }
 
-    /**
-     * @return True if the application requests the use of a separate render thread,
-     *         false otherwise
-     */
-    private static boolean isRenderThreadRequested(Context context) {
-        if (USE_RENDER_THREAD) {
-            synchronized (sRenderThreadQueryLock) {
-                if (!sRenderThreadQueried) {
-                    final PackageManager packageManager = context.getPackageManager();
-                    final String packageName = context.getApplicationInfo().packageName;
-                    try {
-                        ApplicationInfo applicationInfo = packageManager.getApplicationInfo(packageName,
-                                PackageManager.GET_META_DATA);
-                        if (applicationInfo.metaData != null) {
-                            sUseRenderThread = applicationInfo.metaData.getBoolean(
-                                    "android.graphics.renderThread", false);
-                        }
-                    } catch (PackageManager.NameNotFoundException e) {
-                    } finally {
-                        sRenderThreadQueried = true;
-                    }
-                }
-                return sUseRenderThread;
-            }
-        } else {
-            return false;
-        }
-    }
-
     public static void addFirstDrawHandler(Runnable callback) {
         synchronized (sFirstDrawHandlers) {
             if (!sFirstDrawComplete) {
@@ -481,7 +445,7 @@
 
                 // If the application owns the surface, don't enable hardware acceleration
                 if (mSurfaceHolder == null) {
-                    enableHardwareAcceleration(mView.getContext(), attrs);
+                    enableHardwareAcceleration(attrs);
                 }
 
                 boolean restore = false;
@@ -689,7 +653,7 @@
         }
     }
 
-    private void enableHardwareAcceleration(Context context, WindowManager.LayoutParams attrs) {
+    private void enableHardwareAcceleration(WindowManager.LayoutParams attrs) {
         mAttachInfo.mHardwareAccelerated = false;
         mAttachInfo.mHardwareAccelerationRequested = false;
 
@@ -729,11 +693,6 @@
                     return;
                 }
 
-                final boolean renderThread = isRenderThreadRequested(context);
-                if (renderThread) {
-                    Log.i(HardwareRenderer.LOG_TAG, "Render threat initiated");
-                }
-
                 if (mAttachInfo.mHardwareRenderer != null) {
                     mAttachInfo.mHardwareRenderer.destroy(true);
                 }
diff --git a/core/jni/Android.mk b/core/jni/Android.mk
index edc0baf..d0d3508 100644
--- a/core/jni/Android.mk
+++ b/core/jni/Android.mk
@@ -50,6 +50,7 @@
 	android_view_KeyEvent.cpp \
 	android_view_KeyCharacterMap.cpp \
 	android_view_HardwareRenderer.cpp \
+	android_view_GraphicBuffer.cpp \
 	android_view_GLES20DisplayList.cpp \
 	android_view_GLES20Canvas.cpp \
 	android_view_MotionEvent.cpp \
diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp
index 53fde48..e243ed7 100644
--- a/core/jni/AndroidRuntime.cpp
+++ b/core/jni/AndroidRuntime.cpp
@@ -116,6 +116,7 @@
 extern int register_android_graphics_Xfermode(JNIEnv* env);
 extern int register_android_graphics_PixelFormat(JNIEnv* env);
 extern int register_android_view_DisplayEventReceiver(JNIEnv* env);
+extern int register_android_view_GraphicBuffer(JNIEnv* env);
 extern int register_android_view_GLES20DisplayList(JNIEnv* env);
 extern int register_android_view_GLES20Canvas(JNIEnv* env);
 extern int register_android_view_HardwareRenderer(JNIEnv* env);
@@ -1111,6 +1112,7 @@
     REG_JNI(register_android_nio_utils),
     REG_JNI(register_android_graphics_PixelFormat),
     REG_JNI(register_android_graphics_Graphics),
+    REG_JNI(register_android_view_GraphicBuffer),
     REG_JNI(register_android_view_GLES20DisplayList),
     REG_JNI(register_android_view_GLES20Canvas),
     REG_JNI(register_android_view_HardwareRenderer),
diff --git a/core/jni/android_view_GLES20Canvas.cpp b/core/jni/android_view_GLES20Canvas.cpp
index b87fe27..c8fa290 100644
--- a/core/jni/android_view_GLES20Canvas.cpp
+++ b/core/jni/android_view_GLES20Canvas.cpp
@@ -16,18 +16,19 @@
 
 #define LOG_TAG "OpenGLRenderer"
 
-#include <EGL/egl.h>
-
 #include "jni.h"
 #include "GraphicsJNI.h"
 #include <nativehelper/JNIHelp.h>
 
+#include "android_view_GraphicBuffer.h"
+
 #include <android_runtime/AndroidRuntime.h>
 #include <android_runtime/android_graphics_SurfaceTexture.h>
-#include <gui/GLConsumer.h>
 
 #include <androidfw/ResourceTypes.h>
 
+#include <gui/GLConsumer.h>
+
 #include <private/hwui/DrawGlInfo.h>
 
 #include <cutils/properties.h>
@@ -99,10 +100,11 @@
     }
 }
 
-static void android_view_GLES20Canvas_initCaches(JNIEnv* env, jobject clazz) {
+static bool android_view_GLES20Canvas_initCaches(JNIEnv* env, jobject clazz) {
     if (Caches::hasInstance()) {
-        Caches::getInstance().init();
+        return Caches::getInstance().init();
     }
+    return false;
 }
 
 static void android_view_GLES20Canvas_terminateCaches(JNIEnv* env, jobject clazz) {
@@ -112,6 +114,21 @@
 }
 
 // ----------------------------------------------------------------------------
+// Caching
+// ----------------------------------------------------------------------------
+
+static void android_view_GLES20Canvas_initAtlas(JNIEnv* env, jobject clazz,
+        jobject graphicBuffer, jintArray atlasMapArray, jint count) {
+
+    sp<GraphicBuffer> buffer = graphicBufferForJavaObject(env, graphicBuffer);
+    jint* atlasMap = env->GetIntArrayElements(atlasMapArray, NULL);
+
+    Caches::getInstance().assetAtlas.init(buffer, atlasMap, count);
+
+    env->ReleaseIntArrayElements(atlasMapArray, atlasMap, 0);
+}
+
+// ----------------------------------------------------------------------------
 // Constructors
 // ----------------------------------------------------------------------------
 
@@ -350,31 +367,20 @@
 // ----------------------------------------------------------------------------
 
 static void android_view_GLES20Canvas_drawBitmap(JNIEnv* env, jobject clazz,
-        OpenGLRenderer* renderer, SkBitmap* bitmap, jbyteArray buffer,
-        jfloat left, jfloat top, SkPaint* paint) {
-    // This object allows the renderer to allocate a global JNI ref to the buffer object.
-    JavaHeapBitmapRef bitmapRef(env, bitmap, buffer);
-
+        OpenGLRenderer* renderer, SkBitmap* bitmap, jfloat left, jfloat top, SkPaint* paint) {
     renderer->drawBitmap(bitmap, left, top, paint);
 }
 
 static void android_view_GLES20Canvas_drawBitmapRect(JNIEnv* env, jobject clazz,
-        OpenGLRenderer* renderer, SkBitmap* bitmap, jbyteArray buffer,
+        OpenGLRenderer* renderer, SkBitmap* bitmap,
         float srcLeft, float srcTop, float srcRight, float srcBottom,
         float dstLeft, float dstTop, float dstRight, float dstBottom, SkPaint* paint) {
-    // This object allows the renderer to allocate a global JNI ref to the buffer object.
-    JavaHeapBitmapRef bitmapRef(env, bitmap, buffer);
-
     renderer->drawBitmap(bitmap, srcLeft, srcTop, srcRight, srcBottom,
             dstLeft, dstTop, dstRight, dstBottom, paint);
 }
 
 static void android_view_GLES20Canvas_drawBitmapMatrix(JNIEnv* env, jobject clazz,
-        OpenGLRenderer* renderer, SkBitmap* bitmap, jbyteArray buffer, SkMatrix* matrix,
-        SkPaint* paint) {
-    // This object allows the renderer to allocate a global JNI ref to the buffer object.
-    JavaHeapBitmapRef bitmapRef(env, bitmap, buffer);
-
+        OpenGLRenderer* renderer, SkBitmap* bitmap, SkMatrix* matrix, SkPaint* paint) {
     renderer->drawBitmap(bitmap, matrix, paint);
 }
 
@@ -404,12 +410,8 @@
 }
 
 static void android_view_GLES20Canvas_drawBitmapMesh(JNIEnv* env, jobject clazz,
-        OpenGLRenderer* renderer, SkBitmap* bitmap, jbyteArray buffer,
-        jint meshWidth, jint meshHeight, jfloatArray vertices, jint offset,
-        jintArray colors, jint colorOffset, SkPaint* paint) {
-    // This object allows the renderer to allocate a global JNI ref to the buffer object.
-    JavaHeapBitmapRef bitmapRef(env, bitmap, buffer);
-
+        OpenGLRenderer* renderer, SkBitmap* bitmap, jint meshWidth, jint meshHeight,
+         jfloatArray vertices, jint offset, jintArray colors, jint colorOffset, SkPaint* paint) {
     jfloat* verticesArray = vertices ? env->GetFloatArrayElements(vertices, NULL) + offset : NULL;
     jint* colorsArray = colors ? env->GetIntArrayElements(colors, NULL) + colorOffset : NULL;
 
@@ -420,18 +422,13 @@
 }
 
 static void android_view_GLES20Canvas_drawPatch(JNIEnv* env, jobject clazz,
-        OpenGLRenderer* renderer, SkBitmap* bitmap, jbyteArray buffer, jbyteArray chunks,
+        OpenGLRenderer* renderer, SkBitmap* bitmap, jbyteArray chunks,
         float left, float top, float right, float bottom, SkPaint* paint) {
-    // This object allows the renderer to allocate a global JNI ref to the buffer object.
-    JavaHeapBitmapRef bitmapRef(env, bitmap, buffer);
-
     jbyte* storage = env->GetByteArrayElements(chunks, NULL);
     Res_png_9patch* patch = reinterpret_cast<Res_png_9patch*>(storage);
     Res_png_9patch::deserialize(patch);
 
-    renderer->drawPatch(bitmap, &patch->xDivs[0], &patch->yDivs[0],
-            &patch->colors[0], patch->numXDivs, patch->numYDivs, patch->numColors,
-            left, top, right, bottom, paint);
+    renderer->drawPatch(bitmap, patch, left, top, right, bottom, paint);
 
     env->ReleaseByteArrayElements(chunks, storage, 0);
 }
@@ -932,9 +929,12 @@
 
 #ifdef USE_OPENGL_RENDERER
     { "nFlushCaches",       "(I)V",            (void*) android_view_GLES20Canvas_flushCaches },
-    { "nInitCaches",        "()V",             (void*) android_view_GLES20Canvas_initCaches },
+    { "nInitCaches",        "()Z",             (void*) android_view_GLES20Canvas_initCaches },
     { "nTerminateCaches",   "()V",             (void*) android_view_GLES20Canvas_terminateCaches },
 
+    { "nInitAtlas",         "(Landroid/view/GraphicBuffer;[II)V",
+            (void*) android_view_GLES20Canvas_initAtlas },
+
     { "nCreateRenderer",    "()I",             (void*) android_view_GLES20Canvas_createRenderer },
     { "nDestroyRenderer",   "(I)V",            (void*) android_view_GLES20Canvas_destroyRenderer },
     { "nSetViewport",       "(III)V",          (void*) android_view_GLES20Canvas_setViewport },
@@ -977,14 +977,14 @@
     { "nGetMatrix",         "(II)V",           (void*) android_view_GLES20Canvas_getMatrix },
     { "nConcatMatrix",      "(II)V",           (void*) android_view_GLES20Canvas_concatMatrix },
 
-    { "nDrawBitmap",        "(II[BFFI)V",      (void*) android_view_GLES20Canvas_drawBitmap },
-    { "nDrawBitmap",        "(II[BFFFFFFFFI)V",(void*) android_view_GLES20Canvas_drawBitmapRect },
-    { "nDrawBitmap",        "(II[BII)V",       (void*) android_view_GLES20Canvas_drawBitmapMatrix },
+    { "nDrawBitmap",        "(IIFFI)V",        (void*) android_view_GLES20Canvas_drawBitmap },
+    { "nDrawBitmap",        "(IIFFFFFFFFI)V",  (void*) android_view_GLES20Canvas_drawBitmapRect },
+    { "nDrawBitmap",        "(IIII)V",         (void*) android_view_GLES20Canvas_drawBitmapMatrix },
     { "nDrawBitmap",        "(I[IIIFFIIZI)V",  (void*) android_view_GLES20Canvas_drawBitmapData },
 
-    { "nDrawBitmapMesh",    "(II[BII[FI[III)V",(void*) android_view_GLES20Canvas_drawBitmapMesh },
+    { "nDrawBitmapMesh",    "(IIII[FI[III)V",  (void*) android_view_GLES20Canvas_drawBitmapMesh },
 
-    { "nDrawPatch",         "(II[B[BFFFFI)V",  (void*) android_view_GLES20Canvas_drawPatch },
+    { "nDrawPatch",         "(II[BFFFFI)V",    (void*) android_view_GLES20Canvas_drawPatch },
 
     { "nDrawColor",         "(III)V",          (void*) android_view_GLES20Canvas_drawColor },
     { "nDrawRect",          "(IFFFFI)V",       (void*) android_view_GLES20Canvas_drawRect },
diff --git a/core/jni/android_view_GraphicBuffer.cpp b/core/jni/android_view_GraphicBuffer.cpp
new file mode 100644
index 0000000..d68c0b2
--- /dev/null
+++ b/core/jni/android_view_GraphicBuffer.cpp
@@ -0,0 +1,331 @@
+/*
+ * 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.
+ */
+
+#define LOG_TAG "GraphicBuffer"
+
+#include "jni.h"
+#include "JNIHelp.h"
+
+#include "android_os_Parcel.h"
+#include "android_view_GraphicBuffer.h"
+
+#include <android_runtime/AndroidRuntime.h>
+
+#include <binder/Parcel.h>
+
+#include <ui/GraphicBuffer.h>
+#include <ui/PixelFormat.h>
+
+#include <gui/IGraphicBufferAlloc.h>
+#include <gui/ISurfaceComposer.h>
+
+#include <SkCanvas.h>
+#include <SkBitmap.h>
+
+#include <private/gui/ComposerService.h>
+
+namespace android {
+
+// ----------------------------------------------------------------------------
+// Defines
+// ----------------------------------------------------------------------------
+
+// Debug
+#define DEBUG_GRAPHIC_BUFFER 0
+
+// Debug
+#if DEBUG_GRAPHIC_BUFFER
+    #define GB_LOGD(...) ALOGD(__VA_ARGS__)
+    #define GB_LOGW(...) ALOGW(__VA_ARGS__)
+#else
+    #define GB_LOGD(...)
+    #define GB_LOGW(...)
+#endif
+
+#define LOCK_CANVAS_USAGE GraphicBuffer::USAGE_SW_READ_OFTEN | GraphicBuffer::USAGE_SW_WRITE_OFTEN
+
+// ----------------------------------------------------------------------------
+// JNI Helpers
+// ----------------------------------------------------------------------------
+
+static struct {
+    jfieldID mNativeObject;
+} gGraphicBufferClassInfo;
+
+static struct {
+    jmethodID set;
+    jfieldID left;
+    jfieldID top;
+    jfieldID right;
+    jfieldID bottom;
+} gRectClassInfo;
+
+static struct {
+    jfieldID mFinalizer;
+    jfieldID mNativeCanvas;
+    jfieldID mSurfaceFormat;
+} gCanvasClassInfo;
+
+static struct {
+    jfieldID mNativeCanvas;
+} gCanvasFinalizerClassInfo;
+
+#define GET_INT(object, field) \
+    env->GetIntField(object, field)
+
+#define SET_INT(object, field, value) \
+    env->SetIntField(object, field, value)
+
+#define INVOKEV(object, method, ...) \
+    env->CallVoidMethod(object, method, __VA_ARGS__)
+
+// ----------------------------------------------------------------------------
+// Types
+// ----------------------------------------------------------------------------
+
+class GraphicBufferWrapper {
+public:
+    GraphicBufferWrapper(const sp<GraphicBuffer>& buffer): buffer(buffer) {
+    }
+
+    sp<GraphicBuffer> buffer;
+};
+
+// ----------------------------------------------------------------------------
+// GraphicBuffer lifecycle
+// ----------------------------------------------------------------------------
+
+static GraphicBufferWrapper* android_view_GraphiceBuffer_create(JNIEnv* env, jobject clazz,
+        jint width, jint height, jint format, jint usage) {
+
+    sp<ISurfaceComposer> composer(ComposerService::getComposerService());
+    sp<IGraphicBufferAlloc> alloc(composer->createGraphicBufferAlloc());
+    if (alloc == NULL) {
+        GB_LOGW("createGraphicBufferAlloc() failed in GraphicBuffer.create()");
+        return NULL;
+    }
+
+    status_t error;
+    sp<GraphicBuffer> buffer(alloc->createGraphicBuffer(width, height, format, usage, &error));
+    if (buffer == NULL) {
+        GB_LOGW("createGraphicBuffer() failed in GraphicBuffer.create()");
+        return NULL;
+    }
+
+    return new GraphicBufferWrapper(buffer);
+}
+
+static void android_view_GraphiceBuffer_destroy(JNIEnv* env, jobject clazz,
+        GraphicBufferWrapper* wrapper) {
+    delete wrapper;
+}
+
+// ----------------------------------------------------------------------------
+// Canvas management
+// ----------------------------------------------------------------------------
+
+static inline void swapCanvasPtr(JNIEnv* env, jobject canvasObj, SkCanvas* newCanvas) {
+    jobject canvasFinalizerObj = env->GetObjectField(canvasObj, gCanvasClassInfo.mFinalizer);
+    SkCanvas* previousCanvas = reinterpret_cast<SkCanvas*>(
+            GET_INT(canvasObj, gCanvasClassInfo.mNativeCanvas));
+    SET_INT(canvasObj, gCanvasClassInfo.mNativeCanvas, (int) newCanvas);
+    SET_INT(canvasFinalizerObj, gCanvasFinalizerClassInfo.mNativeCanvas, (int) newCanvas);
+    SkSafeUnref(previousCanvas);
+}
+
+static inline SkBitmap::Config convertPixelFormat(int32_t format) {
+    switch (format) {
+        case PIXEL_FORMAT_RGBA_8888:
+            return SkBitmap::kARGB_8888_Config;
+        case PIXEL_FORMAT_RGBX_8888:
+            return SkBitmap::kARGB_8888_Config;
+        case PIXEL_FORMAT_RGB_565:
+            return SkBitmap::kRGB_565_Config;
+        default:
+            return SkBitmap::kNo_Config;
+    }
+}
+
+static jboolean android_view_GraphicBuffer_lockCanvas(JNIEnv* env, jobject,
+        GraphicBufferWrapper* wrapper, jobject canvas, jobject dirtyRect) {
+
+    if (!wrapper) {
+        return false;
+    }
+
+    sp<GraphicBuffer> buffer(wrapper->buffer);
+
+    Rect rect;
+    if (dirtyRect) {
+        rect.left = GET_INT(dirtyRect, gRectClassInfo.left);
+        rect.top = GET_INT(dirtyRect, gRectClassInfo.top);
+        rect.right = GET_INT(dirtyRect, gRectClassInfo.right);
+        rect.bottom = GET_INT(dirtyRect, gRectClassInfo.bottom);
+    } else {
+        rect.set(Rect(buffer->getWidth(), buffer->getHeight()));
+    }
+
+    void* bits = NULL;
+    status_t status = buffer->lock(LOCK_CANVAS_USAGE, rect, &bits);
+
+    if (status) return false;
+    if (!bits) {
+        buffer->unlock();
+        return false;
+    }
+
+    ssize_t bytesCount = buffer->getStride() * bytesPerPixel(buffer->getPixelFormat());
+
+    SkBitmap bitmap;
+    bitmap.setConfig(convertPixelFormat(buffer->getPixelFormat()),
+            buffer->getWidth(), buffer->getHeight(), bytesCount);
+
+    if (buffer->getWidth() > 0 && buffer->getHeight() > 0) {
+        bitmap.setPixels(bits);
+    } else {
+        bitmap.setPixels(NULL);
+    }
+
+    SET_INT(canvas, gCanvasClassInfo.mSurfaceFormat, buffer->getPixelFormat());
+
+    SkCanvas* nativeCanvas = SkNEW_ARGS(SkCanvas, (bitmap));
+    swapCanvasPtr(env, canvas, nativeCanvas);
+
+    SkRect clipRect;
+    clipRect.set(rect.left, rect.top, rect.right, rect.bottom);
+    nativeCanvas->clipRect(clipRect);
+
+    if (dirtyRect) {
+        INVOKEV(dirtyRect, gRectClassInfo.set,
+                int(rect.left), int(rect.top), int(rect.right), int(rect.bottom));
+    }
+
+    return true;
+}
+
+static jboolean android_view_GraphicBuffer_unlockCanvasAndPost(JNIEnv* env, jobject,
+        GraphicBufferWrapper* wrapper, jobject canvas) {
+
+    SkCanvas* nativeCanvas = SkNEW(SkCanvas);
+    swapCanvasPtr(env, canvas, nativeCanvas);
+
+    if (wrapper) {
+        status_t status = wrapper->buffer->unlock();
+        return status == 0;
+    }
+
+    return false;
+}
+
+// ----------------------------------------------------------------------------
+// Serialization
+// ----------------------------------------------------------------------------
+
+static void android_view_GraphiceBuffer_write(JNIEnv* env, jobject clazz,
+        GraphicBufferWrapper* wrapper, jobject dest) {
+    Parcel* parcel = parcelForJavaObject(env, dest);
+    if (parcel) {
+        parcel->write(*wrapper->buffer);
+    }
+}
+
+static GraphicBufferWrapper* android_view_GraphiceBuffer_read(JNIEnv* env, jobject clazz,
+        jobject in) {
+
+    Parcel* parcel = parcelForJavaObject(env, in);
+    if (parcel) {
+        sp<GraphicBuffer> buffer = new GraphicBuffer();
+        parcel->read(*buffer);
+        return new GraphicBufferWrapper(buffer);
+    }
+
+    return NULL;
+}
+
+// ----------------------------------------------------------------------------
+// External helpers
+// ----------------------------------------------------------------------------
+
+sp<GraphicBuffer> graphicBufferForJavaObject(JNIEnv* env, jobject obj) {
+    if (obj) {
+        jint nativeObject = env->GetIntField(obj, gGraphicBufferClassInfo.mNativeObject);
+        GraphicBufferWrapper* wrapper = (GraphicBufferWrapper*) nativeObject;
+        if (wrapper != NULL) {
+            sp<GraphicBuffer> buffer(wrapper->buffer);
+            return buffer;
+        }
+    }
+    return NULL;
+}
+
+// ----------------------------------------------------------------------------
+// JNI Glue
+// ----------------------------------------------------------------------------
+
+#define FIND_CLASS(var, className) \
+        var = env->FindClass(className); \
+        LOG_FATAL_IF(! var, "Unable to find class " className);
+
+#define GET_FIELD_ID(var, clazz, fieldName, fieldDescriptor) \
+        var = env->GetFieldID(clazz, fieldName, fieldDescriptor); \
+        LOG_FATAL_IF(! var, "Unable to find field " fieldName);
+
+#define GET_METHOD_ID(var, clazz, methodName, methodDescriptor) \
+        var = env->GetMethodID(clazz, methodName, methodDescriptor); \
+        LOG_FATAL_IF(!var, "Unable to find method " methodName);
+
+const char* const kClassPathName = "android/view/GraphicBuffer";
+
+static JNINativeMethod gMethods[] = {
+    { "nCreateGraphicBuffer",  "(IIII)I", (void*) android_view_GraphiceBuffer_create },
+    { "nDestroyGraphicBuffer", "(I)V",    (void*) android_view_GraphiceBuffer_destroy },
+
+    { "nWriteGraphicBufferToParcel",  "(ILandroid/os/Parcel;)V",
+            (void*) android_view_GraphiceBuffer_write },
+    { "nReadGraphicBufferFromParcel", "(Landroid/os/Parcel;)I",
+            (void*) android_view_GraphiceBuffer_read },
+
+    { "nLockCanvas", "(ILandroid/graphics/Canvas;Landroid/graphics/Rect;)Z",
+            (void*) android_view_GraphicBuffer_lockCanvas },
+    { "nUnlockCanvasAndPost", "(ILandroid/graphics/Canvas;)Z",
+            (void*) android_view_GraphicBuffer_unlockCanvasAndPost },
+};
+
+int register_android_view_GraphicBuffer(JNIEnv* env) {
+    jclass clazz;
+    FIND_CLASS(clazz, "android/view/GraphicBuffer");
+    GET_FIELD_ID(gGraphicBufferClassInfo.mNativeObject, clazz, "mNativeObject", "I");
+
+    FIND_CLASS(clazz, "android/graphics/Rect");
+    GET_METHOD_ID(gRectClassInfo.set, clazz, "set", "(IIII)V");
+    GET_FIELD_ID(gRectClassInfo.left, clazz, "left", "I");
+    GET_FIELD_ID(gRectClassInfo.top, clazz, "top", "I");
+    GET_FIELD_ID(gRectClassInfo.right, clazz, "right", "I");
+    GET_FIELD_ID(gRectClassInfo.bottom, clazz, "bottom", "I");
+
+    FIND_CLASS(clazz, "android/graphics/Canvas");
+    GET_FIELD_ID(gCanvasClassInfo.mFinalizer, clazz, "mFinalizer",
+            "Landroid/graphics/Canvas$CanvasFinalizer;");
+    GET_FIELD_ID(gCanvasClassInfo.mNativeCanvas, clazz, "mNativeCanvas", "I");
+    GET_FIELD_ID(gCanvasClassInfo.mSurfaceFormat, clazz, "mSurfaceFormat", "I");
+
+    FIND_CLASS(clazz, "android/graphics/Canvas$CanvasFinalizer");
+    GET_FIELD_ID(gCanvasFinalizerClassInfo.mNativeCanvas, clazz, "mNativeCanvas", "I");
+
+    return AndroidRuntime::registerNativeMethods(env, kClassPathName, gMethods, NELEM(gMethods));
+}
+
+};
diff --git a/core/jni/android_view_GraphicBuffer.h b/core/jni/android_view_GraphicBuffer.h
new file mode 100644
index 0000000..509587c
--- /dev/null
+++ b/core/jni/android_view_GraphicBuffer.h
@@ -0,0 +1,27 @@
+/*
+ * 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.
+ */
+
+#include <ui/GraphicBuffer.h>
+
+#include "jni.h"
+
+namespace android {
+
+// This function does not perform any type checking, the specified
+// object must be an instance of android.view.GraphicBuffer
+extern sp<GraphicBuffer> graphicBufferForJavaObject(JNIEnv* env, jobject obj);
+
+}
diff --git a/core/jni/android_view_TextureView.cpp b/core/jni/android_view_TextureView.cpp
index a1985bc..d515696 100644
--- a/core/jni/android_view_TextureView.cpp
+++ b/core/jni/android_view_TextureView.cpp
@@ -126,12 +126,12 @@
 }
 
 static inline void swapCanvasPtr(JNIEnv* env, jobject canvasObj, SkCanvas* newCanvas) {
-  jobject canvasFinalizerObj = env->GetObjectField(canvasObj, gCanvasClassInfo.mFinalizer);
-  SkCanvas* previousCanvas = reinterpret_cast<SkCanvas*>(
+    jobject canvasFinalizerObj = env->GetObjectField(canvasObj, gCanvasClassInfo.mFinalizer);
+    SkCanvas* previousCanvas = reinterpret_cast<SkCanvas*>(
           env->GetIntField(canvasObj, gCanvasClassInfo.mNativeCanvas));
-  env->SetIntField(canvasObj, gCanvasClassInfo.mNativeCanvas, (int)newCanvas);
-  env->SetIntField(canvasFinalizerObj, gCanvasFinalizerClassInfo.mNativeCanvas, (int)newCanvas);
-  SkSafeUnref(previousCanvas);
+    env->SetIntField(canvasObj, gCanvasClassInfo.mNativeCanvas, (int)newCanvas);
+    env->SetIntField(canvasFinalizerObj, gCanvasFinalizerClassInfo.mNativeCanvas, (int)newCanvas);
+    SkSafeUnref(previousCanvas);
 }
 
 static jboolean android_view_TextureView_lockCanvas(JNIEnv* env, jobject,
diff --git a/graphics/java/android/graphics/Atlas.java b/graphics/java/android/graphics/Atlas.java
new file mode 100644
index 0000000..39a5a53
--- /dev/null
+++ b/graphics/java/android/graphics/Atlas.java
@@ -0,0 +1,441 @@
+/*
+ * 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.
+ */
+
+package android.graphics;
+
+/**
+ * @hide
+ */
+public class Atlas {
+    /**
+     * This flag indicates whether the packing algorithm will attempt
+     * to rotate entries to make them fit better in the atlas.
+     */
+    public static final int FLAG_ALLOW_ROTATIONS = 0x1;
+    /**
+     * This flag indicates whether the packing algorithm should leave
+     * an empty 1 pixel wide border around each bitmap. This border can
+     * be useful if the content of the atlas will be used in OpenGL using
+     * bilinear filtering.
+     */
+    public static final int FLAG_ADD_PADDING = 0x2;
+    /**
+     * Default flags: allow rotations and add padding.
+     */
+    public static final int FLAG_DEFAULTS = FLAG_ADD_PADDING;
+
+    /**
+     * Each type defines a different packing algorithm that can
+     * be used by an {@link Atlas}. The best algorithm to use
+     * will depend on the source dataset and the dimensions of
+     * the atlas.
+     */
+    public enum Type {
+        SliceMinArea,
+        SliceMaxArea,
+        SliceShortAxis,
+        SliceLongAxis
+    }
+
+    /**
+     * Represents a bitmap packed in the atlas. Each entry has a location in
+     * pixels in the atlas and a rotation flag. If the entry was rotated, the
+     * bitmap must be rotated by 90 degrees (in either direction as long as
+     * the origin remains the same) before being rendered into the atlas.
+     */
+    public static class Entry {
+        /**
+         * Location, in pixels, of the bitmap on the X axis in the atlas.
+         */
+        public int x;
+        /**
+         * Location, in pixels, of the bitmap on the Y axis in the atlas.
+         */
+        public int y;
+
+        /**
+         * If true, the bitmap must be rotated 90 degrees in the atlas.
+         */
+        public boolean rotated;
+    }
+
+    private final Policy mPolicy;
+
+    /**
+     * Creates a new atlas with the specified algorithm and dimensions
+     * in pixels. Calling this constructor is equivalent to calling
+     * {@link #Atlas(Atlas.Type, int, int, int)} with {@link #FLAG_DEFAULTS}.
+     *
+     * @param type The algorithm to use to pack rectangles in the atlas
+     * @param width The width of the atlas in pixels
+     * @param height The height of the atlas in pixels
+     *
+     * @see #Atlas(Atlas.Type, int, int, int)
+     */
+    public Atlas(Type type, int width, int height) {
+        this(type, width, height, FLAG_DEFAULTS);
+    }
+
+    /**
+     * Creates a new atlas with the specified algorithm and dimensions
+     * in pixels. A set of flags can also be specified to control the
+     * behavior of the atlas.
+     *
+     * @param type The algorithm to use to pack rectangles in the atlas
+     * @param width The width of the atlas in pixels
+     * @param height The height of the atlas in pixels
+     * @param flags Optional flags to control the behavior of the atlas:
+     *              {@link #FLAG_ADD_PADDING}, {@link #FLAG_ALLOW_ROTATIONS}
+     *
+     * @see #Atlas(Atlas.Type, int, int)
+     */
+    public Atlas(Type type, int width, int height, int flags) {
+        mPolicy = findPolicy(type, width, height, flags);
+    }
+
+    /**
+     * Packs a rectangle of the specified dimensions in this atlas.
+     *
+     * @param width The width of the rectangle to pack in the atlas
+     * @param height The height of the rectangle to pack in the atlas
+     *
+     * @return An {@link Entry} instance if the rectangle was packed in
+     *         the atlas, or null if the rectangle could not fit
+     *
+     * @see #pack(int, int, Atlas.Entry)
+     */
+    public Entry pack(int width, int height) {
+        return pack(width, height, null);
+    }
+
+    /**
+     * Packs a rectangle of the specified dimensions in this atlas.
+     *
+     * @param width The width of the rectangle to pack in the atlas
+     * @param height The height of the rectangle to pack in the atlas
+     * @param entry Out parameter that will be filled in with the location
+     *              and attributes of the packed rectangle, can be null
+     *
+     * @return An {@link Entry} instance if the rectangle was packed in
+     *         the atlas, or null if the rectangle could not fit
+     *
+     * @see #pack(int, int)
+     */
+    public Entry pack(int width, int height, Entry entry) {
+        if (entry == null) entry = new Entry();
+        return mPolicy.pack(width, height, entry);
+    }
+
+    private static Policy findPolicy(Type type, int width, int height, int flags) {
+        switch (type) {
+            case SliceMinArea:
+                return new SlicePolicy(width, height, flags,
+                        new SlicePolicy.MinAreaSplitDecision());
+            case SliceMaxArea:
+                return new SlicePolicy(width, height, flags,
+                        new SlicePolicy.MaxAreaSplitDecision());
+            case SliceShortAxis:
+                return new SlicePolicy(width, height, flags,
+                        new SlicePolicy.ShorterFreeAxisSplitDecision());
+            case SliceLongAxis:
+                return new SlicePolicy(width, height, flags,
+                        new SlicePolicy.LongerFreeAxisSplitDecision());
+        }
+        return null;
+    }
+
+    /**
+     * A policy defines how the atlas performs the packing operation.
+     */
+    private static abstract class Policy {
+        abstract Entry pack(int width, int height, Entry entry);
+    }
+
+    /**
+     * The Slice algorightm divides the remaining empty space either
+     * horizontally or vertically after a bitmap is placed in the atlas.
+     *
+     * NOTE: the algorithm is explained below using a tree but is
+     * implemented using a linked list instead for performance reasons.
+     *
+     * The algorithm starts with a single empty cell covering the entire
+     * atlas:
+     *
+     *  -----------------------
+     * |                       |
+     * |                       |
+     * |                       |
+     * |      Empty space      |
+     * |          (C0)         |
+     * |                       |
+     * |                       |
+     * |                       |
+     *  -----------------------
+     *
+     * The tree of cells looks like this:
+     *
+     * N0(free)
+     *
+     * The algorithm then places a bitmap B1, if possible:
+     *
+     *  -----------------------
+     * |        |              |
+     * |   B1   |              |
+     * |        |              |
+     * |--------               |
+     * |                       |
+     * |                       |
+     * |                       |
+     * |                       |
+     *  -----------------------
+     *
+     *  After placing a bitmap in an empty cell, the algorithm splits
+     *  the remaining space in two new empty cells. The split can occur
+     *  vertically or horizontally (this is controlled by the "split
+     *  decision" parameter of the algorithm.)
+     *
+     *  Here is for the instance the result of a vertical split:
+     *
+     *  -----------------------
+     * |        |              |
+     * |   B1   |              |
+     * |        |              |
+     * |--------|      C2      |
+     * |        |              |
+     * |        |              |
+     * |   C1   |              |
+     * |        |              |
+     *  -----------------------
+     *
+     * The cells tree now looks like this:
+     *
+     *       C0(occupied)
+     *           / \
+     *          /   \
+     *         /     \
+     *        /       \
+     *    C1(free)  C2(free)
+     *
+     * For each bitmap to place in the atlas, the Slice algorithm
+     * will visit the free cells until it finds one where a bitmap can
+     * fit. It will then split the now occupied cell and proceed onto
+     * the next bitmap.
+     */
+    private static class SlicePolicy extends Policy {
+        private final Cell mRoot = new Cell();
+
+        private final SplitDecision mSplitDecision;
+
+        private final boolean mAllowRotation;
+        private final int mPadding;
+
+        /**
+         * A cell represents a sub-rectangle of the atlas. A cell is
+         * a node in a linked list representing the available free
+         * space in the atlas.
+         */
+        private static class Cell {
+            int x;
+            int y;
+
+            int width;
+            int height;
+
+            Cell next;
+
+            @Override
+            public String toString() {
+                return String.format("cell[x=%d y=%d width=%d height=%d", x, y, width, height);
+            }
+        }
+
+        SlicePolicy(int width, int height, int flags, SplitDecision splitDecision) {
+            mAllowRotation = (flags & FLAG_ALLOW_ROTATIONS) != 0;
+            mPadding = (flags & FLAG_ADD_PADDING) != 0 ? 1 : 0;
+
+            // The entire atlas is empty at first, minus padding
+            Cell first = new Cell();
+            first.x = first.y = mPadding;
+            first.width = width - 2 * mPadding;
+            first.height = height - 2 * mPadding;
+
+            mRoot.next = first;
+            mSplitDecision = splitDecision;
+        }
+
+        @Override
+        Entry pack(int width, int height, Entry entry) {
+            Cell cell = mRoot.next;
+            Cell prev = mRoot;
+
+            while (cell != null) {
+                if (insert(cell, prev, width, height, entry)) {
+                    return entry;
+                }
+
+                prev = cell;
+                cell = cell.next;
+            }
+
+            return null;
+        }
+
+        /**
+         * Defines how the remaining empty space should be split up:
+         * vertically or horizontally.
+         */
+        private static interface SplitDecision {
+            /**
+             * Returns true if the remaining space defined by
+             * <code>freeWidth</code> and <code>freeHeight</code>
+             * should be split horizontally.
+             *
+             * @param freeWidth The rectWidth of the free space after packing a rectangle
+             * @param freeHeight The rectHeight of the free space after packing a rectangle
+             * @param rectWidth The rectWidth of the rectangle that was packed in a cell
+             * @param rectHeight The rectHeight of the rectangle that was packed in a cell
+             */
+            boolean splitHorizontal(int freeWidth, int freeHeight,
+                    int rectWidth, int rectHeight);
+        }
+
+        // Splits the free area horizontally to minimize the horizontal section area
+        private static class MinAreaSplitDecision implements SplitDecision {
+            @Override
+            public boolean splitHorizontal(int freeWidth, int freeHeight,
+                    int rectWidth, int rectHeight) {
+                return rectWidth * freeHeight > freeWidth * rectHeight;
+            }
+        }
+
+        // Splits the free area horizontally to maximize the horizontal section area
+        private static class MaxAreaSplitDecision implements SplitDecision {
+            @Override
+            public boolean splitHorizontal(int freeWidth, int freeHeight,
+                    int rectWidth, int rectHeight) {
+                return rectWidth * freeHeight <= freeWidth * rectHeight;
+            }
+        }
+
+        // Splits the free area horizontally if the horizontal axis is shorter
+        private static class ShorterFreeAxisSplitDecision implements SplitDecision {
+            @Override
+            public boolean splitHorizontal(int freeWidth, int freeHeight,
+                    int rectWidth, int rectHeight) {
+                return freeWidth <= freeHeight;
+            }
+        }
+
+        // Splits the free area horizontally if the vertical axis is shorter
+        private static class LongerFreeAxisSplitDecision implements SplitDecision {
+            @Override
+            public boolean splitHorizontal(int freeWidth, int freeHeight,
+                    int rectWidth, int rectHeight) {
+                return freeWidth > freeHeight;
+            }
+        }
+
+        /**
+         * Attempts to pack a rectangle of specified dimensions in the available
+         * empty space.
+         *
+         * @param cell The cell representing free space in which to pack the rectangle
+         * @param prev The previous cell in the free space linked list
+         * @param width The width of the rectangle to pack
+         * @param height The height of the rectangle to pack
+         * @param entry Stores the location of the packged rectangle, if it fits
+         *
+         * @return True if the rectangle was packed in the atlas, false otherwise
+         */
+        @SuppressWarnings("SuspiciousNameCombination")
+        private boolean insert(Cell cell, Cell prev, int width, int height, Entry entry) {
+            boolean rotated = false;
+
+            // If the rectangle doesn't fit we'll try to rotate it
+            // if possible before giving up
+            if (cell.width < width || cell.height < height) {
+                if (mAllowRotation) {
+                    if (cell.width < height || cell.height < width) {
+                        return false;
+                    }
+
+                    // Rotate the rectangle
+                    int temp = width;
+                    width = height;
+                    height = temp;
+                    rotated = true;
+                } else {
+                    return false;
+                }
+            }
+
+            // Remaining free space after packing the rectangle
+            int deltaWidth = cell.width - width;
+            int deltaHeight = cell.height - height;
+
+            // Split the remaining free space into two new cells
+            Cell first = new Cell();
+            Cell second = new Cell();
+
+            first.x = cell.x + width + mPadding;
+            first.y = cell.y;
+            first.width = deltaWidth - mPadding;
+
+            second.x = cell.x;
+            second.y = cell.y + height + mPadding;
+            second.height = deltaHeight - mPadding;
+
+            if (mSplitDecision.splitHorizontal(deltaWidth, deltaHeight,
+                    width, height)) {
+                first.height = height;
+                second.width = cell.width;
+            } else {
+                first.height = cell.height;
+                second.width = width;
+
+                // The order of the cells matters for efficient packing
+                // We want to give priority to the cell chosen by the
+                // split decision heuristic
+                Cell temp = first;
+                first = second;
+                second = temp;
+            }
+
+            // Remove degenerate cases to keep the free list as small as possible
+            if (first.width > 0 && first.height > 0) {
+                prev.next = first;
+                prev = first;
+            }
+
+            if (second.width > 0 && second.height > 0) {
+                prev.next = second;
+                second.next = cell.next;
+            } else {
+                prev.next = cell.next;
+            }
+
+            // The cell is now completely removed from the free list
+            cell.next = null;
+
+            // Return the location and rotation of the packed rectangle
+            entry.x = cell.x;
+            entry.y = cell.y;
+            entry.rotated = rotated;
+
+            return true;
+        }
+    }
+}
diff --git a/graphics/java/android/graphics/Bitmap.java b/graphics/java/android/graphics/Bitmap.java
index 89abdef..d26b5a2 100644
--- a/graphics/java/android/graphics/Bitmap.java
+++ b/graphics/java/android/graphics/Bitmap.java
@@ -45,12 +45,9 @@
 
     /**
      * Backing buffer for the Bitmap.
-     * Made public for quick access from drawing methods -- do NOT modify
-     * from outside this class.
-     *
-     * @hide
      */
-    public byte[] mBuffer;
+    @SuppressWarnings("UnusedDeclaration") // native code only
+    private byte[] mBuffer;
 
     @SuppressWarnings({"FieldCanBeLocal", "UnusedDeclaration"}) // Keep to finalize native resources
     private final BitmapFinalizer mFinalizer;
@@ -701,12 +698,10 @@
         if (config == Config.ARGB_8888 && !hasAlpha) {
             nativeErase(bm.mNativeBitmap, 0xff000000);
             nativeSetHasAlpha(bm.mNativeBitmap, hasAlpha);
-        } else {
-            // No need to initialize it to zeroes; it is backed by a VM byte array
-            // which is by definition preinitialized to all zeroes.
-            //
-            //nativeErase(bm.mNativeBitmap, 0);
         }
+        // No need to initialize the bitmap to zeroes with other configs;
+        // it is backed by a VM byte array which is by definition preinitialized
+        // to all zeroes.
         return bm;
     }
 
diff --git a/graphics/java/android/graphics/Canvas.java b/graphics/java/android/graphics/Canvas.java
index cc7f23f..58e7525 100644
--- a/graphics/java/android/graphics/Canvas.java
+++ b/graphics/java/android/graphics/Canvas.java
@@ -1058,14 +1058,13 @@
      *
      * Note: Only supported by hardware accelerated canvas at the moment.
      *
-     * @param bitmap The bitmap to draw as an N-patch
-     * @param chunks The patches information (matches the native struct Res_png_9patch)
+     * @param patch The ninepatch object to render
      * @param dst The destination rectangle.
      * @param paint The paint to draw the bitmap with. may be null
      * 
      * @hide
      */
-    public void drawPatch(Bitmap bitmap, byte[] chunks, RectF dst, Paint paint) {
+    public void drawPatch(NinePatch patch, RectF dst, Paint paint) {
     }    
     
     /**
diff --git a/graphics/java/android/graphics/NinePatch.java b/graphics/java/android/graphics/NinePatch.java
index 6de4d84..81e9d84 100644
--- a/graphics/java/android/graphics/NinePatch.java
+++ b/graphics/java/android/graphics/NinePatch.java
@@ -36,7 +36,10 @@
  */
 public class NinePatch {
     private final Bitmap mBitmap;
-    private final byte[] mChunk;
+    /**
+     * @hide
+     */
+    public final byte[] mChunk;
     private Paint mPaint;
     private String mSrcName;  // Useful for debugging
     private final RectF mRect = new RectF();
@@ -72,6 +75,13 @@
     public void setPaint(Paint p) {
         mPaint = p;
     }
+
+    /**
+     * @hide
+     */
+    public Bitmap getBitmap() {
+        return mBitmap;
+    }
     
     /** 
      * Draw a bitmap of nine patches.
@@ -86,7 +96,7 @@
                        mPaint != null ? mPaint.mNativePaint : 0,
                        canvas.mDensity, mBitmap.mDensity);
         } else {
-            canvas.drawPatch(mBitmap, mChunk, location, mPaint);
+            canvas.drawPatch(this, location, mPaint);
         }
     }
     
@@ -104,7 +114,7 @@
                         canvas.mDensity, mBitmap.mDensity);
         } else {
             mRect.set(location);
-            canvas.drawPatch(mBitmap, mChunk, mRect, mPaint);
+            canvas.drawPatch(this, mRect, mPaint);
         }
     }
 
@@ -122,7 +132,7 @@
                     canvas.mDensity, mBitmap.mDensity);
         } else {
             mRect.set(location);
-            canvas.drawPatch(mBitmap, mChunk, mRect, paint);
+            canvas.drawPatch(this, mRect, paint);
         }
     }
 
diff --git a/graphics/java/android/graphics/drawable/BitmapDrawable.java b/graphics/java/android/graphics/drawable/BitmapDrawable.java
index 75781108..8689261 100644
--- a/graphics/java/android/graphics/drawable/BitmapDrawable.java
+++ b/graphics/java/android/graphics/drawable/BitmapDrawable.java
@@ -584,6 +584,11 @@
         }
 
         @Override
+        public Bitmap getBitmap() {
+            return mBitmap;
+        }
+
+        @Override
         public Drawable newDrawable() {
             return new BitmapDrawable(this, null);
         }
diff --git a/graphics/java/android/graphics/drawable/Drawable.java b/graphics/java/android/graphics/drawable/Drawable.java
index 66f7a5e..6d236d9 100644
--- a/graphics/java/android/graphics/drawable/Drawable.java
+++ b/graphics/java/android/graphics/drawable/Drawable.java
@@ -996,6 +996,13 @@
          * this drawable (and thus require completely reloading it).
          */
         public abstract int getChangingConfigurations();
+
+        /**
+         * @hide
+         */
+        public Bitmap getBitmap() {
+            return null;
+        }
     }
 
     /**
diff --git a/graphics/java/android/graphics/drawable/NinePatchDrawable.java b/graphics/java/android/graphics/drawable/NinePatchDrawable.java
index 8429e24..b83815b 100644
--- a/graphics/java/android/graphics/drawable/NinePatchDrawable.java
+++ b/graphics/java/android/graphics/drawable/NinePatchDrawable.java
@@ -403,7 +403,7 @@
         return this;
     }
 
-    private final static class NinePatchState extends ConstantState {
+    final static class NinePatchState extends ConstantState {
         final NinePatch mNinePatch;
         final Rect mPadding;
         final Insets mOpticalInsets;
@@ -439,6 +439,11 @@
         }
 
         @Override
+        public Bitmap getBitmap() {
+            return mNinePatch.getBitmap();
+        }
+
+        @Override
         public Drawable newDrawable() {
             return new NinePatchDrawable(this, null);
         }
diff --git a/libs/hwui/Android.mk b/libs/hwui/Android.mk
index 281f9a5..3433d0e 100644
--- a/libs/hwui/Android.mk
+++ b/libs/hwui/Android.mk
@@ -10,6 +10,7 @@
 		thread/TaskManager.cpp \
 		font/CacheTexture.cpp \
 		font/Font.cpp \
+		AssetAtlas.cpp \
 		FontRenderer.cpp \
 		GammaFontRenderer.cpp \
 		Caches.cpp \
@@ -54,9 +55,9 @@
 		external/skia/src/ports \
 		external/skia/include/utils
 
-	LOCAL_CFLAGS += -DUSE_OPENGL_RENDERER -DGL_GLEXT_PROTOTYPES
+	LOCAL_CFLAGS += -DUSE_OPENGL_RENDERER -DEGL_EGLEXT_PROTOTYPES -DGL_GLEXT_PROTOTYPES
 	LOCAL_MODULE_CLASS := SHARED_LIBRARIES
-	LOCAL_SHARED_LIBRARIES := liblog libcutils libutils libGLESv2 libskia libui
+	LOCAL_SHARED_LIBRARIES := liblog libcutils libutils libEGL libGLESv2 libskia libui
 	LOCAL_MODULE := libhwui
 	LOCAL_MODULE_TAGS := optional
 
diff --git a/libs/hwui/AssetAtlas.cpp b/libs/hwui/AssetAtlas.cpp
new file mode 100644
index 0000000..cf8cc97
--- /dev/null
+++ b/libs/hwui/AssetAtlas.cpp
@@ -0,0 +1,121 @@
+/*
+ * 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.
+ */
+
+#define LOG_TAG "OpenGLRenderer"
+
+#include "AssetAtlas.h"
+
+#include <GLES2/gl2ext.h>
+
+#include <utils/Log.h>
+
+namespace android {
+namespace uirenderer {
+
+///////////////////////////////////////////////////////////////////////////////
+// Lifecycle
+///////////////////////////////////////////////////////////////////////////////
+
+void AssetAtlas::init(sp<GraphicBuffer> buffer, int* map, int count) {
+    if (mImage != EGL_NO_IMAGE_KHR) {
+        return;
+    }
+
+    // Create the EGLImage object that maps the GraphicBuffer
+    EGLDisplay display = eglGetDisplay(EGL_DEFAULT_DISPLAY);
+    EGLClientBuffer clientBuffer = (EGLClientBuffer) buffer->getNativeBuffer();
+    EGLint attrs[] = { EGL_IMAGE_PRESERVED_KHR, EGL_TRUE, EGL_NONE };
+
+    mImage = eglCreateImageKHR(display, EGL_NO_CONTEXT,
+            EGL_NATIVE_BUFFER_ANDROID, clientBuffer, attrs);
+
+    if (mImage == EGL_NO_IMAGE_KHR) {
+        ALOGW("Error creating atlas image (%#x)", eglGetError());
+        return;
+    }
+
+    // Create a 2D texture to sample from the EGLImage
+    glGenTextures(1, &mTexture);
+    glBindTexture(GL_TEXTURE_2D, mTexture);
+    glEGLImageTargetTexture2DOES(GL_TEXTURE_2D, mImage);
+
+    mWidth = buffer->getWidth();
+    mHeight = buffer->getHeight();
+
+    createEntries(map, count);
+}
+
+void AssetAtlas::terminate() {
+    if (mImage != EGL_NO_IMAGE_KHR) {
+        eglDestroyImageKHR(eglGetDisplay(EGL_DEFAULT_DISPLAY), mImage);
+        mImage = EGL_NO_IMAGE_KHR;
+
+        glDeleteTextures(1, &mTexture);
+        mTexture = 0;
+
+        for (size_t i = 0; i < mEntries.size(); i++) {
+            delete mEntries.valueAt(i);
+        }
+        mEntries.clear();
+
+        mWidth = mHeight = 0;
+    }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// Entries
+///////////////////////////////////////////////////////////////////////////////
+
+AssetAtlas::Entry* AssetAtlas::getEntry(SkBitmap* const bitmap) const {
+    ssize_t index = mEntries.indexOfKey(bitmap);
+    return index >= 0 ? mEntries.valueAt(index) : NULL;
+}
+
+Texture* AssetAtlas::getEntryTexture(SkBitmap* const bitmap) const {
+    ssize_t index = mEntries.indexOfKey(bitmap);
+    return index >= 0 ? &mEntries.valueAt(index)->texture : NULL;
+}
+
+/**
+ * TODO: This method does not take the rotation flag into account
+ */
+void AssetAtlas::createEntries(int* map, int count) {
+    for (int i = 0; i < count; ) {
+        SkBitmap* bitmap = (SkBitmap*) map[i++];
+        int x = map[i++];
+        int y = map[i++];
+        bool rotated = map[i++] > 0;
+
+        // Bitmaps should never be null, we're just extra paranoid
+        if (!bitmap) continue;
+
+        const UvMapper mapper(
+                x / (float) mWidth, (x + bitmap->width()) / (float) mWidth,
+                y / (float) mHeight, (y + bitmap->height()) / (float) mHeight);
+
+        Entry* entry = new Entry(bitmap, x, y, rotated, mapper, *this);
+        entry->texture.id = mTexture;
+        entry->texture.blend = !bitmap->isOpaque();
+        entry->texture.width = bitmap->width();
+        entry->texture.height = bitmap->height();
+        entry->texture.uvMapper = &entry->uvMapper;
+
+        mEntries.add(entry->bitmap, entry);
+    }
+}
+
+}; // namespace uirenderer
+}; // namespace android
diff --git a/libs/hwui/AssetAtlas.h b/libs/hwui/AssetAtlas.h
new file mode 100644
index 0000000..4ede716
--- /dev/null
+++ b/libs/hwui/AssetAtlas.h
@@ -0,0 +1,171 @@
+/*
+ * 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_ASSET_ATLAS_H
+#define ANDROID_HWUI_ASSET_ATLAS_H
+
+#include <GLES2/gl2.h>
+
+#include <EGL/egl.h>
+#include <EGL/eglext.h>
+
+#include <ui/GraphicBuffer.h>
+
+#include <utils/KeyedVector.h>
+
+#include <cutils/compiler.h>
+
+#include <SkBitmap.h>
+
+#include "Texture.h"
+#include "UvMapper.h"
+
+namespace android {
+namespace uirenderer {
+
+/**
+ * An asset atlas holds a collection of framework bitmaps in a single OpenGL
+ * texture. Each bitmap is associated with a location, defined in pixels,
+ * inside the atlas. The atlas is generated by the framework and bound as
+ * an external texture using the EGLImageKHR extension.
+ */
+class AssetAtlas {
+public:
+    /**
+     * Entry representing the position and rotation of a
+     * bitmap inside the atlas.
+     */
+    struct Entry {
+        /**
+         * The bitmap that generated this atlas entry.
+         */
+        SkBitmap* bitmap;
+
+        /**
+         * Location of the bitmap inside the atlas, in pixels.
+         */
+        int x;
+        int y;
+
+        /**
+         * If set, the bitmap is rotated 90 degrees (clockwise)
+         * inside the atlas.
+         */
+        bool rotated;
+
+        /**
+         * Maps texture coordinates in the [0..1] range into the
+         * correct range to sample this entry from the atlas.
+         */
+        const UvMapper uvMapper;
+
+        /**
+         * Atlas this entry belongs to.
+         */
+        const AssetAtlas& atlas;
+
+        /*
+         * A "virtual texture" object that represents the texture
+         * this entry belongs to. This texture should never be
+         * modified.
+         */
+        Texture texture;
+
+    private:
+        Entry(SkBitmap* bitmap, int x, int y, bool rotated,
+                const UvMapper& mapper, const AssetAtlas& atlas):
+                bitmap(bitmap), x(x), y(y), rotated(rotated), uvMapper(mapper), atlas(atlas) { }
+
+        friend class AssetAtlas;
+    };
+
+    AssetAtlas(): mWidth(0), mHeight(0), mTexture(0), mImage(EGL_NO_IMAGE_KHR) { }
+    ~AssetAtlas() { terminate(); }
+
+    /**
+     * Initializes the atlas with the specified buffer and
+     * map. The buffer is a gralloc'd texture that will be
+     * used as an EGLImage. The map is a list of SkBitmap*
+     * and their (x, y) positions as well as their rotation
+     * flags.
+     *
+     * This method returns immediately if the atlas is already
+     * initialized. To re-initialize the atlas, you must
+     * first call terminate().
+     */
+    ANDROID_API void init(sp<GraphicBuffer> buffer, int* map, int count);
+
+    /**
+     * Destroys the atlas texture. This object can be
+     * re-initialized after calling this method.
+     *
+     * After calling this method, the width, height
+     * and texture are set to 0.
+     */
+    ANDROID_API void terminate();
+
+    /**
+     * Returns the width of this atlas in pixels.
+     * Can return 0 if the atlas is not initialized.
+     */
+    uint32_t getWidth() const {
+        return mWidth;
+    }
+
+    /**
+     * Returns the height of this atlas in pixels.
+     * Can return 0 if the atlas is not initialized.
+     */
+    uint32_t getHeight() const {
+        return mHeight;
+    }
+
+    /**
+     * Returns the OpenGL name of the texture backing this atlas.
+     * Can return 0 if the atlas is not initialized.
+     */
+    GLuint getTexture() const {
+        return mTexture;
+    }
+
+    /**
+     * Returns the entry in the atlas associated with the specified
+     * bitmap. If the bitmap is not in the atlas, return NULL.
+     */
+    Entry* getEntry(SkBitmap* const bitmap) const;
+
+    /**
+     * Returns the texture for the atlas entry associated with the
+     * specified bitmap. If the bitmap is not in the atlas, return NULL.
+     */
+    Texture* getEntryTexture(SkBitmap* const bitmap) const;
+
+private:
+    void createEntries(int* map, int count);
+
+    uint32_t mWidth;
+    uint32_t mHeight;
+
+    GLuint mTexture;
+    EGLImageKHR mImage;
+
+    KeyedVector<SkBitmap*, Entry*> mEntries;
+}; // class AssetAtlas
+
+}; // namespace uirenderer
+}; // namespace android
+
+#endif // ANDROID_HWUI_ASSET_ATLAS_H
diff --git a/libs/hwui/Caches.cpp b/libs/hwui/Caches.cpp
index a381a68..c60848c 100644
--- a/libs/hwui/Caches.cpp
+++ b/libs/hwui/Caches.cpp
@@ -58,8 +58,8 @@
     ALOGD("Enabling debug mode %d", mDebugLevel);
 }
 
-void Caches::init() {
-    if (mInitialized) return;
+bool Caches::init() {
+    if (mInitialized) return false;
 
     glGenBuffers(1, &meshBuffer);
     glBindBuffer(GL_ARRAY_BUFFER, meshBuffer);
@@ -82,6 +82,7 @@
     mTextureUnit = 0;
 
     mRegionMesh = NULL;
+    mMeshIndices = 0;
 
     blend = false;
     lastSrcMode = GL_ZERO;
@@ -94,7 +95,11 @@
     debugOverdraw = false;
     debugStencilClip = kStencilHide;
 
+    patchCache.init(*this);
+
     mInitialized = true;
+
+    return true;
 }
 
 void Caches::initFont() {
@@ -191,8 +196,9 @@
     glDeleteBuffers(1, &meshBuffer);
     mCurrentBuffer = 0;
 
-    glDeleteBuffers(1, &mRegionMeshIndices);
+    glDeleteBuffers(1, &mMeshIndices);
     delete[] mRegionMesh;
+    mMeshIndices = 0;
     mRegionMesh = NULL;
 
     fboCache.clear();
@@ -200,6 +206,10 @@
     programCache.clear();
     currentProgram = NULL;
 
+    assetAtlas.terminate();
+
+    patchCache.clear();
+
     mInitialized = false;
 }
 
@@ -227,6 +237,8 @@
             pathCache.getSize(), pathCache.getMaxSize());
     log.appendFormat("  TextDropShadowCache  %8d / %8d\n", dropShadowCache.getSize(),
             dropShadowCache.getMaxSize());
+    log.appendFormat("  PatchCache           %8d / %8d\n",
+            patchCache.getSize(), patchCache.getMaxSize());
     for (uint32_t i = 0; i < fontRenderer->getFontRendererCount(); i++) {
         const uint32_t size = fontRenderer->getFontRendererSize(i);
         log.appendFormat("  FontRenderer %d       %8d / %8d\n", i, size, size);
@@ -234,8 +246,6 @@
     log.appendFormat("Other:\n");
     log.appendFormat("  FboCache             %8d / %8d\n",
             fboCache.getSize(), fboCache.getMaxSize());
-    log.appendFormat("  PatchCache           %8d / %8d\n",
-            patchCache.getSize(), patchCache.getMaxSize());
 
     uint32_t total = 0;
     total += textureCache.getSize();
@@ -244,6 +254,7 @@
     total += gradientCache.getSize();
     total += pathCache.getSize();
     total += dropShadowCache.getSize();
+    total += patchCache.getSize();
     for (uint32_t i = 0; i < fontRenderer->getFontRendererCount(); i++) {
         total += fontRenderer->getFontRendererSize(i);
     }
@@ -357,6 +368,32 @@
     return false;
 }
 
+bool Caches::bindIndicesBuffer() {
+    if (!mMeshIndices) {
+        uint16_t* regionIndices = new uint16_t[REGION_MESH_QUAD_COUNT * 6];
+        for (int i = 0; i < REGION_MESH_QUAD_COUNT; i++) {
+            uint16_t quad = i * 4;
+            int index = i * 6;
+            regionIndices[index    ] = quad;       // top-left
+            regionIndices[index + 1] = quad + 1;   // top-right
+            regionIndices[index + 2] = quad + 2;   // bottom-left
+            regionIndices[index + 3] = quad + 2;   // bottom-left
+            regionIndices[index + 4] = quad + 1;   // top-right
+            regionIndices[index + 5] = quad + 3;   // bottom-right
+        }
+
+        glGenBuffers(1, &mMeshIndices);
+        bool force = bindIndicesBuffer(mMeshIndices);
+        glBufferData(GL_ELEMENT_ARRAY_BUFFER, REGION_MESH_QUAD_COUNT * 6 * sizeof(uint16_t),
+                regionIndices, GL_STATIC_DRAW);
+
+        delete[] regionIndices;
+        return force;
+    }
+
+    return bindIndicesBuffer(mMeshIndices);
+}
+
 bool Caches::unbindIndicesBuffer() {
     if (mCurrentIndicesBuffer) {
         glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
@@ -546,27 +583,6 @@
     // Create the mesh, 2 triangles and 4 vertices per rectangle in the region
     if (!mRegionMesh) {
         mRegionMesh = new TextureVertex[REGION_MESH_QUAD_COUNT * 4];
-
-        uint16_t* regionIndices = new uint16_t[REGION_MESH_QUAD_COUNT * 6];
-        for (int i = 0; i < REGION_MESH_QUAD_COUNT; i++) {
-            uint16_t quad = i * 4;
-            int index = i * 6;
-            regionIndices[index    ] = quad;       // top-left
-            regionIndices[index + 1] = quad + 1;   // top-right
-            regionIndices[index + 2] = quad + 2;   // bottom-left
-            regionIndices[index + 3] = quad + 2;   // bottom-left
-            regionIndices[index + 4] = quad + 1;   // top-right
-            regionIndices[index + 5] = quad + 3;   // bottom-right
-        }
-
-        glGenBuffers(1, &mRegionMeshIndices);
-        bindIndicesBuffer(mRegionMeshIndices);
-        glBufferData(GL_ELEMENT_ARRAY_BUFFER, REGION_MESH_QUAD_COUNT * 6 * sizeof(uint16_t),
-                regionIndices, GL_STATIC_DRAW);
-
-        delete[] regionIndices;
-    } else {
-        bindIndicesBuffer(mRegionMeshIndices);
     }
 
     return mRegionMesh;
diff --git a/libs/hwui/Caches.h b/libs/hwui/Caches.h
index 91b938b..18aeeab 100644
--- a/libs/hwui/Caches.h
+++ b/libs/hwui/Caches.h
@@ -21,13 +21,18 @@
     #define LOG_TAG "OpenGLRenderer"
 #endif
 
+#include <GLES3/gl3.h>
+
+#include <utils/KeyedVector.h>
 #include <utils/Singleton.h>
+#include <utils/Vector.h>
 
 #include <cutils/compiler.h>
 
 #include "thread/TaskProcessor.h"
 #include "thread/TaskManager.h"
 
+#include "AssetAtlas.h"
 #include "FontRenderer.h"
 #include "GammaFontRenderer.h"
 #include "TextureCache.h"
@@ -113,7 +118,7 @@
     /**
      * Initialize caches.
      */
-    void init();
+    bool init();
 
     /**
      * Initialize global system properties.
@@ -172,6 +177,11 @@
      */
     bool unbindMeshBuffer();
 
+    /**
+     * Binds a global indices buffer that can draw up to
+     * REGION_MESH_QUAD_COUNT quads.
+     */
+    bool bindIndicesBuffer();
     bool bindIndicesBuffer(const GLuint buffer);
     bool unbindIndicesBuffer();
 
@@ -290,6 +300,8 @@
     Dither dither;
     Stencil stencil;
 
+    AssetAtlas assetAtlas;
+
     // Debug methods
     PFNGLINSERTEVENTMARKEREXTPROC eventMark;
     PFNGLPUSHGROUPMARKEREXTPROC startMark;
@@ -336,7 +348,9 @@
 
     // Used to render layers
     TextureVertex* mRegionMesh;
-    GLuint mRegionMeshIndices;
+
+    // Global index buffer
+    GLuint mMeshIndices;
 
     mutable Mutex mGarbageLock;
     Vector<Layer*> mLayerGarbage;
diff --git a/libs/hwui/Debug.h b/libs/hwui/Debug.h
index 790c4f4..786f12a 100644
--- a/libs/hwui/Debug.h
+++ b/libs/hwui/Debug.h
@@ -53,8 +53,6 @@
 
 // Turn on to display debug info about 9patch objects
 #define DEBUG_PATCHES 0
-// Turn on to "explode" 9patch objects
-#define DEBUG_EXPLODE_PATCHES 0
 // Turn on to display vertex and tex coords data about 9patch objects
 // This flag requires DEBUG_PATCHES to be turned on
 #define DEBUG_PATCHES_VERTICES 0
diff --git a/libs/hwui/DisplayListOp.h b/libs/hwui/DisplayListOp.h
index a0290e3..990372e 100644
--- a/libs/hwui/DisplayListOp.h
+++ b/libs/hwui/DisplayListOp.h
@@ -26,8 +26,10 @@
 #include <private/hwui/DrawGlInfo.h>
 
 #include "OpenGLRenderer.h"
+#include "AssetAtlas.h"
 #include "DeferredDisplayList.h"
 #include "DisplayListRenderer.h"
+#include "UvMapper.h"
 #include "utils/LinearAllocator.h"
 
 #define CRASH() do { \
@@ -721,7 +723,6 @@
     int mSetBits;
 };
 
-
 ///////////////////////////////////////////////////////////////////////////////
 // DRAW OPERATIONS - these are operations that can draw to the canvas's device
 ///////////////////////////////////////////////////////////////////////////////
@@ -729,9 +730,16 @@
 class DrawBitmapOp : public DrawBoundedOp {
 public:
     DrawBitmapOp(SkBitmap* bitmap, float left, float top, SkPaint* paint)
-            : DrawBoundedOp(left, top, left + bitmap->width(), top + bitmap->height(),
-                    paint),
-            mBitmap(bitmap) {}
+            : DrawBoundedOp(left, top, left + bitmap->width(), top + bitmap->height(), paint),
+            mBitmap(bitmap), mAtlasEntry(NULL) {
+    }
+
+    DrawBitmapOp(SkBitmap* bitmap, float left, float top, SkPaint* paint,
+            const AssetAtlas::Entry* entry)
+            : DrawBoundedOp(left, top, left + bitmap->width(), top + bitmap->height(), paint),
+            mBitmap(bitmap), mAtlasEntry(entry) {
+        if (entry) mUvMapper = entry->uvMapper;
+    }
 
     virtual status_t applyDraw(OpenGLRenderer& renderer, Rect& dirty) {
         return renderer.drawBitmap(mBitmap, mLocalBounds.left, mLocalBounds.top,
@@ -749,14 +757,14 @@
         TextureVertex vertices[6 * ops.size()];
         TextureVertex* vertex = &vertices[0];
 
-        // TODO: manually handle rect clip for bitmaps by adjusting texCoords per op, and allowing
-        // them to be merged in getBatchId()
-        const Rect texCoords(0, 0, 1, 1);
-
-        const float width = mBitmap->width();
-        const float height = mBitmap->height();
+        // TODO: manually handle rect clip for bitmaps by adjusting texCoords per op,
+        // and allowing them to be merged in getBatchId()
         for (unsigned int i = 0; i < ops.size(); i++) {
             const Rect& opBounds = ops[i]->state.mBounds;
+
+            Rect texCoords(0, 0, 1, 1);
+            ((DrawBitmapOp*) ops[i])->mUvMapper.map(texCoords);
+
             SET_TEXTURE(vertex, opBounds, bounds, texCoords, left, top);
             SET_TEXTURE(vertex, opBounds, bounds, texCoords, right, top);
             SET_TEXTURE(vertex, opBounds, bounds, texCoords, left, bottom);
@@ -777,7 +785,7 @@
 
     virtual bool onDefer(OpenGLRenderer& renderer, int* batchId, mergeid_t* mergeId) {
         *batchId = DeferredDisplayList::kOpBatch_Bitmap;
-        *mergeId = (mergeid_t)mBitmap;
+        *mergeId = mAtlasEntry ? (mergeid_t) &mAtlasEntry->atlas : (mergeid_t) mBitmap;
 
         // don't merge A8 bitmaps - the paint's color isn't compared by mergeId, or in
         // MergingDrawBatch::canMergeWith
@@ -787,6 +795,8 @@
     const SkBitmap* bitmap() { return mBitmap; }
 protected:
     SkBitmap* mBitmap;
+    const AssetAtlas::Entry* mAtlasEntry;
+    UvMapper mUvMapper;
 };
 
 class DrawBitmapMatrixOp : public DrawBoundedOp {
@@ -904,20 +914,16 @@
 
 class DrawPatchOp : public DrawBoundedOp {
 public:
-    DrawPatchOp(SkBitmap* bitmap, const int32_t* xDivs,
-            const int32_t* yDivs, const uint32_t* colors, uint32_t width, uint32_t height,
-            int8_t numColors, float left, float top, float right, float bottom,
-            int alpha, SkXfermode::Mode mode)
+    DrawPatchOp(SkBitmap* bitmap, Res_png_9patch* patch,
+            float left, float top, float right, float bottom, int alpha, SkXfermode::Mode mode)
             : DrawBoundedOp(left, top, right, bottom, 0),
-            mBitmap(bitmap), mxDivs(xDivs), myDivs(yDivs),
-            mColors(colors), mxDivsCount(width), myDivsCount(height),
-            mNumColors(numColors), mAlpha(alpha), mMode(mode) {};
+            mBitmap(bitmap), mPatch(patch), mAlpha(alpha), mMode(mode) {
+        mEntry = Caches::getInstance().assetAtlas.getEntry(bitmap);
+    };
 
     virtual status_t applyDraw(OpenGLRenderer& renderer, Rect& dirty) {
         // NOTE: not calling the virtual method, which takes a paint
-        return renderer.drawPatch(mBitmap, mxDivs, myDivs, mColors,
-                mxDivsCount, myDivsCount, mNumColors,
-                mLocalBounds.left, mLocalBounds.top,
+        return renderer.drawPatch(mBitmap, mPatch, mEntry, mLocalBounds.left, mLocalBounds.top,
                 mLocalBounds.right, mLocalBounds.bottom, mAlpha, mMode);
     }
 
@@ -929,20 +935,16 @@
 
     virtual bool onDefer(OpenGLRenderer& renderer, int* batchId, mergeid_t* mergeId) {
         *batchId = DeferredDisplayList::kOpBatch_Patch;
-        *mergeId = (mergeid_t)mBitmap;
+        *mergeId = (mergeid_t) mBitmap;
         return true;
     }
 
 private:
     SkBitmap* mBitmap;
-    const int32_t* mxDivs;
-    const int32_t* myDivs;
-    const uint32_t* mColors;
-    uint32_t mxDivsCount;
-    uint32_t myDivsCount;
-    int8_t mNumColors;
+    Res_png_9patch* mPatch;
     int mAlpha;
     SkXfermode::Mode mMode;
+    AssetAtlas::Entry* mEntry;
 };
 
 class DrawColorOp : public DrawOp {
diff --git a/libs/hwui/DisplayListRenderer.cpp b/libs/hwui/DisplayListRenderer.cpp
index 876c38a..bfd4086 100644
--- a/libs/hwui/DisplayListRenderer.cpp
+++ b/libs/hwui/DisplayListRenderer.cpp
@@ -257,7 +257,8 @@
     bitmap = refBitmap(bitmap);
     paint = refPaint(paint);
 
-    addDrawOp(new (alloc()) DrawBitmapOp(bitmap, left, top, paint));
+    const AssetAtlas::Entry* entry = mCaches.assetAtlas.getEntry(bitmap);
+    addDrawOp(new (alloc()) DrawBitmapOp(bitmap, left, top, paint, entry));
     return DrawGlInfo::kStatusDone;
 }
 
@@ -281,7 +282,8 @@
             (srcBottom - srcTop == dstBottom - dstTop) &&
             (srcRight - srcLeft == dstRight - dstLeft)) {
         // transform simple rect to rect drawing case into position bitmap ops, since they merge
-        addDrawOp(new (alloc()) DrawBitmapOp(bitmap, dstLeft, dstTop, paint));
+        const AssetAtlas::Entry* entry = mCaches.assetAtlas.getEntry(bitmap);
+        addDrawOp(new (alloc()) DrawBitmapOp(bitmap, dstLeft, dstTop, paint, entry));
         return DrawGlInfo::kStatusDone;
     }
 
@@ -313,20 +315,15 @@
     return DrawGlInfo::kStatusDone;
 }
 
-status_t DisplayListRenderer::drawPatch(SkBitmap* bitmap, const int32_t* xDivs,
-        const int32_t* yDivs, const uint32_t* colors, uint32_t width, uint32_t height,
-        int8_t numColors, float left, float top, float right, float bottom, SkPaint* paint) {
+status_t DisplayListRenderer::drawPatch(SkBitmap* bitmap, Res_png_9patch* patch,
+        float left, float top, float right, float bottom, SkPaint* paint) {
     int alpha;
     SkXfermode::Mode mode;
     OpenGLRenderer::getAlphaAndModeDirect(paint, &alpha, &mode);
 
     bitmap = refBitmap(bitmap);
-    xDivs = refBuffer<int>(xDivs, width);
-    yDivs = refBuffer<int>(yDivs, height);
-    colors = refBuffer<uint32_t>(colors, numColors);
 
-    addDrawOp(new (alloc()) DrawPatchOp(bitmap, xDivs, yDivs, colors, width, height, numColors,
-                    left, top, right, bottom, alpha, mode));
+    addDrawOp(new (alloc()) DrawPatchOp(bitmap, patch, left, top, right, bottom, alpha, mode));
     return DrawGlInfo::kStatusDone;
 }
 
diff --git a/libs/hwui/DisplayListRenderer.h b/libs/hwui/DisplayListRenderer.h
index 75abad6..db08921 100644
--- a/libs/hwui/DisplayListRenderer.h
+++ b/libs/hwui/DisplayListRenderer.h
@@ -103,8 +103,7 @@
     virtual status_t drawBitmapData(SkBitmap* bitmap, float left, float top, SkPaint* paint);
     virtual status_t drawBitmapMesh(SkBitmap* bitmap, int meshWidth, int meshHeight,
             float* vertices, int* colors, SkPaint* paint);
-    virtual status_t drawPatch(SkBitmap* bitmap, const int32_t* xDivs, const int32_t* yDivs,
-            const uint32_t* colors, uint32_t width, uint32_t height, int8_t numColors,
+    virtual status_t drawPatch(SkBitmap* bitmap, Res_png_9patch* patch,
             float left, float top, float right, float bottom, SkPaint* paint);
     virtual status_t drawColor(int color, SkXfermode::Mode mode);
     virtual status_t drawRect(float left, float top, float right, float bottom, SkPaint* paint);
diff --git a/libs/hwui/Extensions.h b/libs/hwui/Extensions.h
index 54a3987..a3f7c44 100644
--- a/libs/hwui/Extensions.h
+++ b/libs/hwui/Extensions.h
@@ -33,9 +33,6 @@
 
 class Extensions: public Singleton<Extensions> {
 public:
-    Extensions();
-    ~Extensions();
-
     inline bool hasNPot() const { return mHasNPot; }
     inline bool hasFramebufferFetch() const { return mHasFramebufferFetch; }
     inline bool hasDiscardFramebuffer() const { return mHasDiscardFramebuffer; }
@@ -53,6 +50,9 @@
     void dump() const;
 
 private:
+    Extensions();
+    ~Extensions();
+
     friend class Singleton<Extensions>;
 
     SortedVector<String8> mExtensionList;
diff --git a/libs/hwui/OpenGLRenderer.cpp b/libs/hwui/OpenGLRenderer.cpp
index a4f9860..025e9f8 100644
--- a/libs/hwui/OpenGLRenderer.cpp
+++ b/libs/hwui/OpenGLRenderer.cpp
@@ -1873,7 +1873,7 @@
 
 void OpenGLRenderer::setupDrawMesh(GLvoid* vertices, GLvoid* texCoords, GLuint vbo) {
     bool force = false;
-    if (!vertices) {
+    if (!vertices || vbo) {
         force = mCaches.bindMeshBuffer(vbo == 0 ? mCaches.meshBuffer : vbo);
     } else {
         force = mCaches.unbindMeshBuffer();
@@ -1904,8 +1904,18 @@
     mCaches.unbindIndicesBuffer();
 }
 
-void OpenGLRenderer::setupDrawMeshIndices(GLvoid* vertices, GLvoid* texCoords) {
-    bool force = mCaches.unbindMeshBuffer();
+void OpenGLRenderer::setupDrawMeshIndices(GLvoid* vertices, GLvoid* texCoords, GLuint vbo) {
+    bool force = false;
+    // If vbo is != 0 we want to treat the vertices parameter as an offset inside
+    // a VBO. However, if vertices is set to NULL and vbo == 0 then we want to
+    // use the default VBO found in Caches
+    if (!vertices || vbo) {
+        force = mCaches.bindMeshBuffer(vbo == 0 ? mCaches.meshBuffer : vbo);
+    } else {
+        force = mCaches.unbindMeshBuffer();
+    }
+    mCaches.bindIndicesBuffer();
+
     mCaches.bindPositionVertexPointer(force, vertices);
     if (mCaches.currentProgram->texCoords >= 0) {
         mCaches.bindTexCoordsVertexPointer(force, texCoords);
@@ -1980,9 +1990,11 @@
         texture->setFilter(FILTER(paint), true);
     }
 
+    // No need to check for a UV mapper on the texture object, only ARGB_8888
+    // bitmaps get packed in the atlas
     drawAlpha8TextureMesh(x, y, x + texture->width, y + texture->height, texture->id,
-            paint != NULL, color, alpha, mode, (GLvoid*) NULL,
-            (GLvoid*) gMeshTextureOffset, GL_TRIANGLE_STRIP, gMeshCount, ignoreTransform);
+            paint != NULL, color, alpha, mode, (GLvoid*) NULL, (GLvoid*) gMeshTextureOffset,
+            GL_TRIANGLE_STRIP, gMeshCount, ignoreTransform);
 }
 
 status_t OpenGLRenderer::drawBitmaps(SkBitmap* bitmap, int bitmapCount, TextureVertex* vertices,
@@ -1992,8 +2004,9 @@
     mCaches.setScissorEnabled(mScissorOptimizationDisabled);
 
     mCaches.activeTexture(0);
-    Texture* texture = mCaches.textureCache.get(bitmap);
+    Texture* texture = getTexture(bitmap);
     if (!texture) return DrawGlInfo::kStatusDone;
+
     const AutoTexture autoCleanup(texture);
 
     int alpha;
@@ -2030,7 +2043,7 @@
     }
 
     mCaches.activeTexture(0);
-    Texture* texture = mCaches.textureCache.get(bitmap);
+    Texture* texture = getTexture(bitmap);
     if (!texture) return DrawGlInfo::kStatusDone;
     const AutoTexture autoCleanup(texture);
 
@@ -2053,7 +2066,7 @@
     }
 
     mCaches.activeTexture(0);
-    Texture* texture = mCaches.textureCache.get(bitmap);
+    Texture* texture = getTexture(bitmap);
     if (!texture) return DrawGlInfo::kStatusDone;
     const AutoTexture autoCleanup(texture);
 
@@ -2116,6 +2129,10 @@
         cleanupColors = true;
     }
 
+    mCaches.activeTexture(0);
+    Texture* texture = mCaches.assetAtlas.getEntryTexture(bitmap);
+    const UvMapper& mapper(getMapper(texture));
+
     for (int32_t y = 0; y < meshHeight; y++) {
         for (int32_t x = 0; x < meshWidth; x++) {
             uint32_t i = (y * (meshWidth + 1) + x) * 2;
@@ -2125,6 +2142,8 @@
             float v1 = float(y) / meshHeight;
             float v2 = float(y + 1) / meshHeight;
 
+            mapper.map(u1, v1, u2, v2);
+
             int ax = i + (meshWidth + 1) * 2;
             int ay = ax + 1;
             int bx = i;
@@ -2154,11 +2173,12 @@
         return DrawGlInfo::kStatusDone;
     }
 
-    mCaches.activeTexture(0);
-    Texture* texture = mCaches.textureCache.get(bitmap);
     if (!texture) {
-        if (cleanupColors) delete[] colors;
-        return DrawGlInfo::kStatusDone;
+        texture = mCaches.textureCache.get(bitmap);
+        if (!texture) {
+            if (cleanupColors) delete[] colors;
+            return DrawGlInfo::kStatusDone;
+        }
     }
     const AutoTexture autoCleanup(texture);
 
@@ -2211,17 +2231,19 @@
     }
 
     mCaches.activeTexture(0);
-    Texture* texture = mCaches.textureCache.get(bitmap);
+    Texture* texture = getTexture(bitmap);
     if (!texture) return DrawGlInfo::kStatusDone;
     const AutoTexture autoCleanup(texture);
 
     const float width = texture->width;
     const float height = texture->height;
 
-    const float u1 = fmax(0.0f, srcLeft / width);
-    const float v1 = fmax(0.0f, srcTop / height);
-    const float u2 = fmin(1.0f, srcRight / width);
-    const float v2 = fmin(1.0f, srcBottom / height);
+    float u1 = fmax(0.0f, srcLeft / width);
+    float v1 = fmax(0.0f, srcTop / height);
+    float u2 = fmin(1.0f, srcRight / width);
+    float v2 = fmin(1.0f, srcBottom / height);
+
+    getMapper(texture).map(u1, v1, u2, v2);
 
     mCaches.unbindMeshBuffer();
     resetDrawTextureTexCoords(u1, v1, u2, v2);
@@ -2292,34 +2314,32 @@
     return DrawGlInfo::kStatusDrew;
 }
 
-status_t OpenGLRenderer::drawPatch(SkBitmap* bitmap, const int32_t* xDivs, const int32_t* yDivs,
-        const uint32_t* colors, uint32_t width, uint32_t height, int8_t numColors,
+status_t OpenGLRenderer::drawPatch(SkBitmap* bitmap, Res_png_9patch* patch,
         float left, float top, float right, float bottom, SkPaint* paint) {
     int alpha;
     SkXfermode::Mode mode;
     getAlphaAndMode(paint, &alpha, &mode);
 
-    return drawPatch(bitmap, xDivs, yDivs, colors, width, height, numColors,
+    return drawPatch(bitmap, patch, mCaches.assetAtlas.getEntry(bitmap),
             left, top, right, bottom, alpha, mode);
 }
 
-status_t OpenGLRenderer::drawPatch(SkBitmap* bitmap, const int32_t* xDivs, const int32_t* yDivs,
-        const uint32_t* colors, uint32_t width, uint32_t height, int8_t numColors,
-        float left, float top, float right, float bottom, int alpha, SkXfermode::Mode mode) {
+status_t OpenGLRenderer::drawPatch(SkBitmap* bitmap, Res_png_9patch* patch,
+        AssetAtlas::Entry* entry, float left, float top, float right, float bottom,
+        int alpha, SkXfermode::Mode mode) {
     if (quickReject(left, top, right, bottom)) {
         return DrawGlInfo::kStatusDone;
     }
 
-    alpha *= mSnapshot->alpha;
-
-    const Patch* mesh = mCaches.patchCache.get(bitmap->width(), bitmap->height(),
-            right - left, bottom - top, xDivs, yDivs, colors, width, height, numColors);
+    const Patch* mesh = mCaches.patchCache.get(entry, bitmap->width(), bitmap->height(),
+            right - left, bottom - top, patch);
 
     if (CC_LIKELY(mesh && mesh->verticesCount > 0)) {
         mCaches.activeTexture(0);
-        Texture* texture = mCaches.textureCache.get(bitmap);
+        Texture* texture = entry ? &entry->texture : mCaches.textureCache.get(bitmap);
         if (!texture) return DrawGlInfo::kStatusDone;
         const AutoTexture autoCleanup(texture);
+
         texture->setWrap(GL_CLAMP_TO_EDGE, true);
         texture->setFilter(GL_LINEAR, true);
 
@@ -2342,19 +2362,23 @@
             }
         }
 
+        alpha *= mSnapshot->alpha;
+
         if (CC_LIKELY(pureTranslate)) {
             const float x = (int) floorf(left + currentTransform().getTranslateX() + 0.5f);
             const float y = (int) floorf(top + currentTransform().getTranslateY() + 0.5f);
 
-            drawTextureMesh(x, y, x + right - left, y + bottom - top, texture->id, alpha / 255.0f,
-                    mode, texture->blend, (GLvoid*) 0, (GLvoid*) gMeshTextureOffset,
-                    GL_TRIANGLES, mesh->verticesCount, false, true, mesh->meshBuffer,
-                    true, !mesh->hasEmptyQuads);
+            right = x + right - left;
+            bottom = y + bottom - top;
+            drawIndexedTextureMesh(x, y, right, bottom, texture->id, alpha / 255.0f,
+                    mode, texture->blend, (GLvoid*) mesh->offset, (GLvoid*) mesh->textureOffset,
+                    GL_TRIANGLES, mesh->indexCount, false, true,
+                    mCaches.patchCache.getMeshBuffer(), true, !mesh->hasEmptyQuads);
         } else {
-            drawTextureMesh(left, top, right, bottom, texture->id, alpha / 255.0f,
-                    mode, texture->blend, (GLvoid*) 0, (GLvoid*) gMeshTextureOffset,
-                    GL_TRIANGLES, mesh->verticesCount, false, false, mesh->meshBuffer,
-                    true, !mesh->hasEmptyQuads);
+            drawIndexedTextureMesh(left, top, right, bottom, texture->id, alpha / 255.0f,
+                    mode, texture->blend, (GLvoid*) mesh->offset, (GLvoid*) mesh->textureOffset,
+                    GL_TRIANGLES, mesh->indexCount, false, false,
+                    mCaches.patchCache.getMeshBuffer(), true, !mesh->hasEmptyQuads);
         }
     }
 
@@ -3196,6 +3220,14 @@
 // Drawing implementation
 ///////////////////////////////////////////////////////////////////////////////
 
+Texture* OpenGLRenderer::getTexture(SkBitmap* bitmap) {
+    Texture* texture = mCaches.assetAtlas.getEntryTexture(bitmap);
+    if (!texture) {
+        return mCaches.textureCache.get(bitmap);
+    }
+    return texture;
+}
+
 void OpenGLRenderer::drawPathTexture(const PathTexture* texture,
         float x, float y, SkPaint* paint) {
     if (quickReject(x, y, x + texture->width, y + texture->height)) {
@@ -3389,19 +3421,35 @@
 
     texture->setWrap(GL_CLAMP_TO_EDGE, true);
 
+    GLvoid* vertices = (GLvoid*) NULL;
+    GLvoid* texCoords = (GLvoid*) gMeshTextureOffset;
+
+    if (texture->uvMapper) {
+        vertices = &mMeshVertices[0].position[0];
+        texCoords = &mMeshVertices[0].texture[0];
+
+        Rect uvs(0.0f, 0.0f, 1.0f, 1.0f);
+        texture->uvMapper->map(uvs);
+
+        resetDrawTextureTexCoords(uvs.left, uvs.top, uvs.right, uvs.bottom);
+    }
+
     if (CC_LIKELY(currentTransform().isPureTranslate())) {
         const float x = (int) floorf(left + currentTransform().getTranslateX() + 0.5f);
         const float y = (int) floorf(top + currentTransform().getTranslateY() + 0.5f);
 
         texture->setFilter(GL_NEAREST, true);
         drawTextureMesh(x, y, x + texture->width, y + texture->height, texture->id,
-                alpha / 255.0f, mode, texture->blend, (GLvoid*) NULL,
-                (GLvoid*) gMeshTextureOffset, GL_TRIANGLE_STRIP, gMeshCount, false, true);
+                alpha / 255.0f, mode, texture->blend, vertices, texCoords,
+                GL_TRIANGLE_STRIP, gMeshCount, false, true);
     } else {
         texture->setFilter(FILTER(paint), true);
         drawTextureMesh(left, top, right, bottom, texture->id, alpha / 255.0f, mode,
-                texture->blend, (GLvoid*) NULL, (GLvoid*) gMeshTextureOffset,
-                GL_TRIANGLE_STRIP, gMeshCount);
+                texture->blend, vertices, texCoords, GL_TRIANGLE_STRIP, gMeshCount);
+    }
+
+    if (texture->uvMapper) {
+        resetDrawTextureTexCoords(0.0f, 0.0f, 1.0f, 1.0f);
     }
 }
 
@@ -3438,6 +3486,33 @@
     finishDrawTexture();
 }
 
+void OpenGLRenderer::drawIndexedTextureMesh(float left, float top, float right, float bottom,
+        GLuint texture, float alpha, SkXfermode::Mode mode, bool blend,
+        GLvoid* vertices, GLvoid* texCoords, GLenum drawMode, GLsizei elementsCount,
+        bool swapSrcDst, bool ignoreTransform, GLuint vbo, bool ignoreScale, bool dirty) {
+
+    setupDraw();
+    setupDrawWithTexture();
+    setupDrawColor(alpha, alpha, alpha, alpha);
+    setupDrawColorFilter();
+    setupDrawBlending(blend, mode, swapSrcDst);
+    setupDrawProgram();
+    if (!dirty) setupDrawDirtyRegionsDisabled();
+    if (!ignoreScale) {
+        setupDrawModelView(left, top, right, bottom, ignoreTransform);
+    } else {
+        setupDrawModelViewTranslate(left, top, right, bottom, ignoreTransform);
+    }
+    setupDrawTexture(texture);
+    setupDrawPureColorUniforms();
+    setupDrawColorFilterUniforms();
+    setupDrawMeshIndices(vertices, texCoords, vbo);
+
+    glDrawElements(drawMode, elementsCount, GL_UNSIGNED_SHORT, NULL);
+
+    finishDrawTexture();
+}
+
 void OpenGLRenderer::drawAlpha8TextureMesh(float left, float top, float right, float bottom,
         GLuint texture, bool hasColor, int color, int alpha, SkXfermode::Mode mode,
         GLvoid* vertices, GLvoid* texCoords, GLenum drawMode, GLsizei elementsCount,
diff --git a/libs/hwui/OpenGLRenderer.h b/libs/hwui/OpenGLRenderer.h
index a0ad888..640c7db 100644
--- a/libs/hwui/OpenGLRenderer.h
+++ b/libs/hwui/OpenGLRenderer.h
@@ -34,6 +34,8 @@
 
 #include <cutils/compiler.h>
 
+#include <androidfw/ResourceTypes.h>
+
 #include "Debug.h"
 #include "Extensions.h"
 #include "Matrix.h"
@@ -43,6 +45,7 @@
 #include "Vertex.h"
 #include "SkiaShader.h"
 #include "SkiaColorFilter.h"
+#include "UvMapper.h"
 #include "Caches.h"
 
 namespace android {
@@ -78,7 +81,8 @@
 };
 
 struct DeferredDisplayState {
-    Rect mBounds; // global op bounds, mapped by mMatrix to be in screen space coordinates, clipped.
+    // global op bounds, mapped by mMatrix to be in screen space coordinates, clipped
+    Rect mBounds;
 
     // the below are set and used by the OpenGLRenderer at record and deferred playback
     bool mClipValid;
@@ -248,11 +252,9 @@
     virtual status_t drawBitmapData(SkBitmap* bitmap, float left, float top, SkPaint* paint);
     virtual status_t drawBitmapMesh(SkBitmap* bitmap, int meshWidth, int meshHeight,
             float* vertices, int* colors, SkPaint* paint);
-    virtual status_t drawPatch(SkBitmap* bitmap, const int32_t* xDivs, const int32_t* yDivs,
-            const uint32_t* colors, uint32_t width, uint32_t height, int8_t numColors,
+    virtual status_t drawPatch(SkBitmap* bitmap, Res_png_9patch* patch,
             float left, float top, float right, float bottom, SkPaint* paint);
-    status_t drawPatch(SkBitmap* bitmap, const int32_t* xDivs, const int32_t* yDivs,
-            const uint32_t* colors, uint32_t width, uint32_t height, int8_t numColors,
+    status_t drawPatch(SkBitmap* bitmap, Res_png_9patch* patch, AssetAtlas::Entry* entry,
             float left, float top, float right, float bottom, int alpha, SkXfermode::Mode mode);
     virtual status_t drawColor(int color, SkXfermode::Mode mode);
     virtual status_t drawRect(float left, float top, float right, float bottom, SkPaint* paint);
@@ -798,6 +800,12 @@
             bool swapSrcDst = false, bool ignoreTransform = false, GLuint vbo = 0,
             bool ignoreScale = false, bool dirty = true);
 
+    void drawIndexedTextureMesh(float left, float top, float right, float bottom, GLuint texture,
+            float alpha, SkXfermode::Mode mode, bool blend,
+            GLvoid* vertices, GLvoid* texCoords, GLenum drawMode, GLsizei elementsCount,
+            bool swapSrcDst = false, bool ignoreTransform = false, GLuint vbo = 0,
+            bool ignoreScale = false, bool dirty = true);
+
     void drawAlpha8TextureMesh(float left, float top, float right, float bottom,
             GLuint texture, bool hasColor, int color, int alpha, SkXfermode::Mode mode,
             GLvoid* vertices, GLvoid* texCoords, GLenum drawMode, GLsizei elementsCount,
@@ -943,7 +951,7 @@
     void setupDrawTextGammaUniforms();
     void setupDrawMesh(GLvoid* vertices, GLvoid* texCoords = NULL, GLuint vbo = 0);
     void setupDrawMesh(GLvoid* vertices, GLvoid* texCoords, GLvoid* colors);
-    void setupDrawMeshIndices(GLvoid* vertices, GLvoid* texCoords);
+    void setupDrawMeshIndices(GLvoid* vertices, GLvoid* texCoords, GLuint vbo = 0);
     void setupDrawVertices(GLvoid* vertices);
     void finishDrawTexture();
     void accountForClear(SkXfermode::Mode mode);
@@ -985,6 +993,17 @@
         return *mSnapshot->transform;
     }
 
+    inline const UvMapper& getMapper(const Texture* texture) {
+        return texture && texture->uvMapper ? *texture->uvMapper : mUvMapper;
+    }
+
+    /**
+     * Returns a texture object for the specified bitmap. The texture can
+     * come from the texture cache or an atlas. If this method returns
+     * NULL, the texture could not be found and/or allocated.
+     */
+    Texture* getTexture(SkBitmap* bitmap);
+
     // Dimensions of the drawing surface
     int mWidth, mHeight;
 
@@ -1010,6 +1029,9 @@
     // Used to draw textured quads
     TextureVertex mMeshVertices[4];
 
+    // Default UV mapper
+    const UvMapper mUvMapper;
+
     // shader, filters, and shadow
     DrawModifiers mDrawModifiers;
     SkPaint mFilteredPaint;
diff --git a/libs/hwui/Patch.cpp b/libs/hwui/Patch.cpp
index 45c619e..6b0734a 100644
--- a/libs/hwui/Patch.cpp
+++ b/libs/hwui/Patch.cpp
@@ -20,9 +20,10 @@
 
 #include <utils/Log.h>
 
-#include "Patch.h"
 #include "Caches.h"
+#include "Patch.h"
 #include "Properties.h"
+#include "UvMapper.h"
 
 namespace android {
 namespace uirenderer {
@@ -31,90 +32,61 @@
 // Constructors/destructor
 ///////////////////////////////////////////////////////////////////////////////
 
-Patch::Patch(const uint32_t xCount, const uint32_t yCount, const int8_t emptyQuads):
-        mXCount(xCount), mYCount(yCount), mEmptyQuads(emptyQuads) {
-    // Initialized with the maximum number of vertices we will need
-    // 2 triangles per patch, 3 vertices per triangle
-    uint32_t maxVertices = ((xCount + 1) * (yCount + 1) - emptyQuads) * 2 * 3;
-    mVertices = new TextureVertex[maxVertices];
-    mAllocatedVerticesCount = 0;
-
-    verticesCount = 0;
-    hasEmptyQuads = emptyQuads > 0;
-
-    mColorKey = 0;
-    mXDivs = new int32_t[mXCount];
-    mYDivs = new int32_t[mYCount];
-
-    PATCH_LOGD("    patch: xCount = %d, yCount = %d, emptyQuads = %d, max vertices = %d",
-            xCount, yCount, emptyQuads, maxVertices);
-
-    glGenBuffers(1, &meshBuffer);
+Patch::Patch(): verticesCount(0), indexCount(0), hasEmptyQuads(false) {
 }
 
 Patch::~Patch() {
-    delete[] mVertices;
-    delete[] mXDivs;
-    delete[] mYDivs;
-    glDeleteBuffers(1, &meshBuffer);
-}
-
-///////////////////////////////////////////////////////////////////////////////
-// Patch management
-///////////////////////////////////////////////////////////////////////////////
-
-void Patch::copy(const int32_t* xDivs, const int32_t* yDivs) {
-    memcpy(mXDivs, xDivs, mXCount * sizeof(int32_t));
-    memcpy(mYDivs, yDivs, mYCount * sizeof(int32_t));
-}
-
-void Patch::updateColorKey(const uint32_t colorKey) {
-    mColorKey = colorKey;
-}
-
-bool Patch::matches(const int32_t* xDivs, const int32_t* yDivs,
-        const uint32_t colorKey, const int8_t emptyQuads) {
-
-    bool matches = true;
-
-    if (mEmptyQuads != emptyQuads) {
-        mEmptyQuads = emptyQuads;
-        hasEmptyQuads = emptyQuads > 0;
-        matches = false;
-    }
-
-    if (mColorKey != colorKey) {
-        updateColorKey(colorKey);
-        matches = false;
-    }
-
-    if (memcmp(mXDivs, xDivs, mXCount * sizeof(int32_t))) {
-        memcpy(mXDivs, xDivs, mXCount * sizeof(int32_t));
-        matches = false;
-    }
-
-    if (memcmp(mYDivs, yDivs, mYCount * sizeof(int32_t))) {
-        memcpy(mYDivs, yDivs, mYCount * sizeof(int32_t));
-        matches = false;
-    }
-
-    return matches;
 }
 
 ///////////////////////////////////////////////////////////////////////////////
 // Vertices management
 ///////////////////////////////////////////////////////////////////////////////
 
-void Patch::updateVertices(const float bitmapWidth, const float bitmapHeight,
-        float left, float top, float right, float bottom) {
-    if (hasEmptyQuads) quads.clear();
+uint32_t Patch::getSize() const {
+    return verticesCount * sizeof(TextureVertex);
+}
 
-    // Reset the vertices count here, we will count exactly how many
-    // vertices we actually need when generating the quads
-    verticesCount = 0;
+TextureVertex* Patch::createMesh(const float bitmapWidth, const float bitmapHeight,
+        float left, float top, float right, float bottom, const Res_png_9patch* patch) {
+    UvMapper mapper;
+    return createMesh(bitmapWidth, bitmapHeight, left, top, right, bottom, mapper, patch);
+}
 
-    const uint32_t xStretchCount = (mXCount + 1) >> 1;
-    const uint32_t yStretchCount = (mYCount + 1) >> 1;
+TextureVertex* Patch::createMesh(const float bitmapWidth, const float bitmapHeight,
+        float left, float top, float right, float bottom,
+        const UvMapper& mapper, const Res_png_9patch* patch) {
+
+    const uint32_t* colors = &patch->colors[0];
+    const int8_t numColors = patch->numColors;
+
+    mColorKey = 0;
+    int8_t emptyQuads = 0;
+
+    if (uint8_t(numColors) < sizeof(uint32_t) * 4) {
+        for (int8_t i = 0; i < numColors; i++) {
+            if (colors[i] == 0x0) {
+                emptyQuads++;
+                mColorKey |= 0x1 << i;
+            }
+        }
+    }
+
+    hasEmptyQuads = emptyQuads > 0;
+
+    uint32_t xCount = patch->numXDivs;
+    uint32_t yCount = patch->numYDivs;
+
+    uint32_t maxVertices = ((xCount + 1) * (yCount + 1) - emptyQuads) * 4;
+    if (maxVertices == 0) return NULL;
+
+    TextureVertex* vertices = new TextureVertex[maxVertices];
+    TextureVertex* vertex = vertices;
+
+    const int32_t* xDivs = patch->xDivs;
+    const int32_t* yDivs = patch->yDivs;
+
+    const uint32_t xStretchCount = (xCount + 1) >> 1;
+    const uint32_t yStretchCount = (yCount + 1) >> 1;
 
     float stretchX = 0.0f;
     float stretchY = 0.0f;
@@ -124,8 +96,8 @@
 
     if (xStretchCount > 0) {
         uint32_t stretchSize = 0;
-        for (uint32_t i = 1; i < mXCount; i += 2) {
-            stretchSize += mXDivs[i] - mXDivs[i - 1];
+        for (uint32_t i = 1; i < xCount; i += 2) {
+            stretchSize += xDivs[i] - xDivs[i - 1];
         }
         const float xStretchTex = stretchSize;
         const float fixed = bitmapWidth - stretchSize;
@@ -136,8 +108,8 @@
 
     if (yStretchCount > 0) {
         uint32_t stretchSize = 0;
-        for (uint32_t i = 1; i < mYCount; i += 2) {
-            stretchSize += mYDivs[i] - mYDivs[i - 1];
+        for (uint32_t i = 1; i < yCount; i += 2) {
+            stretchSize += yDivs[i] - yDivs[i - 1];
         }
         const float yStretchTex = stretchSize;
         const float fixed = bitmapHeight - stretchSize;
@@ -146,7 +118,6 @@
         rescaleY = fixed == 0.0f ? 0.0f : fminf(fmaxf(bottom - top, 0.0f) / fixed, 1.0f);
     }
 
-    TextureVertex* vertex = mVertices;
     uint32_t quadCount = 0;
 
     float previousStepY = 0.0f;
@@ -155,8 +126,10 @@
     float y2 = 0.0f;
     float v1 = 0.0f;
 
-    for (uint32_t i = 0; i < mYCount; i++) {
-        float stepY = mYDivs[i];
+    mUvMapper = mapper;
+
+    for (uint32_t i = 0; i < yCount; i++) {
+        float stepY = yDivs[i];
         const float segment = stepY - previousStepY;
 
         if (i & 1) {
@@ -170,15 +143,8 @@
         v1 += vOffset / bitmapHeight;
 
         if (stepY > 0.0f) {
-#if DEBUG_EXPLODE_PATCHES
-            y1 += i * EXPLODE_GAP;
-            y2 += i * EXPLODE_GAP;
-#endif
-            generateRow(vertex, y1, y2, v1, v2, stretchX, rescaleX, right - left,
-                    bitmapWidth, quadCount);
-#if DEBUG_EXPLODE_PATCHES
-            y2 -= i * EXPLODE_GAP;
-#endif
+            generateRow(xDivs, xCount, vertex, y1, y2, v1, v2, stretchX, rescaleX,
+                    right - left, bitmapWidth, quadCount);
         }
 
         y1 = y2;
@@ -189,33 +155,16 @@
 
     if (previousStepY != bitmapHeight) {
         y2 = bottom - top;
-#if DEBUG_EXPLODE_PATCHES
-        y1 += mYCount * EXPLODE_GAP;
-        y2 += mYCount * EXPLODE_GAP;
-#endif
-        generateRow(vertex, y1, y2, v1, 1.0f, stretchX, rescaleX, right - left,
-                bitmapWidth, quadCount);
+        generateRow(xDivs, xCount, vertex, y1, y2, v1, 1.0f, stretchX, rescaleX,
+                right - left, bitmapWidth, quadCount);
     }
 
-    if (verticesCount > 0) {
-        Caches& caches = Caches::getInstance();
-        caches.bindMeshBuffer(meshBuffer);
-        if (mAllocatedVerticesCount < verticesCount) {
-            glBufferData(GL_ARRAY_BUFFER, sizeof(TextureVertex) * verticesCount,
-                    mVertices, GL_DYNAMIC_DRAW);
-            mAllocatedVerticesCount = verticesCount;
-        } else {
-            glBufferSubData(GL_ARRAY_BUFFER, 0,
-                    sizeof(TextureVertex) * verticesCount, mVertices);
-        }
-        caches.resetVertexPointers();
-    }
-
-    PATCH_LOGD("    patch: new vertices count = %d", verticesCount);
+    return vertices;
 }
 
-void Patch::generateRow(TextureVertex*& vertex, float y1, float y2, float v1, float v2,
-        float stretchX, float rescaleX, float width, float bitmapWidth, uint32_t& quadCount) {
+void Patch::generateRow(const int32_t* xDivs, uint32_t xCount, TextureVertex*& vertex,
+        float y1, float y2, float v1, float v2, float stretchX, float rescaleX,
+        float width, float bitmapWidth, uint32_t& quadCount) {
     float previousStepX = 0.0f;
 
     float x1 = 0.0f;
@@ -223,8 +172,8 @@
     float u1 = 0.0f;
 
     // Generate the row quad by quad
-    for (uint32_t i = 0; i < mXCount; i++) {
-        float stepX = mXDivs[i];
+    for (uint32_t i = 0; i < xCount; i++) {
+        float stepX = xDivs[i];
         const float segment = stepX - previousStepX;
 
         if (i & 1) {
@@ -238,14 +187,7 @@
         u1 += uOffset / bitmapWidth;
 
         if (stepX > 0.0f) {
-#if DEBUG_EXPLODE_PATCHES
-            x1 += i * EXPLODE_GAP;
-            x2 += i * EXPLODE_GAP;
-#endif
             generateQuad(vertex, x1, y1, x2, y2, u1, v1, u2, v2, quadCount);
-#if DEBUG_EXPLODE_PATCHES
-            x2 -= i * EXPLODE_GAP;
-#endif
         }
 
         x1 = x2;
@@ -256,10 +198,6 @@
 
     if (previousStepX != bitmapWidth) {
         x2 = width;
-#if DEBUG_EXPLODE_PATCHES
-        x1 += mXCount * EXPLODE_GAP;
-        x2 += mXCount * EXPLODE_GAP;
-#endif
         generateQuad(vertex, x1, y1, x2, y2, u1, v1, 1.0f, v2, quadCount);
     }
 }
@@ -290,18 +228,15 @@
         quads.add(bounds);
     }
 
-    // Left triangle
+    mUvMapper.map(u1, v1, u2, v2);
+
     TextureVertex::set(vertex++, x1, y1, u1, v1);
     TextureVertex::set(vertex++, x2, y1, u2, v1);
     TextureVertex::set(vertex++, x1, y2, u1, v2);
-
-    // Right triangle
-    TextureVertex::set(vertex++, x1, y2, u1, v2);
-    TextureVertex::set(vertex++, x2, y1, u2, v1);
     TextureVertex::set(vertex++, x2, y2, u2, v2);
 
-    // A quad is made of 2 triangles, 6 vertices
-    verticesCount += 6;
+    verticesCount += 4;
+    indexCount += 6;
 
 #if DEBUG_PATCHES_VERTICES
     PATCH_LOGD("    quad %d", oldQuadCount);
diff --git a/libs/hwui/Patch.h b/libs/hwui/Patch.h
index ee7bf70..448cf60 100644
--- a/libs/hwui/Patch.h
+++ b/libs/hwui/Patch.h
@@ -23,62 +23,52 @@
 
 #include <utils/Vector.h>
 
+#include <androidfw/ResourceTypes.h>
+
 #include "Rect.h"
+#include "UvMapper.h"
 #include "Vertex.h"
 
 namespace android {
 namespace uirenderer {
 
 ///////////////////////////////////////////////////////////////////////////////
-// Defines
-///////////////////////////////////////////////////////////////////////////////
-
-#define EXPLODE_GAP 4
-
-///////////////////////////////////////////////////////////////////////////////
 // 9-patch structures
 ///////////////////////////////////////////////////////////////////////////////
 
-/**
- * An OpenGL patch. This contains an array of vertices and an array of
- * indices to render the vertices.
- */
 struct Patch {
-    Patch(const uint32_t xCount, const uint32_t yCount, const int8_t emptyQuads);
+    Patch();
     ~Patch();
 
-    void updateVertices(const float bitmapWidth, const float bitmapHeight,
-            float left, float top, float right, float bottom);
+    /**
+     * Returns the size of this patch's mesh in bytes.
+     */
+    uint32_t getSize() const;
 
-    void updateColorKey(const uint32_t colorKey);
-    void copy(const int32_t* xDivs, const int32_t* yDivs);
-    bool matches(const int32_t* xDivs, const int32_t* yDivs,
-            const uint32_t colorKey, const int8_t emptyQuads);
-
-    GLuint meshBuffer;
     uint32_t verticesCount;
+    uint32_t indexCount;
     bool hasEmptyQuads;
     Vector<Rect> quads;
 
+    GLintptr offset;
+    GLintptr textureOffset;
+
+    TextureVertex* createMesh(const float bitmapWidth, const float bitmapHeight,
+            float left, float top, float right, float bottom,
+            const Res_png_9patch* patch);
+    TextureVertex* createMesh(const float bitmapWidth, const float bitmapHeight,
+            float left, float top, float right, float bottom,
+            const UvMapper& mapper, const Res_png_9patch* patch);
+
 private:
-    TextureVertex* mVertices;
-    uint32_t mAllocatedVerticesCount;
-
-    int32_t* mXDivs;
-    int32_t* mYDivs;
-    uint32_t mColorKey;
-
-    uint32_t mXCount;
-    uint32_t mYCount;
-    int8_t mEmptyQuads;
-
-    void generateRow(TextureVertex*& vertex, float y1, float y2,
-            float v1, float v2, float stretchX, float rescaleX,
+    void generateRow(const int32_t* xDivs, uint32_t xCount, TextureVertex*& vertex,
+            float y1, float y2, float v1, float v2, float stretchX, float rescaleX,
             float width, float bitmapWidth, uint32_t& quadCount);
-    void generateQuad(TextureVertex*& vertex,
-            float x1, float y1, float x2, float y2,
-            float u1, float v1, float u2, float v2,
-            uint32_t& quadCount);
+    void generateQuad(TextureVertex*& vertex, float x1, float y1, float x2, float y2,
+            float u1, float v1, float u2, float v2, uint32_t& quadCount);
+
+    uint32_t mColorKey;
+    UvMapper mUvMapper;
 }; // struct Patch
 
 }; // namespace uirenderer
diff --git a/libs/hwui/PatchCache.cpp b/libs/hwui/PatchCache.cpp
index 62e38d3..5fa75e9 100644
--- a/libs/hwui/PatchCache.cpp
+++ b/libs/hwui/PatchCache.cpp
@@ -16,8 +16,10 @@
 
 #define LOG_TAG "OpenGLRenderer"
 
+#include <utils/JenkinsHash.h>
 #include <utils/Log.h>
 
+#include "Caches.h"
 #include "PatchCache.h"
 #include "Properties.h"
 
@@ -28,107 +30,107 @@
 // Constructors/destructor
 ///////////////////////////////////////////////////////////////////////////////
 
-PatchCache::PatchCache(): mMaxEntries(DEFAULT_PATCH_CACHE_SIZE) {
-}
-
-PatchCache::PatchCache(uint32_t maxEntries): mMaxEntries(maxEntries) {
+PatchCache::PatchCache(): mCache(LruCache<PatchDescription, Patch*>::kUnlimitedCapacity) {
+    char property[PROPERTY_VALUE_MAX];
+    if (property_get(PROPERTY_PATCH_CACHE_SIZE, property, NULL) > 0) {
+        INIT_LOGD("  Setting patch cache size to %skB", property);
+        mMaxSize = KB(atoi(property));
+    } else {
+        INIT_LOGD("  Using default patch cache size of %.2fkB", DEFAULT_PATCH_CACHE_SIZE);
+        mMaxSize = KB(DEFAULT_PATCH_CACHE_SIZE);
+    }
+    mSize = 0;
 }
 
 PatchCache::~PatchCache() {
     clear();
 }
 
+void PatchCache::init(Caches& caches) {
+    glGenBuffers(1, &mMeshBuffer);
+    caches.bindMeshBuffer(mMeshBuffer);
+    caches.resetVertexPointers();
+
+    glBufferData(GL_ARRAY_BUFFER, mMaxSize, NULL, GL_DYNAMIC_DRAW);
+}
+
 ///////////////////////////////////////////////////////////////////////////////
 // Caching
 ///////////////////////////////////////////////////////////////////////////////
 
-int PatchCache::PatchDescription::compare(
-        const PatchCache::PatchDescription& lhs, const PatchCache::PatchDescription& rhs) {
-    int deltaInt = lhs.bitmapWidth - rhs.bitmapWidth;
-    if (deltaInt != 0) return deltaInt;
+hash_t PatchCache::PatchDescription::hash() const {
+    uint32_t hash = JenkinsHashMix(0, android::hash_type(mPatch));
+    hash = JenkinsHashMix(hash, mBitmapWidth);
+    hash = JenkinsHashMix(hash, mBitmapHeight);
+    hash = JenkinsHashMix(hash, mPixelWidth);
+    hash = JenkinsHashMix(hash, mPixelHeight);
+    return JenkinsHashWhiten(hash);
+}
 
-    deltaInt = lhs.bitmapHeight - rhs.bitmapHeight;
-    if (deltaInt != 0) return deltaInt;
-
-    if (lhs.pixelWidth < rhs.pixelWidth) return -1;
-    if (lhs.pixelWidth > rhs.pixelWidth) return +1;
-
-    if (lhs.pixelHeight < rhs.pixelHeight) return -1;
-    if (lhs.pixelHeight > rhs.pixelHeight) return +1;
-
-    deltaInt = lhs.xCount - rhs.xCount;
-    if (deltaInt != 0) return deltaInt;
-
-    deltaInt = lhs.yCount - rhs.yCount;
-    if (deltaInt != 0) return deltaInt;
-
-    deltaInt = lhs.emptyCount - rhs.emptyCount;
-    if (deltaInt != 0) return deltaInt;
-
-    deltaInt = lhs.colorKey - rhs.colorKey;
-    if (deltaInt != 0) return deltaInt;
-
-    return 0;
+int PatchCache::PatchDescription::compare(const PatchCache::PatchDescription& lhs,
+            const PatchCache::PatchDescription& rhs) {
+    return memcmp(&lhs, &rhs, sizeof(PatchDescription));
 }
 
 void PatchCache::clear() {
-    size_t count = mCache.size();
-    for (size_t i = 0; i < count; i++) {
-        delete mCache.valueAt(i);
+    glDeleteBuffers(1, &mMeshBuffer);
+    clearCache();
+    mSize = 0;
+}
+
+void PatchCache::clearCache() {
+    LruCache<PatchDescription, Patch*>::Iterator i(mCache);
+    while (i.next()) {
+        ALOGD("Delete %p", i.value());
+        delete i.value();
     }
     mCache.clear();
 }
 
-Patch* PatchCache::get(const uint32_t bitmapWidth, const uint32_t bitmapHeight,
-        const float pixelWidth, const float pixelHeight,
-        const int32_t* xDivs, const int32_t* yDivs, const uint32_t* colors,
-        const uint32_t width, const uint32_t height, const int8_t numColors) {
+const Patch* PatchCache::get(const AssetAtlas::Entry* entry,
+        const uint32_t bitmapWidth, const uint32_t bitmapHeight,
+        const float pixelWidth, const float pixelHeight, const Res_png_9patch* patch) {
 
-    int8_t transparentQuads = 0;
-    uint32_t colorKey = 0;
-
-    if (uint8_t(numColors) < sizeof(uint32_t) * 4) {
-        for (int8_t i = 0; i < numColors; i++) {
-            if (colors[i] == 0x0) {
-                transparentQuads++;
-                colorKey |= 0x1 << i;
-            }
-        }
-    }
-
-    // If the 9patch is made of only transparent quads
-    if (transparentQuads == int8_t((width + 1) * (height + 1))) {
-        return NULL;
-    }
-
-    const PatchDescription description(bitmapWidth, bitmapHeight,
-            pixelWidth, pixelHeight, width, height, transparentQuads, colorKey);
-
-    ssize_t index = mCache.indexOfKey(description);
-    Patch* mesh = NULL;
-    if (index >= 0) {
-        mesh = mCache.valueAt(index);
-    }
+    const PatchDescription description(bitmapWidth, bitmapHeight, pixelWidth, pixelHeight, patch);
+    const Patch* mesh = mCache.get(description);
 
     if (!mesh) {
-        PATCH_LOGD("New patch mesh "
-                "xCount=%d yCount=%d, w=%.2f h=%.2f, bw=%.2f bh=%.2f",
-                width, height, pixelWidth, pixelHeight, bitmapWidth, bitmapHeight);
+        Patch* newMesh = new Patch();
+        TextureVertex* vertices;
 
-        mesh = new Patch(width, height, transparentQuads);
-        mesh->updateColorKey(colorKey);
-        mesh->copy(xDivs, yDivs);
-        mesh->updateVertices(bitmapWidth, bitmapHeight, 0.0f, 0.0f, pixelWidth, pixelHeight);
-
-        if (mCache.size() >= mMaxEntries) {
-            delete mCache.valueAt(mCache.size() - 1);
-            mCache.removeItemsAt(mCache.size() - 1, 1);
+        if (entry) {
+            vertices = newMesh->createMesh(bitmapWidth, bitmapHeight,
+                    0.0f, 0.0f, pixelWidth, pixelHeight, entry->uvMapper, patch);
+        } else {
+            vertices = newMesh->createMesh(bitmapWidth, bitmapHeight,
+                    0.0f, 0.0f, pixelWidth, pixelHeight, patch);
         }
 
-        mCache.add(description, mesh);
-    } else if (!mesh->matches(xDivs, yDivs, colorKey, transparentQuads)) {
-        PATCH_LOGD("Patch mesh does not match, refreshing vertices");
-        mesh->updateVertices(bitmapWidth, bitmapHeight, 0.0f, 0.0f, pixelWidth, pixelHeight);
+        if (vertices) {
+            Caches& caches = Caches::getInstance();
+            caches.bindMeshBuffer(mMeshBuffer);
+            caches.resetVertexPointers();
+
+            // TODO: Simply remove the oldest items until we have enough room
+            // This will require to keep a list of free blocks in the VBO
+            uint32_t size = newMesh->getSize();
+            if (mSize + size > mMaxSize) {
+                clearCache();
+                glBufferData(GL_ARRAY_BUFFER, mMaxSize, NULL, GL_DYNAMIC_DRAW);
+                mSize = 0;
+            }
+
+            newMesh->offset = (GLintptr) mSize;
+            newMesh->textureOffset = newMesh->offset + gMeshTextureOffset;
+            mSize += size;
+
+            glBufferSubData(GL_ARRAY_BUFFER, newMesh->offset, size, vertices);
+
+            delete[] vertices;
+        }
+
+        mCache.put(description, newMesh);
+        return newMesh;
     }
 
     return mesh;
diff --git a/libs/hwui/PatchCache.h b/libs/hwui/PatchCache.h
index 0822cba..129a0dc 100644
--- a/libs/hwui/PatchCache.h
+++ b/libs/hwui/PatchCache.h
@@ -17,8 +17,13 @@
 #ifndef ANDROID_HWUI_PATCH_CACHE_H
 #define ANDROID_HWUI_PATCH_CACHE_H
 
-#include <utils/KeyedVector.h>
+#include <GLES2/gl2.h>
 
+#include <utils/LruCache.h>
+
+#include <androidfw/ResourceTypes.h>
+
+#include "AssetAtlas.h"
 #include "Debug.h"
 #include "Patch.h"
 
@@ -40,45 +45,47 @@
 // Cache
 ///////////////////////////////////////////////////////////////////////////////
 
+class Caches;
+
 class PatchCache {
 public:
     PatchCache();
-    PatchCache(uint32_t maxCapacity);
     ~PatchCache();
+    void init(Caches& caches);
 
-    Patch* get(const uint32_t bitmapWidth, const uint32_t bitmapHeight,
-            const float pixelWidth, const float pixelHeight,
-            const int32_t* xDivs, const int32_t* yDivs, const uint32_t* colors,
-            const uint32_t width, const uint32_t height, const int8_t numColors);
+    const Patch* get(const AssetAtlas::Entry* entry,
+            const uint32_t bitmapWidth, const uint32_t bitmapHeight,
+            const float pixelWidth, const float pixelHeight, const Res_png_9patch* patch);
     void clear();
 
     uint32_t getSize() const {
-        return mCache.size();
+        return mSize;
     }
 
     uint32_t getMaxSize() const {
-        return mMaxEntries;
+        return mMaxSize;
+    }
+
+    GLuint getMeshBuffer() const {
+        return mMeshBuffer;
     }
 
 private:
-    /**
-     * Description of a patch.
-     */
+    void clearCache();
+
     struct PatchDescription {
-        PatchDescription(): bitmapWidth(0), bitmapHeight(0), pixelWidth(0), pixelHeight(0),
-                xCount(0), yCount(0), emptyCount(0), colorKey(0) {
+        PatchDescription(): mPatch(NULL), mBitmapWidth(0), mBitmapHeight(0),
+                mPixelWidth(0), mPixelHeight(0) {
         }
 
         PatchDescription(const uint32_t bitmapWidth, const uint32_t bitmapHeight,
-                const float pixelWidth, const float pixelHeight,
-                const uint32_t xCount, const uint32_t yCount,
-                const int8_t emptyCount, const uint32_t colorKey):
-                bitmapWidth(bitmapWidth), bitmapHeight(bitmapHeight),
-                pixelWidth(pixelWidth), pixelHeight(pixelHeight),
-                xCount(xCount), yCount(yCount),
-                emptyCount(emptyCount), colorKey(colorKey) {
+                const float pixelWidth, const float pixelHeight, const Res_png_9patch* patch):
+                mPatch(patch), mBitmapWidth(bitmapWidth), mBitmapHeight(bitmapHeight),
+                mPixelWidth(pixelWidth), mPixelHeight(pixelHeight) {
         }
 
+        hash_t hash() const;
+
         static int compare(const PatchDescription& lhs, const PatchDescription& rhs);
 
         bool operator==(const PatchDescription& other) const {
@@ -99,21 +106,24 @@
             return PatchDescription::compare(lhs, rhs);
         }
 
+        friend inline hash_t hash_type(const PatchDescription& entry) {
+            return entry.hash();
+        }
+
     private:
-        uint32_t bitmapWidth;
-        uint32_t bitmapHeight;
-        float pixelWidth;
-        float pixelHeight;
-        uint32_t xCount;
-        uint32_t yCount;
-        int8_t emptyCount;
-        uint32_t colorKey;
+        const Res_png_9patch* mPatch;
+        uint32_t mBitmapWidth;
+        uint32_t mBitmapHeight;
+        float mPixelWidth;
+        float mPixelHeight;
 
     }; // struct PatchDescription
 
-    uint32_t mMaxEntries;
-    KeyedVector<PatchDescription, Patch*> mCache;
+    uint32_t mMaxSize;
+    uint32_t mSize;
+    LruCache<PatchDescription, Patch*> mCache;
 
+    GLuint mMeshBuffer;
 }; // class PatchCache
 
 }; // namespace uirenderer
diff --git a/libs/hwui/Program.cpp b/libs/hwui/Program.cpp
index 14a2376..c127d68 100644
--- a/libs/hwui/Program.cpp
+++ b/libs/hwui/Program.cpp
@@ -15,6 +15,9 @@
  */
 
 #define LOG_TAG "OpenGLRenderer"
+#define ATRACE_TAG ATRACE_TAG_VIEW
+
+#include <utils/Trace.h>
 
 #include "Program.h"
 
@@ -25,7 +28,6 @@
 // Base program
 ///////////////////////////////////////////////////////////////////////////////
 
-// TODO: Program instance should be created from a factory method
 Program::Program(const ProgramDescription& description, const char* vertex, const char* fragment) {
     mInitialized = false;
     mHasColorUniform = false;
@@ -50,7 +52,9 @@
                 texCoords = -1;
             }
 
+            ATRACE_BEGIN("linkProgram");
             glLinkProgram(mProgramId);
+            ATRACE_END();
 
             GLint status;
             glGetProgramiv(mProgramId, GL_LINK_STATUS, &status);
@@ -87,6 +91,9 @@
 
 Program::~Program() {
     if (mInitialized) {
+        // This would ideally happen after linking the program
+        // but Tegra drivers, especially when perfhud is enabled,
+        // sometimes crash if we do so
         glDetachShader(mProgramId, mVertexShader);
         glDetachShader(mProgramId, mFragmentShader);
 
@@ -132,6 +139,8 @@
 }
 
 GLuint Program::buildShader(const char* source, GLenum type) {
+    ATRACE_CALL();
+
     GLuint shader = glCreateShader(type);
     glShaderSource(shader, 1, &source, 0);
     glCompileShader(shader);
@@ -153,20 +162,24 @@
 
 void Program::set(const mat4& projectionMatrix, const mat4& modelViewMatrix,
         const mat4& transformMatrix, bool offset) {
-    mat4 p(projectionMatrix);
-    if (offset) {
-        // offset screenspace xy by an amount that compensates for typical precision
-        // issues in GPU hardware that tends to paint hor/vert lines in pixels shifted
-        // up and to the left.
-        // This offset value is based on an assumption that some hardware may use as
-        // little as 12.4 precision, so we offset by slightly more than 1/16.
-        p.translate(.065, .065, 0);
+    if (projectionMatrix != mProjection) {
+        if (CC_LIKELY(!offset)) {
+            glUniformMatrix4fv(projection, 1, GL_FALSE, &projectionMatrix.data[0]);
+        } else {
+            mat4 p(projectionMatrix);
+            // offset screenspace xy by an amount that compensates for typical precision
+            // issues in GPU hardware that tends to paint hor/vert lines in pixels shifted
+            // up and to the left.
+            // This offset value is based on an assumption that some hardware may use as
+            // little as 12.4 precision, so we offset by slightly more than 1/16.
+            p.translate(.065, .065, 0);
+            glUniformMatrix4fv(projection, 1, GL_FALSE, &p.data[0]);
+        }
+        mProjection = projectionMatrix;
     }
 
     mat4 t(transformMatrix);
     t.multiply(modelViewMatrix);
-
-    glUniformMatrix4fv(projection, 1, GL_FALSE, &p.data[0]);
     glUniformMatrix4fv(transform, 1, GL_FALSE, &t.data[0]);
 }
 
diff --git a/libs/hwui/Program.h b/libs/hwui/Program.h
index e8b6d47..a252209 100644
--- a/libs/hwui/Program.h
+++ b/libs/hwui/Program.h
@@ -430,10 +430,13 @@
     bool mUse;
     bool mInitialized;
 
+    // Uniforms caching
     bool mHasColorUniform;
     int mColorUniform;
 
     bool mHasSampler;
+
+    mat4 mProjection;
 }; // class Program
 
 }; // namespace uirenderer
diff --git a/libs/hwui/Properties.h b/libs/hwui/Properties.h
index 6eea00c..87ac845 100644
--- a/libs/hwui/Properties.h
+++ b/libs/hwui/Properties.h
@@ -133,6 +133,7 @@
 #define PROPERTY_RENDER_BUFFER_CACHE_SIZE "ro.hwui.r_buffer_cache_size"
 #define PROPERTY_GRADIENT_CACHE_SIZE "ro.hwui.gradient_cache_size"
 #define PROPERTY_PATH_CACHE_SIZE "ro.hwui.path_cache_size"
+#define PROPERTY_PATCH_CACHE_SIZE "ro.hwui.patch_cache_size"
 #define PROPERTY_DROP_SHADOW_CACHE_SIZE "ro.hwui.drop_shadow_cache_size"
 #define PROPERTY_FBO_CACHE_SIZE "ro.hwui.fbo_cache_size"
 
@@ -178,7 +179,7 @@
 #define DEFAULT_LAYER_CACHE_SIZE 16.0f
 #define DEFAULT_RENDER_BUFFER_CACHE_SIZE 2.0f
 #define DEFAULT_PATH_CACHE_SIZE 10.0f
-#define DEFAULT_PATCH_CACHE_SIZE 512
+#define DEFAULT_PATCH_CACHE_SIZE 128 // in kB
 #define DEFAULT_GRADIENT_CACHE_SIZE 0.5f
 #define DEFAULT_DROP_SHADOW_CACHE_SIZE 2.0f
 #define DEFAULT_FBO_CACHE_SIZE 16
@@ -195,6 +196,8 @@
 
 // Converts a number of mega-bytes into bytes
 #define MB(s) s * 1024 * 1024
+// Converts a number of kilo-bytes into bytes
+#define KB(s) s * 1024
 
 static DebugLevel readDebugLevel() {
     char property[PROPERTY_VALUE_MAX];
diff --git a/libs/hwui/Texture.h b/libs/hwui/Texture.h
index 8d88bdc..dd39cae 100644
--- a/libs/hwui/Texture.h
+++ b/libs/hwui/Texture.h
@@ -22,6 +22,8 @@
 namespace android {
 namespace uirenderer {
 
+class UvMapper;
+
 /**
  * Represents an OpenGL texture.
  */
@@ -42,6 +44,8 @@
         firstWrap = true;
 
         id = 0;
+
+        uvMapper = NULL;
     }
 
     void setWrap(GLenum wrap, bool bindTexture = false, bool force = false,
@@ -125,6 +129,11 @@
      */
     bool mipMap;
 
+    /**
+     * Optional, pointer to a texture coordinates mapper.
+     */
+    const UvMapper* uvMapper;
+
 private:
     /**
      * Last wrap modes set on this texture. Defaults to GL_CLAMP_TO_EDGE.
diff --git a/libs/hwui/UvMapper.h b/libs/hwui/UvMapper.h
new file mode 100644
index 0000000..70428d2
--- /dev/null
+++ b/libs/hwui/UvMapper.h
@@ -0,0 +1,133 @@
+/*
+ * 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_UV_MAPPER_H
+#define ANDROID_HWUI_UV_MAPPER_H
+
+#include "Rect.h"
+
+namespace android {
+namespace uirenderer {
+
+/**
+ * This class can be used to map UV coordinates from the [0..1]
+ * range to other arbitrary ranges. All the methods below assume
+ * that the input values lie in the [0..1] range already.
+ */
+class UvMapper {
+public:
+    /**
+     * Using this constructor is equivalent to not using any mapping at all.
+     * UV coordinates in the [0..1] range remain in the [0..1] range.
+     */
+    UvMapper(): mIdentity(true), mMinU(0.0f), mMaxU(1.0f), mMinV(0.0f), mMaxV(1.0f) {
+    }
+
+    /**
+     * Creates a new mapper with the specified ranges for U and V coordinates.
+     * The parameter minU must be < maxU and minV must be < maxV.
+     */
+    UvMapper(float minU, float maxU, float minV, float maxV):
+        mMinU(minU), mMaxU(maxU), mMinV(minV), mMaxV(maxV) {
+        checkIdentity();
+    }
+
+    /**
+     * Returns true if calling the map*() methods has no effect (that is,
+     * texture coordinates remain in the 0..1 range.)
+     */
+    bool isIdentity() const {
+        return mIdentity;
+    }
+
+    /**
+     * Changes the U and V mapping ranges.
+     * The parameter minU must be < maxU and minV must be < maxV.
+     */
+    void setMapping(float minU, float maxU, float minV, float maxV) {
+        mMinU = minU;
+        mMaxU = maxU;
+        mMinV = minV;
+        mMaxV = maxV;
+        checkIdentity();
+    }
+
+    /**
+     * Maps a single value in the U range.
+     */
+    void mapU(float& u) const {
+        if (!mIdentity) u = lerp(mMinU, mMaxU, u);
+    }
+
+    /**
+     * Maps a single value in the V range.
+     */
+    void mapV(float& v) const {
+        if (!mIdentity) v = lerp(mMinV, mMaxV, v);
+    }
+
+    /**
+     * Maps the specified rectangle in place. This method assumes:
+     * - left = min. U
+     * - top = min. V
+     * - right = max. U
+     * - bottom = max. V
+     */
+    void map(Rect& texCoords) const {
+        if (!mIdentity) {
+            texCoords.left = lerp(mMinU, mMaxU, texCoords.left);
+            texCoords.right = lerp(mMinU, mMaxU, texCoords.right);
+            texCoords.top = lerp(mMinV, mMaxV, texCoords.top);
+            texCoords.bottom = lerp(mMinV, mMaxV, texCoords.bottom);
+        }
+    }
+
+    /**
+     * Maps the specified UV coordinates in place.
+     */
+    void map(float& u1, float& v1, float& u2, float& v2) const {
+        if (!mIdentity) {
+            u1 = lerp(mMinU, mMaxU, u1);
+            u2 = lerp(mMinU, mMaxU, u2);
+            v1 = lerp(mMinV, mMaxV, v1);
+            v2 = lerp(mMinV, mMaxV, v2);
+        }
+    }
+
+    void dump() const {
+        ALOGD("mapper[minU=%.2f maxU=%.2f minV=%.2f maxV=%.2f]", mMinU, mMaxU, mMinV, mMaxV);
+    }
+
+private:
+    static float lerp(float start, float stop, float amount) {
+        return start + (stop - start) * amount;
+    }
+
+    void checkIdentity() {
+        mIdentity = mMinU == 0.0f && mMaxU == 1.0f && mMinV == 0.0f && mMaxV == 1.0f;
+    }
+
+    bool mIdentity;
+    float mMinU;
+    float mMaxU;
+    float mMinV;
+    float mMaxV;
+};
+
+}; // namespace uirenderer
+}; // namespace android
+
+#endif // ANDROID_HWUI_UV_MAPPER_H
diff --git a/services/java/com/android/server/AssetAtlasService.java b/services/java/com/android/server/AssetAtlasService.java
new file mode 100644
index 0000000..b18be1c
--- /dev/null
+++ b/services/java/com/android/server/AssetAtlasService.java
@@ -0,0 +1,730 @@
+/*
+ * 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.
+ */
+
+package com.android.server;
+
+import android.content.Context;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.res.Resources;
+import android.graphics.Atlas;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.PixelFormat;
+import android.graphics.PorterDuff;
+import android.graphics.PorterDuffXfermode;
+import android.graphics.drawable.Drawable;
+import android.os.Environment;
+import android.os.RemoteException;
+import android.os.SystemProperties;
+import android.util.Log;
+import android.util.LongSparseArray;
+import android.view.GraphicBuffer;
+import android.view.IAssetAtlas;
+
+import java.io.BufferedReader;
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.OutputStreamWriter;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+/**
+ * This service is responsible for packing preloaded bitmaps into a single
+ * atlas texture. The resulting texture can be shared across processes to
+ * reduce overall memory usage.
+ *
+ * @hide
+ */
+public class AssetAtlasService extends IAssetAtlas.Stub {
+    /**
+     * Name of the <code>AssetAtlasService</code>.
+     */
+    public static final String ASSET_ATLAS_SERVICE = "assetatlas";
+
+    private static final String LOG_TAG = "Atlas";
+
+    // Turns debug logs on/off. Debug logs are kept to a minimum and should
+    // remain on to diagnose issues
+    private static final boolean DEBUG_ATLAS = true;
+
+    // When set to true the content of the atlas will be saved to disk
+    // in /data/system/atlas.png. The shared GraphicBuffer may be empty
+    private static final boolean DEBUG_ATLAS_TEXTURE = false;
+
+    // Minimum size in pixels to consider for the resulting texture
+    private static final int MIN_SIZE = 768;
+    // Maximum size in pixels to consider for the resulting texture
+    private static final int MAX_SIZE = 2048;
+    // Increment in number of pixels between size variants when looking
+    // for the best texture dimensions
+    private static final int STEP = 64;
+
+    // This percentage of the total number of pixels represents the minimum
+    // number of pixels we want to be able to pack in the atlas
+    private static final float PACKING_THRESHOLD = 0.8f;
+
+    // Defines the number of int fields used to represent a single entry
+    // in the atlas map. This number defines the size of the array returned
+    // by the getMap(). See the mAtlasMap field for more information
+    private static final int ATLAS_MAP_ENTRY_FIELD_COUNT = 4;
+
+    // Specifies how our GraphicBuffer will be used. To get proper swizzling
+    // the buffer will be written to using OpenGL (from JNI) so we can leave
+    // the software flag set to "never"
+    private static final int GRAPHIC_BUFFER_USAGE = GraphicBuffer.USAGE_SW_READ_NEVER |
+            GraphicBuffer.USAGE_SW_WRITE_NEVER | GraphicBuffer.USAGE_HW_TEXTURE;
+
+    // This boolean is set to true if an atlas was successfully
+    // computed and rendered
+    private final AtomicBoolean mAtlasReady = new AtomicBoolean(false);
+
+    private final Context mContext;
+
+    // Version name of the current build, used to identify changes to assets list
+    private final String mVersionName;
+
+    // Holds the atlas' data. This buffer can be mapped to
+    // OpenGL using an EGLImage
+    private GraphicBuffer mBuffer;
+
+    // Describes how bitmaps are placed in the atlas. Each bitmap is
+    // represented by several entries in the array:
+    // int0: SkBitmap*, the native bitmap object
+    // int1: x position
+    // int2: y position
+    // int3: rotated, 1 if the bitmap must be rotated, 0 otherwise
+    // NOTE: This will need to be handled differently to support 64 bit pointers
+    private int[] mAtlasMap;
+
+    /**
+     * Creates a new service. Upon creating, the service will gather the list of
+     * assets to consider for packing into the atlas and spawn a new thread to
+     * start the packing work.
+     *
+     * @param context The context giving access to preloaded resources
+     */
+    public AssetAtlasService(Context context) {
+        mContext = context;
+        mVersionName = queryVersionName(context);
+
+        ArrayList<Bitmap> bitmaps = new ArrayList<Bitmap>(300);
+        int totalPixelCount = 0;
+
+        // We only care about drawables that hold bitmaps
+        final Resources resources = context.getResources();
+        final LongSparseArray<Drawable.ConstantState> drawables = resources.getPreloadedDrawables();
+
+        final int count = drawables.size();
+        for (int i = 0; i < count; i++) {
+            final Bitmap bitmap = drawables.valueAt(i).getBitmap();
+            if (bitmap != null && bitmap.getConfig() == Bitmap.Config.ARGB_8888) {
+                bitmaps.add(bitmap);
+                totalPixelCount += bitmap.getWidth() * bitmap.getHeight();
+            }
+        }
+
+        // Our algorithms perform better when the bitmaps are first sorted
+        // The comparator will sort the bitmap by width first, then by height
+        Collections.sort(bitmaps, new Comparator<Bitmap>() {
+            @Override
+            public int compare(Bitmap b1, Bitmap b2) {
+                if (b1.getWidth() == b2.getWidth()) {
+                    return b2.getHeight() - b1.getHeight();
+                }
+                return b2.getWidth() - b1.getWidth();
+            }
+        });
+
+        // Kick off the packing work on a worker thread
+        new Thread(new Renderer(bitmaps, totalPixelCount)).start();
+    }
+
+    /**
+     * Queries the version name stored in framework's AndroidManifest.
+     * The version name can be used to identify possible changes to
+     * framework resources.
+     *
+     * @see #getBuildIdentifier(String)
+     */
+    private static String queryVersionName(Context context) {
+        try {
+            String packageName = context.getPackageName();
+            PackageInfo info = context.getPackageManager().getPackageInfo(packageName, 0);
+            return info.versionName;
+        } catch (PackageManager.NameNotFoundException e) {
+            Log.w(LOG_TAG, "Could not get package info", e);
+        }
+        return null;
+    }
+
+    /**
+     * Callback invoked by the server thread to indicate we can now run
+     * 3rd party code.
+     */
+    public void systemReady() {
+    }
+
+    /**
+     * The renderer does all the work:
+     */
+    private class Renderer implements Runnable {
+        private final ArrayList<Bitmap> mBitmaps;
+        private final int mPixelCount;
+
+        private int mNativeBitmap;
+
+        // Used for debugging only
+        private Bitmap mAtlasBitmap;
+
+        Renderer(ArrayList<Bitmap> bitmaps, int pixelCount) {
+            mBitmaps = bitmaps;
+            mPixelCount = pixelCount;
+        }
+
+        /**
+         * 1. On first boot or after every update, brute-force through all the
+         *    possible atlas configurations and look for the best one (maximimize
+         *    number of packed assets and minimize texture size)
+         *    a. If a best configuration was computed, write it out to disk for
+         *       future use
+         * 2. Read best configuration from disk
+         * 3. Compute the packing using the best configuration
+         * 4. Allocate a GraphicBuffer
+         * 5. Render assets in the buffer
+         */
+        @Override
+        public void run() {
+            Configuration config = chooseConfiguration(mBitmaps, mPixelCount, mVersionName);
+            if (DEBUG_ATLAS) Log.d(LOG_TAG, "Loaded configuration: " + config);
+
+            if (config != null) {
+                mBuffer = GraphicBuffer.create(config.width, config.height,
+                        PixelFormat.RGBA_8888, GRAPHIC_BUFFER_USAGE);
+
+                if (mBuffer != null) {
+                    Atlas atlas = new Atlas(config.type, config.width, config.height, config.flags);
+                    if (renderAtlas(mBuffer, atlas, config.count)) {
+                        mAtlasReady.set(true);
+                    }
+                }
+            }
+        }
+
+        /**
+         * Renders a list of bitmaps into the atlas. The position of each bitmap
+         * was decided by the packing algorithm and will be honored by this
+         * method. If need be this method will also rotate bitmaps.
+         *
+         * @param buffer The buffer to render the atlas entries into
+         * @param atlas The atlas to pack the bitmaps into
+         * @param packCount The number of bitmaps that will be packed in the atlas
+         *
+         * @return true if the atlas was rendered, false otherwise
+         */
+        @SuppressWarnings("MismatchedReadAndWriteOfArray")
+        private boolean renderAtlas(GraphicBuffer buffer, Atlas atlas, int packCount) {
+            // Use a Source blend mode to improve performance, the target bitmap
+            // will be zero'd out so there's no need to waste time applying blending
+            final Paint paint = new Paint();
+            paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC));
+
+            // We always render the atlas into a bitmap. This bitmap is then
+            // uploaded into the GraphicBuffer using OpenGL to swizzle the content
+            final Canvas canvas = acquireCanvas(buffer.getWidth(), buffer.getHeight());
+            if (canvas == null) return false;
+
+            final Atlas.Entry entry = new Atlas.Entry();
+
+            mAtlasMap = new int[packCount * ATLAS_MAP_ENTRY_FIELD_COUNT];
+            int[] atlasMap = mAtlasMap;
+            int mapIndex = 0;
+
+            boolean result = false;
+            try {
+                final long startRender = System.nanoTime();
+                final int count = mBitmaps.size();
+
+                for (int i = 0; i < count; i++) {
+                    final Bitmap bitmap = mBitmaps.get(i);
+                    if (atlas.pack(bitmap.getWidth(), bitmap.getHeight(), entry) != null) {
+                        // We have more bitmaps to pack than the current configuration
+                        // says, we were most likely not able to detect a change in the
+                        // list of preloaded drawables, abort and delete the configuration
+                        if (mapIndex >= mAtlasMap.length) {
+                            deleteDataFile();
+                            break;
+                        }
+
+                        canvas.save();
+                        canvas.translate(entry.x, entry.y);
+                        if (entry.rotated) {
+                            canvas.translate(bitmap.getHeight(), 0.0f);
+                            canvas.rotate(90.0f);
+                        }
+                        canvas.drawBitmap(bitmap, 0.0f, 0.0f, null);
+                        canvas.restore();
+
+                        atlasMap[mapIndex++] = bitmap.mNativeBitmap;
+                        atlasMap[mapIndex++] = entry.x;
+                        atlasMap[mapIndex++] = entry.y;
+                        atlasMap[mapIndex++] = entry.rotated ? 1 : 0;
+                    }
+                }
+
+                final long endRender = System.nanoTime();
+                if (mNativeBitmap != 0) {
+                    result = nUploadAtlas(buffer, mNativeBitmap);
+                }
+
+                final long endUpload = System.nanoTime();
+                if (DEBUG_ATLAS) {
+                    float renderDuration = (endRender - startRender) / 1000.0f / 1000.0f;
+                    float uploadDuration = (endUpload - endRender) / 1000.0f / 1000.0f;
+                    Log.d(LOG_TAG, String.format("Rendered atlas in %.2fms (%.2f+%.2fms)",
+                            renderDuration + uploadDuration, renderDuration, uploadDuration));
+                }
+
+            } finally {
+                releaseCanvas(canvas);
+            }
+
+            return result;
+        }
+
+        /**
+         * Returns a Canvas for the specified buffer. If {@link #DEBUG_ATLAS_TEXTURE}
+         * is turned on, the returned Canvas will render into a local bitmap that
+         * will then be saved out to disk for debugging purposes.
+         * @param width
+         * @param height
+         */
+        private Canvas acquireCanvas(int width, int height) {
+            if (DEBUG_ATLAS_TEXTURE) {
+                mAtlasBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
+                return new Canvas(mAtlasBitmap);
+            } else {
+                Canvas canvas = new Canvas();
+                mNativeBitmap = nAcquireAtlasCanvas(canvas, width, height);
+                return canvas;
+            }
+        }
+
+        /**
+         * Releases the canvas used to render into the buffer. Calling this method
+         * will release any resource previously acquired. If {@link #DEBUG_ATLAS_TEXTURE}
+         * is turend on, calling this method will write the content of the atlas
+         * to disk in /data/system/atlas.png for debugging.
+         */
+        private void releaseCanvas(Canvas canvas) {
+            if (DEBUG_ATLAS_TEXTURE) {
+                canvas.setBitmap(null);
+
+                File systemDirectory = new File(Environment.getDataDirectory(), "system");
+                File dataFile = new File(systemDirectory, "atlas.png");
+
+                try {
+                    FileOutputStream out = new FileOutputStream(dataFile);
+                    mAtlasBitmap.compress(Bitmap.CompressFormat.PNG, 100, out);
+                    out.close();
+                } catch (FileNotFoundException e) {
+                    // Ignore
+                } catch (IOException e) {
+                    // Ignore
+                }
+
+                mAtlasBitmap.recycle();
+                mAtlasBitmap = null;
+            } else {
+                nReleaseAtlasCanvas(canvas, mNativeBitmap);
+            }
+        }
+    }
+
+    private static native int nAcquireAtlasCanvas(Canvas canvas, int width, int height);
+    private static native void nReleaseAtlasCanvas(Canvas canvas, int bitmap);
+    private static native boolean nUploadAtlas(GraphicBuffer buffer, int bitmap);
+
+    @Override
+    public GraphicBuffer getBuffer() throws RemoteException {
+        return mAtlasReady.get() ? mBuffer : null;
+    }
+
+    @Override
+    public int[] getMap() throws RemoteException {
+        return mAtlasReady.get() ? mAtlasMap : null;
+    }
+
+    /**
+     * Finds the best atlas configuration to pack the list of supplied bitmaps.
+     * This method takes advantage of multi-core systems by spawning a number
+     * of threads equal to the number of available cores.
+     */
+    private static Configuration computeBestConfiguration(
+            ArrayList<Bitmap> bitmaps, int pixelCount) {
+        if (DEBUG_ATLAS) Log.d(LOG_TAG, "Computing best atlas configuration...");
+
+        long begin = System.nanoTime();
+        List<WorkerResult> results = Collections.synchronizedList(new ArrayList<WorkerResult>());
+
+        // Don't bother with an extra thread if there's only one processor
+        int cpuCount = Runtime.getRuntime().availableProcessors();
+        if (cpuCount == 1) {
+            new ComputeWorker(MIN_SIZE, MAX_SIZE, STEP, bitmaps, pixelCount, results, null).run();
+        } else {
+            int start = MIN_SIZE;
+            int end = MAX_SIZE - (cpuCount - 1) * STEP;
+            int step = STEP * cpuCount;
+
+            final CountDownLatch signal = new CountDownLatch(cpuCount);
+
+            for (int i = 0; i < cpuCount; i++, start += STEP, end += STEP) {
+                ComputeWorker worker = new ComputeWorker(start, end, step,
+                        bitmaps, pixelCount, results, signal);
+                new Thread(worker, "Atlas Worker #" + (i + 1)).start();
+            }
+
+            try {
+                signal.await(10, TimeUnit.SECONDS);
+            } catch (InterruptedException e) {
+                Log.w(LOG_TAG, "Could not complete configuration computation");
+                return null;
+            }
+        }
+
+        // Maximize the number of packed bitmaps, minimize the texture size
+        Collections.sort(results, new Comparator<WorkerResult>() {
+            @Override
+            public int compare(WorkerResult r1, WorkerResult r2) {
+                int delta = r2.count - r1.count;
+                if (delta != 0) return delta;
+                return r1.width * r1.height - r2.width * r2.height;
+            }
+        });
+
+        if (DEBUG_ATLAS) {
+            float delay = (System.nanoTime() - begin) / 1000.0f / 1000.0f / 1000.0f;
+            Log.d(LOG_TAG, String.format("Found best atlas configuration in %.2fs", delay));
+        }
+
+        WorkerResult result = results.get(0);
+        return new Configuration(result.type, result.width, result.height, result.count);
+    }
+
+    /**
+     * Returns the path to the file containing the best computed
+     * atlas configuration.
+     */
+    private static File getDataFile() {
+        File systemDirectory = new File(Environment.getDataDirectory(), "system");
+        return new File(systemDirectory, "framework_atlas.config");
+    }
+
+    private static void deleteDataFile() {
+        Log.w(LOG_TAG, "Current configuration inconsistent with assets list");
+        if (!getDataFile().delete()) {
+            Log.w(LOG_TAG, "Could not delete the current configuration");
+        }
+    }
+
+    private File getFrameworkResourcesFile() {
+        return new File(mContext.getApplicationInfo().sourceDir);
+    }
+
+    /**
+     * Returns the best known atlas configuration. This method will either
+     * read the configuration from disk or start a brute-force search
+     * and save the result out to disk.
+     */
+    private Configuration chooseConfiguration(ArrayList<Bitmap> bitmaps, int pixelCount,
+            String versionName) {
+        Configuration config = null;
+
+        final File dataFile = getDataFile();
+        if (dataFile.exists()) {
+            config = readConfiguration(dataFile, versionName);
+        }
+
+        if (config == null) {
+            config = computeBestConfiguration(bitmaps, pixelCount);
+            if (config != null) writeConfiguration(config, dataFile, versionName);
+        }
+
+        return config;
+    }
+
+    /**
+     * Writes the specified atlas configuration to the specified file.
+     */
+    private void writeConfiguration(Configuration config, File file, String versionName) {
+        BufferedWriter writer = null;
+        try {
+            writer = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(file)));
+            writer.write(getBuildIdentifier(versionName));
+            writer.newLine();
+            writer.write(config.type.toString());
+            writer.newLine();
+            writer.write(String.valueOf(config.width));
+            writer.newLine();
+            writer.write(String.valueOf(config.height));
+            writer.newLine();
+            writer.write(String.valueOf(config.count));
+            writer.newLine();
+            writer.write(String.valueOf(config.flags));
+            writer.newLine();
+        } catch (FileNotFoundException e) {
+            Log.w(LOG_TAG, "Could not write " + file, e);
+        } catch (IOException e) {
+            Log.w(LOG_TAG, "Could not write " + file, e);
+        } finally {
+            if (writer != null) {
+                try {
+                    writer.close();
+                } catch (IOException e) {
+                    // Ignore
+                }
+            }
+        }
+    }
+
+    /**
+     * Reads an atlas configuration from the specified file. This method
+     * returns null if an error occurs or if the configuration is invalid.
+     */
+    private Configuration readConfiguration(File file, String versionName) {
+        BufferedReader reader = null;
+        Configuration config = null;
+        try {
+            reader = new BufferedReader(new InputStreamReader(new FileInputStream(file)));
+
+            if (checkBuildIdentifier(reader, versionName)) {
+                Atlas.Type type = Atlas.Type.valueOf(reader.readLine());
+                int width = readInt(reader, MIN_SIZE, MAX_SIZE);
+                int height = readInt(reader, MIN_SIZE, MAX_SIZE);
+                int count = readInt(reader, 0, Integer.MAX_VALUE);
+                int flags = readInt(reader, Integer.MIN_VALUE, Integer.MAX_VALUE);
+
+                config = new Configuration(type, width, height, count, flags);
+            }
+        } catch (IllegalArgumentException e) {
+            Log.w(LOG_TAG, "Invalid parameter value in " + file, e);
+        } catch (FileNotFoundException e) {
+            Log.w(LOG_TAG, "Could not read " + file, e);
+        } catch (IOException e) {
+            Log.w(LOG_TAG, "Could not read " + file, e);
+        } finally {
+            if (reader != null) {
+                try {
+                    reader.close();
+                } catch (IOException e) {
+                    // Ignore
+                }
+            }
+        }
+        return config;
+    }
+
+    private static int readInt(BufferedReader reader, int min, int max) throws IOException {
+        return Math.max(min, Math.min(max, Integer.parseInt(reader.readLine())));
+    }
+
+    /**
+     * Compares the next line in the specified buffered reader to the current
+     * build identifier. Returns whether the two values are equal.
+     *
+     * @see #getBuildIdentifier(String)
+     */
+    private boolean checkBuildIdentifier(BufferedReader reader, String versionName)
+            throws IOException {
+        String deviceBuildId = getBuildIdentifier(versionName);
+        String buildId = reader.readLine();
+        return deviceBuildId.equals(buildId);
+    }
+
+    /**
+     * Returns an identifier for the current build that can be used to detect
+     * likely changes to framework resources. The build identifier is made of
+     * several distinct values:
+     *
+     * build fingerprint/framework version name/file size of framework resources apk
+     *
+     * Only the build fingerprint should be necessary on user builds but
+     * the other values are useful to detect changes on eng builds during
+     * development.
+     *
+     * This identifier does not attempt to be exact: a new identifier does not
+     * necessarily mean the preloaded drawables have changed. It is important
+     * however that whenever the list of preloaded drawables changes, this
+     * identifier changes as well.
+     *
+     * @see #checkBuildIdentifier(java.io.BufferedReader, String)
+     */
+    private String getBuildIdentifier(String versionName) {
+        return SystemProperties.get("ro.build.fingerprint", "") + '/' + versionName + '/' +
+                String.valueOf(getFrameworkResourcesFile().length());
+    }
+
+    /**
+     * Atlas configuration. Specifies the algorithm, dimensions and flags to use.
+     */
+    private static class Configuration {
+        final Atlas.Type type;
+        final int width;
+        final int height;
+        final int count;
+        final int flags;
+
+        Configuration(Atlas.Type type, int width, int height, int count) {
+            this(type, width, height, count, Atlas.FLAG_DEFAULTS);
+        }
+
+        Configuration(Atlas.Type type, int width, int height, int count, int flags) {
+            this.type = type;
+            this.width = width;
+            this.height = height;
+            this.count = count;
+            this.flags = flags;
+        }
+
+        @Override
+        public String toString() {
+            return type.toString() + " (" + width + "x" + height + ") flags=0x" +
+                    Integer.toHexString(flags) + " count=" + count;
+        }
+    }
+
+    /**
+     * Used during the brute-force search to gather information about each
+     * variant of the packing algorithm.
+     */
+    private static class WorkerResult {
+        Atlas.Type type;
+        int width;
+        int height;
+        int count;
+
+        WorkerResult(Atlas.Type type, int width, int height, int count) {
+            this.type = type;
+            this.width = width;
+            this.height = height;
+            this.count = count;
+        }
+
+        @Override
+        public String toString() {
+            return String.format("%s %dx%d", type.toString(), width, height);
+        }
+    }
+
+    /**
+     * A compute worker will try a finite number of variations of the packing
+     * algorithms and save the results in a supplied list.
+     */
+    private static class ComputeWorker implements Runnable {
+        private final int mStart;
+        private final int mEnd;
+        private final int mStep;
+        private final List<Bitmap> mBitmaps;
+        private final List<WorkerResult> mResults;
+        private final CountDownLatch mSignal;
+        private final int mThreshold;
+
+        /**
+         * Creates a new compute worker to brute-force through a range of
+         * packing algorithms variants.
+         *
+         * @param start The minimum texture width to try
+         * @param end The maximum texture width to try
+         * @param step The number of pixels to increment the texture width by at each step
+         * @param bitmaps The list of bitmaps to pack in the atlas
+         * @param pixelCount The total number of pixels occupied by the list of bitmaps
+         * @param results The list of results in which to save the brute-force search results
+         * @param signal Latch to decrement when this worker is done, may be null
+         */
+        ComputeWorker(int start, int end, int step, List<Bitmap> bitmaps, int pixelCount,
+                List<WorkerResult> results, CountDownLatch signal) {
+            mStart = start;
+            mEnd = end;
+            mStep = step;
+            mBitmaps = bitmaps;
+            mResults = results;
+            mSignal = signal;
+
+            // Minimum number of pixels we want to be able to pack
+            int threshold = (int) (pixelCount * PACKING_THRESHOLD);
+            // Make sure we can find at least one configuration
+            while (threshold > MAX_SIZE * MAX_SIZE) {
+                threshold >>= 1;
+            }
+            mThreshold = threshold;
+        }
+
+        @Override
+        public void run() {
+            if (DEBUG_ATLAS) Log.d(LOG_TAG, "Running " + Thread.currentThread().getName());
+
+            Atlas.Entry entry = new Atlas.Entry();
+            for (Atlas.Type type : Atlas.Type.values()) {
+                for (int width = mStart; width < mEnd; width += mStep) {
+                    for (int height = MIN_SIZE; height < MAX_SIZE; height += STEP) {
+                        // If the atlas is not big enough, skip it
+                        if (width * height <= mThreshold) continue;
+
+                        final int count = packBitmaps(type, width, height, entry);
+                        if (count > 0) {
+                            mResults.add(new WorkerResult(type, width, height, count));
+                            // If we were able to pack everything let's stop here
+                            // Increasing the height further won't make things better
+                            if (count == mBitmaps.size()) {
+                                break;
+                            }
+                        }
+                    }
+                }
+            }
+
+            if (mSignal != null) {
+                mSignal.countDown();
+            }
+        }
+
+        private int packBitmaps(Atlas.Type type, int width, int height, Atlas.Entry entry) {
+            int total = 0;
+            Atlas atlas = new Atlas(type, width, height);
+
+            final int count = mBitmaps.size();
+            for (int i = 0; i < count; i++) {
+                final Bitmap bitmap = mBitmaps.get(i);
+                if (atlas.pack(bitmap.getWidth(), bitmap.getHeight(), entry) != null) {
+                    total++;
+                }
+            }
+
+            return total;
+        }
+    }
+}
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 92f72ba..25818e4 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -373,6 +373,7 @@
         TextServicesManagerService tsms = null;
         LockSettingsService lockSettings = null;
         DreamManagerService dreamy = null;
+        AssetAtlasService atlas = null;
 
         // Bring up services needed for UI.
         if (factoryTest != SystemServer.FACTORY_TEST_LOW_LEVEL) {
@@ -796,6 +797,16 @@
                 }
             }
 
+            if (!disableNonCoreServices) {
+                try {
+                    Slog.i(TAG, "Assets Atlas Service");
+                    atlas = new AssetAtlasService(context);
+                    ServiceManager.addService(AssetAtlasService.ASSET_ATLAS_SERVICE, atlas);
+                } catch (Throwable e) {
+                    reportWtf("starting AssetAtlasService", e);
+                }
+            }
+
             try {
                 Slog.i(TAG, "IdleMaintenanceService");
                 new IdleMaintenanceService(context);
@@ -910,6 +921,7 @@
         final TextServicesManagerService textServiceManagerServiceF = tsms;
         final StatusBarManagerService statusBarF = statusBar;
         final DreamManagerService dreamyF = dreamy;
+        final AssetAtlasService atlasF = atlas;
         final InputManagerService inputManagerF = inputManager;
         final TelephonyRegistry telephonyRegistryF = telephonyRegistry;
 
@@ -1036,6 +1048,11 @@
                     reportWtf("making DreamManagerService ready", e);
                 }
                 try {
+                    if (atlasF != null) atlasF.systemReady();
+                } catch (Throwable e) {
+                    reportWtf("making AssetAtlasService ready", e);
+                }
+                try {
                     // TODO(BT) Pass parameter to input manager
                     if (inputManagerF != null) inputManagerF.systemReady();
                 } catch (Throwable e) {
diff --git a/services/jni/Android.mk b/services/jni/Android.mk
index b313d48..e416676 100644
--- a/services/jni/Android.mk
+++ b/services/jni/Android.mk
@@ -3,6 +3,7 @@
 
 LOCAL_SRC_FILES:= \
     com_android_server_AlarmManagerService.cpp \
+    com_android_server_AssetAtlasService.cpp \
     com_android_server_BatteryService.cpp \
     com_android_server_input_InputApplicationHandle.cpp \
     com_android_server_input_InputManagerService.cpp \
@@ -43,7 +44,11 @@
     libskia \
     libgui \
     libusbhost \
-    libsuspend
+    libsuspend \
+    libEGL \
+    libGLESv2
+
+LOCAL_CFLAGS += -DEGL_EGLEXT_PROTOTYPES -DGL_GLEXT_PROTOTYPES
 
 ifeq ($(WITH_MALLOC_LEAK_CHECK),true)
     LOCAL_CFLAGS += -DMALLOC_LEAK_CHECK
diff --git a/services/jni/com_android_server_AssetAtlasService.cpp b/services/jni/com_android_server_AssetAtlasService.cpp
new file mode 100644
index 0000000..62e950f
--- /dev/null
+++ b/services/jni/com_android_server_AssetAtlasService.cpp
@@ -0,0 +1,271 @@
+/*
+ * 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.
+ */
+
+#define LOG_TAG "AssetAtlasService"
+
+#include "jni.h"
+#include "JNIHelp.h"
+
+#include <android_view_GraphicBuffer.h>
+
+#include <GLES2/gl2.h>
+#include <GLES2/gl2ext.h>
+
+#include <EGL/egl.h>
+#include <EGL/eglext.h>
+
+#include <SkCanvas.h>
+#include <SkBitmap.h>
+
+namespace android {
+
+// ----------------------------------------------------------------------------
+// Defines
+// ----------------------------------------------------------------------------
+
+// Defines how long to wait for the GPU when uploading the atlas
+// This timeout is defined in nanoseconds (see EGL_KHR_fence_sync extension)
+#define FENCE_TIMEOUT 2000000000
+
+// ----------------------------------------------------------------------------
+// JNI Helpers
+// ----------------------------------------------------------------------------
+
+static struct {
+    jfieldID mFinalizer;
+    jfieldID mNativeCanvas;
+} gCanvasClassInfo;
+
+static struct {
+    jfieldID mNativeCanvas;
+} gCanvasFinalizerClassInfo;
+
+#define GET_INT(object, field) \
+    env->GetIntField(object, field)
+
+#define SET_INT(object, field, value) \
+    env->SetIntField(object, field, value)
+
+// ----------------------------------------------------------------------------
+// Canvas management
+// ----------------------------------------------------------------------------
+
+static inline void swapCanvasPtr(JNIEnv* env, jobject canvasObj, SkCanvas* newCanvas) {
+    jobject canvasFinalizerObj = env->GetObjectField(canvasObj, gCanvasClassInfo.mFinalizer);
+    SkCanvas* previousCanvas = reinterpret_cast<SkCanvas*>(
+            GET_INT(canvasObj, gCanvasClassInfo.mNativeCanvas));
+    SET_INT(canvasObj, gCanvasClassInfo.mNativeCanvas, (int) newCanvas);
+    SET_INT(canvasFinalizerObj, gCanvasFinalizerClassInfo.mNativeCanvas, (int) newCanvas);
+    SkSafeUnref(previousCanvas);
+}
+
+static SkBitmap* com_android_server_AssetAtlasService_acquireCanvas(JNIEnv* env, jobject,
+        jobject canvas, jint width, jint height) {
+
+    SkBitmap* bitmap = new SkBitmap;
+    bitmap->setConfig(SkBitmap::kARGB_8888_Config, width, height);
+    bitmap->allocPixels();
+    bitmap->eraseColor(0);
+
+    SkCanvas* nativeCanvas = SkNEW_ARGS(SkCanvas, (*bitmap));
+    swapCanvasPtr(env, canvas, nativeCanvas);
+
+    return bitmap;
+}
+
+static void com_android_server_AssetAtlasService_releaseCanvas(JNIEnv* env, jobject,
+        jobject canvas, SkBitmap* bitmap) {
+
+    SkCanvas* nativeCanvas = SkNEW(SkCanvas);
+    swapCanvasPtr(env, canvas, nativeCanvas);
+
+    delete bitmap;
+}
+
+#define CLEANUP_GL_AND_RETURN(result) \
+    if (fence != EGL_NO_SYNC_KHR) eglDestroySyncKHR(display, fence); \
+    if (image) eglDestroyImageKHR(display, image); \
+    if (texture) glDeleteTextures(1, &texture); \
+    if (surface != EGL_NO_SURFACE) eglDestroySurface(display, surface); \
+    if (context != EGL_NO_CONTEXT) eglDestroyContext(display, context); \
+    eglMakeCurrent(display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); \
+    eglReleaseThread(); \
+    eglTerminate(display); \
+    return result;
+
+static jboolean com_android_server_AssetAtlasService_upload(JNIEnv* env, jobject,
+        jobject graphicBuffer, SkBitmap* bitmap) {
+
+    // The goal of this method is to copy the bitmap into the GraphicBuffer
+    // using the GPU to swizzle the texture content
+    sp<GraphicBuffer> buffer(graphicBufferForJavaObject(env, graphicBuffer));
+
+    if (buffer != NULL) {
+        EGLDisplay display = eglGetDisplay(EGL_DEFAULT_DISPLAY);
+        if (display == EGL_NO_DISPLAY) return false;
+
+        EGLint major;
+        EGLint minor;
+        if (!eglInitialize(display, &major, &minor)) {
+            ALOGW("Could not initialize EGL");
+            return false;
+        }
+
+        // We're going to use a 1x1 pbuffer surface later on
+        // The configuration doesn't really matter for what we're trying to do
+        EGLint configAttrs[] = {
+                EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
+                EGL_RED_SIZE, 8,
+                EGL_GREEN_SIZE, 8,
+                EGL_BLUE_SIZE, 8,
+                EGL_ALPHA_SIZE, 0,
+                EGL_DEPTH_SIZE, 0,
+                EGL_STENCIL_SIZE, 0,
+                EGL_NONE
+        };
+        EGLConfig configs[1];
+        EGLint configCount;
+        if (!eglChooseConfig(display, configAttrs, configs, 1, &configCount)) {
+            ALOGW("Could not select EGL configuration");
+            eglReleaseThread();
+            eglTerminate(display);
+            return false;
+        }
+        if (configCount <= 0) {
+            ALOGW("Could not find EGL configuration");
+            eglReleaseThread();
+            eglTerminate(display);
+            return false;
+        }
+
+        // These objects are initialized below but the default "null"
+        // values are used to cleanup properly at any point in the
+        // initialization sequence
+        GLuint texture = 0;
+        EGLImageKHR image = EGL_NO_IMAGE_KHR;
+        EGLSurface surface = EGL_NO_SURFACE;
+        EGLSyncKHR fence = EGL_NO_SYNC_KHR;
+
+        EGLint attrs[] = { EGL_CONTEXT_CLIENT_VERSION, 2, EGL_NONE };
+        EGLContext context = eglCreateContext(display, configs[0], EGL_NO_CONTEXT, attrs);
+        if (context == EGL_NO_CONTEXT) {
+            ALOGW("Could not create EGL context");
+            CLEANUP_GL_AND_RETURN(false);
+        }
+
+        // Create the 1x1 pbuffer
+        EGLint surfaceAttrs[] = { EGL_WIDTH, 1, EGL_HEIGHT, 1, EGL_NONE };
+        surface = eglCreatePbufferSurface(display, configs[0], surfaceAttrs);
+        if (surface == EGL_NO_SURFACE) {
+            ALOGW("Could not create EGL surface");
+            CLEANUP_GL_AND_RETURN(false);
+        }
+
+        if (!eglMakeCurrent(display, surface, surface, context)) {
+            ALOGW("Could not change current EGL context");
+            CLEANUP_GL_AND_RETURN(false);
+        }
+
+        // We use an EGLImage to access the content of the GraphicBuffer
+        // The EGL image is later bound to a 2D texture
+        EGLClientBuffer clientBuffer = (EGLClientBuffer) buffer->getNativeBuffer();
+        EGLint imageAttrs[] = { EGL_IMAGE_PRESERVED_KHR, EGL_TRUE, EGL_NONE };
+        image = eglCreateImageKHR(display, EGL_NO_CONTEXT,
+                EGL_NATIVE_BUFFER_ANDROID, clientBuffer, imageAttrs);
+        if (image == EGL_NO_IMAGE_KHR) {
+            ALOGW("Could not create EGL image");
+            CLEANUP_GL_AND_RETURN(false);
+        }
+
+        glGenTextures(1, &texture);
+        glBindTexture(GL_TEXTURE_2D, texture);
+        glEGLImageTargetTexture2DOES(GL_TEXTURE_2D, image);
+        if (glGetError() != GL_NO_ERROR) {
+            ALOGW("Could not create/bind texture");
+            CLEANUP_GL_AND_RETURN(false);
+        }
+
+        // Upload the content of the bitmap in the GraphicBuffer
+        glPixelStorei(GL_UNPACK_ALIGNMENT, bitmap->bytesPerPixel());
+        glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, bitmap->width(), bitmap->height(),
+                GL_RGBA, GL_UNSIGNED_BYTE, bitmap->getPixels());
+        if (glGetError() != GL_NO_ERROR) {
+            ALOGW("Could not upload to texture");
+            CLEANUP_GL_AND_RETURN(false);
+        }
+
+        // The fence is used to wait for the texture upload to finish
+        // properly. We cannot rely on glFlush() and glFinish() as
+        // some drivers completely ignore these API calls
+        fence = eglCreateSyncKHR(display, EGL_SYNC_FENCE_KHR, NULL);
+        if (fence == EGL_NO_SYNC_KHR) {
+            ALOGW("Could not create sync fence %#x", eglGetError());
+            CLEANUP_GL_AND_RETURN(false);
+        }
+
+        // The flag EGL_SYNC_FLUSH_COMMANDS_BIT_KHR will trigger a
+        // pipeline flush (similar to what a glFlush() would do.)
+        EGLint waitStatus = eglClientWaitSyncKHR(display, fence,
+                EGL_SYNC_FLUSH_COMMANDS_BIT_KHR, FENCE_TIMEOUT);
+        if (waitStatus != EGL_CONDITION_SATISFIED_KHR) {
+            ALOGW("Failed to wait for the fence %#x", eglGetError());
+            CLEANUP_GL_AND_RETURN(false);
+        }
+
+        CLEANUP_GL_AND_RETURN(true);
+    }
+
+    return false;
+}
+
+// ----------------------------------------------------------------------------
+// JNI Glue
+// ----------------------------------------------------------------------------
+
+#define FIND_CLASS(var, className) \
+        var = env->FindClass(className); \
+        LOG_FATAL_IF(! var, "Unable to find class " className);
+
+#define GET_FIELD_ID(var, clazz, fieldName, fieldDescriptor) \
+        var = env->GetFieldID(clazz, fieldName, fieldDescriptor); \
+        LOG_FATAL_IF(! var, "Unable to find field " fieldName);
+
+const char* const kClassPathName = "com/android/server/AssetAtlasService";
+
+static JNINativeMethod gMethods[] = {
+    { "nAcquireAtlasCanvas", "(Landroid/graphics/Canvas;II)I",
+            (void*) com_android_server_AssetAtlasService_acquireCanvas },
+    { "nReleaseAtlasCanvas", "(Landroid/graphics/Canvas;I)V",
+            (void*) com_android_server_AssetAtlasService_releaseCanvas },
+    { "nUploadAtlas", "(Landroid/view/GraphicBuffer;I)Z",
+            (void*) com_android_server_AssetAtlasService_upload },
+};
+
+int register_android_server_AssetAtlasService(JNIEnv* env) {
+    jclass clazz;
+
+    FIND_CLASS(clazz, "android/graphics/Canvas");
+    GET_FIELD_ID(gCanvasClassInfo.mFinalizer, clazz, "mFinalizer",
+            "Landroid/graphics/Canvas$CanvasFinalizer;");
+    GET_FIELD_ID(gCanvasClassInfo.mNativeCanvas, clazz, "mNativeCanvas", "I");
+
+    FIND_CLASS(clazz, "android/graphics/Canvas$CanvasFinalizer");
+    GET_FIELD_ID(gCanvasFinalizerClassInfo.mNativeCanvas, clazz, "mNativeCanvas", "I");
+
+    return jniRegisterNativeMethods(env, kClassPathName, gMethods, NELEM(gMethods));
+}
+
+};
diff --git a/services/jni/onload.cpp b/services/jni/onload.cpp
index 423ebd1..bb679aa 100644
--- a/services/jni/onload.cpp
+++ b/services/jni/onload.cpp
@@ -34,6 +34,7 @@
 int register_android_server_SystemServer(JNIEnv* env);
 int register_android_server_location_GpsLocationProvider(JNIEnv* env);
 int register_android_server_connectivity_Vpn(JNIEnv* env);
+int register_android_server_AssetAtlasService(JNIEnv* env);
 };
 
 using namespace android;
@@ -63,6 +64,7 @@
     register_android_server_SystemServer(env);
     register_android_server_location_GpsLocationProvider(env);
     register_android_server_connectivity_Vpn(env);
+    register_android_server_AssetAtlasService(env);
 
     return JNI_VERSION_1_4;
 }
diff --git a/tests/HwAccelerationTest/AndroidManifest.xml b/tests/HwAccelerationTest/AndroidManifest.xml
index 46a539e..bdd8aa6 100644
--- a/tests/HwAccelerationTest/AndroidManifest.xml
+++ b/tests/HwAccelerationTest/AndroidManifest.xml
@@ -41,6 +41,16 @@
         </activity>
 
         <activity
+                android:name="AssetsAtlasActivity"
+                android:label="Atlas/Framework"
+                android:theme="@android:style/Theme.Holo.Light">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="com.android.test.hwui.TEST" />
+            </intent-filter>
+        </activity>
+
+        <activity
                 android:name="ScaledTextActivity"
                 android:label="Text/Scaled"
                 android:theme="@android:style/Theme.Holo.Light">
diff --git a/tests/HwAccelerationTest/src/com/android/test/hwui/AssetsAtlasActivity.java b/tests/HwAccelerationTest/src/com/android/test/hwui/AssetsAtlasActivity.java
new file mode 100644
index 0000000..df7e3bb
--- /dev/null
+++ b/tests/HwAccelerationTest/src/com/android/test/hwui/AssetsAtlasActivity.java
@@ -0,0 +1,66 @@
+/*
+ * 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.
+ */
+
+package com.android.test.hwui;
+
+import android.app.Activity;
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Matrix;
+import android.graphics.Rect;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
+import android.os.Bundle;
+import android.view.View;
+import com.android.internal.R;
+
+@SuppressWarnings({"UnusedDeclaration"})
+public class AssetsAtlasActivity extends Activity {
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(new BitmapsView(this));
+    }
+
+    static class BitmapsView extends View {
+        private final Bitmap mBitmap;
+
+        BitmapsView(Context c) {
+            super(c);
+
+            Drawable d = c.getResources().getDrawable(R.drawable.text_select_handle_left);
+            mBitmap = ((BitmapDrawable) d).getBitmap();
+        }
+
+        @Override
+        protected void onDraw(Canvas canvas) {
+            super.onDraw(canvas);
+
+            final Matrix matrix = new Matrix();
+            matrix.setScale(0.5f, 0.5f);
+
+            final Rect src = new Rect(0, 0, mBitmap.getWidth() / 2, mBitmap.getHeight() / 2);
+            final Rect dst = new Rect(0, 0, mBitmap.getWidth(), mBitmap.getHeight());
+
+            canvas.drawBitmap(mBitmap, 0.0f, 0.0f, null);
+            canvas.translate(0.0f, mBitmap.getHeight());
+            canvas.drawBitmap(mBitmap, matrix, null);
+            canvas.translate(0.0f, mBitmap.getHeight());
+            canvas.drawBitmap(mBitmap, src, dst, null);
+        }
+    }
+}