Optimize saveLayer() when the clip flag is set.

This speeds up applications, especially Launcher.
diff --git a/libs/hwui/FboCache.cpp b/libs/hwui/FboCache.cpp
index 77fbda2..2ef71c2 100644
--- a/libs/hwui/FboCache.cpp
+++ b/libs/hwui/FboCache.cpp
@@ -16,6 +16,8 @@
 
 #define LOG_TAG "OpenGLRenderer"
 
+#include <stdlib.h>
+
 #include "FboCache.h"
 #include "Properties.h"
 
@@ -57,14 +59,31 @@
 ///////////////////////////////////////////////////////////////////////////////
 
 void FboCache::clear() {
-
+    for (size_t i = 0; i < mCache.size(); i++) {
+        const GLuint fbo = mCache.itemAt(i);
+        glDeleteFramebuffers(1, &fbo);
+    }
+    mCache.clear();
 }
 
 GLuint FboCache::get() {
-    return 0;
+    GLuint fbo;
+    if (mCache.size() > 0) {
+        fbo = mCache.itemAt(mCache.size() - 1);
+        mCache.removeAt(mCache.size() - 1);
+    } else {
+        glGenFramebuffers(1, &fbo);
+    }
+    return fbo;
 }
 
 bool FboCache::put(GLuint fbo) {
+    if (mCache.size() < mMaxSize) {
+        mCache.add(fbo);
+        return true;
+    }
+
+    glDeleteFramebuffers(1, &fbo);
     return false;
 }
 
diff --git a/libs/hwui/FboCache.h b/libs/hwui/FboCache.h
index 66f66ea..ec4afe9 100644
--- a/libs/hwui/FboCache.h
+++ b/libs/hwui/FboCache.h
@@ -21,8 +21,6 @@
 
 #include <utils/SortedVector.h>
 
