Initial import of the flexible GL surface view
authorPatrick Walton <pwalton@mozilla.com>
Tue, 31 Jan 2012 21:35:52 -0800
changeset 90858 dc18daa0b61de3c280f9426637beb785dd338ec5
parent 90857 7b8accc06c2648c34e9057e0925fb259b9fc309a
child 90859 7ee7e99b22dbbb65189a110548ef9346da5873ae
push idunknown
push userunknown
push dateunknown
milestone12.0a1
Initial import of the flexible GL surface view
mobile/android/base/Makefile.in
mobile/android/base/gfx/FlexibleGLSurfaceView.java
mobile/android/base/gfx/GLController.java
mobile/android/base/gfx/GLThread.java
mobile/android/base/gfx/LayerView.java
--- a/mobile/android/base/Makefile.in
+++ b/mobile/android/base/Makefile.in
@@ -103,18 +103,21 @@ FENNEC_JAVA_FILES = \
   TabsTray.java \
   gfx/AbstractLayerView.java \
   gfx/BitmapUtils.java \
   gfx/BufferedCairoImage.java \
   gfx/CairoGLInfo.java \
   gfx/CairoImage.java \
   gfx/CairoUtils.java \
   gfx/CheckerboardImage.java \
+  gfx/FlexibleGLSurfaceView.java \
   gfx/FloatSize.java \
   gfx/GeckoSoftwareLayerClient.java \
