Use only one GL context per process, share chaches.

Change-Id: Ieabaa25338d2f4b8d4fd90e7401ad6e7452eae11
diff --git a/core/java/android/view/GLES20Canvas.java b/core/java/android/view/GLES20Canvas.java
index 4c72e95..b058685 100644
--- a/core/java/android/view/GLES20Canvas.java
+++ b/core/java/android/view/GLES20Canvas.java
@@ -44,7 +44,7 @@
     @SuppressWarnings({"FieldCanBeLocal", "UnusedDeclaration"})
     private final GL mGl;
     private final boolean mOpaque;
-    private final int mRenderer;
+    private int mRenderer;
     
     private int mWidth;
     private int mHeight;
@@ -76,16 +76,25 @@
         mOpaque = !translucent;
 
         mRenderer = nCreateRenderer();
+        if (mRenderer == 0) {
+            throw new IllegalStateException("Could not create GLES20Canvas renderer");
+        }
     }
-
+    
     private native int nCreateRenderer();
 
-    @Override
-    protected void finalize() throws Throwable {
-        try {
-            super.finalize();
-        } finally {
+    /**
+     * This method <strong>must</strong> be called before releasing a
+     * reference to a GLES20Canvas. This method is responsible for freeing
+     * native resources associated with the hardware. Not invoking this
+     * method properly can result in memory leaks.
+     * 
+     * @hide
+     */
+    public synchronized void destroy() {
+        if (mRenderer != 0) {
             nDestroyRenderer(mRenderer);
+            mRenderer = 0;
         }
     }
 
diff --git a/core/java/android/view/HardwareRenderer.java b/core/java/android/view/HardwareRenderer.java
index ca60a89..32d8123 100644
--- a/core/java/android/view/HardwareRenderer.java
+++ b/core/java/android/view/HardwareRenderer.java
@@ -35,9 +35,10 @@
  * @hide
  */
 public abstract class HardwareRenderer {
+    private static final String LOG_TAG = "HardwareRenderer";
+
     private boolean mEnabled;
     private boolean mRequested = true;
-    private static final String LOG_TAG = "HardwareRenderer";
 
     /**
      * Indicates whether hardware acceleration is available under any form for
@@ -70,9 +71,8 @@
      * 
      * @param width Width of the drawing surface.
      * @param height Height of the drawing surface.
-     * @param attachInfo The AttachInfo used to render the ViewRoot. 
      */
-    abstract void setup(int width, int height, View.AttachInfo attachInfo);
+    abstract void setup(int width, int height);
 
     /**
      * Draws the specified view.
@@ -96,12 +96,11 @@
      */
     void initializeIfNeeded(int width, int height, View.AttachInfo attachInfo,
             SurfaceHolder holder) {
-
         if (isRequested()) {
             // We lost the gl context, so recreate it.
             if (!isEnabled()) {
                 if (initialize(holder)) {
-                    setup(width, height, attachInfo);
+                    setup(width, height);
                 }
             }
         }        
@@ -165,18 +164,23 @@
     static abstract class GlRenderer extends HardwareRenderer {
         private static final int EGL_CONTEXT_CLIENT_VERSION = 0x3098;
 
-        EGL10 mEgl;
-        EGLDisplay mEglDisplay;
-        EGLContext mEglContext;
-        EGLSurface mEglSurface;
-        EGLConfig mEglConfig;
+        static EGLContext sEglContext;
+        static EGL10 sEgl;
+        static EGLDisplay sEglDisplay;
+        static EGLConfig sEglConfig;
 
+        private static Thread sEglThread;        
+
+        EGLSurface mEglSurface;
+        
         GL mGl;
-        Canvas mCanvas;
+        GLES20Canvas mCanvas;
 
         final int mGlVersion;
         final boolean mTranslucent;
 
+        private boolean mDestroyed;
+
         GlRenderer(int glVersion, boolean translucent) {
             mGlVersion = glVersion;
             mTranslucent = translucent;
@@ -189,7 +193,7 @@
          */
         void checkErrors() {
             if (isEnabled()) {
-                int error = mEgl.eglGetError();
+                int error = sEgl.eglGetError();
                 if (error != EGL10.EGL_SUCCESS) {
                     // something bad has happened revert to
                     // normal rendering.
@@ -208,13 +212,17 @@
             if (isRequested() && !isEnabled()) {
                 initializeEgl();
                 mGl = createEglSurface(holder);
+                mDestroyed = false;
 
                 if (mGl != null) {
-                    int err = mEgl.eglGetError();
+                    int err = sEgl.eglGetError();
                     if (err != EGL10.EGL_SUCCESS) {
                         destroy();
                         setRequested(false);
                     } else {
+                        if (mCanvas != null) {
+                            destroyCanvas();
+                        }
                         mCanvas = createCanvas();
                         if (mCanvas != null) {
                             setEnabled(true);
@@ -229,64 +237,75 @@
             return false;
         }
 
-        abstract Canvas createCanvas();
+        private void destroyCanvas() {
+            mCanvas.destroy();
+            mCanvas = null;
+        }
+
+        abstract GLES20Canvas createCanvas();
 
         void initializeEgl() {
-            mEgl = (EGL10) EGLContext.getEGL();
+            if (sEglContext != null) return;
+
+            sEglThread = Thread.currentThread();
+            sEgl = (EGL10) EGLContext.getEGL();
             
             // Get to the default display.
-            mEglDisplay = mEgl.eglGetDisplay(EGL10.EGL_DEFAULT_DISPLAY);
+            sEglDisplay = sEgl.eglGetDisplay(EGL10.EGL_DEFAULT_DISPLAY);
             
-            if (mEglDisplay == EGL10.EGL_NO_DISPLAY) {
+            if (sEglDisplay == EGL10.EGL_NO_DISPLAY) {
                 throw new RuntimeException("eglGetDisplay failed");
             }
             
             // We can now initialize EGL for that display
             int[] version = new int[2];
-            if (!mEgl.eglInitialize(mEglDisplay, version)) {
+            if (!sEgl.eglInitialize(sEglDisplay, version)) {
                 throw new RuntimeException("eglInitialize failed");
             }
-            mEglConfig = getConfigChooser(mGlVersion).chooseConfig(mEgl, mEglDisplay);
+            sEglConfig = getConfigChooser(mGlVersion).chooseConfig(sEgl, sEglDisplay);
             
             /*
             * Create an EGL context. We want to do this as rarely as we can, because an
             * EGL context is a somewhat heavy object.
             */
-            mEglContext = createContext(mEgl, mEglDisplay, mEglConfig);
+            sEglContext = createContext(sEgl, sEglDisplay, sEglConfig);
         }
 
         GL createEglSurface(SurfaceHolder holder) {
             // Check preconditions.
-            if (mEgl == null) {
+            if (sEgl == null) {
                 throw new RuntimeException("egl not initialized");
             }
-            if (mEglDisplay == null) {
+            if (sEglDisplay == null) {
                 throw new RuntimeException("eglDisplay not initialized");
             }
-            if (mEglConfig == null) {
+            if (sEglConfig == null) {
                 throw new RuntimeException("mEglConfig not initialized");
             }
+            if (Thread.currentThread() != sEglThread) {
+                throw new IllegalStateException("HardwareRenderer cannot be used " 
+                        + "from multiple threads");
+            }
 
             /*
              *  The window size has changed, so we need to create a new
              *  surface.
              */
             if (mEglSurface != null && mEglSurface != EGL10.EGL_NO_SURFACE) {
-
                 /*
                  * Unbind and destroy the old EGL surface, if
                  * there is one.
                  */
-                mEgl.eglMakeCurrent(mEglDisplay, EGL10.EGL_NO_SURFACE,
+                sEgl.eglMakeCurrent(sEglDisplay, EGL10.EGL_NO_SURFACE,
                         EGL10.EGL_NO_SURFACE, EGL10.EGL_NO_CONTEXT);
-                mEgl.eglDestroySurface(mEglDisplay, mEglSurface);
+                sEgl.eglDestroySurface(sEglDisplay, mEglSurface);
             }
 
             // Create an EGL surface we can render into.
-            mEglSurface = mEgl.eglCreateWindowSurface(mEglDisplay, mEglConfig, holder, null);
+            mEglSurface = sEgl.eglCreateWindowSurface(sEglDisplay, sEglConfig, holder, null);
 
             if (mEglSurface == null || mEglSurface == EGL10.EGL_NO_SURFACE) {
-                int error = mEgl.eglGetError();
+                int error = sEgl.eglGetError();
                 if (error == EGL10.EGL_BAD_NATIVE_WINDOW) {
                     Log.e("EglHelper", "createWindowSurface returned EGL_BAD_NATIVE_WINDOW.");
                     return null;
@@ -298,11 +317,11 @@
              * Before we can issue GL commands, we need to make sure
              * the context is current and bound to a surface.
              */
-            if (!mEgl.eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface, mEglContext)) {
+            if (!sEgl.eglMakeCurrent(sEglDisplay, mEglSurface, mEglSurface, sEglContext)) {
                 throw new RuntimeException("eglMakeCurrent failed");
             }
 
-            return mEglContext.getGL();
+            return sEglContext.getGL();
         }
 
         EGLContext createContext(EGL10 egl, EGLDisplay eglDisplay, EGLConfig eglConfig) {
@@ -315,7 +334,6 @@
         @Override
         void initializeIfNeeded(int width, int height, View.AttachInfo attachInfo,
                 SurfaceHolder holder) {
-
             if (isRequested()) {
                 checkErrors();
                 super.initializeIfNeeded(width, height, attachInfo, holder);
@@ -324,28 +342,34 @@
         
         @Override
         void destroy() {
-            if (!isEnabled()) return;
+            if (!isEnabled() || mDestroyed) return;
 
-            mEgl.eglMakeCurrent(mEglDisplay, EGL10.EGL_NO_SURFACE,
+            mDestroyed = true;
+
+            checkCurrent();
+            // Destroy the Canvas first in case it needs to use a GL context
+            // to perform its cleanup.
+            destroyCanvas();
+
+            sEgl.eglMakeCurrent(sEglDisplay, EGL10.EGL_NO_SURFACE,
                     EGL10.EGL_NO_SURFACE, EGL10.EGL_NO_CONTEXT);
-            mEgl.eglDestroyContext(mEglDisplay, mEglContext);
-            mEgl.eglDestroySurface(mEglDisplay, mEglSurface);
-            mEgl.eglTerminate(mEglDisplay);
+            sEgl.eglDestroySurface(sEglDisplay, mEglSurface);
 
-            mEglContext = null;
             mEglSurface = null;
-            mEglDisplay = null;
-            mEgl = null;
             mGl = null;
-            mCanvas = null;
+
+            // mEgl.eglDestroyContext(mEglDisplay, mEglContext);
+            // mEglContext = null;            
+            // mEgl.eglTerminate(mEglDisplay);
+            // mEgl = null;
+            // mEglDisplay = null;
 
             setEnabled(false);
-        }        
-        
+        }
+
         @Override
-        void setup(int width, int height, View.AttachInfo attachInfo) {
-            final float scale = attachInfo.mApplicationScale;
-            mCanvas.setViewport((int) (width * scale + 0.5f), (int) (height * scale + 0.5f));
+        void setup(int width, int height) {
+            mCanvas.setViewport(width, height);
         }
         
         boolean canDraw() {
@@ -363,7 +387,8 @@
          * @param glVersion
          */
         EglConfigChooser getConfigChooser(int glVersion) {
-            return new ComponentSizeChooser(glVersion, 8, 8, 8, mTranslucent ? 8 : 0, 0, 0);
+            // TODO: Check for mTranslucent here, which means at least 2 EGL contexts
+            return new ComponentSizeChooser(glVersion, 8, 8, 8, 8, 0, 0);
         }
 
         @Override
@@ -373,14 +398,7 @@
                 attachInfo.mIgnoreDirtyState = true;
                 view.mPrivateFlags |= View.DRAWN;
 
-                // TODO: Don't check the current context when we have one per UI thread
-                // TODO: Use a threadlocal flag to know whether the surface has changed
-                if (mEgl.eglGetCurrentContext() != mEglContext ||
-                        mEgl.eglGetCurrentSurface(EGL10.EGL_DRAW) != mEglSurface) {
-                    if (!mEgl.eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface, mEglContext)) {
-                        throw new RuntimeException("eglMakeCurrent failed");
-                    }
-                }
+                checkCurrent();
 
                 onPreDraw();
 
@@ -396,11 +414,22 @@
 
                 attachInfo.mIgnoreDirtyState = false;
 
-                mEgl.eglSwapBuffers(mEglDisplay, mEglSurface);
+                sEgl.eglSwapBuffers(sEglDisplay, mEglSurface);
                 checkErrors();
             }
         }
 
+        private void checkCurrent() {
+            // TODO: Don't check the current context when we have one per UI thread
+            // TODO: Use a threadlocal flag to know whether the surface has changed
+            if (sEgl.eglGetCurrentContext() != sEglContext ||
+                    sEgl.eglGetCurrentSurface(EGL10.EGL_DRAW) != mEglSurface) {
+                if (!sEgl.eglMakeCurrent(sEglDisplay, mEglSurface, mEglSurface, sEglContext)) {
+                    throw new RuntimeException("eglMakeCurrent failed");
+                }
+            }
+        }
+
         static abstract class EglConfigChooser {
             final int[] mConfigSpec;
             private final int mGlVersion;
@@ -496,7 +525,7 @@
                         int g = findConfigAttrib(egl, display, config, EGL10.EGL_GREEN_SIZE, 0);
                         int b = findConfigAttrib(egl, display, config, EGL10.EGL_BLUE_SIZE, 0);
                         int a = findConfigAttrib(egl, display, config, EGL10.EGL_ALPHA_SIZE, 0);
-                        if (r == mRedSize && g == mGreenSize && b == mBlueSize && a >= mAlphaSize) {
+                        if (r >= mRedSize && g >= mGreenSize && b >= mBlueSize && a >= mAlphaSize) {
                             return config;
                         }
                     }
@@ -514,7 +543,7 @@
             }
         }
     }
-    
+
     /**
      * Hardware renderer using OpenGL ES 2.0.
      */
@@ -526,8 +555,9 @@
         }
 
         @Override
-        Canvas createCanvas() {
-            return mGlCanvas = new GLES20Canvas(mGl, mTranslucent);
+        GLES20Canvas createCanvas() {
+            // TODO: Pass mTranslucent instead of true
+            return mGlCanvas = new GLES20Canvas(mGl, true);
         }
 
         @Override
diff --git a/core/java/android/view/ViewRoot.java b/core/java/android/view/ViewRoot.java
index d32ccb1..5999aba 100644
--- a/core/java/android/view/ViewRoot.java
+++ b/core/java/android/view/ViewRoot.java
@@ -251,7 +251,7 @@
         mTempRect = new Rect();
         mVisRect = new Rect();
         mWinFrame = new Rect();
-        mWindow = new W(this, context);
+        mWindow = new W(this);
         mInputMethodCallback = new InputMethodCallback(this);
         mViewVisibility = View.GONE;
         mTransparentRegion = new Region();
@@ -469,6 +469,7 @@
             if (attrs != null &&
                     (attrs.flags & WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED) != 0) {
                 final boolean translucent = attrs.format != PixelFormat.OPAQUE;
+                destroyHardwareRenderer();
                 mHwRenderer = HardwareRenderer.createGlRenderer(2, translucent);
             }
         }
@@ -677,9 +678,7 @@
             attachInfo.mWindowVisibility = viewVisibility;
             host.dispatchWindowVisibilityChanged(viewVisibility);
             if (viewVisibility != View.VISIBLE || mNewSurfaceNeeded) {
-                if (mHwRenderer != null) {
-                    mHwRenderer.destroy();
-                }
+                destroyHardwareRenderer();
             }
             if (viewVisibility == View.GONE) {
                 // After making a window gone, we will count it as being
@@ -963,7 +962,7 @@
             }
             
             if (hwIntialized) {
-                mHwRenderer.setup(mWidth, mHeight, mAttachInfo);
+                mHwRenderer.setup(mWidth, mHeight);
             }
 
             boolean focusChangedDueToTouchMode = ensureTouchModeLocally(
@@ -1598,9 +1597,9 @@
         mAttachInfo.mRootView = null;
         mAttachInfo.mSurface = null;
 
-        if (mHwRenderer != null) {
-            mHwRenderer.destroy();
-        }
+        destroyHardwareRenderer();
+        mHwRenderer = null;
+        
         mSurface.release();
 
         if (mInputChannel != null) {
@@ -1625,6 +1624,12 @@
         }
     }
 
+    private void destroyHardwareRenderer() {
+        if (mHwRenderer != null) {
+            mHwRenderer.destroy();
+        }
+    }
+
     void updateConfiguration(Configuration config, boolean force) {
         if (DEBUG_CONFIGURATION) Log.v(TAG,
                 "Applying new config to window "
@@ -2734,10 +2739,6 @@
     public void childDrawableStateChanged(View child) {
     }
 
-    protected Rect getWindowFrame() {
-        return mWinFrame;
-    }
-
     void checkThread() {
         if (mThread != Thread.currentThread()) {
             throw new CalledFromWrongThreadException(
@@ -2816,16 +2817,15 @@
     static class W extends IWindow.Stub {
         private final WeakReference<ViewRoot> mViewRoot;
 
-        public W(ViewRoot viewRoot, Context context) {
+        W(ViewRoot viewRoot) {
             mViewRoot = new WeakReference<ViewRoot>(viewRoot);
         }
 
-        public void resized(int w, int h, Rect coveredInsets,
-                Rect visibleInsets, boolean reportDraw, Configuration newConfig) {
+        public void resized(int w, int h, Rect coveredInsets, Rect visibleInsets,
+                boolean reportDraw, Configuration newConfig) {
             final ViewRoot viewRoot = mViewRoot.get();
             if (viewRoot != null) {
-                viewRoot.dispatchResized(w, h, coveredInsets,
-                        visibleInsets, reportDraw, newConfig);
+                viewRoot.dispatchResized(w, h, coveredInsets, visibleInsets, reportDraw, newConfig);
             }
         }
 
diff --git a/core/jni/android_view_GLES20Canvas.cpp b/core/jni/android_view_GLES20Canvas.cpp
index 4c6eced..98c03a6 100644
--- a/core/jni/android_view_GLES20Canvas.cpp
+++ b/core/jni/android_view_GLES20Canvas.cpp
@@ -34,7 +34,6 @@
 #include <SkiaShader.h>
 #include <SkiaColorFilter.h>
 #include <Rect.h>
-#include <ui/Rect.h>
 
 #include "TextLayout.h"
 
@@ -418,7 +417,7 @@
     { "nDrawTextRun",       "(ILjava/lang/String;IIIIFFII)V",
             (void*) android_view_GLES20Canvas_drawTextRun },
 
-    {   "nGetClipBounds",     "(ILandroid/graphics/Rect;)Z",
+    { "nGetClipBounds",     "(ILandroid/graphics/Rect;)Z",
             (void*) android_view_GLES20Canvas_getClipBounds },
 #endif
 };