HardwareRenderer.java revision b8c0de2c2726f4e8f0029710047efe0c8e6661ed
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                if (checkCurrent()) {
482                    onPreDraw();
483
484                    Canvas canvas = mCanvas;
485                    int saveCount = canvas.save();
486                    callbacks.onHardwarePreDraw(canvas);
487
488                    try {
489                        view.draw(canvas);
490                    } finally {
491                        callbacks.onHardwarePostDraw(canvas);
492                        canvas.restoreToCount(saveCount);
493                    }
494
495                    onPostDraw();
496
497                    if (ViewDebug.DEBUG_PROFILE_DRAWING) {
498                        EventLog.writeEvent(60000, SystemClock.elapsedRealtime() - startTime);
499                    }
500
501                    attachInfo.mIgnoreDirtyState = false;
502
503                    sEgl.eglSwapBuffers(sEglDisplay, mEglSurface);
504                    checkEglErrors();
505                }
506            }
507        }
508
509        private boolean 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                    return false;
519                }
520            }
521            return true;
522        }
523
524        static abstract class EglConfigChooser {
525            final int[] mConfigSpec;
526            private final int mGlVersion;
527
528            EglConfigChooser(int glVersion, int[] configSpec) {
529                mGlVersion = glVersion;
530                mConfigSpec = filterConfigSpec(configSpec);
531            }
532
533            EGLConfig chooseConfig(EGL10 egl, EGLDisplay display) {
534                int[] index = new int[1];
535                if (!egl.eglChooseConfig(display, mConfigSpec, null, 0, index)) {
536                    throw new IllegalArgumentException("eglChooseConfig failed "
537                            + getEGLErrorString(egl.eglGetError()));
538                }
539
540                int numConfigs = index[0];
541                if (numConfigs <= 0) {
542                    throw new IllegalArgumentException("No configs match configSpec");
543                }
544
545                EGLConfig[] configs = new EGLConfig[numConfigs];
546                if (!egl.eglChooseConfig(display, mConfigSpec, configs, numConfigs, index)) {
547                    throw new IllegalArgumentException("eglChooseConfig failed "
548                            + getEGLErrorString(egl.eglGetError()));
549                }
550
551                EGLConfig config = chooseConfig(egl, display, configs);
552                if (config == null) {
553                    throw new IllegalArgumentException("No config chosen");
554                }
555
556                return config;
557            }
558
559            abstract EGLConfig chooseConfig(EGL10 egl, EGLDisplay display, EGLConfig[] configs);
560
561            private int[] filterConfigSpec(int[] configSpec) {
562                if (mGlVersion != 2) {
563                    return configSpec;
564                }
565                /* We know none of the subclasses define EGL_RENDERABLE_TYPE.
566                 * And we know the configSpec is well formed.
567                 */
568                int len = configSpec.length;
569                int[] newConfigSpec = new int[len + 2];
570                System.arraycopy(configSpec, 0, newConfigSpec, 0, len - 1);
571                newConfigSpec[len - 1] = EGL10.EGL_RENDERABLE_TYPE;
572                newConfigSpec[len] = 4; /* EGL_OPENGL_ES2_BIT */
573                newConfigSpec[len + 1] = EGL10.EGL_NONE;
574                return newConfigSpec;
575            }
576        }
577
578        /**
579         * Choose a configuration with exactly the specified r,g,b,a sizes,
580         * and at least the specified depth and stencil sizes.
581         */
582        static class ComponentSizeChooser extends EglConfigChooser {
583            private int[] mValue;
584
585            private int mRedSize;
586            private int mGreenSize;
587            private int mBlueSize;
588            private int mAlphaSize;
589            private int mDepthSize;
590            private int mStencilSize;
591
592            ComponentSizeChooser(int glVersion, int redSize, int greenSize, int blueSize,
593                    int alphaSize, int depthSize, int stencilSize) {
594                super(glVersion, new int[] {
595                        EGL10.EGL_RED_SIZE, redSize,
596                        EGL10.EGL_GREEN_SIZE, greenSize,
597                        EGL10.EGL_BLUE_SIZE, blueSize,
598                        EGL10.EGL_ALPHA_SIZE, alphaSize,
599                        EGL10.EGL_DEPTH_SIZE, depthSize,
600                        EGL10.EGL_STENCIL_SIZE, stencilSize,
601                        EGL10.EGL_NONE });
602                mValue = new int[1];
603                mRedSize = redSize;
604                mGreenSize = greenSize;
605                mBlueSize = blueSize;
606                mAlphaSize = alphaSize;
607                mDepthSize = depthSize;
608                mStencilSize = stencilSize;
609           }
610
611            @Override
612            EGLConfig chooseConfig(EGL10 egl, EGLDisplay display, EGLConfig[] configs) {
613                for (EGLConfig config : configs) {
614                    int d = findConfigAttrib(egl, display, config, EGL10.EGL_DEPTH_SIZE, 0);
615                    int s = findConfigAttrib(egl, display, config, EGL10.EGL_STENCIL_SIZE, 0);
616                    if (d >= mDepthSize && s >= mStencilSize) {
617                        int r = findConfigAttrib(egl, display, config, EGL10.EGL_RED_SIZE, 0);
618                        int g = findConfigAttrib(egl, display, config, EGL10.EGL_GREEN_SIZE, 0);
619                        int b = findConfigAttrib(egl, display, config, EGL10.EGL_BLUE_SIZE, 0);
620                        int a = findConfigAttrib(egl, display, config, EGL10.EGL_ALPHA_SIZE, 0);
621                        if (r >= mRedSize && g >= mGreenSize && b >= mBlueSize && a >= mAlphaSize) {
622                            return config;
623                        }
624                    }
625                }
626                return null;
627            }
628
629            private int findConfigAttrib(EGL10 egl, EGLDisplay display, EGLConfig config,
630                    int attribute, int defaultValue) {
631                if (egl.eglGetConfigAttrib(display, config, attribute, mValue)) {
632                    return mValue[0];
633                }
634
635                return defaultValue;
636            }
637        }
638    }
639
640    /**
641     * Hardware renderer using OpenGL ES 2.0.
642     */
643    static class Gl20Renderer extends GlRenderer {
644        private GLES20Canvas mGlCanvas;
645
646        Gl20Renderer(boolean translucent) {
647            super(2, translucent);
648        }
649
650        @Override
651        GLES20Canvas createCanvas() {
652            return mGlCanvas = new GLES20Canvas(mTranslucent);
653        }
654
655        @Override
656        boolean canDraw() {
657            return super.canDraw() && mGlCanvas != null;
658        }
659
660        @Override
661        void onPreDraw() {
662            mGlCanvas.onPreDraw();
663        }
664
665        @Override
666        void onPostDraw() {
667            mGlCanvas.onPostDraw();
668        }
669
670        @Override
671        void destroy(boolean full) {
672            try {
673                super.destroy(full);
674            } finally {
675                if (full && mGlCanvas != null) {
676                    mGlCanvas = null;
677                }
678            }
679        }
680
681        @Override
682        DisplayList createDisplayList() {
683            return new GLES20DisplayList();
684        }
685
686        static HardwareRenderer create(boolean translucent) {
687            if (GLES20Canvas.isAvailable()) {
688                return new Gl20Renderer(translucent);
689            }
690            return null;
691        }
692    }
693}
694