+  gfx/GLController.java \
+  gfx/GLThread.java \
   gfx/InputConnectionHandler.java \
   gfx/IntSize.java \
   gfx/Layer.java \
   gfx/LayerClient.java \
   gfx/LayerController.java \
   gfx/LayerRenderer.java \
   gfx/LayerView.java \
   gfx/MultiTileLayer.java \
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/gfx/FlexibleGLSurfaceView.java
@@ -0,0 +1,164 @@
+/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
+ * ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (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.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Mozilla Android code.
+ *
+ * The Initial Developer of the Original Code is Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2011-2012
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Patrick Walton <pcwalton@mozilla.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+package org.mozilla.gecko.gfx;
+
+import android.content.Context;
+import android.graphics.PixelFormat;
+import android.opengl.GLSurfaceView;
+import android.util.AttributeSet;
+import android.view.SurfaceHolder;
+import android.view.SurfaceView;
+
+public class FlexibleGLSurfaceView extends SurfaceView implements SurfaceHolder.Callback {
+    private static final String LOGTAG = "GeckoFlexibleGLSurfaceView";
+
+    private GLSurfaceView.Renderer mRenderer;
+    private GLThread mGLThread; // Protected by this class's monitor.
+    private GLController mController;
+
+    public FlexibleGLSurfaceView(Context context) {
+        super(context);
+        init();
+    }
+
+    public FlexibleGLSurfaceView(Context context, AttributeSet attributeSet) {
+        super(context, attributeSet);
+        init();
+    }
+
+    public void init() {
+        SurfaceHolder holder = getHolder();
+        holder.addCallback(this);
+        holder.setFormat(PixelFormat.RGB_565);
+
+        mController = new GLController(this);
+    }
+
+    public void setRenderer(GLSurfaceView.Renderer renderer) {
+        mRenderer = renderer;
+    }
+
+    public GLSurfaceView.Renderer getRenderer() {
+        return mRenderer;
+    }
+
+    public synchronized void requestRender() {
+        if (mGLThread != null) {
+            mGLThread.renderFrame();
+        }
+    }
+
+    /**
+     * Creates a Java GL thread. After this is called, the FlexibleGLSurfaceView may be used just
+     * like a GLSurfaceView. It is illegal to access the controller after this has been called.
+     */
+    public synchronized void createGLThread() {
+        if (mGLThread != null) {
+            throw new FlexibleGLSurfaceViewException("createGLThread() called with a GL thread " +
+                                                     "already in place!");
+        }
+
+        mGLThread = new GLThread(mController);
+        mGLThread.start();
+    }
+
+    /**
+     * Destroys the Java GL thread. Returns a Thread that completes when the Java GL thread is
+     * fully shut down.
+     */
+    public synchronized Thread destroyGLThread() {
+        if (mGLThread == null) {
+            throw new FlexibleGLSurfaceViewException("destroyGLThread() called with no GL " +
+                                                     "thread active!");
+        }
+
+        Thread glThread = mGLThread;
+        mGLThread.shutdown();
+        mGLThread = null;
+        return glThread;
+    }
+
+    public synchronized void recreateSurface() {
+        if (mGLThread == null) {
+            throw new FlexibleGLSurfaceViewException("recreateSurface() called with no GL " +
+                                                     "thread active!");
+        }
+
+        mGLThread.recreateSurface();
+    }
+
+    public synchronized GLController getGLController() {
+        if (mGLThread != null) {
+            throw new FlexibleGLSurfaceViewException("getGLController() called with a GL thread " +
+                                                     "active; shut down the GL thread first!");
+        }
+
+        return mController;
+    }
+
+    public synchronized void surfaceChanged(SurfaceHolder holder, int format, int width,
+                                            int height) {
+        mController.sizeChanged(width, height);
+        if (mGLThread != null) {
+            mGLThread.surfaceChanged(width, height);
+        }
+    }
+
+    public synchronized void surfaceCreated(SurfaceHolder holder) {
+        mController.surfaceCreated();
+        if (mGLThread != null) {
+            mGLThread.surfaceCreated();
+        }
+    }
+
+    public synchronized void surfaceDestroyed(SurfaceHolder holder) {
+        mController.surfaceDestroyed();
+        if (mGLThread != null) {
+            mGLThread.surfaceDestroyed();
+        }
+    }
+
+    public static class FlexibleGLSurfaceViewException extends RuntimeException {
+        public static final long serialVersionUID = 1L;
+
+        FlexibleGLSurfaceViewException(String e) {
+            super(e);
+        }
+    }
+}
+
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/gfx/GLController.java
@@ -0,0 +1,256 @@
+/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
+ * ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (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.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Mozilla Android code.
+ *
+ * The Initial Developer of the Original Code is Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2011-2012
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Patrick Walton <pcwalton@mozilla.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+package org.mozilla.gecko.gfx;
+
+import android.util.Log;
+import android.view.SurfaceHolder;
+import android.view.SurfaceView;
+import javax.microedition.khronos.egl.EGL10;
+import javax.microedition.khronos.egl.EGL11;
+import javax.microedition.khronos.egl.EGLConfig;
+import javax.microedition.khronos.egl.EGLContext;
+import javax.microedition.khronos.egl.EGLDisplay;
+import javax.microedition.khronos.egl.EGLSurface;
+import javax.microedition.khronos.opengles.GL;
+import javax.microedition.khronos.opengles.GL10;
+
+public class GLController {
+    private static final int EGL_CONTEXT_CLIENT_VERSION = 0x3098;
+    private static final String LOGTAG = "GeckoGLController";
+
+    private FlexibleGLSurfaceView mView;
+    private int mGLVersion;
+    private boolean mSurfaceValid;
+    private int mWidth, mHeight;
+
+    private EGL10 mEGL;
+    private EGLDisplay mEGLDisplay;
+    private EGLConfig mEGLConfig;
+    private EGLContext mEGLContext;
+    private EGLSurface mEGLSurface;
+
+    private GL mGL;
+
+    private static final int[] CONFIG_SPEC = {
+        EGL10.EGL_RED_SIZE, 5,
+        EGL10.EGL_GREEN_SIZE, 6,
+        EGL10.EGL_BLUE_SIZE, 5,
+        EGL10.EGL_NONE
+    };
+
+    public GLController(FlexibleGLSurfaceView view) {
+        mView = view;
+        mGLVersion = 1;
+        mSurfaceValid = false;
+    }
+
+    public void setGLVersion(int version) {
+        mGLVersion = version;
+    }
+
+    /** You must call this on the same thread you intend to use OpenGL on. */
+    public void initGLContext() {
+        initEGLContext();
+        createEGLSurface();
+    }
+
+    public void disposeGLContext() {
+        if (!mEGL.eglMakeCurrent(mEGLDisplay, EGL10.EGL_NO_SURFACE, EGL10.EGL_NO_SURFACE,
+                                 EGL10.EGL_NO_CONTEXT)) {
+            throw new GLControllerException("EGL context could not be released!");
+        }
+
+        if (mEGLSurface != null) {
+            if (!mEGL.eglDestroySurface(mEGLDisplay, mEGLSurface)) {
+                throw new GLControllerException("EGL surface could not be destroyed!");
+            }
+
+            mEGLSurface = null;
+        }
+
+        if (mEGLContext == null) {
+            if (!mEGL.eglDestroyContext(mEGLDisplay, mEGLContext)) {
+                throw new GLControllerException("EGL context could not be destroyed!");
+            }
+
+            mGL = null;
+            mEGLDisplay = null;
+            mEGLConfig = null;
+            mEGLContext = null;
+        }
+    }
+
+    public GL getGL()                       { return mEGLContext.getGL(); }
+    public EGLDisplay getEGLDisplay()       { return mEGLDisplay;         }
+    public EGLConfig getEGLConfig()         { return mEGLConfig;          }
+    public EGLContext getEGLContext()       { return mEGLContext;         }
+    public EGLSurface getEGLSurface()       { return mEGLSurface;         }
+    public FlexibleGLSurfaceView getView()  { return mView;               }
+
+    public boolean hasSurface() {
+        return mEGLSurface != null;
+    }
+
+    public boolean swapBuffers() {
+        return mEGL.eglSwapBuffers(mEGLDisplay, mEGLSurface);
+    }
+
+    public boolean checkForLostContext() {
+        if (mEGL.eglGetError() != EGL11.EGL_CONTEXT_LOST) {
+            return false;
+        }
+
+        mEGLDisplay = null;
+        mEGLConfig = null;
+        mEGLContext = null;
+        mEGLSurface = null;
+        mGL = null;
+        return true;
+    }
+
+    public synchronized void waitForValidSurface() {
+        while (!mSurfaceValid) {
+            try {
+                wait();
+            } catch (InterruptedException e) {
+                throw new RuntimeException(e);
+            }
+        }
+    }
+
+    public synchronized int getWidth() {
+        return mWidth;
+    }
+
+    public synchronized int getHeight() {
+        return mHeight;
+    }
+
+    synchronized void surfaceCreated() {
+        mSurfaceValid = true;
+        notifyAll();
+    }
+
+    synchronized void surfaceDestroyed() {
+        mSurfaceValid = false;
+        notifyAll();
+    }
+
+    synchronized void sizeChanged(int newWidth, int newHeight) {
+        mWidth = newWidth;
+        mHeight = newHeight;
+    }
+
+    private void initEGLContext() {
+        mEGL = (EGL10)EGLContext.getEGL();
+
+        mEGLDisplay = mEGL.eglGetDisplay(EGL10.EGL_DEFAULT_DISPLAY);
+        if (mEGLDisplay == EGL10.EGL_NO_DISPLAY) {
+            throw new GLControllerException("eglGetDisplay() failed");
+        }
+
+        int[] version = new int[2];
+        if (!mEGL.eglInitialize(mEGLDisplay, version)) {
+            throw new GLControllerException("eglInitialize() failed");
+        }
+
+        mEGLConfig = chooseConfig();
+
+        int[] attribList = { EGL_CONTEXT_CLIENT_VERSION, mGLVersion, EGL10.EGL_NONE };
+        mEGLContext = mEGL.eglCreateContext(mEGLDisplay, mEGLConfig, EGL10.EGL_NO_CONTEXT,
+                                            attribList);
+        if (mEGLContext == null || mEGLContext == EGL10.EGL_NO_CONTEXT) {
+            throw new GLControllerException("createContext() failed");
+        }
+    }
+
+    private EGLConfig chooseConfig() {
+        int[] numConfigs = new int[1];
+        if (!mEGL.eglChooseConfig(mEGLDisplay, CONFIG_SPEC, null, 0, numConfigs) ||
+                numConfigs[0] <= 0) {
+            throw new GLControllerException("No available EGL configurations");
+        }
+
+        EGLConfig[] configs = new EGLConfig[numConfigs[0]];
+        if (!mEGL.eglChooseConfig(mEGLDisplay, CONFIG_SPEC, configs, numConfigs[0], numConfigs)) {
+            throw new GLControllerException("No EGL configuration for that specification");
+        }
+
+        // Select the first 565 RGB configuration.
+        int[] red = new int[1], green = new int[1], blue = new int[1];
+        for (EGLConfig config : configs) {
+            mEGL.eglGetConfigAttrib(mEGLDisplay, config, EGL10.EGL_RED_SIZE, red);
+            mEGL.eglGetConfigAttrib(mEGLDisplay, config, EGL10.EGL_GREEN_SIZE, green);
+            mEGL.eglGetConfigAttrib(mEGLDisplay, config, EGL10.EGL_BLUE_SIZE, blue);
+            if (red[0] == 5 && green[0] == 6 && blue[0] == 5) {
+                return config;
+            }
+        }
+
+        throw new GLControllerException("No suitable EGL configuration found");
+    }
+
+    private void createEGLSurface() {
+        SurfaceHolder surfaceHolder = mView.getHolder();
+        mEGLSurface = mEGL.eglCreateWindowSurface(mEGLDisplay, mEGLConfig, surfaceHolder, null);
+        if (mEGLSurface == null || mEGLSurface == EGL10.EGL_NO_SURFACE) {
+            throw new GLControllerException("EGL window surface could not be created!");
+        }
+
+        if (!mEGL.eglMakeCurrent(mEGLDisplay, mEGLSurface, mEGLSurface, mEGLContext)) {
+            throw new GLControllerException("EGL surface could not be made into the current " +
+                                            "surface!");
+        }
+
+        mGL = mEGLContext.getGL();
+
+        if (mView.getRenderer() != null) {
+            mView.getRenderer().onSurfaceCreated((GL10)mGL, mEGLConfig);
+            mView.getRenderer().onSurfaceChanged((GL10)mGL, mView.getWidth(), mView.getHeight());
+        }
+    }
+
+    public static class GLControllerException extends RuntimeException {
+        public static final long serialVersionUID = 1L;
+
+        GLControllerException(String e) {
+            super(e);
+        }
+    }
+}
+
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/gfx/GLThread.java
@@ -0,0 +1,181 @@
+/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
+ * ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (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.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Mozilla Android code.
+ *
+ * The Initial Developer of the Original Code is Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2011-2012
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Patrick Walton <pcwalton@mozilla.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+package org.mozilla.gecko.gfx;
+
+import android.opengl.GLSurfaceView;
+import android.view.SurfaceHolder;
+import javax.microedition.khronos.opengles.GL10;
+import java.util.concurrent.Future;
+import java.util.concurrent.LinkedBlockingQueue;
+
+// A GL thread managed by Java. It is not necessary to use this class to use the
+// FlexibleGLSurfaceView, but it can be helpful, especially if the GL rendering is to be done
+// entirely in Java.
+class GLThread extends Thread {
+    private LinkedBlockingQueue<Runnable> mQueue;
+    private GLController mController;
+    private boolean mRenderQueued;
+
+    public GLThread(GLController controller) {
+        mQueue = new LinkedBlockingQueue<Runnable>();
+        mController = controller;
+    }
+
+    @Override
+    public void run() {
+        while (true) {
+            Runnable runnable;
+            try {
+                runnable = mQueue.take();
+            } catch (InterruptedException e) {
+                throw new RuntimeException(e);
+            }
+
+            runnable.run();
+            if (runnable instanceof ShutdownMessage) {
+                break;
+            }
+        }
+    }
+
+    public void recreateSurface() {
+        mQueue.add(new RecreateSurfaceMessage());
+    }
+
+    public void renderFrame() {
+        // Make sure there's only one render event in the queue at a time.
+        synchronized (this) {
+            if (!mRenderQueued) {
+                mQueue.add(new RenderFrameMessage());
+                mRenderQueued = true;
+            }
+        }
+    }
+
+    public void shutdown() {
+        mQueue.add(new ShutdownMessage());
+    }
+
+    public void surfaceChanged(int width, int height) {
+        mQueue.add(new SizeChangedMessage(width, height));
+    }
+
+    public void surfaceCreated() {
+        mQueue.add(new SurfaceCreatedMessage());
+    }
+
+    public void surfaceDestroyed() {
+        mQueue.add(new SurfaceDestroyedMessage());
+    }
+
+    private void doRecreateSurface() {
+        mController.disposeGLContext();
+        mController.initGLContext();
+    }
+
+    private GLSurfaceView.Renderer getRenderer() {
+        return mController.getView().getRenderer();
+    }
+
+    private class RecreateSurfaceMessage implements Runnable {
+        public void run() {
+            doRecreateSurface();
+        }
+    }
+
+    private class RenderFrameMessage implements Runnable {
+        public void run() {
+            synchronized (GLThread.this) {
+                mRenderQueued = false;
+            }
+
+            // Bail out if the surface was lost.
+            if (mController.getEGLSurface() == null) {
+                return;
+            }
+
+            GLSurfaceView.Renderer renderer = getRenderer();
+            if (renderer != null) {
+                renderer.onDrawFrame((GL10)mController.getGL());
+            }
+
+            mController.swapBuffers();
+            //if (!mController.swapBuffers() && mController.checkForLostContext()) {
+            //    doRecreateSurface();
+            //}
+        }
+    }
+
+    private class ShutdownMessage implements Runnable {
+        public void run() {
+            mController.disposeGLContext();
+            mController = null;
+        }
+    }
+
+    private class SizeChangedMessage implements Runnable {
+        private int mWidth, mHeight;
+
+        public SizeChangedMessage(int width, int height) {
+            mWidth = width;
+            mHeight = height;
+        }
+
+        public void run() {
+            GLSurfaceView.Renderer renderer = getRenderer();
+            if (renderer != null) {
+                renderer.onSurfaceChanged((GL10)mController.getGL(), mWidth, mHeight);
+            }
+        }
+    }
+
+    private class SurfaceCreatedMessage implements Runnable {
+        public void run() {
+            if (!mController.hasSurface()) {
+                mController.initGLContext();
+            }
+        }
+    }
+
+    private class SurfaceDestroyedMessage implements Runnable {
+        public void run() {
+            mController.disposeGLContext();
+        }
+    }
+}
+
--- a/mobile/android/base/gfx/LayerView.java
+++ b/mobile/android/base/gfx/LayerView.java
@@ -64,18 +64,18 @@ import org.json.JSONException;
 import org.json.JSONObject;
 
 /**
  * A view rendered by the layer compositor.
  *
  * This view delegates to LayerRenderer to actually do the drawing. Its role is largely that of a
  * mediator between the LayerRenderer and the LayerController.
  */
-public class LayerView extends GLSurfaceView
-    implements AbstractLayerView, GeckoEventListener {
+public class LayerView extends FlexibleGLSurfaceView implements AbstractLayerView,
+                                                                GeckoEventListener {
     private Context mContext;
     private LayerController mController;
     private InputConnectionHandler mInputConnectionHandler;
     private LayerRenderer mRenderer;
     private GestureDetector mGestureDetector;
     private SimpleScaleGestureDetector mScaleGestureDetector;
     private long mRenderTime;
     private boolean mRenderTimeReset;
@@ -87,31 +87,32 @@ public class LayerView extends GLSurface
 
     public LayerView(Context context, LayerController controller) {
         super(context);
 
         mContext = context;
         mController = controller;
         mRenderer = new LayerRenderer(this);
         setRenderer(mRenderer);
-        setRenderMode(RENDERMODE_WHEN_DIRTY);
         mGestureDetector = new GestureDetector(context, controller.getGestureListener());
         mScaleGestureDetector =
             new SimpleScaleGestureDetector(controller.getScaleGestureListener());
         mGestureDetector.setOnDoubleTapListener(controller.getDoubleTapListener());
         mInputConnectionHandler = null;
 
         setFocusable(true);
         setFocusableInTouchMode(true);
 
         GeckoAppShell.registerGeckoEventListener("Preferences:Data", this);
         JSONArray jsonPrefs = new JSONArray();
         jsonPrefs.put(touchEventsPrefName);
         GeckoEvent event = new GeckoEvent("Preferences:Get", jsonPrefs.toString());
         GeckoAppShell.sendEventToGecko(event);
+
+        createGLThread();
     }
 
     public void handleMessage(String event, JSONObject message) {
         if (event.equals("Preferences:Data")) {
             try {
                 JSONArray jsonPrefs = message.getJSONArray("preferences");
                 for (int i = 0; i < jsonPrefs.length(); i++) {
                     JSONObject jPref = jsonPrefs.getJSONObject(i);