HardwareRenderer.java revision 0f761d6b8f5d5a607c87dbcdca6fe0ec7911970e
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     * Indicates that the current process cannot use hardware rendering.
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 request 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        GLES20Canvas 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                    destroy(true);
279                    if (error != EGL11.EGL_CONTEXT_LOST) {
280                        // we'll try again if it was context lost
281                        setRequested(false);
282                    }
283                    Log.w(LOG_TAG, "EGL error: " + getEGLErrorString(error));
284                }
285            }
286        }
287
288        @Override
289        boolean initialize(SurfaceHolder holder) {
290            if (isRequested() && !isEnabled()) {
291                initializeEgl();
292                mGl = createEglSurface(holder);
293                mDestroyed = false;
294
295                if (mGl != null) {
296                    int err = sEgl.eglGetError();
297                    if (err != EGL10.EGL_SUCCESS) {
298                        destroy(true);
299                        setRequested(false);
300                    } else {
301                        if (mCanvas == null) {
302                            mCanvas = createCanvas();
303                        }
304                        if (mCanvas != null) {
305                            setEnabled(true);
306                        } else {
307                            Log.w(LOG_TAG, "Hardware accelerated Canvas could not be created");
308                        }
309                    }
310
311                    return mCanvas != null;
312                }
313            }
314            return false;
315        }
316
317        abstract GLES20Canvas createCanvas();
318
319        void initializeEgl() {
320            if (sEglContext != null) return;
321
322            sEglThread = Thread.currentThread();
323            sEgl = (EGL10) EGLContext.getEGL();
324
325            // Get to the default display.
326            sEglDisplay = sEgl.eglGetDisplay(EGL10.EGL_DEFAULT_DISPLAY);
327
328            if (sEglDisplay == EGL10.EGL_NO_DISPLAY) {
329                throw new RuntimeException("eglGetDisplay failed "
330                        + getEGLErrorString(sEgl.eglGetError()));
331            }
332
333            // We can now initialize EGL for that display
334            int[] version = new int[2];
335            if (!sEgl.eglInitialize(sEglDisplay, version)) {
336                throw new RuntimeException("eglInitialize failed "
337                        + getEGLErrorString(sEgl.eglGetError()));
338            }
339            sEglConfig = getConfigChooser(mGlVersion).chooseConfig(sEgl, sEglDisplay);
340
341            /*
342            * Create an EGL context. We want to do this as rarely as we can, because an
343            * EGL context is a somewhat heavy object.
344            */
345            sEglContext = createContext(sEgl, sEglDisplay, sEglConfig);
346        }
347
348        GL createEglSurface(SurfaceHolder holder) {
349            // Check preconditions.
350            if (sEgl == null) {
351                throw new RuntimeException("egl not initialized");
352            }
353            if (sEglDisplay == null) {
354                throw new RuntimeException("eglDisplay not initialized");
355            }
356            if (sEglConfig == null) {
357                throw new RuntimeException("mEglConfig not initialized");
358            }
359            if (Thread.currentThread() != sEglThread) {
360                throw new IllegalStateException("HardwareRenderer cannot be used "
361                        + "from multiple threads");
362            }
363
364            /*
365             *  The window size has changed, so we need to create a new
366             *  surface.
367             */
368            if (mEglSurface != null && mEglSurface != EGL10.EGL_NO_SURFACE) {
369                /*
370                 * Unbind and destroy the old EGL surface, if
371                 * there is one.
372                 */
373                sEgl.eglMakeCurrent(sEglDisplay, EGL10.EGL_NO_SURFACE,
374                        EGL10.EGL_NO_SURFACE, EGL10.EGL_NO_CONTEXT);
375                sEgl.eglDestroySurface(sEglDisplay, mEglSurface);
376            }
377
378            // Create an EGL surface we can render into.
379            mEglSurface = sEgl.eglCreateWindowSurface(sEglDisplay, sEglConfig, holder, null);
380
381            if (mEglSurface == null || mEglSurface == EGL10.EGL_NO_SURFACE) {
382                int error = sEgl.eglGetError();
383                if (error == EGL10.EGL_BAD_NATIVE_WINDOW) {
384                    Log.e("EglHelper", "createWindowSurface returned EGL_BAD_NATIVE_WINDOW.");
385                    return null;
386                }
387                throw new RuntimeException("createWindowSurface failed "
388                        + getEGLErrorString(error));
389            }
390
391            /*
392             * Before we can issue GL commands, we need to make sure
393             * the context is current and bound to a surface.
394             */
395            if (!sEgl.eglMakeCurrent(sEglDisplay, mEglSurface, mEglSurface, sEglContext)) {
396                throw new RuntimeException("eglMakeCurrent failed "
397                        + getEGLErrorString(sEgl.eglGetError()));
398            }
399
400            return sEglContext.getGL();
401        }
402
403        EGLContext createContext(EGL10 egl, EGLDisplay eglDisplay, EGLConfig eglConfig) {
404            int[] attrib_list = { EGL_CONTEXT_CLIENT_VERSION, mGlVersion, EGL10.EGL_NONE };
405
406            return egl.eglCreateContext(eglDisplay, eglConfig, EGL10.EGL_NO_CONTEXT,
407                    mGlVersion != 0 ? attrib_list : null);
408        }
409
410        @Override
411        void initializeIfNeeded(int width, int height, View.AttachInfo attachInfo,
412                SurfaceHolder holder) {
413            if (isRequested()) {
414                checkEglErrors();
415                super.initializeIfNeeded(width, height, attachInfo, holder);
416            }
417        }
418
419        @Override
420        void destroy(boolean full) {
421            if (full && mCanvas != null) {
422                mCanvas = null;
423            }
424
425            if (!isEnabled() || mDestroyed) return;
426
427            mDestroyed = true;
428
429            sEgl.eglMakeCurrent(sEglDisplay, EGL10.EGL_NO_SURFACE,
430                    EGL10.EGL_NO_SURFACE, EGL10.EGL_NO_CONTEXT);
431            sEgl.eglDestroySurface(sEglDisplay, mEglSurface);
432
433            mEglSurface = null;
434            mGl = null;
435
436            setEnabled(false);
437        }
438
439        @Override
440        void setup(int width, int height) {
441            mCanvas.setViewport(width, height);
442        }
443
444        boolean canDraw() {
445            return mGl != null && mCanvas != null;
446        }
447
448        void onPreDraw() {
449        }
450
451        void onPostDraw() {
452        }
453
454        /**
455         * Defines the EGL configuration for this renderer.
456         *
457         * @return An {@link android.view.HardwareRenderer.GlRenderer.EglConfigChooser}.
458         */
459        EglConfigChooser getConfigChooser(int glVersion) {
460            return new ComponentSizeChooser(glVersion, 8, 8, 8, 8, 0, 0);
461        }
462
463        @Override
464        void draw(View view, View.AttachInfo attachInfo, HardwareDrawCallbacks callbacks) {
465            if (canDraw()) {
466                attachInfo.mDrawingTime = SystemClock.uptimeMillis();
467                attachInfo.mIgnoreDirtyState = true;
468                view.mPrivateFlags |= View.DRAWN;
469
470                long startTime;
471                if (ViewDebug.DEBUG_PROFILE_DRAWING) {
472                    startTime = SystemClock.elapsedRealtime();
473                }
474
475                checkCurrent();
476
477                onPreDraw();
478
479                Canvas canvas = mCanvas;
480                int saveCount = canvas.save();
481                callbacks.onHardwarePreDraw(canvas);
482
483                try {
484                    view.draw(canvas);
485                } finally {
486                    callbacks.onHardwarePostDraw(canvas);
487                    canvas.restoreToCount(saveCount);
488                }
489
490                onPostDraw();
491
492                if (ViewDebug.DEBUG_PROFILE_DRAWING) {
493                    EventLog.writeEvent(60000, SystemClock.elapsedRealtime() - startTime);
494                }
495
496                attachInfo.mIgnoreDirtyState = false;
497
498                sEgl.eglSwapBuffers(sEglDisplay, mEglSurface);
499                checkEglErrors();
500            }
501        }
502
503        private void checkCurrent() {
504            // TODO: Don't check the current context when we have one per UI thread
505            // TODO: Use a threadlocal flag to know whether the surface has changed
506            if (sEgl.eglGetCurrentContext() != sEglContext ||
507                    sEgl.eglGetCurrentSurface(EGL10.EGL_DRAW) != mEglSurface) {
508                if (!sEgl.eglMakeCurrent(sEglDisplay, mEglSurface, mEglSurface, sEglContext)) {
509                    throw new RuntimeException("eglMakeCurrent failed "
510                            + getEGLErrorString(sEgl.eglGetError()));
511                }
512            }
513        }
514
515        static abstract class EglConfigChooser {
516            final int[] mConfigSpec;
517            private final int mGlVersion;
518
519            EglConfigChooser(int glVersion, int[] configSpec) {
520                mGlVersion = glVersion;
521                mConfigSpec = filterConfigSpec(configSpec);
522            }
523
524            EGLConfig chooseConfig(EGL10 egl, EGLDisplay display) {
525                int[] index = new int[1];
526                if (!egl.eglChooseConfig(display, mConfigSpec, null, 0, index)) {
527                    throw new IllegalArgumentException("eglChooseConfig failed "
528                            + getEGLErrorString(egl.eglGetError()));
529                }
530
531                int numConfigs = index[0];
532                if (numConfigs <= 0) {
533                    throw new IllegalArgumentException("No configs match configSpec");
534                }
535
536                EGLConfig[] configs = new EGLConfig[numConfigs];
537                if (!egl.eglChooseConfig(display, mConfigSpec, configs, numConfigs, index)) {
538                    throw new IllegalArgumentException("eglChooseConfig failed "
539                            + getEGLErrorString(egl.eglGetError()));
540                }
541
542                EGLConfig config = chooseConfig(egl, display, configs);
543                if (config == null) {
544                    throw new IllegalArgumentException("No config chosen");
545                }
546
547                return config;
548            }
549
550            abstract EGLConfig chooseConfig(EGL10 egl, EGLDisplay display, EGLConfig[] configs);
551
552            private int[] filterConfigSpec(int[] configSpec) {
553                if (mGlVersion != 2) {
554                    return configSpec;
555                }
556                /* We know none of the subclasses define EGL_RENDERABLE_TYPE.
557                 * And we know the configSpec is well formed.
558                 */
559                int len = configSpec.length;
560                int[] newConfigSpec = new int[len + 2];
561                System.arraycopy(configSpec, 0, newConfigSpec, 0, len - 1);
562                newConfigSpec[len - 1] = EGL10.EGL_RENDERABLE_TYPE;
563                newConfigSpec[len] = 4; /* EGL_OPENGL_ES2_BIT */
564                newConfigSpec[len + 1] = EGL10.EGL_NONE;
565                return newConfigSpec;
566            }
567        }
568
569        /**
570         * Choose a configuration with exactly the specified r,g,b,a sizes,
571         * and at least the specified depth and stencil sizes.
572         */
573        static class ComponentSizeChooser extends EglConfigChooser {
574            private int[] mValue;
575
576            private int mRedSize;
577            private int mGreenSize;
578            private int mBlueSize;
579            private int mAlphaSize;
580            private int mDepthSize;
581            private int mStencilSize;
582
583            ComponentSizeChooser(int glVersion, int redSize, int greenSize, int blueSize,
584                    int alphaSize, int depthSize, int stencilSize) {
585                super(glVersion, new int[] {
586                        EGL10.EGL_RED_SIZE, redSize,
587                        EGL10.EGL_GREEN_SIZE, greenSize,
588                        EGL10.EGL_BLUE_SIZE, blueSize,
589                        EGL10.EGL_ALPHA_SIZE, alphaSize,
590                        EGL10.EGL_DEPTH_SIZE, depthSize,
591                        EGL10.EGL_STENCIL_SIZE, stencilSize,
592                        EGL10.EGL_NONE });
593                mValue = new int[1];
594                mRedSize = redSize;
595                mGreenSize = greenSize;
596                mBlueSize = blueSize;
597                mAlphaSize = alphaSize;
598                mDepthSize = depthSize;
599                mStencilSize = stencilSize;
600           }
601
602            @Override
603            EGLConfig chooseConfig(EGL10 egl, EGLDisplay display, EGLConfig[] configs) {
604                for (EGLConfig config : configs) {
605                    int d = findConfigAttrib(egl, display, config, EGL10.EGL_DEPTH_SIZE, 0);
606                    int s = findConfigAttrib(egl, display, config, EGL10.EGL_STENCIL_SIZE, 0);
607                    if (d >= mDepthSize && s >= mStencilSize) {
608                        int r = findConfigAttrib(egl, display, config, EGL10.EGL_RED_SIZE, 0);
609                        int g = findConfigAttrib(egl, display, config, EGL10.EGL_GREEN_SIZE, 0);
610                        int b = findConfigAttrib(egl, display, config, EGL10.EGL_BLUE_SIZE, 0);
611                        int a = findConfigAttrib(egl, display, config, EGL10.EGL_ALPHA_SIZE, 0);
612                        if (r >= mRedSize && g >= mGreenSize && b >= mBlueSize && a >= mAlphaSize) {
613                            return config;
614                        }
615                    }
616                }
617                return null;
618            }
619
620            private int findConfigAttrib(EGL10 egl, EGLDisplay display, EGLConfig config,
621                    int attribute, int defaultValue) {
622                if (egl.eglGetConfigAttrib(display, config, attribute, mValue)) {
623                    return mValue[0];
624                }
625
626                return defaultValue;
627            }
628        }
629    }
630
631    /**
632     * Hardware renderer using OpenGL ES 2.0.
633     */
634    static class Gl20Renderer extends GlRenderer {
635        private GLES20Canvas mGlCanvas;
636
637        Gl20Renderer(boolean translucent) {
638            super(2, translucent);
639        }
640
641        @Override
642        GLES20Canvas createCanvas() {
643            return mGlCanvas = new GLES20Canvas(mTranslucent);
644        }
645
646        @Override
647        void onPreDraw() {
648            mGlCanvas.onPreDraw();
649        }
650
651        @Override
652        void onPostDraw() {
653            mGlCanvas.onPostDraw();
654        }
655
656        @Override
657        DisplayList createDisplayList() {
658            return new GLES20DisplayList();
659        }
660
661        static HardwareRenderer create(boolean translucent) {
662            if (GLES20Canvas.isAvailable()) {
663                return new Gl20Renderer(translucent);
664            }
665            return null;
666        }
667    }
668}
669