Add a render buffer cache to reuse stencil buffers
Bug #7146141

This new cache is used in a similar way to LayerCache. It helps
reuse already allocated stencil buffers and thus avoid churning
memory on every frame.

Change-Id: I19551d72da52c40039e65904563600e492c8b193
diff --git a/core/java/android/view/HardwareRenderer.java b/core/java/android/view/HardwareRenderer.java
index e0d48a4..7c4bcfd 100644
--- a/core/java/android/view/HardwareRenderer.java
+++ b/core/java/android/view/HardwareRenderer.java
@@ -48,7 +48,7 @@
 import static javax.microedition.khronos.egl.EGL10.*;
 
 /**
- * Interface for rendering a ViewAncestor using hardware acceleration.
+ * Interface for rendering a view hierarchy using hardware acceleration.
  * 
  * @hide
  */
diff --git a/libs/hwui/Android.mk b/libs/hwui/Android.mk
index 2111a56..d533fa0 100644
--- a/libs/hwui/Android.mk
+++ b/libs/hwui/Android.mk
@@ -28,6 +28,7 @@
 		PathTessellator.cpp \
 		Program.cpp \
 		ProgramCache.cpp \
+		RenderBufferCache.cpp \
 		ResourceCache.cpp \
 		ShapeCache.cpp \
 		SkiaColorFilter.cpp \
diff --git a/libs/hwui/Caches.cpp b/libs/hwui/Caches.cpp
index 492bb7d..74201d1 100644
--- a/libs/hwui/Caches.cpp
+++ b/libs/hwui/Caches.cpp
@@ -186,6 +186,8 @@
             textureCache.getSize(), textureCache.getMaxSize());
     log.appendFormat("  LayerCache           %8d / %8d\n",
             layerCache.getSize(), layerCache.getMaxSize());
+    log.appendFormat("  RenderBufferCache    %8d / %8d\n",
+            renderBufferCache.getSize(), renderBufferCache.getMaxSize());
     log.appendFormat("  GradientCache        %8d / %8d\n",
             gradientCache.getSize(), gradientCache.getMaxSize());
     log.appendFormat("  PathCache            %8d / %8d\n",
@@ -215,6 +217,7 @@
     uint32_t total = 0;
     total += textureCache.getSize();
     total += layerCache.getSize();
+    total += renderBufferCache.getSize();
     total += gradientCache.getSize();
     total += pathCache.getSize();
     total += dropShadowCache.getSize();
@@ -298,6 +301,7 @@
             // fall through
         case kFlushMode_Layers:
             layerCache.clear();
+            renderBufferCache.clear();
             break;
     }
 
diff --git a/libs/hwui/Caches.h b/libs/hwui/Caches.h
index 0fa54fb..d70c0e3 100644
--- a/libs/hwui/Caches.h
+++ b/libs/hwui/Caches.h
@@ -29,6 +29,7 @@
 #include "GammaFontRenderer.h"
 #include "TextureCache.h"
 #include "LayerCache.h"
+#include "RenderBufferCache.h"
 #include "GradientCache.h"
 #include "PatchCache.h"
 #include "ProgramCache.h"
@@ -249,6 +250,7 @@
 
     TextureCache textureCache;
     LayerCache layerCache;
+    RenderBufferCache renderBufferCache;
     GradientCache gradientCache;
     ProgramCache programCache;
     PathCache pathCache;
diff --git a/libs/hwui/Debug.h b/libs/hwui/Debug.h
index 5f8baac..20eb5e1 100644
--- a/libs/hwui/Debug.h
+++ b/libs/hwui/Debug.h
@@ -44,6 +44,9 @@
 // Turn on to display info about layers
 #define DEBUG_LAYERS 0
 
+// Turn on to display info about render buffers
+#define DEBUG_RENDER_BUFFERS 0
+
 // Turn on to make stencil operations easier to debug
 #define DEBUG_STENCIL 0
 
