Allow layers with a stencil buffer to be resized on the fly
Bug #7146141

This change moves the resizeLayer() from LayerCache (where it should
never have been anyway) to Layer. This makes a little more sense.

Change-Id: I8b2f9c19c558e738405a58b9e71ec5799fc6be88
diff --git a/libs/hwui/Layer.cpp b/libs/hwui/Layer.cpp
index 1d85b70..79dbfb0 100644
--- a/libs/hwui/Layer.cpp
+++ b/libs/hwui/Layer.cpp
@@ -54,6 +54,51 @@
     deleteTexture();
 }
 
+uint32_t Layer::computeIdealWidth(uint32_t layerWidth) {
+    return uint32_t(ceilf(layerWidth / float(LAYER_SIZE)) * LAYER_SIZE);
+}
+
+uint32_t Layer::computeIdealHeight(uint32_t layerHeight) {
+    return uint32_t(ceilf(layerHeight / float(LAYER_SIZE)) * LAYER_SIZE);
+}
+
+bool Layer::resize(const uint32_t width, const uint32_t height) {
+    uint32_t desiredWidth = computeIdealWidth(width);
+    uint32_t desiredHeight = computeIdealWidth(height);
+
+    if (desiredWidth <= getWidth() && desiredHeight <= getHeight()) {
+        return true;
+    }
+
+    uint32_t oldWidth = getWidth();
+    uint32_t oldHeight = getHeight();
+
+    setSize(desiredWidth, desiredHeight);
+
+    if (fbo) {
+        Caches::getInstance().activeTexture(0);
+        bindTexture();
+        allocateTexture(GL_RGBA, GL_UNSIGNED_BYTE);
+
+        if (glGetError() != GL_NO_ERROR) {
+            setSize(oldWidth, oldHeight);
+            return false;
+        }
+    }
+
+    if (stencil) {
+        bindStencilRenderBuffer();
+        allocateStencilRenderBuffer();
+
+        if (glGetError() != GL_NO_ERROR) {
+            setSize(oldWidth, oldHeight);
+            return false;
+        }
+    }
+
+    return true;
+}
+
 void Layer::removeFbo(bool flush) {
     if (stencil) {
         // TODO: recycle & cache instead of simply deleting
diff --git a/libs/hwui/Layer.h b/libs/hwui/Layer.h
index 9ef4894..a551b3f 100644
--- a/libs/hwui/Layer.h
+++ b/libs/hwui/Layer.h
@@ -48,6 +48,9 @@
     Layer(const uint32_t layerWidth, const uint32_t layerHeight);
     ~Layer();
 
+    static uint32_t computeIdealWidth(uint32_t layerWidth);
+    static uint32_t computeIdealHeight(uint32_t layerHeight);
+
     /**
      * Calling this method will remove (either by recycling or
      * destroying) the associated FBO, if present, and any render
@@ -91,6 +94,17 @@
         return texture.height;
     }
 
+    /**
+     * Resize the layer and its texture if needed.
+     *
+     * @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(const uint32_t width, const uint32_t height);
+
     void setSize(uint32_t width, uint32_t height) {
         texture.width = width;
         texture.height = height;
@@ -203,6 +217,12 @@
         }
     }
 
+    inline void bindStencilRenderBuffer() {
+        if (stencil) {
+            glBindRenderbuffer(GL_RENDERBUFFER, stencil);
+        }
+    }
+
     inline void generateTexture() {
         if (!texture.id) {
             glGenTextures(1, &texture.id);
@@ -229,7 +249,16 @@
 #if DEBUG_LAYERS
         ALOGD("  Allocate layer: %dx%d", getWidth(), getHeight());
 #endif
-        glTexImage2D(renderTarget, 0, format, getWidth(), getHeight(), 0, format, storage, NULL);
+        if (texture.id) {
+            glTexImage2D(renderTarget, 0, format, getWidth(), getHeight(), 0,
+                    format, storage, NULL);
+        }
+    }
+
+    inline void allocateStencilRenderBuffer() {
+        if (stencil) {
+            glRenderbufferStorage(GL_RENDERBUFFER, GL_STENCIL_INDEX8, getWidth(), getHeight());
+        }
     }
 
     inline mat4& getTexTransform() {
diff --git a/libs/hwui/LayerCache.cpp b/libs/hwui/LayerCache.cpp
index cfc5b04..4278464 100644
--- a/libs/hwui/LayerCache.cpp
+++ b/libs/hwui/LayerCache.cpp
@@ -136,31 +136,6 @@
     }
 }
 
-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->getWidth() && entry.mHeight <= layer->getHeight()) {
-        return true;
-    }
-
-    uint32_t oldWidth = layer->getWidth();
-    uint32_t oldHeight = layer->getHeight();
-
-    Caches::getInstance().activeTexture(0);
-    layer->bindTexture();
-    layer->setSize(entry.mWidth, entry.mHeight);
-    layer->allocateTexture(GL_RGBA, GL_UNSIGNED_BYTE);
-
-    if (glGetError() != GL_NO_ERROR) {
-        layer->setSize(oldWidth, oldHeight);
-        return false;
-    }
-
-    return true;
-}
-
 bool LayerCache::put(Layer* layer) {
     if (!layer->isCacheable()) return false;
 
diff --git a/libs/hwui/LayerCache.h b/libs/hwui/LayerCache.h
index fc2cd91..7720b42 100644
--- a/libs/hwui/LayerCache.h
+++ b/libs/hwui/LayerCache.h
@@ -72,17 +72,6 @@
      * 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.
@@ -108,8 +97,8 @@
         }
 
         LayerEntry(const uint32_t layerWidth, const uint32_t layerHeight): mLayer(NULL) {
-            mWidth = uint32_t(ceilf(layerWidth / float(LAYER_SIZE)) * LAYER_SIZE);
-            mHeight = uint32_t(ceilf(layerHeight / float(LAYER_SIZE)) * LAYER_SIZE);
+            mWidth = Layer::computeIdealWidth(layerWidth);
+            mHeight = Layer::computeIdealHeight(layerHeight);
         }
 
         LayerEntry(Layer* layer):
diff --git a/libs/hwui/LayerRenderer.cpp b/libs/hwui/LayerRenderer.cpp
index ba59bb39..61bedbb 100644
--- a/libs/hwui/LayerRenderer.cpp
+++ b/libs/hwui/LayerRenderer.cpp
@@ -264,7 +264,7 @@
     if (layer) {
         LAYER_RENDERER_LOGD("Resizing layer fbo = %d to %dx%d", layer->getFbo(), width, height);
 
-        if (Caches::getInstance().layerCache.resize(layer, width, height)) {
+        if (layer->resize(width, height)) {
             layer->layer.set(0.0f, 0.0f, width, height);
             layer->texCoords.set(0.0f, height / float(layer->getHeight()),
                     width / float(layer->getWidth()), 0.0f);
diff --git a/libs/hwui/OpenGLRenderer.cpp b/libs/hwui/OpenGLRenderer.cpp
index be34b40..f55bc9d 100644
--- a/libs/hwui/OpenGLRenderer.cpp
+++ b/libs/hwui/OpenGLRenderer.cpp
@@ -1248,11 +1248,12 @@
         // TODO: See Layer::removeFbo(). The stencil renderbuffer should be cached
         GLuint buffer;
         glGenRenderbuffers(1, &buffer);
-        glBindRenderbuffer(GL_RENDERBUFFER, buffer);
-        glRenderbufferStorage(GL_RENDERBUFFER, GL_STENCIL_INDEX8,
-                layer->getWidth(), layer->getHeight());
-        glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_RENDERBUFFER, buffer);
+
         layer->setStencilRenderBuffer(buffer);
+        layer->bindStencilRenderBuffer();
+        layer->allocateStencilRenderBuffer();
+
+        glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_RENDERBUFFER, buffer);
     }
 }
 
diff --git a/tests/HwAccelerationTest/res/layout/view_layers_5.xml b/tests/HwAccelerationTest/res/layout/view_layers_5.xml
index 36cf8c9..5baf583 100644
--- a/tests/HwAccelerationTest/res/layout/view_layers_5.xml
+++ b/tests/HwAccelerationTest/res/layout/view_layers_5.xml
@@ -48,13 +48,32 @@
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
             android:text="Grow layer" />
+
+        <Button
+            android:onClick="enableClip"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="Circle clip" />
+
+        <Button
+            android:onClick="disableClip"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="No clip" />
         
     </LinearLayout>
 
-    <ListView
-        android:id="@+id/list1"
+    <view class="com.android.test.hwui.ViewLayersActivity5$ClipFrameLayout"
+        android:id="@+id/container"
         android:layout_width="0dip"
         android:layout_height="match_parent"
-        android:layout_weight="1" />
+        android:layout_weight="1">
+
+        <ListView
+            android:id="@+id/list1"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent" />
+
+    </view>
 
 </LinearLayout>
diff --git a/tests/HwAccelerationTest/src/com/android/test/hwui/ViewLayersActivity5.java b/tests/HwAccelerationTest/src/com/android/test/hwui/ViewLayersActivity5.java
index 2664977..cbbb7ef 100644
--- a/tests/HwAccelerationTest/src/com/android/test/hwui/ViewLayersActivity5.java
+++ b/tests/HwAccelerationTest/src/com/android/test/hwui/ViewLayersActivity5.java
@@ -19,14 +19,18 @@
 import android.app.Activity;
 import android.content.Context;
 import android.content.res.Resources;
+import android.graphics.Canvas;
 import android.graphics.Paint;
+import android.graphics.Path;
 import android.graphics.PorterDuff;
 import android.graphics.PorterDuffColorFilter;
 import android.os.Bundle;
+import android.util.AttributeSet;
 import android.util.DisplayMetrics;
 import android.view.View;
 import android.view.ViewGroup;
 import android.widget.ArrayAdapter;
+import android.widget.FrameLayout;
 import android.widget.ListView;
 import android.widget.TextView;
 
@@ -37,30 +41,75 @@
     @Override
     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
-        
+
+        init();
+
         setContentView(R.layout.view_layers_5);
-
-        mPaint.setColorFilter(new PorterDuffColorFilter(0xff00ff00, PorterDuff.Mode.MULTIPLY));
-
         setupList(R.id.list1);
     }
 
+    public static class ClipFrameLayout extends FrameLayout {
+        private final Path mClipPath = new Path();
+        private boolean mClipEnabled;
+
+        public ClipFrameLayout(Context context, AttributeSet attrs) {
+            super(context, attrs);
+        }
+
+        public ClipFrameLayout(Context context, AttributeSet attrs, int defStyle) {
+            super(context, attrs, defStyle);
+        }
+
+        public boolean isClipEnabled() {
+            return mClipEnabled;
+        }
+
+        public void setClipEnabled(boolean clipEnabled) {
+            mClipEnabled = clipEnabled;
+            invalidate();
+        }
+
+        @Override
+        protected void dispatchDraw(Canvas canvas) {
+            if (mClipEnabled) {
+                mClipPath.reset();
+                mClipPath.addCircle(getWidth() / 2.0f, getHeight() / 2.0f,
+                        Math.min(getWidth(), getHeight()) / 3.0f, Path.Direction.CW);
+
+                canvas.clipPath(mClipPath);
+            }
+            super.dispatchDraw(canvas);
+        }
+    }
+
+    private void init() {
+        mPaint.setColorFilter(new PorterDuffColorFilter(0xff00ff00, PorterDuff.Mode.MULTIPLY));
+    }
+
+    public void enableClip(View v) {
+        ((ClipFrameLayout) findViewById(R.id.container)).setClipEnabled(true);
+    }
+
+    public void disableClip(View v) {
+        ((ClipFrameLayout) findViewById(R.id.container)).setClipEnabled(false);
+    }
+
     public void enableLayer(View v) {
-        findViewById(R.id.list1).setLayerType(View.LAYER_TYPE_HARDWARE, mPaint);
+        findViewById(R.id.container).setLayerType(View.LAYER_TYPE_HARDWARE, mPaint);
     }
 
     public void disableLayer(View v) {
-        findViewById(R.id.list1).setLayerType(View.LAYER_TYPE_NONE, null);
+        findViewById(R.id.container).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();
+        findViewById(R.id.container).getLayoutParams().height = ViewGroup.LayoutParams.MATCH_PARENT;
+        findViewById(R.id.container).requestLayout();
     }
 
     public void shrinkLayer(View v) {
-        findViewById(R.id.list1).getLayoutParams().height = 300;
-        findViewById(R.id.list1).requestLayout();
+        findViewById(R.id.container).getLayoutParams().height = 300;
+        findViewById(R.id.container).requestLayout();
     }
     
     private void setupList(int listId) {