HardwareRenderer.java revision fb8b763f762ae21923c58d64caa729b012f40e05
1/*
2 * Copyright (C) 2010 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
17
18package android.view;
19
20import android.graphics.Canvas;
21import android.os.SystemClock;
22import android.util.Log;
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;
31
32/**
33 * Interface for rendering a ViewRoot using hardware acceleration.
34 *
35 * @hide
36 */
37public abstract class HardwareRenderer {
38    private static final String LOG_TAG = "HardwareRenderer";
39
40    private boolean mEnabled;
41    private boolean mRequested = true;
42
43    /**
44     * Indicates whether hardware acceleration is available under any form for
45     * the view hierarchy.
46     *
47     * @return True if the view hierarchy can potentially be hardware accelerated,
48     *         false otherwise
49     */
50    public static boolean isAvailable() {
51        return GLES20Canvas.isAvailable();
52    }
53
54    /**
55     * Destroys the hardware rendering context.
56     */
57    abstract void destroy();
58
59    /**
60     * Initializes the hardware renderer for the specified surface.
61     *
62     * @param holder The holder for the surface to hardware accelerate.
63     *
64     * @return True if the initialization was successful, false otherwise.
65     */
66    abstract boolean initialize(SurfaceHolder holder);
67
68    /**
69     * Setup the hardware renderer for drawing. This is called for every
70     * frame to draw.
71     *
72     * @param width Width of the drawing surface.
73     * @param height Height of the drawing surface.
74     */
75    abstract void setup(int width, int height);
76
77    /**
78     * Draws the specified view.
79     *
80     * @param view The view to draw.
81     * @param attachInfo AttachInfo tied to the specified view.
82     */
83    abstract void draw(View view, View.AttachInfo attachInfo, int yOffset);
84
85    /**
86     * Initializes the hardware renderer for the specified surface and setup the
87     * renderer for drawing, if needed. This is invoked when the ViewRoot has
88     * potentially lost the hardware renderer. The hardware renderer should be
89     * reinitialized and setup when the render {@link #isRequested()} and
90     * {@link #isEnabled()}.
91     *
92     * @param width The width of the drawing surface.
93     * @param height The height of the drawing surface.
94     * @param attachInfo The
95     * @param holder
96     */
97    void initializeIfNeeded(int width, int height, View.AttachInfo attachInfo,
98            SurfaceHolder holder) {
99        if (isRequested()) {
100            // We lost the gl context, so recreate it.
101            if (!isEnabled()) {
102                if (initialize(holder)) {
103                    setup(width, height);
104                }
105            }
106        }
107    }
108
109    /**
110     * Creates a hardware renderer using OpenGL.
111     *
112     * @param glVersion The version of OpenGL to use (1 for OpenGL 1, 11 for OpenGL 1.1, etc.)
113     * @param translucent True if the surface is translucent, false otherwise
114     *
115     * @return A hardware renderer backed by OpenGL.
116     */
117    static HardwareRenderer createGlRenderer(int glVersion, boolean translucent) {
118        switch (glVersion) {
119            case 2:
120                return Gl20Renderer.create(translucent);
121        }
122        throw new IllegalArgumentException("Unknown GL version: " + glVersion);
123    }
124
125    /**
126     * Indicates whether hardware acceleration is currently enabled.
127     *
128     * @return True if hardware acceleration is in use, false otherwise.
129     */
130    boolean isEnabled() {
131        return mEnabled;
132    }
133
134    /**
135     * Indicates whether hardware acceleration is currently enabled.
136     *
137     * @param enabled True if the hardware renderer is in use, false otherwise.
138     */
139    void setEnabled(boolean enabled) {
140        mEnabled = enabled;
141    }
142
143    /**
144     * Indicates whether hardware acceleration is currently request but not
145     * necessarily enabled yet.
146     *
147     * @return True if requested, false otherwise.
148     */
149    boolean isRequested() {
150        return mRequested;
151    }
152
153    /**
154     * Indicates whether hardware acceleration is currently request but not
155     * necessarily enabled yet.
156     *
157     * @return True to request hardware acceleration, false otherwise.
158     */
159    void setRequested(boolean requested) {
160        mRequested = requested;
161    }
162
163    @SuppressWarnings({"deprecation"})
164    static abstract class GlRenderer extends HardwareRenderer {
165        private static final int EGL_CONTEXT_CLIENT_VERSION = 0x3098;
166
167        static EGLContext sEglContext;
168        static EGL10 sEgl;
169        static EGLDisplay sEglDisplay;
170        static EGLConfig sEglConfig;
171
172        private static Thread sEglThread;
173
174        EGLSurface mEglSurface;
175
176        GL mGl;
177        GLES20Canvas mCanvas;
178
179        final int mGlVersion;
180        final boolean mTranslucent;
181
182        private boolean mDestroyed;
183
184        GlRenderer(int glVersion, boolean translucent) {
185            mGlVersion = glVersion;
186            mTranslucent = translucent;
187        }
188
189        /**
190         * Checks for OpenGL errors. If an error has occured, {@link #destroy()}
191         * is invoked and the requested flag is turned off. The error code is
192         * also logged as a warning.
193         */
194        void checkErrors() {
195            if (isEnabled()) {
196                int error = sEgl.eglGetError();
197                if (error != EGL10.EGL_SUCCESS) {
198                    // something bad has happened revert to
199                    // normal rendering.
200                    destroy();
201                    if (error != EGL11.EGL_CONTEXT_LOST) {
202                        // we'll try again if it was context lost
203                        setRequested(false);
204                    }
205                    Log.w(LOG_TAG, "OpenGL error: " + error);
206                }
207            }
208        }
209
210        @Override
211        boolean initialize(SurfaceHolder holder) {
212            if (isRequested() && !isEnabled()) {
213                initializeEgl();
214                mGl = createEglSurface(holder);
215                mDestroyed = false;
216
217                if (mGl != null) {
218                    int err = sEgl.eglGetError();
219                    if (err != EGL10.EGL_SUCCESS) {
220                        destroy();
221                        setRequested(false);
222                    } else {
223                        if (mCanvas != null) {
224                            destroyCanvas();
225                        }
226                        mCanvas = createCanvas();
227                        if (mCanvas != null) {
228                            setEnabled(true);
229                        } else {
230                            Log.w(LOG_TAG, "Hardware accelerated Canvas could not be created");
231                        }
232                    }
233
234                    return mCanvas != null;
235                }
236            }
237            return false;
238        }
239
240        private void destroyCanvas() {
241            mCanvas.destroy();
242            mCanvas = null;
243        }
244
245        abstract GLES20Canvas createCanvas();
246
247        void initializeEgl() {
248            if (sEglContext != null) return;
249
250            sEglThread = Thread.currentThread();
251            sEgl = (EGL10) EGLContext.getEGL();
252
253            // Get to the default display.
254            sEglDisplay = sEgl.eglGetDisplay(EGL10.EGL_DEFAULT_DISPLAY);
255
256            if (sEglDisplay == EGL10.EGL_NO_DISPLAY) {
257                throw new RuntimeException("eglGetDisplay failed");
258            }
259
260            // We can now initialize EGL for that display
261            int[] version = new int[2];
262            if (!sEgl.eglInitialize(sEglDisplay, version)) {
263                throw new RuntimeException("eglInitialize failed");
264            }
265            sEglConfig = getConfigChooser(mGlVersion).chooseConfig(sEgl, sEglDisplay);
266
267            /*
268            * Create an EGL context. We want to do this as rarely as we can, because an
269            * EGL context is a somewhat heavy object.
270            */
271            sEglContext = createContext(sEgl, sEglDisplay, sEglConfig);
272        }
273
274        GL createEglSurface(SurfaceHolder holder) {
275            // Check preconditions.
276            if (sEgl == null) {
277                throw new RuntimeException("egl not initialized");
278            }
279            if (sEglDisplay == null) {
280                throw new RuntimeException("eglDisplay not initialized");
281            }
282            if (sEglConfig == null) {
283                throw new RuntimeException("mEglConfig not initialized");
284            }
285            if (Thread.currentThread() != sEglThread) {
286                throw new IllegalStateException("HardwareRenderer cannot be used "
287                        + "from multiple threads");
288            }
289
290            /*
291             *  The window size has changed, so we need to create a new
292             *  surface.
293             */
294            if (mEglSurface != null && mEglSurface != EGL10.EGL_NO_SURFACE) {
295                /*
296                 * Unbind and destroy the old EGL surface, if
297                 * there is one.
298                 */
299                sEgl.eglMakeCurrent(sEglDisplay, EGL10.EGL_NO_SURFACE,
300                        EGL10.EGL_NO_SURFACE, EGL10.EGL_NO_CONTEXT);
301                sEgl.eglDestroySurface(sEglDisplay, mEglSurface);
302            }
303
304            // Create an EGL surface we can render into.
305            mEglSurface = sEgl.eglCreateWindowSurface(sEglDisplay, sEglConfig, holder, null);
306
307            if (mEglSurface == null || mEglSurface == EGL10.EGL_NO_SURFACE) {
308                int error = sEgl.eglGetError();
309                if (error == EGL10.EGL_BAD_NATIVE_WINDOW) {
310                    Log.e("EglHelper", "createWindowSurface returned EGL_BAD_NATIVE_WINDOW.");
311                    return null;
312                }
313                throw new RuntimeException("createWindowSurface failed");
314            }
315
316            /*
317             * Before we can issue GL commands, we need to make sure
318             * the context is current and bound to a surface.
319             */
320            if (!sEgl.eglMakeCurrent(sEglDisplay, mEglSurface, mEglSurface, sEglContext)) {
321                throw new RuntimeException("eglMakeCurrent failed");
322            }
323
324            return sEglContext.getGL();
325        }
326
327        EGLContext createContext(EGL10 egl, EGLDisplay eglDisplay, EGLConfig eglConfig) {
328            int[] attrib_list = { EGL_CONTEXT_CLIENT_VERSION, mGlVersion, EGL10.EGL_NONE };
329
330            return egl.eglCreateContext(eglDisplay, eglConfig, EGL10.EGL_NO_CONTEXT,
331                    mGlVersion != 0 ? attrib_list : null);
332        }
333
334        @Override
335        void initializeIfNeeded(int width, int height, View.AttachInfo attachInfo,
336                SurfaceHolder holder) {
337            if (isRequested()) {
338                checkErrors();
339                super.initializeIfNeeded(width, height, attachInfo, holder);
340            }
341        }
342
343        @Override
344        void destroy() {
345            if (!isEnabled() || mDestroyed) return;
346
347            mDestroyed = true;
348
349            checkCurrent();
350            // Destroy the Canvas first in case it needs to use a GL context
351            // to perform its cleanup.
352            destroyCanvas();
353
354            sEgl.eglMakeCurrent(sEglDisplay, EGL10.EGL_NO_SURFACE,
355                    EGL10.EGL_NO_SURFACE, EGL10.EGL_NO_CONTEXT);
356            sEgl.eglDestroySurface(sEglDisplay, mEglSurface);
357
358            mEglSurface = null;
359            mGl = null;
360
361            // mEgl.eglDestroyContext(mEglDisplay, mEglContext);
362            // mEglContext = null;
363            // mEgl.eglTerminate(mEglDisplay);
364            // mEgl = null;
365            // mEglDisplay = null;
366
367            setEnabled(false);
368        }
369
370        @Override
371        void setup(int width, int height) {
372            mCanvas.setViewport(width, height);
373        }
374
375        boolean canDraw() {
376            return mGl != null && mCanvas != null;
377        }
378
379        void onPreDraw() {
380        }
381
382        /**
383         * Defines the EGL configuration for this renderer. The default configuration
384         * is RGBX, no depth, no stencil.
385         *
386         * @return An {@link android.view.HardwareRenderer.GlRenderer.EglConfigChooser}.
387         * @param glVersion
388         */
389        EglConfigChooser getConfigChooser(int glVersion) {
390            // TODO: Check for mTranslucent here, which means at least 2 EGL contexts
391            return new ComponentSizeChooser(glVersion, 8, 8, 8, 8, 0, 0);
392        }
393
394        @Override
395        void draw(View view, View.AttachInfo attachInfo, int yOffset) {
396            if (canDraw()) {
397                attachInfo.mDrawingTime = SystemClock.uptimeMillis();
398                attachInfo.mIgnoreDirtyState = true;
399                view.mPrivateFlags |= View.DRAWN;
400
401                checkCurrent();
402
403                onPreDraw();
404
405                Canvas canvas = mCanvas;
406                int saveCount = canvas.save();
407                canvas.translate(0, -yOffset);
408
409                try {
410                    view.draw(canvas);
411                } finally {
412                    canvas.restoreToCount(saveCount);
413                }
414
415                attachInfo.mIgnoreDirtyState = false;
416
417                sEgl.eglSwapBuffers(sEglDisplay, mEglSurface);
418                checkErrors();
419            }
420        }
421
422        private void checkCurrent() {
423            // TODO: Don't check the current context when we have one per UI thread
424            // TODO: Use a threadlocal flag to know whether the surface has changed
425            if (sEgl.eglGetCurrentContext() != sEglContext ||
426                    sEgl.eglGetCurrentSurface(EGL10.EGL_DRAW) != mEglSurface) {
427                if (!sEgl.eglMakeCurrent(sEglDisplay, mEglSurface, mEglSurface, sEglContext)) {
428                    throw new RuntimeException("eglMakeCurrent failed");
429                }
430            }
431        }
432
433        static abstract class EglConfigChooser {
434            final int[] mConfigSpec;
435            private final int mGlVersion;
436
437            EglConfigChooser(int glVersion, int[] configSpec) {
438                mGlVersion = glVersion;
439                mConfigSpec = filterConfigSpec(configSpec);
440            }
441
442            EGLConfig chooseConfig(EGL10 egl, EGLDisplay display) {
443                int[] index = new int[1];
444                if (!egl.eglChooseConfig(display, mConfigSpec, null, 0, index)) {
445                    throw new IllegalArgumentException("eglChooseConfig failed");
446                }
447
448                int numConfigs = index[0];
449                if (numConfigs <= 0) {
450                    throw new IllegalArgumentException("No configs match configSpec");
451                }
452
453                EGLConfig[] configs = new EGLConfig[numConfigs];
454                if (!egl.eglChooseConfig(display, mConfigSpec, configs, numConfigs, index)) {
455                    throw new IllegalArgumentException("eglChooseConfig failed");
456                }
457
458                EGLConfig config = chooseConfig(egl, display, configs);
459                if (config == null) {
460                    throw new IllegalArgumentException("No config chosen");
461                }
462
463                return config;
464            }
465
466            abstract EGLConfig chooseConfig(EGL10 egl, EGLDisplay display, EGLConfig[] configs);
467
468            private int[] filterConfigSpec(int[] configSpec) {
469                if (mGlVersion != 2) {
470                    return configSpec;
471                }
472                /* We know none of the subclasses define EGL_RENDERABLE_TYPE.
473                 * And we know the configSpec is well formed.
474                 */
475                int len = configSpec.length;
476                int[] newConfigSpec = new int[len + 2];
477                System.arraycopy(configSpec, 0, newConfigSpec, 0, len - 1);
478                newConfigSpec[len - 1] = EGL10.EGL_RENDERABLE_TYPE;
479                newConfigSpec[len] = 4; /* EGL_OPENGL_ES2_BIT */
480                newConfigSpec[len + 1] = EGL10.EGL_NONE;
481                return newConfigSpec;
482            }
483        }
484
485        /**
486         * Choose a configuration with exactly the specified r,g,b,a sizes,
487         * and at least the specified depth and stencil sizes.
488         */
489        static class ComponentSizeChooser extends EglConfigChooser {
490            private int[] mValue;
491
492            private int mRedSize;
493            private int mGreenSize;
494            private int mBlueSize;
495            private int mAlphaSize;
496            private int mDepthSize;
497            private int mStencilSize;
498
499            ComponentSizeChooser(int glVersion, int redSize, int greenSize, int blueSize,
500                    int alphaSize, int depthSize, int stencilSize) {
501                super(glVersion, new int[] {
502                        EGL10.EGL_RED_SIZE, redSize,
503                        EGL10.EGL_GREEN_SIZE, greenSize,
504                        EGL10.EGL_BLUE_SIZE, blueSize,
505                        EGL10.EGL_ALPHA_SIZE, alphaSize,
506                        EGL10.EGL_DEPTH_SIZE, depthSize,
507                        EGL10.EGL_STENCIL_SIZE, stencilSize,
508                        EGL10.EGL_NONE });
509                mValue = new int[1];
510                mRedSize = redSize;
511                mGreenSize = greenSize;
512                mBlueSize = blueSize;
513                mAlphaSize = alphaSize;
514                mDepthSize = depthSize;
515                mStencilSize = stencilSize;
516           }
517
518            @Override
519            EGLConfig chooseConfig(EGL10 egl, EGLDisplay display, EGLConfig[] configs) {
520                for (EGLConfig config : configs) {
521                    int d = findConfigAttrib(egl, display, config, EGL10.EGL_DEPTH_SIZE, 0);
522                    int s = findConfigAttrib(egl, display, config, EGL10.EGL_STENCIL_SIZE, 0);
523                    if (d >= mDepthSize && s >= mStencilSize) {
524                        int r = findConfigAttrib(egl, display, config, EGL10.EGL_RED_SIZE, 0);
525                        int g = findConfigAttrib(egl, display, config, EGL10.EGL_GREEN_SIZE, 0);
526                        int b = findConfigAttrib(egl, display, config, EGL10.EGL_BLUE_SIZE, 0);
527                        int a = findConfigAttrib(egl, display, config, EGL10.EGL_ALPHA_SIZE, 0);
528                        if (r >= mRedSize && g >= mGreenSize && b >= mBlueSize && a >= mAlphaSize) {
529                            return config;
530                        }
531                    }
532                }
533                return null;
534            }
535
536            private int findConfigAttrib(EGL10 egl, EGLDisplay display, EGLConfig config,
537                    int attribute, int defaultValue) {
538                if (egl.eglGetConfigAttrib(display, config, attribute, mValue)) {
539                    return mValue[0];
540                }
541
542                return defaultValue;
543            }
544        }
545    }
546
547    /**
548     * Hardware renderer using OpenGL ES 2.0.
549     */
550    static class Gl20Renderer extends GlRenderer {
551        private GLES20Canvas mGlCanvas;
552
553        Gl20Renderer(boolean translucent) {
554            super(2, translucent);
555        }
556
557        @Override
558        GLES20Canvas createCanvas() {
559            // TODO: Pass mTranslucent instead of true
560            return mGlCanvas = new GLES20Canvas(mGl, true);
561        }
562
563        @Override
564        void onPreDraw() {
565            mGlCanvas.onPreDraw();
566        }
567
568        static HardwareRenderer create(boolean translucent) {
569            if (GLES20Canvas.isAvailable()) {
570                return new Gl20Renderer(translucent);
571            }
572            return null;
573        }
574    }
575}
576