diff --git a/libs/hwui/Layer.cpp b/libs/hwui/Layer.cpp
index 9247b1d..1899002 100644
--- a/libs/hwui/Layer.cpp
+++ b/libs/hwui/Layer.cpp
@@ -101,14 +101,13 @@
 
 void Layer::removeFbo(bool flush) {
     if (stencil) {
-        // TODO: recycle & cache instead of simply deleting
         GLuint previousFbo;
         glGetIntegerv(GL_FRAMEBUFFER_BINDING, (GLint*) &previousFbo);
         if (fbo != previousFbo) glBindFramebuffer(GL_FRAMEBUFFER, fbo);
         glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_RENDERBUFFER, 0);
         if (fbo != previousFbo) glBindFramebuffer(GL_FRAMEBUFFER, previousFbo);
 
-        delete stencil;
+        Caches::getInstance().renderBufferCache.put(stencil);
         stencil = NULL;
     }
 
diff --git a/libs/hwui/LayerCache.cpp b/libs/hwui/LayerCache.cpp
index 4278464..a0709af 100644
--- a/libs/hwui/LayerCache.cpp
+++ b/libs/hwui/LayerCache.cpp
@@ -21,7 +21,6 @@
 #include <utils/Log.h>
 
 #include "Caches.h"
-#include "Debug.h"
 #include "LayerCache.h"
 #include "Properties.h"
 
diff --git a/libs/hwui/LayerCache.h b/libs/hwui/LayerCache.h
index 7720b42..221bfe0 100644
--- a/libs/hwui/LayerCache.h
+++ b/libs/hwui/LayerCache.h
@@ -19,7 +19,6 @@
 
 #include "Debug.h"
 #include "Layer.h"
-#include "Properties.h"
 #include "utils/SortedList.h"
 
 namespace android {
@@ -54,7 +53,7 @@
      * size of the cache goes down.
      *
      * @param width The desired width of the layer
-     * @param width The desired height of the layer
+     * @param height The desired height of the layer
      */
     Layer* get(const uint32_t width, const uint32_t height);
 
@@ -91,6 +90,7 @@
      */
     void dump();
 
+private:
     struct LayerEntry {
         LayerEntry():
             mLayer(NULL), mWidth(0), mHeight(0) {
@@ -115,12 +115,19 @@
             return compare(*this, other) != 0;
         }
 
+        friend inline int strictly_order_type(const LayerEntry& lhs, const LayerEntry& rhs) {
+            return LayerEntry::compare(lhs, rhs) < 0;
+        }
+
+        friend inline int compare_type(const LayerEntry& lhs, const LayerEntry& rhs) {
+            return LayerEntry::compare(lhs, rhs);
+        }
+
         Layer* mLayer;
         uint32_t mWidth;
         uint32_t mHeight;
     }; // struct LayerEntry
 
-private:
     void deleteLayer(Layer* layer);
 
     SortedList<LayerEntry> mCache;
@@ -129,15 +136,6 @@
     uint32_t mMaxSize;
 }; // class LayerCache
 
-inline int strictly_order_type(const LayerCache::LayerEntry& lhs,
-        const LayerCache::LayerEntry& rhs) {
-    return LayerCache::LayerEntry::compare(lhs, rhs) < 0;
-}
-
-inline int compare_type(const LayerCache::LayerEntry& lhs, const LayerCache::LayerEntry& rhs) {
-    return LayerCache::LayerEntry::compare(lhs, rhs);
-}
-
 }; // namespace uirenderer
 }; // namespace android
 
diff --git a/libs/hwui/OpenGLRenderer.cpp b/libs/hwui/OpenGLRenderer.cpp
index 62f268d..2431e54 100644
--- a/libs/hwui/OpenGLRenderer.cpp
+++ b/libs/hwui/OpenGLRenderer.cpp
@@ -1273,11 +1273,8 @@
         // attach the new render buffer then turn tiling back on
         endTiling();
 
