Allocate layers from the layers pool.
Bug #3413433

This change will be beneficial to Launcher to avoid hiccups when
swiping pages of icons. When a layer is discarded, it is kept
in the layers pool instead of being destroyed right away. This
favors memory reuse over allocations.

Change-Id: Ifb6944ba83d6ceb67c331527c0827b26ce648eb1
diff --git a/core/java/android/view/GLES20Layer.java b/core/java/android/view/GLES20Layer.java
index 0230430..6000a4a 100644
--- a/core/java/android/view/GLES20Layer.java
+++ b/core/java/android/view/GLES20Layer.java
@@ -66,10 +66,11 @@
     @Override
     void resize(int width, int height) {
         if (!isValid() || width <= 0 || height <= 0) return;
-        if (width > mLayerWidth || height > mLayerHeight) {
-            mWidth = width;
-            mHeight = height;
 
+        mWidth = width;
+        mHeight = height;
+        
+        if (width != mLayerWidth || height != mLayerHeight) {
             int[] layerInfo = new int[2];
 
             GLES20Canvas.nResizeLayer(mLayer, width, height, layerInfo);
diff --git a/libs/hwui/Caches.cpp b/libs/hwui/Caches.cpp
index bffab95..ebf7aa0 100644
--- a/libs/hwui/Caches.cpp
+++ b/libs/hwui/Caches.cpp
@@ -20,6 +20,7 @@
 
 #include "Caches.h"
 #include "Properties.h"
+#include "LayerRenderer.h"
 
 namespace android {
 
@@ -116,12 +117,7 @@
     size_t count = mLayerGarbage.size();
     for (size_t i = 0; i < count; i++) {
         Layer* layer = mLayerGarbage.itemAt(i);
-        if (layer) {
-            if (layer->fbo) glDeleteFramebuffers(1, &layer->fbo);
-            if (layer->texture) glDeleteTextures(1, &layer->texture);
-
-            delete layer;
-        }
+        LayerRenderer::destroyLayer(layer);
     }
     mLayerGarbage.clear();
 }
diff --git a/libs/hwui/LayerCache.cpp b/libs/hwui/LayerCache.cpp
index 7667af5..a9710ad 100644
--- a/libs/hwui/LayerCache.cpp
+++ b/libs/hwui/LayerCache.cpp
@@ -128,6 +128,31 @@
     return layer;
 }
 
+bool LayerCache::resize(Layer* layer, const uint32_t width, const uint32_t height) {
+    // TODO: We should be smarter and see if we have a texture of the appropriate
+    //       size already in the cache, and reuse it instead of creating a new one
+
+    LayerEntry entry(width, height);
+    if (entry.mWidth <= layer->width && entry.mHeight <= layer->height) {
+        return true;
+    }
+
+    glActiveTexture(GL_TEXTURE0);
+    glBindTexture(GL_TEXTURE_2D, layer->texture);
+
+    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, entry.mWidth, entry.mHeight, 0,
+            GL_RGBA, GL_UNSIGNED_BYTE, NULL);
+
+    if (glGetError() != GL_NO_ERROR) {
+        return false;
+    }
+
+    layer->width = entry.mWidth;
+    layer->height = entry.mHeight;
+
+    return true;
+}
+
 bool LayerCache::put(Layer* layer) {
     const uint32_t size = layer->width * layer->height * 4;
     // Don't even try to cache a layer that's bigger than the cache
diff --git a/libs/hwui/LayerCache.h b/libs/hwui/LayerCache.h
index 1333a73..d2d5f39 100644
--- a/libs/hwui/LayerCache.h
+++ b/libs/hwui/LayerCache.h
@@ -76,6 +76,17 @@
      * Clears the cache. This causes all layers to be deleted.
      */
     void clear();
+    /**
+     * Resize the specified layer if needed.
+     *
+     * @param layer The layer to resize
+     * @param width The new width of the layer
+     * @param height The new height of the layer
+     *
+     * @return True if the layer was resized or nothing happened, false if
+     *         a failure occurred during the resizing operation
+     */
+    bool resize(Layer* layer, const uint32_t width, const uint32_t height);
 
     /**
      * Sets the maximum size of the cache in bytes.
diff --git a/libs/hwui/LayerRenderer.cpp b/libs/hwui/LayerRenderer.cpp
index 60ff0bf..24f9739 100644
--- a/libs/hwui/LayerRenderer.cpp
+++ b/libs/hwui/LayerRenderer.cpp
@@ -18,6 +18,7 @@
 
 #include <ui/Rect.h>
 
+#include "LayerCache.h"
 #include "LayerRenderer.h"
 #include "Properties.h"
 #include "Rect.h"
@@ -34,21 +35,24 @@
 
     glBindFramebuffer(GL_FRAMEBUFFER, mLayer->fbo);
 
+    const float width = mLayer->layer.getWidth();
+    const float height = mLayer->layer.getHeight();
+
 #if RENDER_LAYERS_AS_REGIONS
     Rect dirty(left, top, right, bottom);
     if (dirty.isEmpty() || (dirty.left <= 0 && dirty.top <= 0 &&
-            dirty.right >= mLayer->width && dirty.bottom >= mLayer->height)) {
+            dirty.right >= width && dirty.bottom >= height)) {
         mLayer->region.clear();
-        dirty.set(0.0f, 0.0f, mLayer->width, mLayer->height);
+        dirty.set(0.0f, 0.0f, width, height);
     } else {
-        dirty.intersect(0.0f, 0.0f, mLayer->width, mLayer->height);
+        dirty.intersect(0.0f, 0.0f, width, height);
         android::Rect r(dirty.left, dirty.top, dirty.right, dirty.bottom);
         mLayer->region.subtractSelf(r);
     }
 
     OpenGLRenderer::prepareDirty(dirty.left, dirty.top, dirty.right, dirty.bottom, opaque);
 #else
-    OpenGLRenderer::prepareDirty(0.0f, 0.0f, mLayer->width, mLayer->height, opaque);
+    OpenGLRenderer::prepareDirty(0.0f, 0.0f, width, height, opaque);
 #endif
 }
 
@@ -162,64 +166,56 @@
 Layer* LayerRenderer::createLayer(uint32_t width, uint32_t height, bool isOpaque) {
     LAYER_RENDERER_LOGD("Creating new layer %dx%d", width, height);
 
-    Layer* layer = new Layer(width, height);
+    GLuint fbo = Caches::getInstance().fboCache.get();
+    if (!fbo) {
+        LOGW("Could not obtain an FBO");
+        return NULL;
+    }
+
+    glActiveTexture(GL_TEXTURE0);
+    Layer* layer = Caches::getInstance().layerCache.get(width, height);
+    if (!layer) {
+        LOGW("Could not obtain a layer");
+        return NULL;
+    }
+
+    layer->fbo = fbo;
+    layer->layer.set(0.0f, 0.0f, width, height);
+    layer->texCoords.set(0.0f, height / float(layer->height),
+            width / float(layer->width), 0.0f);
+    layer->alpha = 255;
+    layer->mode = SkXfermode::kSrcOver_Mode;
+    layer->blend = !isOpaque;
+    layer->colorFilter = NULL;
+    layer->region.clear();
 
     GLuint previousFbo;
     glGetIntegerv(GL_FRAMEBUFFER_BINDING, (GLint*) &previousFbo);
 
-    glGenFramebuffers(1, &layer->fbo);
     glBindFramebuffer(GL_FRAMEBUFFER, layer->fbo);
-
-    if (glGetError() != GL_NO_ERROR) {
-        glBindFramebuffer(GL_FRAMEBUFFER, previousFbo);
-        glDeleteBuffers(1, &layer->fbo);
-        return 0;
-    }
-
-    glActiveTexture(GL_TEXTURE0);
-    glGenTextures(1, &layer->texture);
     glBindTexture(GL_TEXTURE_2D, layer->texture);
 
-    glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
+    // Initialize the texture if needed
+    if (layer->empty) {
+        layer->empty = false;
+        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, layer->width, layer->height, 0,
+                GL_RGBA, GL_UNSIGNED_BYTE, NULL);
 
-    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
-    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
-
-    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
-    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
-
-    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0,
-            GL_RGBA, GL_UNSIGNED_BYTE, NULL);
-
-    if (glGetError() != GL_NO_ERROR) {
-        glBindFramebuffer(GL_FRAMEBUFFER, previousFbo);
-        glDeleteBuffers(1, &layer->fbo);
-        glDeleteTextures(1, &layer->texture);
-        delete layer;
-        return 0;
+        if (glGetError() != GL_NO_ERROR) {
+            LOGD("Could not allocate texture");
+            glBindFramebuffer(GL_FRAMEBUFFER, previousFbo);
+            glDeleteTextures(1, &layer->texture);
+            Caches::getInstance().fboCache.put(fbo);
+            delete layer;
+            return NULL;
+        }
     }
 
     glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D,
             layer->texture, 0);
 
-    if (glGetError() != GL_NO_ERROR) {
-        glBindFramebuffer(GL_FRAMEBUFFER, previousFbo);
-        glDeleteBuffers(1, &layer->fbo);
-        glDeleteTextures(1, &layer->texture);
-        delete layer;
-        return 0;
-    }
-
     glBindFramebuffer(GL_FRAMEBUFFER, previousFbo);
 
-    layer->layer.set(0.0f, 0.0f, width, height);
-    layer->texCoords.set(0.0f, 1.0f, 1.0f, 0.0f);
-    layer->alpha = 255;
-    layer->mode = SkXfermode::kSrcOver_Mode;
-    layer->blend = !isOpaque;
-    layer->empty = false;
-    layer->colorFilter = NULL;
-
     return layer;
 }
 
@@ -227,27 +223,17 @@
     if (layer) {
         LAYER_RENDERER_LOGD("Resizing layer fbo = %d to %dx%d", layer->fbo, width, height);
 
-        glActiveTexture(GL_TEXTURE0);
-        glBindTexture(GL_TEXTURE_2D, layer->texture);
-
-        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0,
-                GL_RGBA, GL_UNSIGNED_BYTE, NULL);
-
-        if (glGetError() != GL_NO_ERROR) {
-            glDeleteBuffers(1, &layer->fbo);
-            glDeleteTextures(1, &layer->texture);
-
-            layer->width = 0;
-            layer->height = 0;
-            layer->fbo = 0;
-            layer->texture = 0;
-
+        if (Caches::getInstance().layerCache.resize(layer, width, height)) {
+            layer->layer.set(0.0f, 0.0f, width, height);
+            layer->texCoords.set(0.0f, height / float(layer->height),
+                    width / float(layer->width), 0.0f);
+        } else {
+            if (layer->texture) glDeleteTextures(1, &layer->texture);
+            delete layer;
             return false;
         }
-
-        layer->width = width;
-        layer->height = height;
     }
+
     return true;
 }
 
@@ -255,10 +241,16 @@
     if (layer) {
         LAYER_RENDERER_LOGD("Destroying layer, fbo = %d", layer->fbo);
 
-        if (layer->fbo) glDeleteFramebuffers(1, &layer->fbo);
-        if (layer->texture) glDeleteTextures(1, &layer->texture);
+        if (layer->fbo) {
+            Caches::getInstance().fboCache.put(layer->fbo);
+        }
 
-        delete layer;
+        if (!Caches::getInstance().layerCache.put(layer)) {
+            if (layer->texture) glDeleteTextures(1, &layer->texture);
+            delete layer;
+        } else {
+            layer->region.clear();
+        }
     }
 }
 
diff --git a/libs/hwui/Properties.h b/libs/hwui/Properties.h
index 2f230b5..2d8b6f3 100644
--- a/libs/hwui/Properties.h
+++ b/libs/hwui/Properties.h
@@ -64,14 +64,14 @@
 // Converts a number of mega-bytes into bytes
 #define MB(s) s * 1024 * 1024
 
-#define DEFAULT_TEXTURE_CACHE_SIZE 20.0f
-#define DEFAULT_LAYER_CACHE_SIZE 8.0f
+#define DEFAULT_TEXTURE_CACHE_SIZE 24.0f
+#define DEFAULT_LAYER_CACHE_SIZE 24.0f
 #define DEFAULT_PATH_CACHE_SIZE 4.0f
 #define DEFAULT_SHAPE_CACHE_SIZE 1.0f
 #define DEFAULT_PATCH_CACHE_SIZE 512
 #define DEFAULT_GRADIENT_CACHE_SIZE 0.5f
 #define DEFAULT_DROP_SHADOW_CACHE_SIZE 2.0f
-#define DEFAULT_FBO_CACHE_SIZE 12
+#define DEFAULT_FBO_CACHE_SIZE 16
 
 #define DEFAULT_TEXT_GAMMA 1.4f
 #define DEFAULT_TEXT_BLACK_GAMMA_THRESHOLD 64
diff --git a/tests/HwAccelerationTest/AndroidManifest.xml b/tests/HwAccelerationTest/AndroidManifest.xml
index fc50334..3535809 100644
--- a/tests/HwAccelerationTest/AndroidManifest.xml
+++ b/tests/HwAccelerationTest/AndroidManifest.xml
@@ -96,7 +96,16 @@
                 <category android:name="android.intent.category.LAUNCHER" />
             </intent-filter>
         </activity>
-        
+
+        <activity
+                android:name="ViewLayersActivity6"
+                android:label="_ViewLayers6">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+
         <activity
                 android:name="AlphaLayersActivity"
                 android:label="_αLayers">
diff --git a/tests/HwAccelerationTest/res/layout/view_layers_6.xml b/tests/HwAccelerationTest/res/layout/view_layers_6.xml
new file mode 100644
index 0000000..36cf8c9
--- /dev/null
+++ b/tests/HwAccelerationTest/res/layout/view_layers_6.xml
@@ -0,0 +1,60 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2011 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:orientation="horizontal"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+
+    <LinearLayout
+        android:orientation="vertical"
+        android:layout_width="0dip"
+        android:layout_height="match_parent"
+        android:layout_weight="1">
+
+        <Button
+            android:onClick="enableLayer"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="Enable layer" />
+
+        <Button
+            android:onClick="disableLayer"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="Disable layer" />
+
+        <Button
+            android:onClick="shrinkLayer"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="Shrink layer" />
+
+        <Button
+            android:onClick="growLayer"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="Grow layer" />
+        
+    </LinearLayout>
+
+    <ListView
+        android:id="@+id/list1"
+        android:layout_width="0dip"
+        android:layout_height="match_parent"
+        android:layout_weight="1" />
+
+</LinearLayout>
diff --git a/tests/HwAccelerationTest/src/com/android/test/hwui/ViewLayersActivity6.java b/tests/HwAccelerationTest/src/com/android/test/hwui/ViewLayersActivity6.java
new file mode 100644
index 0000000..2edfec7
--- /dev/null
+++ b/tests/HwAccelerationTest/src/com/android/test/hwui/ViewLayersActivity6.java
@@ -0,0 +1,131 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.test.hwui;
+
+import android.app.Activity;
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Paint;
+import android.graphics.PorterDuff;
+import android.graphics.PorterDuffColorFilter;
+import android.os.Bundle;
+import android.util.DisplayMetrics;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ArrayAdapter;
+import android.widget.ListView;
+import android.widget.TextView;
+
+@SuppressWarnings({"UnusedDeclaration"})
+public class ViewLayersActivity6 extends Activity {
+    private final Paint mPaint = new Paint();
+    
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        
+        setContentView(R.layout.view_layers_6);
+
+        mPaint.setColorFilter(new PorterDuffColorFilter(0xff00ff00, PorterDuff.Mode.MULTIPLY));
+
+        setupList(R.id.list1);
+    }
+
+    public void enableLayer(View v) {
+        findViewById(R.id.list1).setLayerType(View.LAYER_TYPE_HARDWARE, mPaint);
+    }
+    
+    public void disableLayer(View v) {
+        findViewById(R.id.list1).setLayerType(View.LAYER_TYPE_NONE, null);
+    }
+    
+    public void growLayer(View v) {
+        findViewById(R.id.list1).getLayoutParams().height = ViewGroup.LayoutParams.MATCH_PARENT;
+        findViewById(R.id.list1).requestLayout();
+    }
+    
+    public void shrinkLayer(View v) {
+        findViewById(R.id.list1).getLayoutParams().height = 300;        
+        findViewById(R.id.list1).requestLayout();
+    }
+    
+    private void setupList(int listId) {
+        final ListView list = (ListView) findViewById(listId);
+        list.setAdapter(new SimpleListAdapter(this));
+    }
+
+    private static class SimpleListAdapter extends ArrayAdapter<String> {
+        public SimpleListAdapter(Context context) {
+            super(context, android.R.layout.simple_list_item_1, DATA_LIST);
+        }
+
+        @Override
+        public View getView(int position, View convertView, ViewGroup parent) {
+            TextView v = (TextView) super.getView(position, convertView, parent);
+            final Resources r = getContext().getResources();
+            final DisplayMetrics metrics = r.getDisplayMetrics();
+            v.setCompoundDrawablePadding((int) (6 * metrics.density + 0.5f));
+            v.setCompoundDrawablesWithIntrinsicBounds(r.getDrawable(R.drawable.icon),
+                    null, null, null);
+            return v;
+        }
+    }
+
+    private static final String[] DATA_LIST = {
+            "Afghanistan", "Albania", "Algeria", "American Samoa", "Andorra",
+            "Angola", "Anguilla", "Antarctica", "Antigua and Barbuda", "Argentina",
+            "Armenia", "Aruba", "Australia", "Austria", "Azerbaijan",
+            "Bahrain", "Bangladesh", "Barbados", "Belarus", "Belgium",
+            "Belize", "Benin", "Bermuda", "Bhutan", "Bolivia",
+            "Bosnia and Herzegovina", "Botswana", "Bouvet Island", "Brazil",
+            "British Indian Ocean Territory", "British Virgin Islands", "Brunei", "Bulgaria",
+            "Burkina Faso", "Burundi", "Cote d'Ivoire", "Cambodia", "Cameroon", "Canada", "Cape Verde",
+            "Cayman Islands", "Central African Republic", "Chad", "Chile", "China",
+            "Christmas Island", "Cocos (Keeling) Islands", "Colombia", "Comoros", "Congo",
+            "Cook Islands", "Costa Rica", "Croatia", "Cuba", "Cyprus", "Czech Republic",
+            "Democratic Republic of the Congo", "Denmark", "Djibouti", "Dominica", "Dominican Republic",
+            "East Timor", "Ecuador", "Egypt", "El Salvador", "Equatorial Guinea", "Eritrea",
+            "Estonia", "Ethiopia", "Faeroe Islands", "Falkland Islands", "Fiji", "Finland",
+            "Former Yugoslav Republic of Macedonia", "France", "French Guiana", "French Polynesia",
+            "French Southern Territories", "Gabon", "Georgia", "Germany", "Ghana", "Gibraltar",
+            "Greece", "Greenland", "Grenada", "Guadeloupe", "Guam", "Guatemala", "Guinea", "Guinea-Bissau",
+            "Guyana", "Haiti", "Heard Island and McDonald Islands", "Honduras", "Hong Kong", "Hungary",
+            "Iceland", "India", "Indonesia", "Iran", "Iraq", "Ireland", "Israel", "Italy", "Jamaica",
+            "Japan", "Jordan", "Kazakhstan", "Kenya", "Kiribati", "Kuwait", "Kyrgyzstan", "Laos",
+            "Latvia", "Lebanon", "Lesotho", "Liberia", "Libya", "Liechtenstein", "Lithuania", "Luxembourg",
+            "Macau", "Madagascar", "Malawi", "Malaysia", "Maldives", "Mali", "Malta", "Marshall Islands",
+            "Martinique", "Mauritania", "Mauritius", "Mayotte", "Mexico", "Micronesia", "Moldova",
+            "Monaco", "Mongolia", "Montserrat", "Morocco", "Mozambique", "Myanmar", "Namibia",
+            "Nauru", "Nepal", "Netherlands", "Netherlands Antilles", "New Caledonia", "New Zealand",
+            "Nicaragua", "Niger", "Nigeria", "Niue", "Norfolk Island", "North Korea", "Northern Marianas",
+            "Norway", "Oman", "Pakistan", "Palau", "Panama", "Papua New Guinea", "Paraguay", "Peru",
+            "Philippines", "Pitcairn Islands", "Poland", "Portugal", "Puerto Rico", "Qatar",
+            "Reunion", "Romania", "Russia", "Rwanda", "Sqo Tome and Principe", "Saint Helena",
+            "Saint Kitts and Nevis", "Saint Lucia", "Saint Pierre and Miquelon",
+            "Saint Vincent and the Grenadines", "Samoa", "San Marino", "Saudi Arabia", "Senegal",
+            "Seychelles", "Sierra Leone", "Singapore", "Slovakia", "Slovenia", "Solomon Islands",
+            "Somalia", "South Africa", "South Georgia and the South Sandwich Islands", "South Korea",
+            "Spain", "Sri Lanka", "Sudan", "Suriname", "Svalbard and Jan Mayen", "Swaziland", "Sweden",
+            "Switzerland", "Syria", "Taiwan", "Tajikistan", "Tanzania", "Thailand", "The Bahamas",
+            "The Gambia", "Togo", "Tokelau", "Tonga", "Trinidad and Tobago", "Tunisia", "Turkey",
+            "Turkmenistan", "Turks and Caicos Islands", "Tuvalu", "Virgin Islands", "Uganda",
+            "Ukraine", "United Arab Emirates", "United Kingdom",
+            "United States", "United States Minor Outlying Islands", "Uruguay", "Uzbekistan",
+            "Vanuatu", "Vatican City", "Venezuela", "Vietnam", "Wallis and Futuna", "Western Sahara",
+            "Yemen", "Yugoslavia", "Zambia", "Zimbabwe"
+    };
+}