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