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