Optimize saveLayer() when the clip flag is set.

This speeds up applications, especially Launcher.
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() {