GLSurfaceView.java revision c2310f32b0a1373012fd4a2db8cebdb46507b901
1/*
2 * Copyright (C) 2008 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package android.opengl;
18
19import java.io.IOException;
20import java.io.Writer;
21import java.util.ArrayList;
22import java.util.concurrent.Semaphore;
23
24import javax.microedition.khronos.egl.EGL10;
25import javax.microedition.khronos.egl.EGL11;
26import javax.microedition.khronos.egl.EGLConfig;
27import javax.microedition.khronos.egl.EGLContext;
28import javax.microedition.khronos.egl.EGLDisplay;
29import javax.microedition.khronos.egl.EGLSurface;
30import javax.microedition.khronos.opengles.GL;
31import javax.microedition.khronos.opengles.GL10;
32
33import android.content.Context;
34import android.util.AttributeSet;
35import android.util.Log;
36import android.view.SurfaceHolder;
37import android.view.SurfaceView;
38
39/**
40 * An implementation of SurfaceView that uses the dedicated surface for
41 * displaying an OpenGL animation.  This allows the animation to run in a
42 * separate thread, without requiring that it be driven by the update mechanism
43 * of the view hierarchy.
44 *
45 * The application-specific rendering code is delegated to a GLView.Renderer
46 * instance.
47 */
48public class GLSurfaceView extends SurfaceView implements SurfaceHolder.Callback {
49    public final static int RENDERMODE_WHEN_DIRTY = 0;
50    public final static int RENDERMODE_CONTUOUSLY = 1;
51
52    /**
53     * Check glError() after every GL call.
54     */
55    public final static int DEBUG_CHECK_GL_ERROR = 1;
56
57    /**
58     * Log GL calls to the system log at "verbose" level with tag "GLSurfaceView".
59     */
60    public final static int DEBUG_LOG_GL_CALLS = 2;
61
62    public GLSurfaceView(Context context) {
63        super(context);
64        init();
65    }
66
67    public GLSurfaceView(Context context, AttributeSet attrs) {
68        super(context, attrs);
69        init();
70    }
71
72    private void init() {
73        // Install a SurfaceHolder.Callback so we get notified when the
74        // underlying surface is created and destroyed
75        SurfaceHolder holder = getHolder();
76        holder.addCallback(this);
77        holder.setType(SurfaceHolder.SURFACE_TYPE_GPU);
78    }
79
80    /**
81     * Set the glWrapper to a new value. The current glWrapper is used
82     * whenever a surface is created. The default value is null.
83     * @param glWrapper the new GLWrapper
84     */
85    public void setGLWrapper(GLWrapper glWrapper) {
86        mGLWrapper = glWrapper;
87    }
88
89    /**
90     * Set the debug flags to a new value. The debug flags take effect
91     * whenever a surface is created. The default value is zero.
92     * @param debugFlags the new debug flags
93     * @see #DEBUG_CHECK_GL_ERROR
94     * @see #DEBUG_LOG_GL_CALLS
95     */
96    public void setDebugFlags(int debugFlags) {
97        mDebugFlags = debugFlags;
98    }
99
100    public int getDebugFlags() {
101        return mDebugFlags;
102    }
103
104    /**
105     * Set the renderer associated with this view. Can only be called once.
106     * @param renderer
107     */
108    public void setRenderer(Renderer renderer) {
109        if (mGLThread != null) {
110            throw new IllegalStateException(
111                    "setRenderer has already been called for this instance.");
112        }
113        if (mEGLConfigChooser == null) {
114            mEGLConfigChooser = new SimpleEGLConfigChooser(true);
115        }
116        mGLThread = new GLThread(renderer);
117        mGLThread.start();
118    }
119
120    /**
121     * Set the EGLConfigChooser associated with this view. If this method is
122     * called at all, it must be called before {@link #setRenderer(Renderer)}
123     * is called.
124     * <p>
125     * The supplied configChooser will be used to choose a configuration.
126     * @param configChooser
127     */
128    public void setEGLConfigChooser(EGLConfigChooser configChooser) {
129        if (mGLThread != null) {
130            throw new IllegalStateException(
131                    "setRenderer has already been called for this instance.");
132        }
133        mEGLConfigChooser = configChooser;
134    }
135
136    /**
137     * Set the EGLConfigChooser associated with this view. If this method is
138     * called, it must be called before {@link #setRenderer(Renderer)}
139     * is called.
140     * <p>
141     * This method installs a config chooser which will choose a config
142     * as close to 16-bit RGB as possible, with or without an optional depth
143     * buffer as close to 16-bits as possible.
144     * <p>
145     * If no setEGLConfigChooser method is called, then by default the
146     * view will choose a config as close to 16-bit RGB as possible, with
147     * a depth buffer as close to 16-bits as possible.
148     *
149     * @param needDepth
150     */
151    public void setEGLConfigChooser(boolean needDepth) {
152        setEGLConfigChooser(new SimpleEGLConfigChooser(needDepth));
153    }
154
155    /**
156     * Set the EGLConfigChooser associated with this view. If this method is
157     * called, it must be called before {@link #setRenderer(Renderer)}
158     * is called.
159     * <p>
160     * This method installs a config chooser which will choose a config
161     * with at least the specified component sizes, and as close
162     * to the specified component sizes as possible.
163     *
164     */
165    public void setEGLConfigChooser(int redSize, int greenSize, int blueSize,
166            int alphaSize, int depthSize, int stencilSize) {
167        setEGLConfigChooser(new ComponentSizeChooser(redSize, greenSize,
168                blueSize, alphaSize, depthSize, stencilSize));
169    }
170    /**
171     * Set the rendering mode. When the renderMode is
172     * RENDERMODE_CONTINUOUSLY, the renderer is called
173     * repeatedly to re-render the scene. When the rendermode
174     * is RENDERMODE_WHEN_DIRTY, the renderer only rendered when the surface
175     * is created, or when requestRender is called. Defaults to RENDERMODE_CONTINUOUSLY.
176     * @param renderMode one of the RENDERMODE_X constants
177     */
178    public void setRenderMode(int renderMode) {
179        mGLThread.setRenderMode(renderMode);
180    }
181
182    /**
183     * Get the current rendering mode. May be called
184     * from any thread. Must not be called before a renderer has been set.
185     * @return true if the renderer will render continuously.
186     */
187    public int getRenderMode() {
188        return mGLThread.getRenderMode();
189    }
190
191    /**
192     * Request that the renderer render a frame. May be called
193     * from any thread. Must not be called before a renderer has been set.
194     * This method is typically used when the render mode has been set to
195     * false, so that frames are only rendered on demand.
196     */
197    public void requestRender() {
198        mGLThread.requestRender();
199    }
200
201    public void surfaceCreated(SurfaceHolder holder) {
202        mGLThread.surfaceCreated();
203    }
204
205    public void surfaceDestroyed(SurfaceHolder holder) {
206        // Surface will be destroyed when we return
207        mGLThread.surfaceDestroyed();
208    }
209
210    public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) {
211        mGLThread.onWindowResize(w, h);
212    }
213
214    /**
215     * Inform the view that the activity is paused. The owner of this view must
216     * call this method when the activity is paused.
217     */
218    public void onPause() {
219        mGLThread.onPause();
220    }
221
222    /**
223     * Inform the view that the activity is resumed. The owner of this view must
224     * call this method when the activity is resumed.
225     */
226    public void onResume() {
227        mGLThread.onResume();
228    }
229
230    /**
231     * Queue an "event" to be run on the GL rendering thread.
232     * @param r the runnable to be run on the GL rendering thread.
233     */
234    public void queueEvent(Runnable r) {
235        mGLThread.queueEvent(r);
236    }
237
238    @Override
239    protected void onDetachedFromWindow() {
240        super.onDetachedFromWindow();
241        mGLThread.requestExitAndWait();
242    }
243
244    // ----------------------------------------------------------------------
245
246    public interface GLWrapper {
247      GL wrap(GL gl);
248    }
249
250    // ----------------------------------------------------------------------
251
252    /**
253     * A generic renderer interface.
254     */
255    public interface Renderer {
256        /**
257         * Surface created.
258         * Called when the surface is created. Called when the application
259         * starts, and whenever the GPU is reinitialized. This will
260         * typically happen when the device awakes after going to sleep.
261         * Set your textures here.
262         * @param gl the GL interface. Use <code>instanceof</code> to
263         * test if the interface supports GL11 or higher interfaces.
264         * @param config the EGLConfig of the created surface. Can be used
265         * to create matching pbuffers.
266         */
267        void onSurfaceCreated(GL10 gl, EGLConfig config);
268        /**
269         * Surface changed size.
270         * Called after the surface is created and whenever
271         * the OpenGL ES surface size changes. Set your viewport here.
272         * @param gl the GL interface. Use <code>instanceof</code> to
273         * test if the interface supports GL11 or higher interfaces.
274         * @param width
275         * @param height
276         */
277        void onSurfaceChanged(GL10 gl, int width, int height);
278        /**
279         * Draw the current frame.
280         * @param gl the GL interface. Use <code>instanceof</code> to
281         * test if the interface supports GL11 or higher interfaces.
282         */
283        void onDrawFrame(GL10 gl);
284    }
285
286    /**
287     * An interface for choosing a configuration from a list of
288     * potential configurations.
289     *
290     */
291    public interface EGLConfigChooser {
292        /**
293         * Choose a configuration from the list. Implementors typically
294         * implement this method by calling
295         * {@link EGL10#eglChooseConfig} and iterating through the results.
296         * @param egl the EGL10 for the current display.
297         * @param display the current display.
298         * @return the chosen configuration.
299         */
300        EGLConfig chooseConfig(EGL10 egl, EGLDisplay display);
301    }
302
303    private static abstract class BaseConfigChooser
304            implements EGLConfigChooser {
305        public BaseConfigChooser(int[] configSpec) {
306            mConfigSpec = configSpec;
307        }
308        public EGLConfig chooseConfig(EGL10 egl, EGLDisplay display) {
309            int[] num_config = new int[1];
310            egl.eglChooseConfig(display, mConfigSpec, null, 0, num_config);
311
312            int numConfigs = num_config[0];
313
314            if (numConfigs <= 0) {
315                throw new IllegalArgumentException(
316                        "No configs match configSpec");
317            }
318
319            EGLConfig[] configs = new EGLConfig[numConfigs];
320            egl.eglChooseConfig(display, mConfigSpec, configs, numConfigs,
321                    num_config);
322            EGLConfig config = chooseConfig(egl, display, configs);
323            if (config == null) {
324                throw new IllegalArgumentException("No config chosen");
325            }
326            return config;
327        }
328
329        abstract EGLConfig chooseConfig(EGL10 egl, EGLDisplay display,
330                EGLConfig[] configs);
331
332        protected int[] mConfigSpec;
333    }
334
335    private static class ComponentSizeChooser extends BaseConfigChooser {
336        public ComponentSizeChooser(int redSize, int greenSize, int blueSize,
337                int alphaSize, int depthSize, int stencilSize) {
338            super(new int[] {
339                    EGL10.EGL_RED_SIZE, redSize,
340                    EGL10.EGL_GREEN_SIZE, greenSize,
341                    EGL10.EGL_BLUE_SIZE, blueSize,
342                    EGL10.EGL_ALPHA_SIZE, alphaSize,
343                    EGL10.EGL_DEPTH_SIZE, depthSize,
344                    EGL10.EGL_STENCIL_SIZE, stencilSize,
345                    EGL10.EGL_NONE});
346            mValue = new int[1];
347            mRedSize = redSize;
348            mGreenSize = greenSize;
349            mBlueSize = blueSize;
350            mAlphaSize = alphaSize;
351            mDepthSize = depthSize;
352            mStencilSize = stencilSize;
353       }
354
355        @Override
356        public EGLConfig chooseConfig(EGL10 egl, EGLDisplay display,
357                EGLConfig[] configs) {
358            EGLConfig closestConfig = null;
359            int closestDistance = 1000;
360            for(EGLConfig config : configs) {
361                int r = findConfigAttrib(egl, display, config,
362                        EGL10.EGL_RED_SIZE, 0);
363                int g = findConfigAttrib(egl, display, config,
364                         EGL10.EGL_GREEN_SIZE, 0);
365                int b = findConfigAttrib(egl, display, config,
366                          EGL10.EGL_BLUE_SIZE, 0);
367                int a = findConfigAttrib(egl, display, config,
368                        EGL10.EGL_ALPHA_SIZE, 0);
369                int d = findConfigAttrib(egl, display, config,
370                        EGL10.EGL_DEPTH_SIZE, 0);
371                int s = findConfigAttrib(egl, display, config,
372                        EGL10.EGL_STENCIL_SIZE, 0);
373                int distance = Math.abs(r - mRedSize)
374                    + Math.abs(g - mGreenSize)
375                    + Math.abs(b - mBlueSize) + Math.abs(a - mAlphaSize)
376                    + Math.abs(d - mDepthSize) + Math.abs(s - mStencilSize);
377                if (distance < closestDistance) {
378                    closestDistance = distance;
379                    closestConfig = config;
380                }
381            }
382            return closestConfig;
383        }
384
385        private int findConfigAttrib(EGL10 egl, EGLDisplay display,
386                EGLConfig config, int attribute, int defaultValue) {
387
388            if (egl.eglGetConfigAttrib(display, config, attribute, mValue)) {
389                return mValue[0];
390            }
391            return defaultValue;
392        }
393
394        private int[] mValue;
395        // Subclasses can adjust these values:
396        protected int mRedSize;
397        protected int mGreenSize;
398        protected int mBlueSize;
399        protected int mAlphaSize;
400        protected int mDepthSize;
401        protected int mStencilSize;
402        }
403
404    /**
405     * This class will choose a supported surface as close to
406     * RGB565 as possible, with or without a depth buffer.
407     *
408     */
409    private static class SimpleEGLConfigChooser extends ComponentSizeChooser {
410        public SimpleEGLConfigChooser(boolean withDepthBuffer) {
411            super(4, 4, 4, 0, withDepthBuffer ? 16 : 0, 0);
412            // Adjust target values. This way we'll accept a 4444 or
413            // 555 buffer if there's no 565 buffer available.
414            mRedSize = 5;
415            mGreenSize = 6;
416            mBlueSize = 5;
417        }
418    }
419
420    /**
421     * An EGL helper class.
422     */
423
424    private class EglHelper {
425        public EglHelper() {
426
427        }
428
429        /**
430         * Initialize EGL for a given configuration spec.
431         * @param configSpec
432         */
433        public void start(){
434            /*
435             * Get an EGL instance
436             */
437            mEgl = (EGL10) EGLContext.getEGL();
438
439            /*
440             * Get to the default display.
441             */
442            mEglDisplay = mEgl.eglGetDisplay(EGL10.EGL_DEFAULT_DISPLAY);
443
444            /*
445             * We can now initialize EGL for that display
446             */
447            int[] version = new int[2];
448            mEgl.eglInitialize(mEglDisplay, version);
449            mEglConfig = mEGLConfigChooser.chooseConfig(mEgl, mEglDisplay);
450
451            /*
452            * Create an OpenGL ES context. This must be done only once, an
453            * OpenGL context is a somewhat heavy object.
454            */
455            mEglContext = mEgl.eglCreateContext(mEglDisplay, mEglConfig,
456                    EGL10.EGL_NO_CONTEXT, null);
457
458            mEglSurface = null;
459        }
460
461        /*
462         * React to the creation of a new surface by creating and returning an
463         * OpenGL interface that renders to that surface.
464         */
465        public GL createSurface(SurfaceHolder holder) {
466            /*
467             *  The window size has changed, so we need to create a new
468             *  surface.
469             */
470            if (mEglSurface != null) {
471
472                /*
473                 * Unbind and destroy the old EGL surface, if
474                 * there is one.
475                 */
476                mEgl.eglMakeCurrent(mEglDisplay, EGL10.EGL_NO_SURFACE,
477                        EGL10.EGL_NO_SURFACE, EGL10.EGL_NO_CONTEXT);
478                mEgl.eglDestroySurface(mEglDisplay, mEglSurface);
479            }
480
481            /*
482             * Create an EGL surface we can render into.
483             */
484            mEglSurface = mEgl.eglCreateWindowSurface(mEglDisplay,
485                    mEglConfig, holder, null);
486
487            /*
488             * Before we can issue GL commands, we need to make sure
489             * the context is current and bound to a surface.
490             */
491            mEgl.eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface,
492                    mEglContext);
493
494
495            GL gl = mEglContext.getGL();
496            if (mGLWrapper != null) {
497                gl = mGLWrapper.wrap(gl);
498            }
499
500            if ((mDebugFlags & (DEBUG_CHECK_GL_ERROR | DEBUG_LOG_GL_CALLS))!= 0) {
501                int configFlags = 0;
502                Writer log = null;
503                if ((mDebugFlags & DEBUG_CHECK_GL_ERROR) != 0) {
504                    configFlags |= GLDebugHelper.CONFIG_CHECK_GL_ERROR;
505                }
506                if ((mDebugFlags & DEBUG_LOG_GL_CALLS) != 0) {
507                    log = new LogWriter();
508                }
509                gl = GLDebugHelper.wrap(gl, configFlags, log);
510            }
511            return gl;
512        }
513
514        /**
515         * Display the current render surface.
516         * @return false if the context has been lost.
517         */
518        public boolean swap() {
519            mEgl.eglSwapBuffers(mEglDisplay, mEglSurface);
520
521            /*
522             * Always check for EGL_CONTEXT_LOST, which means the context
523             * and all associated data were lost (For instance because
524             * the device went to sleep). We need to sleep until we
525             * get a new surface.
526             */
527            return mEgl.eglGetError() != EGL11.EGL_CONTEXT_LOST;
528        }
529
530        public void finish() {
531            if (mEglSurface != null) {
532                mEgl.eglMakeCurrent(mEglDisplay, EGL10.EGL_NO_SURFACE,
533                        EGL10.EGL_NO_SURFACE,
534                        EGL10.EGL_NO_CONTEXT);
535                mEgl.eglDestroySurface(mEglDisplay, mEglSurface);
536                mEglSurface = null;
537            }
538            if (mEglContext != null) {
539                mEgl.eglDestroyContext(mEglDisplay, mEglContext);
540                mEglContext = null;
541            }
542            if (mEglDisplay != null) {
543                mEgl.eglTerminate(mEglDisplay);
544                mEglDisplay = null;
545            }
546        }
547
548        EGL10 mEgl;
549        EGLDisplay mEglDisplay;
550        EGLSurface mEglSurface;
551        EGLConfig mEglConfig;
552        EGLContext mEglContext;
553    }
554
555    /**
556     * A generic GL Thread. Takes care of initializing EGL and GL. Delegates
557     * to a Renderer instance to do the actual drawing. Can be configured to
558     * render continuously or on request.
559     *
560     */
561    class GLThread extends Thread {
562        GLThread(Renderer renderer) {
563            super();
564            mDone = false;
565            mWidth = 0;
566            mHeight = 0;
567            mRequestRender = true;
568            mRenderMode = RENDERMODE_CONTUOUSLY;
569            mRenderer = renderer;
570            setName("GLThread");
571        }
572
573        @Override
574        public void run() {
575            /*
576             * When the android framework launches a second instance of
577             * an activity, the new instance's onCreate() method may be
578             * called before the first instance returns from onDestroy().
579             *
580             * This semaphore ensures that only one instance at a time
581             * accesses EGL.
582             */
583            try {
584                try {
585                sEglSemaphore.acquire();
586                } catch (InterruptedException e) {
587                    return;
588                }
589                guardedRun();
590            } catch (InterruptedException e) {
591                // fall thru and exit normally
592            } finally {
593                sEglSemaphore.release();
594            }
595        }
596
597        private void guardedRun() throws InterruptedException {
598            mEglHelper = new EglHelper();
599            mEglHelper.start();
600
601            GL10 gl = null;
602            boolean tellRendererSurfaceCreated = true;
603            boolean tellRendererSurfaceChanged = true;
604
605            /*
606             * This is our main activity thread's loop, we go until
607             * asked to quit.
608             */
609            while (!mDone) {
610
611                /*
612                 *  Update the asynchronous state (window size)
613                 */
614                int w, h;
615                boolean changed;
616                boolean needStart = false;
617                synchronized (this) {
618                    Runnable r;
619                    while ((r = getEvent()) != null) {
620                        r.run();
621                    }
622                    if (mPaused) {
623                        mEglHelper.finish();
624                        needStart = true;
625                    }
626                    while (needToWait()) {
627                        wait();
628                    }
629                    if (mDone) {
630                        break;
631                    }
632                    changed = mSizeChanged;
633                    w = mWidth;
634                    h = mHeight;
635                    mSizeChanged = false;
636                    mRequestRender = false;
637                }
638                if (needStart) {
639                    mEglHelper.start();
640                    tellRendererSurfaceCreated = true;
641                    changed = true;
642                }
643                if (changed) {
644                    gl = (GL10) mEglHelper.createSurface(getHolder());
645                    tellRendererSurfaceChanged = true;
646                }
647                if (tellRendererSurfaceCreated) {
648                    mRenderer.onSurfaceCreated(gl, mEglHelper.mEglConfig);
649                    tellRendererSurfaceCreated = false;
650                }
651                if (tellRendererSurfaceChanged) {
652                    mRenderer.onSurfaceChanged(gl, w, h);
653                    tellRendererSurfaceChanged = false;
654                }
655                if ((w > 0) && (h > 0)) {
656                    /* draw a frame here */
657                    mRenderer.onDrawFrame(gl);
658
659                    /*
660                     * Once we're done with GL, we need to call swapBuffers()
661                     * to instruct the system to display the rendered frame
662                     */
663                    mEglHelper.swap();
664                }
665             }
666
667            /*
668             * clean-up everything...
669             */
670            mEglHelper.finish();
671        }
672
673        private boolean needToWait() {
674            if (mDone) {
675                return false;
676            }
677
678            if (mPaused || (! mHasSurface)) {
679                return true;
680            }
681
682            if ((mWidth > 0) && (mHeight > 0) && (mRequestRender || (mRenderMode == RENDERMODE_CONTUOUSLY))) {
683                return false;
684            }
685
686            return true;
687        }
688
689        public void setRenderMode(int renderMode) {
690            if ( !((RENDERMODE_WHEN_DIRTY <= renderMode) && (renderMode <= RENDERMODE_CONTUOUSLY)) ) {
691                throw new IllegalArgumentException("renderMode");
692            }
693            synchronized(this) {
694                mRenderMode = renderMode;
695                if (renderMode == RENDERMODE_CONTUOUSLY) {
696                    notify();
697                }
698            }
699        }
700
701        public int getRenderMode() {
702            synchronized(this) {
703                return mRenderMode;
704            }
705        }
706
707        public void requestRender() {
708            synchronized(this) {
709                mRequestRender = true;
710                notify();
711            }
712        }
713
714        public void surfaceCreated() {
715            synchronized(this) {
716                mHasSurface = true;
717                notify();
718            }
719        }
720
721        public void surfaceDestroyed() {
722            synchronized(this) {
723                mHasSurface = false;
724                notify();
725            }
726        }
727
728        public void onPause() {
729            synchronized (this) {
730                mPaused = true;
731            }
732        }
733
734        public void onResume() {
735            synchronized (this) {
736                mPaused = false;
737                notify();
738            }
739        }
740
741        public void onWindowResize(int w, int h) {
742            synchronized (this) {
743                mWidth = w;
744                mHeight = h;
745                mSizeChanged = true;
746                notify();
747            }
748        }
749
750        public void requestExitAndWait() {
751            // don't call this from GLThread thread or it is a guaranteed
752            // deadlock!
753            synchronized(this) {
754                mDone = true;
755                notify();
756            }
757            try {
758                join();
759            } catch (InterruptedException ex) {
760                Thread.currentThread().interrupt();
761            }
762        }
763
764        /**
765         * Queue an "event" to be run on the GL rendering thread.
766         * @param r the runnable to be run on the GL rendering thread.
767         */
768        public void queueEvent(Runnable r) {
769            synchronized(this) {
770                mEventQueue.add(r);
771            }
772        }
773
774        private Runnable getEvent() {
775            synchronized(this) {
776                if (mEventQueue.size() > 0) {
777                    return mEventQueue.remove(0);
778                }
779
780            }
781            return null;
782        }
783
784        private boolean mDone;
785        private boolean mPaused;
786        private boolean mHasSurface;
787        private int mWidth;
788        private int mHeight;
789        private int mRenderMode;
790        private boolean mRequestRender;
791        private Renderer mRenderer;
792        private ArrayList<Runnable> mEventQueue = new ArrayList<Runnable>();
793        private EglHelper mEglHelper;
794    }
795
796    static class LogWriter extends Writer {
797
798        @Override public void close() {
799            flushBuilder();
800        }
801
802        @Override public void flush() {
803            flushBuilder();
804        }
805
806        @Override public void write(char[] buf, int offset, int count) {
807            for(int i = 0; i < count; i++) {
808                char c = buf[offset + i];
809                if ( c == '\n') {
810                    flushBuilder();
811                }
812                else {
813                    mBuilder.append(c);
814                }
815            }
816        }
817
818        private void flushBuilder() {
819            if (mBuilder.length() > 0) {
820                Log.v("GLSurfaceView", mBuilder.toString());
821                mBuilder.delete(0, mBuilder.length());
822            }
823        }
824
825        private StringBuilder mBuilder = new StringBuilder();
826    }
827
828    private static final Semaphore sEglSemaphore = new Semaphore(1);
829    private boolean mSizeChanged = true;
830
831    private GLThread mGLThread;
832    private EGLConfigChooser mEGLConfigChooser;
833    private GLWrapper mGLWrapper;
834    private int mDebugFlags;
835}
836