Add a layer (FBO) cache.

The cache is used to draw layers so that a new
texture does not have to be recreated every time
a call to saveLayer() happens.

The FBO cache used a KeyedVector, which is a bad
idea. The cache should be able to store several
FBOs of the same size (this happens a lot during
scrolling with fading edges for instance.) This
will be changed in a future CL.

Change-Id: Ic316189e625f0dbcf0d273a71cc981a433d48726
diff --git a/libs/hwui/Android.mk b/libs/hwui/Android.mk
index 95ebda1..394aeca 100644
--- a/libs/hwui/Android.mk
+++ b/libs/hwui/Android.mk
@@ -2,6 +2,7 @@
 include $(CLEAR_VARS)
 
 LOCAL_SRC_FILES:= \
+	LayerCache.cpp \
 	Matrix.cpp \
 	OpenGLRenderer.cpp \
 	Program.cpp \
diff --git a/libs/hwui/GenerationCache.h b/libs/hwui/GenerationCache.h
index 56693da..d92a0bb 100644
--- a/libs/hwui/GenerationCache.h
+++ b/libs/hwui/GenerationCache.h
@@ -27,7 +27,7 @@
 class OnEntryRemoved {
 public:
     virtual ~OnEntryRemoved() { };
-    virtual void operator()(EntryKey key, EntryValue value) = 0;
+    virtual void operator()(EntryKey& key, EntryValue& value) = 0;
 }; // class OnEntryRemoved
 
 template<typename K, typename V>
@@ -40,15 +40,15 @@
         kUnlimitedCapacity,
     };
 
-    void setOnEntryRemovedListener(OnEntryRemoved<K*, V*>* listener);
+    void setOnEntryRemovedListener(OnEntryRemoved<K, V>* listener);
 
     void clear();
 
-    bool contains(K* key) const;
-    V* get(K* key);
-    void put(K* key, V* value);
-    V* remove(K* key);
-    void removeOldest();
+    bool contains(K key) const;
+    V get(K key);
+    void put(K key, V value);
+    V remove(K key);
+    V removeOldest();
 
     uint32_t size() const;
 
@@ -68,18 +68,18 @@
         sp<Entry<EntryKey, EntryValue> > child;
     }; // struct Entry
 
-    void addToCache(sp<Entry<K*, V*> > entry, K* key, V* value);
-    void attachToCache(sp<Entry<K*, V*> > entry);
-    void detachFromCache(sp<Entry<K*, V*> > entry);
+    void addToCache(sp<Entry<K, V> > entry, K key, V value);
+    void attachToCache(sp<Entry<K, V> > entry);
+    void detachFromCache(sp<Entry<K, V> > entry);
 
     uint32_t mMaxCapacity;
 
-    OnEntryRemoved<K*, V*>* mListener;
+    OnEntryRemoved<K, V>* mListener;
 
-    KeyedVector<K*, sp<Entry<K*, V*> > > mCache;
+    KeyedVector<K, sp<Entry<K, V> > > mCache;
 
-    sp<Entry<K*, V*> > mOldest;
-    sp<Entry<K*, V*> > mYougest;
+    sp<Entry<K, V> > mOldest;
+    sp<Entry<K, V> > mYougest;
 }; // class GenerationCache
 
 template<typename K, typename V>
@@ -88,7 +88,7 @@
 }
 
 template<typename K, typename V>
-void GenerationCache<K, V>::setOnEntryRemovedListener(OnEntryRemoved<K*, V*>* listener) {
+void GenerationCache<K, V>::setOnEntryRemovedListener(OnEntryRemoved<K, V>* listener) {
     mListener = listener;
 }
 
@@ -106,15 +106,15 @@
 }
 
 template<typename K, typename V>