-#include "GenerationCache.h"
-
 namespace android {
 namespace uirenderer {
 
diff --git a/libs/hwui/GenerationCache.h b/libs/hwui/GenerationCache.h
index 070e33f..35c6bea 100644
--- a/libs/hwui/GenerationCache.h
+++ b/libs/hwui/GenerationCache.h
@@ -65,6 +65,7 @@
     void put(K key, V value);
     V remove(K key);
     V removeOldest();
+    V getValueAt(uint32_t index) const;
 
     uint32_t size() const;
 
@@ -128,6 +129,11 @@
 }
 
 template<typename K, typename V>
+V GenerationCache<K, V>::getValueAt(uint32_t index) const {
+    return mCache.valueAt(index);
+}
+
+template<typename K, typename V>
 V GenerationCache<K, V>::get(K key) {
     ssize_t index = mCache.indexOfKey(key);
     if (index >= 0) {
diff --git a/libs/hwui/Layer.h b/libs/hwui/Layer.h
index a0cc5d6..6024765 100644
--- a/libs/hwui/Layer.h
+++ b/libs/hwui/Layer.h
@@ -32,21 +32,14 @@
  * Dimensions of a layer.
  */
 struct LayerSize {
-    LayerSize(): width(0), height(0), id(0) { }
-    LayerSize(const uint32_t width, const uint32_t height): width(width), height(height), id(0) { }
-    LayerSize(const LayerSize& size): width(size.width), height(size.height), id(size.id) { }
+    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;
 
-    // Incremental id used by the layer cache to store multiple
-    // LayerSize with the same dimensions
-    uint32_t id;
-
     bool operator<(const LayerSize& rhs) const {
-        if (id != 0 && rhs.id != 0 && id != rhs.id) {
-            return id < rhs.id;
-        }
         if (width == rhs.width) {
             return height < rhs.height;
         }
@@ -54,12 +47,12 @@
     }
 
     bool operator==(const LayerSize& rhs) const {
-        return id == rhs.id && width == rhs.width && height == rhs.height;
+        return width == rhs.width && height == rhs.height;
     }
 }; // struct LayerSize
 
 /**
- * A layer has dimensions and is backed by an OpenGL texture.
+ * A layer has dimensions and is backed by an OpenGL texture or FBO.
  */
 struct Layer {
     /**
@@ -71,6 +64,11 @@
      */
     GLuint texture;
     /**
+     * Name of the FBO used to render the layer. If the name is 0
+     * this layer is not backed by an FBO, but a simple texture.
+     */
+    GLuint fbo;
+    /**
      * Opacity of the layer.
      */
     int alpha;
diff --git a/libs/hwui/LayerCache.cpp b/libs/hwui/LayerCache.cpp
index 8c70cf9..2183718 100644
--- a/libs/hwui/LayerCache.cpp
+++ b/libs/hwui/LayerCache.cpp
@@ -32,7 +32,7 @@
 
 LayerCache::LayerCache():
         mCache(GenerationCache<LayerSize, Layer*>::kUnlimitedCapacity),
-        mIdGenerator(1), mSize(0), mMaxSize(MB(DEFAULT_LAYER_CACHE_SIZE)) {
+        mSize(0), mMaxSize(MB(DEFAULT_LAYER_CACHE_SIZE)) {
     char property[PROPERTY_VALUE_MAX];
     if (property_get(PROPERTY_LAYER_CACHE_SIZE, property, NULL) > 0) {
         LOGD("  Setting layer cache size to %sMB", property);
@@ -44,7 +44,7 @@
 
 LayerCache::LayerCache(uint32_t maxByteSize):
         mCache(GenerationCache<LayerSize, Layer*>::kUnlimitedCapacity),
-        mIdGenerator(1), mSize(0), mMaxSize(maxByteSize) {
+        mSize(0), mMaxSize(maxByteSize) {
 }
 
 LayerCache::~LayerCache() {
@@ -110,6 +110,7 @@
         layer = new Layer;
         layer->blend = true;
         layer->empty = true;
+        layer->fbo = 0;
 
         glGenTextures(1, &layer->texture);
         glBindTexture(GL_TEXTURE_2D, layer->texture);
@@ -121,6 +122,14 @@
 
         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 DEBUG_LAYERS
+        uint32_t size = mCache.size();
+        for (uint32_t i = 0; i < size; i++) {
+            LayerSize ls = mCache.getKeyAt(i);
+            LAYER_LOGD("  Layer size %dx%d", ls.width, ls.height);
+        }
+#endif
     }
 
     return layer;
@@ -133,9 +142,10 @@
         while (mSize + size > mMaxSize) {
             Layer* oldest = mCache.removeOldest();
             deleteLayer(oldest);
+            LAYER_LOGD("  Deleting layer %.2fx%.2f", oldest->layer.getWidth(),
+                    oldest->layer.getHeight());
         }
 
-        layerSize.id = mIdGenerator++;
         mCache.put(layerSize, layer);
         mSize += size;
 
diff --git a/libs/hwui/LayerCache.h b/libs/hwui/LayerCache.h
index c0c7542..cbb7ae2 100644
--- a/libs/hwui/LayerCache.h
+++ b/libs/hwui/LayerCache.h
@@ -64,6 +64,7 @@
      * @param size The dimensions of the desired layer
      */
     Layer* get(LayerSize& size);
+
     /**
      * Adds the layer to the cache. The layer will not be added if there is
      * not enough space available.
@@ -96,7 +97,6 @@
     void deleteLayer(Layer* layer);
 
     GenerationCache<LayerSize, Layer*> mCache;
-    uint32_t mIdGenerator;
 
     uint32_t mSize;
     uint32_t mMaxSize;
diff --git a/libs/hwui/OpenGLRenderer.cpp b/libs/hwui/OpenGLRenderer.cpp
index e3790f5..ee5fe22 100644
--- a/libs/hwui/OpenGLRenderer.cpp
+++ b/libs/hwui/OpenGLRenderer.cpp
@@ -145,6 +145,9 @@
 
     mWidth = width;
     mHeight = height;
+
+    mFirstSnapshot->height = height;
+    mFirstSnapshot->viewport.set(0, 0, width, height);
 }
 
 void OpenGLRenderer::prepare() {
@@ -185,7 +188,7 @@
 }
 
 void OpenGLRenderer::releaseContext() {
-    glViewport(0, 0, mWidth, mHeight);
+    glViewport(0, 0, mSnapshot->viewport.getWidth(), mSnapshot->viewport.getHeight());
 
     glEnable(GL_SCISSOR_TEST);
     setScissorFromClip();
@@ -237,10 +240,17 @@
 bool OpenGLRenderer::restoreSnapshot() {
     bool restoreClip = mSnapshot->flags & Snapshot::kFlagClipSet;
     bool restoreLayer = mSnapshot->flags & Snapshot::kFlagIsLayer;
+    bool restoreOrtho = mSnapshot->flags & Snapshot::kFlagDirtyOrtho;
 
     sp<Snapshot> current = mSnapshot;
     sp<Snapshot> previous = mSnapshot->previous;
 
+    if (restoreOrtho) {
+        Rect& r = previous->viewport;
+        glViewport(r.left, r.top, r.right, r.bottom);
+        mOrthoMatrix.load(current->orthoMatrix);
+    }
+
     mSaveCount--;
     mSnapshot = previous;
 
@@ -261,7 +271,8 @@
 
 int OpenGLRenderer::saveLayer(float left, float top, float right, float bottom,
         const SkPaint* p, int flags) {
-    int count = saveSnapshot(flags);
+    const GLuint previousFbo = mSnapshot->fbo;
+    const int count = saveSnapshot(flags);
 
     int alpha = 255;
     SkXfermode::Mode mode;
@@ -281,7 +292,7 @@
         mode = SkXfermode::kSrcOver_Mode;
     }
 
-    createLayer(mSnapshot, left, top, right, bottom, alpha, mode, flags);
+    createLayer(mSnapshot, left, top, right, bottom, alpha, mode, flags, previousFbo);
 
     return count;
 }
@@ -346,17 +357,21 @@
  *     something actually gets drawn are the layers regions cleared.
  */
 bool OpenGLRenderer::createLayer(sp<Snapshot> snapshot, float left, float top,
-        float right, float bottom, int alpha, SkXfermode::Mode mode,int flags) {
-    LAYER_LOGD("Requesting layer %fx%f", right - left, bottom - top);
+        float right, float bottom, int alpha, SkXfermode::Mode mode,
+        int flags, GLuint previousFbo) {
+    LAYER_LOGD("Requesting layer %.2fx%.2f", right - left, bottom - top);
     LAYER_LOGD("Layer cache size = %d", mCaches.layerCache.getSize());
 
+    const bool fboLayer = flags & SkCanvas::kClipToLayer_SaveFlag;
+
     // Window coordinates of the layer
     Rect bounds(left, top, right, bottom);
-    mSnapshot->transform->mapRect(bounds);
-
-    // Layers only make sense if they are in the framebuffer's bounds
-    bounds.intersect(*mSnapshot->clipRect);
-    bounds.snapToPixelBoundaries();
+    if (!fboLayer) {
+        mSnapshot->transform->mapRect(bounds);
+        // Layers only make sense if they are in the framebuffer's bounds
+        bounds.intersect(*mSnapshot->clipRect);
+        bounds.snapToPixelBoundaries();
+    }
 
     if (bounds.isEmpty() || bounds.getWidth() > mMaxTextureSize ||
             bounds.getHeight() > mMaxTextureSize) {
@@ -379,29 +394,77 @@
     snapshot->flags |= Snapshot::kFlagIsLayer;
     snapshot->layer = layer;
 
-    // Copy the framebuffer into the layer
-    glBindTexture(GL_TEXTURE_2D, layer->texture);
+    if (fboLayer) {
+        layer->fbo = mCaches.fboCache.get();
 
-    // TODO: Workaround for b/3054204
-    glCopyTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, bounds.left, mHeight - bounds.bottom,
-            bounds.getWidth(), bounds.getHeight(), 0);
+        snapshot->flags |= Snapshot::kFlagIsFboLayer;
+        snapshot->fbo = layer->fbo;
+        snapshot->resetTransform(-bounds.left, -bounds.top, 0.0f);
+        snapshot->resetClip(0.0f, 0.0f, bounds.getWidth(), bounds.getHeight());
+        snapshot->viewport.set(0.0f, 0.0f, bounds.getWidth(), bounds.getHeight());
+        snapshot->height = bounds.getHeight();
+        snapshot->flags |= Snapshot::kFlagDirtyOrtho;
+        snapshot->orthoMatrix.load(mOrthoMatrix);
 
-    // TODO: Waiting for b/3054204 to be fixed
-//    if (layer->empty) {
-//        glCopyTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, bounds.left, mHeight - bounds.bottom,
-//                bounds.getWidth(), bounds.getHeight(), 0);
-//        layer->empty = false;
-//    } else {
-//        glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, bounds.left, mHeight - bounds.bottom,
-//                bounds.getWidth(), bounds.getHeight());
-//    }
-
-    if (flags & SkCanvas::kClipToLayer_SaveFlag && mSnapshot->clipTransformed(bounds)) {
         setScissorFromClip();
-    }
 
-    // Enqueue the buffer coordinates to clear the corresponding region later
-    mLayers.push(new Rect(bounds));
+        // Bind texture to FBO
+        glBindFramebuffer(GL_FRAMEBUFFER, layer->fbo);
+        glBindTexture(GL_TEXTURE_2D, layer->texture);
+
+        // Initialize the texture if needed
+        if (layer->empty) {
+            layer->empty = false;
+            glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, size.width, size.height, 0,
+                    GL_RGBA, GL_UNSIGNED_BYTE, NULL);
+        }
+
+        glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D,
+                layer->texture, 0);
+
+        GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER);
+        if (status != GL_FRAMEBUFFER_COMPLETE) {
+            LOGE("Framebuffer incomplete (GL error code 0x%x)", status);
+
+            glBindFramebuffer(GL_FRAMEBUFFER, previousFbo);
+            glDeleteTextures(1, &layer->texture);
+            mCaches.fboCache.put(layer->fbo);
+
+            delete layer;
+
+            return false;
+        }
+
+        // Clear the FBO
+        glDisable(GL_SCISSOR_TEST);
+        glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
+        glClear(GL_COLOR_BUFFER_BIT);
+        glEnable(GL_SCISSOR_TEST);
+
+        // Change the ortho projection
+        glViewport(0, 0, bounds.getWidth(), bounds.getHeight());
+        mOrthoMatrix.loadOrtho(0.0f, bounds.getWidth(), bounds.getHeight(), 0.0f, -1.0f, 1.0f);
+    } else {
+        // Copy the framebuffer into the layer
+        glBindTexture(GL_TEXTURE_2D, layer->texture);
+
+        // TODO: Workaround for b/3054204
+        glCopyTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, bounds.left, mHeight - bounds.bottom,
+                bounds.getWidth(), bounds.getHeight(), 0);
+
+        // TODO: Waiting for b/3054204 to be fixed
+        // if (layer->empty) {
+        //     glCopyTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, bounds.left, mHeight - bounds.bottom,
+        //             bounds.getWidth(), bounds.getHeight(), 0);
+        //     layer->empty = false;
+        // } else {
+        //     glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, bounds.left, mHeight - bounds.bottom,
+        //             bounds.getWidth(), bounds.getHeight());
+        //  }
+
+        // Enqueue the buffer coordinates to clear the corresponding region later
+        mLayers.push(new Rect(bounds));
+    }
 
     return true;
 }
@@ -415,14 +478,21 @@
         return;
     }
 
+    const bool fboLayer = current->flags & SkCanvas::kClipToLayer_SaveFlag;
+
+    if (fboLayer) {
+        // Unbind current FBO and restore previous one
+        glBindFramebuffer(GL_FRAMEBUFFER, previous->fbo);
+    }
+
     // Restore the clip from the previous snapshot
     const Rect& clip = *previous->clipRect;
-    glScissor(clip.left, mHeight - clip.bottom, clip.getWidth(), clip.getHeight());
+    glScissor(clip.left, previous->height - clip.bottom, clip.getWidth(), clip.getHeight());
 
     Layer* layer = current->layer;
     const Rect& rect = layer->layer;
 
-    if (layer->alpha < 255) {
+    if (!fboLayer && layer->alpha < 255) {
         drawColorRect(rect.left, rect.top, rect.right, rect.bottom,
                 layer->alpha << 24, SkXfermode::kDstIn_Mode, true);
     }
@@ -430,20 +500,32 @@
     // Layers are already drawn with a top-left origin, don't flip the texture
     resetDrawTextureTexCoords(0.0f, 1.0f, 1.0f, 0.0f);
 
-    drawTextureMesh(rect.left, rect.top, rect.right, rect.bottom, layer->texture,
-            1.0f, layer->mode, layer->blend, &mMeshVertices[0].position[0],
-            &mMeshVertices[0].texture[0], GL_TRIANGLE_STRIP, gMeshCount, true, true);
+    if (fboLayer) {
+        drawTextureRect(rect.left, rect.top, rect.right, rect.bottom,
+                layer->texture, layer->alpha / 255.0f, layer->mode, layer->blend);
+    } else {
+        drawTextureMesh(rect.left, rect.top, rect.right, rect.bottom, layer->texture,
+                1.0f, layer->mode, layer->blend, &mMeshVertices[0].position[0],
+                &mMeshVertices[0].texture[0], GL_TRIANGLE_STRIP, gMeshCount, true, true);
+    }
 
     resetDrawTextureTexCoords(0.0f, 0.0f, 1.0f, 1.0f);
 
+    if (fboLayer) {
+        // Detach the texture from the FBO
+        glBindFramebuffer(GL_FRAMEBUFFER, current->fbo);
+        glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0);
+        glBindFramebuffer(GL_FRAMEBUFFER, previous->fbo);
+
+        // Put the FBO name back in the cache, if it doesn't fit, it will be destroyed
+        mCaches.fboCache.put(current->fbo);
+    }
+
     LayerSize size(rect.getWidth(), rect.getHeight());
-    // Failing to add the layer to the cache should happen only if the
-    // layer is too large
+    // Failing to add the layer to the cache should happen only if the layer is too large
     if (!mCaches.layerCache.put(size, layer)) {
         LAYER_LOGD("Deleting layer");
-
         glDeleteTextures(1, &layer->texture);
-
         delete layer;
     }
 }
@@ -503,7 +585,7 @@
 
 void OpenGLRenderer::setScissorFromClip() {
     const Rect& clip = *mSnapshot->clipRect;
-    glScissor(clip.left, mHeight - clip.bottom, clip.getWidth(), clip.getHeight());
+    glScissor(clip.left, mSnapshot->height - clip.bottom, clip.getWidth(), clip.getHeight());
 }
 
 const Rect& OpenGLRenderer::getClipBounds() {
diff --git a/libs/hwui/OpenGLRenderer.h b/libs/hwui/OpenGLRenderer.h
index 1974cf0..e3d4653 100644
--- a/libs/hwui/OpenGLRenderer.h
+++ b/libs/hwui/OpenGLRenderer.h
@@ -167,11 +167,12 @@
      * @param alpha The translucency of the layer
      * @param mode The blending mode of the layer
      * @param flags The layer save flags
+     * @param previousFbo The name of the current framebuffer
      *
      * @return True if the layer was successfully created, false otherwise
      */
     bool createLayer(sp<Snapshot> snapshot, float left, float top, float right, float bottom,
-            int alpha, SkXfermode::Mode mode, int flags);
+            int alpha, SkXfermode::Mode mode, int flags, GLuint previousFbo);
 
     /**
      * Clears all the regions corresponding to the current list of layers.
diff --git a/libs/hwui/Properties.h b/libs/hwui/Properties.h
index d573805..3012824 100644
--- a/libs/hwui/Properties.h
+++ b/libs/hwui/Properties.h
@@ -45,7 +45,7 @@
 #define MB(s) s * 1024 * 1024
 
 #define DEFAULT_TEXTURE_CACHE_SIZE 18.0f
-#define DEFAULT_LAYER_CACHE_SIZE 4.0f
+#define DEFAULT_LAYER_CACHE_SIZE 8.0f
 #define DEFAULT_PATH_CACHE_SIZE 5.0f
 #define DEFAULT_PATCH_CACHE_SIZE 100
 #define DEFAULT_GRADIENT_CACHE_SIZE 0.5f
diff --git a/libs/hwui/Snapshot.h b/libs/hwui/Snapshot.h
index c736a1c..3d74b4c 100644
--- a/libs/hwui/Snapshot.h
+++ b/libs/hwui/Snapshot.h
@@ -43,7 +43,7 @@
  */
 class Snapshot: public LightRefBase<Snapshot> {
 public:
-    Snapshot(): flags(0), previous(NULL), layer(NULL) {
+    Snapshot(): flags(0), previous(NULL), layer(NULL), fbo(0) {
         transform = &mTransformRoot;
         clipRect = &mClipRectRoot;
     }
@@ -53,7 +53,8 @@
      * the previous snapshot.
      */
     Snapshot(const sp<Snapshot>& s, int saveFlags):
-            flags(0), previous(s), layer(NULL) {
+            flags(0), previous(s), layer(NULL),
+            fbo(s->fbo), viewport(s->viewport), height(s->height) {
         if (saveFlags & SkCanvas::kMatrix_SaveFlag) {
             mTransformRoot.load(*s->transform);
             transform = &mTransformRoot;
@@ -91,9 +92,19 @@
          */
         kFlagIsLayer = 0x2,
         /**
+         * Indicates that this snapshot is a special type of layer
+         * backed by an FBO. This flag only makes sense when the
+         * flag kFlagIsLayer is also set.
+         */
+        kFlagIsFboLayer = 0x4,
+        /**
          * Indicates that the local clip should be recomputed.
          */
-        kFlagDirtyLocalClip = 0x4,
+        kFlagDirtyLocalClip = 0x8,
+        /**
+         * Indicates that this snapshot has changed the ortho matrix.
+         */
+        kFlagDirtyOrtho = 0x10,
     };
 
     /**
@@ -169,6 +180,17 @@
         return mLocalClip;
     }
 
+    void resetTransform(float x, float y, float z) {
+        transform = &mTransformRoot;
+        transform->loadTranslate(x, y, z);
+    }
+
+    void resetClip(float left, float top, float right, float bottom) {
+        clipRect = &mClipRectRoot;
+        clipRect->set(left, top, right, bottom);
+        flags |= Snapshot::kFlagClipSet | Snapshot::kFlagDirtyLocalClip;
+    }
+
     /**
      * Dirty flags.
      */
@@ -185,6 +207,26 @@
     Layer* layer;
 
     /**
+     * Only set when the flag kFlagIsFboLayer is set.
+     */
+    GLuint fbo;
+
+    /**
+     * Current viewport.
+     */
+    Rect viewport;
+
+    /**
+     * Height of the framebuffer the snapshot is rendering into.
+     */
+    int height;
+
+    /**
+     * Contains the previous ortho matrix.
+     */
+    mat4 orthoMatrix;
+
+    /**
      * Local transformation. Holds the current translation, scale and
      * rotation values.
      */