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