-bool GenerationCache<K, V>::contains(K* key) const {
+bool GenerationCache<K, V>::contains(K key) const {
     return mCache.indexOfKey(key) >= 0;
 }
 
 template<typename K, typename V>
-V* GenerationCache<K, V>::get(K* key) {
+V GenerationCache<K, V>::get(K key) {
     ssize_t index = mCache.indexOfKey(key);
     if (index >= 0) {
-        sp<Entry<K*, V*> > entry = mCache.valueAt(index);
+        sp<Entry<K, V> > entry = mCache.valueAt(index);
         if (entry.get()) {
             detachFromCache(entry);
             attachToCache(entry);
@@ -126,24 +126,24 @@
 }
 
 template<typename K, typename V>
-void GenerationCache<K, V>::put(K* key, V* value) {
+void GenerationCache<K, V>::put(K key, V value) {
     if (mMaxCapacity != kUnlimitedCapacity && mCache.size() >= mMaxCapacity) {
         removeOldest();
     }
 
     ssize_t index = mCache.indexOfKey(key);
     if (index >= 0) {
-        sp<Entry<K*, V*> > entry = mCache.valueAt(index);
+        sp<Entry<K, V> > entry = mCache.valueAt(index);
         detachFromCache(entry);
         addToCache(entry, key, value);
     } else {
-        sp<Entry<K*, V*> > entry = new Entry<K*, V*>;
+        sp<Entry<K, V> > entry = new Entry<K, V>;
         addToCache(entry, key, value);
     }
 }
 
 template<typename K, typename V>
-void GenerationCache<K, V>::addToCache(sp<Entry<K*, V*> > entry, K* key, V* value) {
+void GenerationCache<K, V>::addToCache(sp<Entry<K, V> > entry, K key, V value) {
     entry->key = key;
     entry->value = value;
     mCache.add(key, entry);
@@ -151,29 +151,33 @@
 }
 
 template<typename K, typename V>
-V* GenerationCache<K, V>::remove(K* key) {
+V GenerationCache<K, V>::remove(K key) {
     ssize_t index = mCache.indexOfKey(key);
     if (index >= 0) {
-        sp<Entry<K*, V*> > entry = mCache.valueAt(index);
+        sp<Entry<K, V> > entry = mCache.valueAt(index);
         if (mListener) {
             (*mListener)(entry->key, entry->value);
         }
         mCache.removeItemsAt(index, 1);
         detachFromCache(entry);
+
+        return entry->value;
     }
 
     return NULL;
 }
 
 template<typename K, typename V>
-void GenerationCache<K, V>::removeOldest() {
+V GenerationCache<K, V>::removeOldest() {
     if (mOldest.get()) {
-        remove(mOldest->key);
+        return remove(mOldest->key);
     }
+
+    return NULL;
 }
 
 template<typename K, typename V>
-void GenerationCache<K, V>::attachToCache(sp<Entry<K*, V*> > entry) {
+void GenerationCache<K, V>::attachToCache(sp<Entry<K, V> > entry) {
     if (!mYougest.get()) {
         mYougest = mOldest = entry;
     } else {
@@ -184,7 +188,7 @@
 }
 
 template<typename K, typename V>
-void GenerationCache<K, V>::detachFromCache(sp<Entry<K*, V*> > entry) {
+void GenerationCache<K, V>::detachFromCache(sp<Entry<K, V> > entry) {
     if (entry->parent.get()) {
         entry->parent->child = entry->child;
     }
diff --git a/libs/hwui/Layer.h b/libs/hwui/Layer.h
new file mode 100644
index 0000000..70d64ca
--- /dev/null
+++ b/libs/hwui/Layer.h
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_UI_LAYER_H
+#define ANDROID_UI_LAYER_H
+
+#include <GLES2/gl2.h>
+
+#include <SkXfermode.h>
+
+#include "Rect.h"
+
+namespace android {
+namespace uirenderer {
+
+/**
+ * Dimensions of a layer.
+ */
+struct LayerSize {
+    LayerSize(): width(0), height(0) { }
+    LayerSize(const uint32_t width, const uint32_t height): width(width), height(height) { }
+    LayerSize(const LayerSize& size): width(size.width), height(size.height) { }
+
+    uint32_t width;
+    uint32_t height;
+
+    bool operator<(const LayerSize& rhs) const {
+        if (width == rhs.width) {
+            return height < rhs.height;
+        }
+        return width < rhs.width;
+    }
+
+    bool operator==(const LayerSize& rhs) const {
+        return width == rhs.width && height == rhs.height;
+    }
+};
+
+/**
+ * A layer has dimensions and is backed by an OpenGL texture.
+ */
+struct Layer {
+    /**
+     * Coordinates of the layer corresponding to this snapshot.
+     * Only set when the flag kFlagIsLayer is set.
+     */
+    Rect layer;
+    /**
+     * Name of the texture used to render the layer.
+     * Only set when the flag kFlagIsLayer is set.
+     */
+    GLuint texture;
+    /**
+     * Name of the FBO used to render the layer.
+     * Only set when the flag kFlagIsLayer is set.
+     */
+    GLuint fbo;
+    /**
+     * Opacity of the layer.
+     * Only set when the flag kFlagIsLayer is set.
+     */
+    float alpha;
+    /**
+     * Blending mode of the layer.
+     * Only set when the flag kFlagIsLayer is set.
+     */
+    SkXfermode::Mode mode;
+    /**
+     * Indicates whether this layer should be blended.
+     */
+    bool blend;
+}; // struct Layer
+
+}; // namespace uirenderer
+}; // namespace android
+
+#endif // ANDROID_UI_LAYER_H
diff --git a/libs/hwui/LayerCache.cpp b/libs/hwui/LayerCache.cpp
new file mode 100644
index 0000000..0d3214d
--- /dev/null
+++ b/libs/hwui/LayerCache.cpp
@@ -0,0 +1,114 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "OpenGLRenderer"
+
+#include <GLES2/gl2.h>
+
+#include "LayerCache.h"
+
+namespace android {
+namespace uirenderer {
+
+///////////////////////////////////////////////////////////////////////////////
+// Constructors/destructor
+///////////////////////////////////////////////////////////////////////////////
+
+LayerCache::LayerCache(uint32_t maxByteSize):
+        mCache(GenerationCache<LayerSize, Layer*>::kUnlimitedCapacity),
+        mSize(0), mMaxSize(maxByteSize) {
+}
+
+LayerCache::~LayerCache() {
+    mCache.setOnEntryRemovedListener(this);
+    mCache.clear();
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// Size management
+///////////////////////////////////////////////////////////////////////////////
+
+uint32_t LayerCache::getSize() {
+    return mSize;
+}
+
+uint32_t LayerCache::getMaxSize() {
+    return mMaxSize;
+}
+
+void LayerCache::setMaxSize(uint32_t maxSize) {
+    mMaxSize = maxSize;
+    while (mSize > mMaxSize) {
+        Layer* oldest = mCache.removeOldest();
+        deleteLayer(oldest);
+    }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// Callbacks
+///////////////////////////////////////////////////////////////////////////////
+
+void LayerCache::operator()(LayerSize& size, Layer*& layer) {
+    deleteLayer(layer);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// Caching
+///////////////////////////////////////////////////////////////////////////////
+
+void LayerCache::deleteLayer(Layer* layer) {
+    if (layer) {
+        mSize -= layer->layer.getWidth() * layer->layer.getHeight() * 4;
+
+        glDeleteFramebuffers(1, &layer->fbo);
+        glDeleteTextures(1, &layer->texture);
+        delete layer;
+    }
+}
+
+void LayerCache::clear() {
+    mCache.setOnEntryRemovedListener(this);
+    mCache.clear();
+    mCache.setOnEntryRemovedListener(NULL);
+}
+
+Layer* LayerCache::get(LayerSize& size) {
+    Layer* layer = mCache.remove(size);
+    if (layer) {
+        mSize -= layer->layer.getWidth() * layer->layer.getHeight() * 4;
+    }
+    return layer;
+}
+
+bool LayerCache::put(LayerSize& layerSize, Layer* layer) {
+    const uint32_t size = layerSize.width * layerSize.height * 4;
+    // Don't even try to cache a layer that's bigger than the cache
+    if (size < mMaxSize) {
+        while (mSize + size > mMaxSize) {
+            Layer* oldest = mCache.removeOldest();
+            deleteLayer(oldest);
+        }
+
+        mCache.put(layerSize, layer);
+        mSize += size;
+
+        return true;
+    }
+    return false;
+}
+
+}; // namespace uirenderer
+}; // namespace android
diff --git a/libs/hwui/LayerCache.h b/libs/hwui/LayerCache.h
new file mode 100644
index 0000000..3696848
--- /dev/null
+++ b/libs/hwui/LayerCache.h
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_UI_LAYER_CACHE_H
+#define ANDROID_UI_LAYER_CACHE_H
+
+#include "Layer.h"
+#include "GenerationCache.h"
+
+namespace android {
+namespace uirenderer {
+
+class LayerCache: public OnEntryRemoved<LayerSize, Layer*> {
+public:
+    LayerCache(uint32_t maxByteSize);
+    ~LayerCache();
+
+    /**
+     * Used as a callback when an entry is removed from the cache.
+     * Do not invoke directly.
+     */
+    void operator()(LayerSize& bitmap, Layer*& texture);
+
+    /**
+     * Returns the layer of specified dimensions, NULL if cannot be found.
+     */
+    Layer* get(LayerSize& size);
+    /**
+     * Adds the layer to the cache. The layer will not be added if there is
+     * not enough space available.
+     *
+     * @return True if the layer was added, false otherwise.
+     */
+    bool put(LayerSize& size, Layer* layer);
+    /**
+     * Clears the cache. This causes all layers to be deleted.
+     */
+    void clear();
+
+    /**
+     * Sets the maximum size of the cache in bytes.
+     */
+    void setMaxSize(uint32_t maxSize);
+    /**
+     * Returns the maximum size of the cache in bytes.
+     */
+    uint32_t getMaxSize();
+    /**
+     * Returns the current size of the cache in bytes.
+     */
+    uint32_t getSize();
+
+private:
+    void deleteLayer(Layer* layer);
+
+    GenerationCache<LayerSize, Layer*> mCache;
+
+    uint32_t mSize;
+    uint32_t mMaxSize;
+}; // class LayerCache
+
+}; // namespace uirenderer
+}; // namespace android
+
+#endif // ANDROID_UI_LAYER_CACHE_H
diff --git a/libs/hwui/OpenGLRenderer.cpp b/libs/hwui/OpenGLRenderer.cpp
index 9df1c67..9f2cc24 100644
--- a/libs/hwui/OpenGLRenderer.cpp
+++ b/libs/hwui/OpenGLRenderer.cpp
@@ -34,19 +34,30 @@
 // Defines
 ///////////////////////////////////////////////////////////////////////////////
 
+// Debug
+#define DEBUG_LAYERS 0
+
 // These properties are defined in mega-bytes
 #define PROPERTY_TEXTURE_CACHE_SIZE "ro.hwui.texture_cache_size"
 #define PROPERTY_LAYER_CACHE_SIZE "ro.hwui.layer_cache_size"
 
+#define DEFAULT_TEXTURE_CACHE_SIZE 20
+#define DEFAULT_LAYER_CACHE_SIZE 10
+
 // Converts a number of mega-bytes into bytes
 #define MB(s) s * 1024 * 1024
 
-#define DEFAULT_TEXTURE_CACHE_SIZE MB(20)
-#define DEFAULT_LAYER_CACHE_SIZE MB(10)
-
+// Generates simple and textured vertices
 #define SV(x, y) { { x, y } }
 #define FV(x, y, u, v) { { x, y }, { u, v } }
 
+// Debug
+#ifdef DEBUG_LAYERS
+    #define LAYER_LOGD(...) LOGD(__VA_ARGS__)
+#else
+    #define LAYER_LOGD(...)
+#endif
+
 ///////////////////////////////////////////////////////////////////////////////
 // Globals
 ///////////////////////////////////////////////////////////////////////////////
@@ -92,12 +103,24 @@
 // Constructors/destructor
 ///////////////////////////////////////////////////////////////////////////////
 
-OpenGLRenderer::OpenGLRenderer(): mTextureCache(DEFAULT_TEXTURE_CACHE_SIZE) {
+OpenGLRenderer::OpenGLRenderer():
+        mTextureCache(MB(DEFAULT_TEXTURE_CACHE_SIZE)),
+        mLayerCache(MB(DEFAULT_LAYER_CACHE_SIZE)) {
     LOGD("Create OpenGLRenderer");
 
     char property[PROPERTY_VALUE_MAX];
     if (property_get(PROPERTY_TEXTURE_CACHE_SIZE, property, NULL) > 0) {
+        LOGD("  Setting texture cache size to %sMB", property);
         mTextureCache.setMaxSize(MB(atoi(property)));
+    } else {
+        LOGD("  Using default texture cache size of %dMB", DEFAULT_TEXTURE_CACHE_SIZE);
+    }
+
+    if (property_get(PROPERTY_LAYER_CACHE_SIZE, property, NULL) > 0) {
+        LOGD("  Setting layer cache size to %sMB", property);
+        mLayerCache.setMaxSize(MB(atoi(property)));
+    } else {
+        LOGD("  Using default layer cache size of %dMB", DEFAULT_LAYER_CACHE_SIZE);
     }
 
     mDrawColorShader = new DrawColorProgram;
@@ -110,6 +133,7 @@
     LOGD("Destroy OpenGLRenderer");
 
     mTextureCache.clear();
+    mLayerCache.clear();
 }
 
 ///////////////////////////////////////////////////////////////////////////////
@@ -205,6 +229,11 @@
 }
 
 void OpenGLRenderer::composeLayer(sp<Snapshot> current, sp<Snapshot> previous) {
+    if (!current->layer) {
+        LOGE("Attempting to compose a layer that does not exist");
+        return;
+    }
+
     // Unbind current FBO and restore previous one
     // Most of the time, previous->fbo will be 0 to bind the default buffer
     glBindFramebuffer(GL_FRAMEBUFFER, previous->fbo);
@@ -213,17 +242,25 @@
     const Rect& clip = previous->getMappedClip();
     glScissor(clip.left, mHeight - clip.bottom, clip.getWidth(), clip.getHeight());
 
+    Layer* layer = current->layer;
+
     // Compute the correct texture coordinates for the FBO texture
     // The texture is currently as big as the window but drawn with
     // a quad of the appropriate size
-    const Rect& layer = current->layer;
+    const Rect& rect = layer->layer;
 
-    drawTextureRect(layer.left, layer.top, layer.right, layer.bottom,
-            current->texture, current->alpha, current->mode, true, true);
+    drawTextureRect(rect.left, rect.top, rect.right, rect.bottom,
+            layer->texture, layer->alpha, layer->mode, layer->blend, true);
 
-    // TODO Don't delete these things, but cache them
-    glDeleteFramebuffers(1, &current->fbo);
-    glDeleteTextures(1, &current->texture);
+    LayerSize size(rect.getWidth(), rect.getHeight());
+    if (!mLayerCache.put(size, layer)) {
+        LAYER_LOGD("Deleting layer");
+
+        glDeleteFramebuffers(1, &layer->fbo);
+        glDeleteTextures(1, &layer->texture);
+
+        delete layer;
+    }
 }
 
 ///////////////////////////////////////////////////////////////////////////////
@@ -262,43 +299,62 @@
 
 bool OpenGLRenderer::createLayer(sp<Snapshot> snapshot, float left, float top,
         float right, float bottom, int alpha, SkXfermode::Mode mode,int flags) {
-    // Generate the FBO and attach the texture
-    glGenFramebuffers(1, &snapshot->fbo);
-    glBindFramebuffer(GL_FRAMEBUFFER, snapshot->fbo);
 
-    // Generate the texture in which the FBO will draw
-    glGenTextures(1, &snapshot->texture);
-    glBindTexture(GL_TEXTURE_2D, snapshot->texture);
+    LayerSize size(right - left, bottom - top);
+    Layer* layer = mLayerCache.get(size);
 
-    // The FBO will not be scaled, so we can use lower quality filtering
-    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
-    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+    LAYER_LOGD("Requesting layer %dx%d", size.width, size.height);
+    LAYER_LOGD("Layer cache size = %d", mLayerCache.getSize());
 
-    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
-    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+    if (!layer) {
+        LAYER_LOGD("Creating new layer");
 
-    // TODO VERY IMPORTANT: Fix TextView to not call saveLayer() all the time
-    // TODO Use an FBO cache
+        layer = new Layer;
+        layer->blend = true;
 
-    const GLsizei width = right - left;
-    const GLsizei height = bottom - top;
+        // Generate the FBO and attach the texture
+        glGenFramebuffers(1, &layer->fbo);
+        glBindFramebuffer(GL_FRAMEBUFFER, layer->fbo);
 
-    const GLint format = (flags & SkCanvas::kHasAlphaLayer_SaveFlag) ? GL_RGBA : GL_RGB;
-    glTexImage2D(GL_TEXTURE_2D, 0, format, width, height, 0, format, GL_UNSIGNED_BYTE, NULL);
-    glBindTexture(GL_TEXTURE_2D, 0);
+        // Generate the texture in which the FBO will draw
+        glGenTextures(1, &layer->texture);
+        glBindTexture(GL_TEXTURE_2D, layer->texture);
 
-    // Bind texture to FBO
-    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D,
-            snapshot->texture, 0);
+        // The FBO will not be scaled, so we can use lower quality filtering
+        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
 
-    GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER);
-    if (status != GL_FRAMEBUFFER_COMPLETE) {
-        LOGD("Framebuffer incomplete (GL error code 0x%x)", status);
+        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
 
-        glDeleteFramebuffers(1, &snapshot->fbo);
-        glDeleteTextures(1, &snapshot->texture);
+        // TODO VERY IMPORTANT: Fix TextView to not call saveLayer() all the time
 
-        return false;
+        const GLsizei width = right - left;
+        const GLsizei height = bottom - top;
+
+        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);
+        glBindTexture(GL_TEXTURE_2D, 0);
+
+        // Bind texture to FBO
+        glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D,
+                layer->texture, 0);
+
+        GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER);
+        if (status != GL_FRAMEBUFFER_COMPLETE) {
+            LOGD("Framebuffer incomplete (GL error code 0x%x)", status);
+
+            GLuint previousFbo = snapshot->previous.get() ? snapshot->previous->fbo : 0;
+            glBindFramebuffer(GL_FRAMEBUFFER, previousFbo);
+
+            glDeleteFramebuffers(1, &layer->fbo);
+            glDeleteTextures(1, &layer->texture);
+            delete layer;
+
+            return false;
+        }
+    } else {
+        LAYER_LOGD("Reusing layer");
+        glBindFramebuffer(GL_FRAMEBUFFER, layer->fbo);
     }
 
     // Clear the FBO
@@ -307,10 +363,14 @@
     glClear(GL_COLOR_BUFFER_BIT);
     glEnable(GL_SCISSOR_TEST);
 
+    // Save the layer in the snapshot
     snapshot->flags |= Snapshot::kFlagIsLayer;
-    snapshot->mode = mode;
-    snapshot->alpha = alpha / 255.0f;
-    snapshot->layer.set(left, top, right, bottom);
+    layer->mode = mode;
+    layer->alpha = alpha / 255.0f;
+    layer->layer.set(left, top, right, bottom);
+
+    snapshot->layer = layer;
+    snapshot->fbo = layer->fbo;
 
     // Creates a new snapshot to draw into the FBO
     saveSnapshot();
diff --git a/libs/hwui/OpenGLRenderer.h b/libs/hwui/OpenGLRenderer.h
index bd5f84f..fa9592a 100644
--- a/libs/hwui/OpenGLRenderer.h
+++ b/libs/hwui/OpenGLRenderer.h
@@ -32,7 +32,9 @@
 #include "Rect.h"
 #include "Snapshot.h"
 #include "Texture.h"
+#include "Layer.h"
 #include "TextureCache.h"
+#include "LayerCache.h"
 
 namespace android {
 namespace uirenderer {
@@ -240,6 +242,7 @@
 
     // Used to cache all drawBitmap textures
     TextureCache mTextureCache;
+    LayerCache mLayerCache;
 }; // class OpenGLRenderer
 
 }; // namespace uirenderer
diff --git a/libs/hwui/Snapshot.h b/libs/hwui/Snapshot.h
index d1809f3..17ca440 100644
--- a/libs/hwui/Snapshot.h
+++ b/libs/hwui/Snapshot.h
@@ -20,10 +20,9 @@
 #include <GLES2/gl2.h>
 #include <GLES2/gl2ext.h>
 
-#include <SkXfermode.h>
-
 #include <utils/RefBase.h>
 
+#include "Layer.h"
 #include "Matrix.h"
 #include "Rect.h"
 
@@ -41,8 +40,7 @@
  */
 class Snapshot: public LightRefBase<Snapshot> {
 public:
-    Snapshot() {
-    }
+    Snapshot(): layer(NULL), fbo(0) { }
 
     /**
      * Copies the specified snapshot. Only the transform and clip rectangle
@@ -56,10 +54,8 @@
             clipRect(s->clipRect),
             flags(kFlagDirtyTransform),
             previous(s),
-            layer(0.0f, 0.0f, 0.0f, 0.0f),
-            texture(0),
-            fbo(0),
-            alpha(255) {
+            layer(NULL),
+            fbo(s->fbo) {
     }
 
     /**
@@ -126,30 +122,10 @@
     sp<Snapshot> previous;
 
     /**
-     * Coordinates of the layer corresponding to this snapshot.
      * Only set when the flag kFlagIsLayer is set.
      */
-    Rect layer;
-    /**
-     * Name of the texture used to render the layer.
-     * Only set when the flag kFlagIsLayer is set.
-     */
-    GLuint texture;
-    /**
-     * Name of the FBO used to render the layer.
-     * Only set when the flag kFlagIsLayer is set.
-     */
+    Layer* layer;
     GLuint fbo;
-    /**
-     * Opacity of the layer.
-     * Only set when the flag kFlagIsLayer is set.
-     */
-    float alpha;
-    /**
-     * Blending mode of the layer.
-     * Only set when the flag kFlagIsLayer is set.
-     */
-    SkXfermode::Mode mode;
 
     /**
      * Contains the previous ortho matrix.
diff --git a/libs/hwui/TextureCache.cpp b/libs/hwui/TextureCache.cpp
index 93ee138..612f04e 100644
--- a/libs/hwui/TextureCache.cpp
+++ b/libs/hwui/TextureCache.cpp
@@ -28,7 +28,7 @@
 ///////////////////////////////////////////////////////////////////////////////
 
 TextureCache::TextureCache(uint32_t maxByteSize):
-        mCache(GenerationCache<SkBitmap, Texture>::kUnlimitedCapacity),
+        mCache(GenerationCache<SkBitmap*, Texture*>::kUnlimitedCapacity),
         mSize(0), mMaxSize(maxByteSize) {
     mCache.setOnEntryRemovedListener(this);
 }
@@ -60,7 +60,7 @@
 // Callbacks
 ///////////////////////////////////////////////////////////////////////////////
 
-void TextureCache::operator()(SkBitmap* bitmap, Texture* texture) {
+void TextureCache::operator()(SkBitmap*& bitmap, Texture*& texture) {
     if (bitmap) {
         const uint32_t size = bitmap->rowBytes() * bitmap->height();
         mSize -= size;
diff --git a/libs/hwui/TextureCache.h b/libs/hwui/TextureCache.h
index 250efc5..bed1191 100644
--- a/libs/hwui/TextureCache.h
+++ b/libs/hwui/TextureCache.h
@@ -39,7 +39,7 @@
      * Used as a callback when an entry is removed from the cache.
      * Do not invoke directly.
      */
-    void operator()(SkBitmap* bitmap, Texture* texture);
+    void operator()(SkBitmap*& bitmap, Texture*& texture);
 
     /**
      * Returns the texture associated with the specified bitmap. If the texture
@@ -78,7 +78,7 @@
      */
     void generateTexture(SkBitmap* bitmap, Texture* texture, bool regenerate = false);
 
-    GenerationCache<SkBitmap, Texture> mCache;
+    GenerationCache<SkBitmap*, Texture*> mCache;
 
     uint32_t mSize;
     uint32_t mMaxSize;