-        RenderBuffer* buffer = new RenderBuffer(
+        RenderBuffer* buffer = mCaches.renderBufferCache.get(
                 Stencil::getSmallestStencilFormat(), layer->getWidth(), layer->getHeight());
-        buffer->bind();
-        buffer->allocate();
-
         layer->setStencilRenderBuffer(buffer);
 
         startTiling(layer->clipRect, layer->layer.getHeight());
diff --git a/libs/hwui/Properties.h b/libs/hwui/Properties.h
index 1e8765b..0c75242 100644
--- a/libs/hwui/Properties.h
+++ b/libs/hwui/Properties.h
@@ -34,9 +34,9 @@
 // Textures used by layers must have dimensions multiples of this number
 #define LAYER_SIZE 64
 
-// Defines the size in bits of the stencil buffer
+// Defines the size in bits of the stencil buffer for the framebuffer
 // Note: Only 1 bit is required for clipping but more bits are required
-// to properly implement the winding fill rule when rasterizing paths
+// to properly implement overdraw debugging
 #define STENCIL_BUFFER_SIZE 8
 
 /**
@@ -85,6 +85,7 @@
 // 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 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_SHAPE_CACHE_SIZE "ro.hwui.shape_cache_size"
@@ -130,6 +131,7 @@
 
 #define DEFAULT_TEXTURE_CACHE_SIZE 24.0f
 #define DEFAULT_LAYER_CACHE_SIZE 16.0f
+#define DEFAULT_RENDER_BUFFER_CACHE_SIZE 2.0f
 #define DEFAULT_PATH_CACHE_SIZE 4.0f
 #define DEFAULT_SHAPE_CACHE_SIZE 1.0f
 #define DEFAULT_PATCH_CACHE_SIZE 512
diff --git a/libs/hwui/RenderBuffer.h b/libs/hwui/RenderBuffer.h
index 927f265..a9ad3d7 100644
--- a/libs/hwui/RenderBuffer.h
+++ b/libs/hwui/RenderBuffer.h
@@ -39,7 +39,7 @@
     }
 
     ~RenderBuffer() {
-        if (mName && mAllocated) {
+        if (mName) {
             glDeleteRenderbuffers(1, &mName);
         }
     }
@@ -154,6 +154,29 @@
         return false;
     }
 
+    /**
+     * Returns the name of the specified render buffer format.
+     */
+    static const char* formatName(GLenum format) {
+        switch (format) {
+            case GL_STENCIL_INDEX8:
+                return "STENCIL_8";
+            case GL_STENCIL_INDEX1_OES:
+                return "STENCIL_1";
+            case GL_STENCIL_INDEX4_OES:
+                return "STENCIL_4";
+            case GL_DEPTH_COMPONENT16:
+                return "DEPTH_16";
+            case GL_RGBA4:
+                return "RGBA_444";
+            case GL_RGB565:
+                return "RGB_565";
+            case GL_RGB5_A1:
+                return "RGBA_5551";
+        }
+        return "Unknown";
+    }
+
 private:
     GLenum mFormat;
 
diff --git a/libs/hwui/RenderBufferCache.cpp b/libs/hwui/RenderBufferCache.cpp
new file mode 100644
index 0000000..830a13a
--- /dev/null
+++ b/libs/hwui/RenderBufferCache.cpp
@@ -0,0 +1,166 @@
+/*
+ * 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 <utils/Log.h>
+
+#include "Debug.h"
+#include "Properties.h"
+#include "RenderBufferCache.h"
+
+namespace android {
+namespace uirenderer {
+
+///////////////////////////////////////////////////////////////////////////////
+// Defines
+///////////////////////////////////////////////////////////////////////////////
+
+// Debug
+#if DEBUG_RENDER_BUFFERS
+    #define RENDER_BUFFER_LOGD(...) ALOGD(__VA_ARGS__)
+#else
+    #define RENDER_BUFFER_LOGD(...)
+#endif
+
+///////////////////////////////////////////////////////////////////////////////
+// Constructors/destructor
+///////////////////////////////////////////////////////////////////////////////
+
+RenderBufferCache::RenderBufferCache(): mSize(0), mMaxSize(MB(DEFAULT_RENDER_BUFFER_CACHE_SIZE)) {
+    char property[PROPERTY_VALUE_MAX];
+    if (property_get(PROPERTY_RENDER_BUFFER_CACHE_SIZE, property, NULL) > 0) {
+        INIT_LOGD("  Setting render buffer cache size to %sMB", property);
+        setMaxSize(MB(atof(property)));
+    } else {
+        INIT_LOGD("  Using default render buffer cache size of %.2fMB",
+                DEFAULT_RENDER_BUFFER_CACHE_SIZE);
+    }
+}
+
+RenderBufferCache::~RenderBufferCache() {
+    clear();
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// Size management
+///////////////////////////////////////////////////////////////////////////////
+
+uint32_t RenderBufferCache::getSize() {
+    return mSize;
+}
+
+uint32_t RenderBufferCache::getMaxSize() {
+    return mMaxSize;
+}
+
+void RenderBufferCache::setMaxSize(uint32_t maxSize) {
+    clear();
+    mMaxSize = maxSize;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// Caching
+///////////////////////////////////////////////////////////////////////////////
+
+int RenderBufferCache::RenderBufferEntry::compare(
+        const RenderBufferCache::RenderBufferEntry& lhs,
+        const RenderBufferCache::RenderBufferEntry& rhs) {
+    int deltaInt = int(lhs.mWidth) - int(rhs.mWidth);
+    if (deltaInt != 0) return deltaInt;
+
+    deltaInt = int(lhs.mHeight) - int(rhs.mHeight);
+    if (deltaInt != 0) return deltaInt;
+
+    return int(lhs.mFormat) - int(rhs.mFormat);
+}
+
+void RenderBufferCache::deleteBuffer(RenderBuffer* buffer) {
+    if (buffer) {
+        RENDER_BUFFER_LOGD("Deleted %s render buffer (%dx%d)",
+                RenderBuffer::formatName(buffer->getFormat()),
+                buffer->getWidth(), buffer->getHeight());
+
+        mSize -= buffer->getSize();
+        delete buffer;
+    }
+}
+
+void RenderBufferCache::clear() {
+    size_t count = mCache.size();
+    for (size_t i = 0; i < count; i++) {
+        deleteBuffer(mCache.itemAt(i).mBuffer);
+    }
+    mCache.clear();
+}
+
+RenderBuffer* RenderBufferCache::get(GLenum format, const uint32_t width, const uint32_t height) {
+    RenderBuffer* buffer = NULL;
+
+    RenderBufferEntry entry(format, width, height);
+    ssize_t index = mCache.indexOf(entry);
+
+    if (index >= 0) {
+        entry = mCache.itemAt(index);
+        mCache.removeAt(index);
+
+        buffer = entry.mBuffer;
+        mSize -= buffer->getSize();
+
+        RENDER_BUFFER_LOGD("Found %s render buffer (%dx%d)",
+                RenderBuffer::formatName(format), width, height);
+    } else {
+        buffer = new RenderBuffer(format, width, height);
+
+        RENDER_BUFFER_LOGD("Created new %s render buffer (%dx%d)",
+                RenderBuffer::formatName(format), width, height);
+    }
+
+    buffer->bind();
+    buffer->allocate();
+
+    return buffer;
+}
+
+bool RenderBufferCache::put(RenderBuffer* buffer) {
+    if (!buffer) return false;
+
+    const uint32_t size = buffer->getSize();
+    if (size < mMaxSize) {
+        while (mSize + size > mMaxSize) {
+            size_t position = 0;
+
+            RenderBuffer* victim = mCache.itemAt(position).mBuffer;
+            deleteBuffer(victim);
+            mCache.removeAt(position);
+        }
+
+        RenderBufferEntry entry(buffer);
+
+        mCache.add(entry);
+        mSize += size;
+
+        RENDER_BUFFER_LOGD("Added %s render buffer (%dx%d)",
+                RenderBuffer::formatName(buffer->getFormat()),
+                buffer->getWidth(), buffer->getHeight());
+
+        return true;
+    }
+    return false;
+}
+
+}; // namespace uirenderer
+}; // namespace android
diff --git a/libs/hwui/RenderBufferCache.h b/libs/hwui/RenderBufferCache.h
new file mode 100644
index 0000000..af8060f
--- /dev/null
+++ b/libs/hwui/RenderBufferCache.h
@@ -0,0 +1,130 @@
+/*
+ * 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_RENDER_BUFFER_CACHE_H
+#define ANDROID_HWUI_RENDER_BUFFER_CACHE_H
+
+#include <GLES2/gl2.h>
+
+#include "RenderBuffer.h"
+#include "utils/SortedList.h"
+
+namespace android {
+namespace uirenderer {
+
+class RenderBufferCache {
+public:
+    RenderBufferCache();
+    ~RenderBufferCache();
+
+    /**
+     * Returns a buffer with the exact specified dimensions. If no suitable
+     * buffer can be found, a new one is created and returned. If creating a
+     * new buffer fails, NULL is returned.
+     *
+     * When a buffer is obtained from the cache, it is removed and the total
+     * size of the cache goes down.
+     *
+     * The returned buffer is always allocated and bound
+     * (see RenderBuffer::isAllocated()).
+     *
+     * @param format The desired render buffer format
+     * @param width The desired width of the buffer
+     * @param height The desired height of the buffer
+     */
+    RenderBuffer* get(GLenum format, const uint32_t width, const uint32_t height);
+
+    /**
+     * Adds the buffer to the cache. The buffer will not be added if there is
+     * not enough space available. Adding a buffer can cause other buffer to
+     * be removed from the cache.
+     *
+     * @param buffer The render buffer to add to the cache
+     *
+     * @return True if the buffer was added, false otherwise.
+     */
+    bool put(RenderBuffer* buffer);
+    /**
+     * 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:
+    struct RenderBufferEntry {
+        RenderBufferEntry():
+            mBuffer(NULL), mWidth(0), mHeight(0) {
+        }
+
+        RenderBufferEntry(GLenum format, const uint32_t width, const uint32_t height):
+            mBuffer(NULL), mFormat(format), mWidth(width), mHeight(height) {
+        }
+
+        RenderBufferEntry(RenderBuffer* buffer):
+            mBuffer(buffer), mFormat(buffer->getFormat()),
+            mWidth(buffer->getWidth()), mHeight(buffer->getHeight()) {
+        }
+
+        static int compare(const RenderBufferEntry& lhs, const RenderBufferEntry& rhs);
+
+        bool operator==(const RenderBufferEntry& other) const {
+            return compare(*this, other) == 0;
+        }
+
+        bool operator!=(const RenderBufferEntry& other) const {
+            return compare(*this, other) != 0;
+        }
+
+        friend inline int strictly_order_type(const RenderBufferEntry& lhs,
+                const RenderBufferEntry& rhs) {
+            return RenderBufferEntry::compare(lhs, rhs) < 0;
+        }
+
+        friend inline int compare_type(const RenderBufferEntry& lhs,
+                const RenderBufferEntry& rhs) {
+            return RenderBufferEntry::compare(lhs, rhs);
+        }
+
+        RenderBuffer* mBuffer;
+        GLenum mFormat;
+        uint32_t mWidth;
+        uint32_t mHeight;
+    }; // struct RenderBufferEntry
+
+    void deleteBuffer(RenderBuffer* buffer);
+
+    SortedList<RenderBufferEntry> mCache;
+
+    uint32_t mSize;
+    uint32_t mMaxSize;
+}; // class RenderBufferCache
+
+}; // namespace uirenderer
+}; // namespace android
+
+#endif // ANDROID_HWUI_RENDER_BUFFER_CACHE_H