HardwareRenderer.java revision 8fb954263dd2f918ad339045cc6d82e346515599
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.Log;
23
24import javax.microedition.khronos.egl.EGL10;
25import javax.microedition.khronos.egl.EGL11;
26import javax.microedition.khronos.egl.EGLConfig;
27import javax.microedition.khronos.egl.EGLContext;
28import javax.microedition.khronos.egl.EGLDisplay;
29import javax.microedition.khronos.egl.EGLSurface;
30import javax.microedition.khronos.opengles.GL;
31
32/**
33 * Interface for rendering a ViewRoot using hardware acceleration.
34 *
35 * @hide
36 */
37public abstract class HardwareRenderer {
38    private boolean mEnabled;
39    private boolean mRequested = true;
40    private static final String LOG_TAG = "HardwareRenderer";
41
42    /**
43     * Indicates whether hardware acceleration is available under any form for
44     * the view hierarchy.
45     *
46     * @return True if the view hierarchy can potentially be hardware accelerated,
47     *         false otherwise
48     */
49    public static boolean isAvailable() {
50        return GLES20Canvas.isAvailable();
51    }
52
53    /**
54     * Destroys the hardware rendering context.
55     */
56    abstract void destroy();
57
58    /**
59     * Initializes the hardware renderer for the specified surface.
60     *
61     * @param holder The holder for the surface to hardware accelerate.
62     *
63     * @return True if the initialization was successful, false otherwise.
64     */
65    abstract boolean initialize(SurfaceHolder holder);
66
67    /**
68     * Setup the hardware renderer for drawing. This is called for every
69     * frame to draw.
70     *
71     * @param width Width of the drawing surface.
72     * @param height Height of the drawing surface.
73     * @param attachInfo The AttachInfo used to render the ViewRoot.
74     */
75    abstract void setup(int width, int height, View.AttachInfo attachInfo);
76
77    /**
78     * Draws the specified view.
79     *
80     * @param view The view to draw.
81     * @param attachInfo AttachInfo tied to the specified view.
82     */
83    abstract void draw(View view, View.AttachInfo attachInfo, int yOffset);
84
85    /**
86     * Initializes the hardware renderer for the specified surface and setup the
87     * renderer for drawing, if needed. This is invoked when the ViewRoot has
88     * potentially lost the hardware renderer. The hardware renderer should be
89     * reinitialized and setup when the render {@link #isRequested()} and
90     * {@link #isEnabled()}.
91     *
92     * @param width The width of the drawing surface.
93     * @param height The height of the drawing surface.
94     * @param attachInfo The
95     * @param holder
96     */
97    void initializeIfNeeded(int width, int height, View.AttachInfo attachInfo,
98            SurfaceHolder holder) {
99
100        if (isRequested()) {
101            // We lost the gl context, so recreate it.
102            if (!isEnabled()) {
103                if (initialize(holder)) {
104                    setup(width, height, attachInfo);
105                }
106            }
107        }
108    }
109
110    /**
111     * Creates a hardware renderer using OpenGL.
112     *
113     * @param glVersion The version of OpenGL to use (1 for OpenGL 1, 11 for OpenGL 1.1, etc.)
114     * @param translucent True if the surface is translucent, false otherwise
115     *
116     * @return A hardware renderer backed by OpenGL.
117     */
118    static HardwareRenderer createGlRenderer(int glVersion, boolean translucent) {
119        switch (glVersion) {
120            case 2:
121                return Gl20Renderer.create(translucent);
122        }
123        throw new IllegalArgumentException("Unknown GL version: " + glVersion);
124    }
125
126    /**
127     * Indicates whether hardware acceleration is currently enabled.
128     *
129     * @return True if hardware acceleration is in use, false otherwise.
130     */
131    boolean isEnabled() {
132        return mEnabled;
133    }
134
135    /**
136     * Indicates whether hardware acceleration is currently enabled.
137     *
138     * @param enabled True if the hardware renderer is in use, false otherwise.
139     */
140    void setEnabled(boolean enabled) {
141        mEnabled = enabled;
142    }
143
144    /**
145     * Indicates whether hardware acceleration is currently request but not
146     * necessarily enabled yet.
147     *
148     * @return True if requested, false otherwise.
149     */
150    boolean isRequested() {
151        return mRequested;
152    }
153
154    /**
155     * Indicates whether hardware acceleration is currently request but not
156     * necessarily enabled yet.
157     *
158     * @return True to request hardware acceleration, false otherwise.
159     */
160    void setRequested(boolean requested) {
161        mRequested = requested;
162    }
163
164    @SuppressWarnings({"deprecation"})
165    static abstract class GlRenderer extends HardwareRenderer {
166        private static final int EGL_CONTEXT_CLIENT_VERSION = 0x3098;
167
168        EGL10 mEgl;
169        EGLDisplay mEglDisplay;
170        EGLContext mEglContext;
171        EGLSurface mEglSurface;
172        EGLConfig mEglConfig;
173
174        GL mGl;
175        Canvas mCanvas;
176
177        final int mGlVersion;
178        final boolean mTranslucent;
179
180        GlRenderer(int glVersion, boolean translucent) {
181            mGlVersion = glVersion;
182            mTranslucent = translucent;
183        }
184
185        /**
186         * Checks for OpenGL errors. If an error has occured, {@link #destroy()}
187         * is invoked and the requested flag is turned off. The error code is
188         * also logged as a warning.
189         */
190        void checkErrors() {
191            if (isEnabled()) {
192                int error = mEgl.eglGetError();
193                if (error != EGL10.EGL_SUCCESS) {
194                    // something bad has happened revert to
195                    // normal rendering.
196                    destroy();
197                    if (error != EGL11.EGL_CONTEXT_LOST) {
198                        // we'll try again if it was context lost
199                        setRequested(false);
200                    }
201                    Log.w(LOG_TAG, "OpenGL error: " + error);
202                }
203            }
204        }
205
206        @Override
207        boolean initialize(SurfaceHolder holder) {
208            if (isRequested() && !isEnabled()) {
209                initializeEgl();
210                mGl = createEglSurface(holder);
211
212                if (mGl != null) {
213                    int err = mEgl.eglGetError();
214                    if (err != EGL10.EGL_SUCCESS) {
215                        destroy();
216                        setRequested(false);
217                    } else {
218                        mCanvas = createCanvas();
219                        if (mCanvas != null) {
220                            setEnabled(true);
221                        } else {
222                            Log.w(LOG_TAG, "Hardware accelerated Canvas could not be created");
223                        }
224                    }
225
226                    return mCanvas != null;
227                }
228            }
229            return false;
230        }
231
232        abstract Canvas createCanvas();
233
234        void initializeEgl() {
235            mEgl = (EGL10) EGLContext.getEGL();
236
237            // Get to the default display.
238            mEglDisplay = mEgl.eglGetDisplay(EGL10.EGL_DEFAULT_DISPLAY);
239
240            if (mEglDisplay == EGL10.EGL_NO_DISPLAY) {
241                throw new RuntimeException("eglGetDisplay failed");
242            }
243
244            // We can now initialize EGL for that display
245            int[] version = new int[2];
246            if (!mEgl.eglInitialize(mEglDisplay, version)) {
247                throw new RuntimeException("eglInitialize failed");
248            }
249            mEglConfig = getConfigChooser(mGlVersion).chooseConfig(mEgl, mEglDisplay);
250
251            /*
252            * Create an EGL context. We want to do this as rarely as we can, because an
253            * EGL context is a somewhat heavy object.
254            */
255            mEglContext = createContext(mEgl, mEglDisplay, mEglConfig);
256        }
257
258        GL createEglSurface(SurfaceHolder holder) {
259            // Check preconditions.
260            if (mEgl == null) {
261                throw new RuntimeException("egl not initialized");
262            }
263            if (mEglDisplay == null) {
264                throw new RuntimeException("eglDisplay not initialized");
265            }
266            if (mEglConfig == null) {
267                throw new RuntimeException("mEglConfig not initialized");
268            }
269
270            /*
271             *  The window size has changed, so we need to create a new
272             *  surface.
273             */
274            if (mEglSurface != null && mEglSurface != EGL10.EGL_NO_SURFACE) {
275
276                /*
277                 * Unbind and destroy the old EGL surface, if
278                 * there is one.
279                 */
280                mEgl.eglMakeCurrent(mEglDisplay, EGL10.EGL_NO_SURFACE,
281                        EGL10.EGL_NO_SURFACE, EGL10.EGL_NO_CONTEXT);
282                mEgl.eglDestroySurface(mEglDisplay, mEglSurface);
283            }
284
285            // Create an EGL surface we can render into.
286            mEglSurface = mEgl.eglCreateWindowSurface(mEglDisplay, mEglConfig, holder, null);
287
288            if (mEglSurface == null || mEglSurface == EGL10.EGL_NO_SURFACE) {
289                int error = mEgl.eglGetError();
290                if (error == EGL10.EGL_BAD_NATIVE_WINDOW) {
291                    Log.e("EglHelper", "createWindowSurface returned EGL_BAD_NATIVE_WINDOW.");
292                    return null;
293                }
294                throw new RuntimeException("createWindowSurface failed");
295            }
296
297            /*
298             * Before we can issue GL commands, we need to make sure
299             * the context is current and bound to a surface.
300             */
301            if (!mEgl.eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface, mEglContext)) {
302                throw new RuntimeException("eglMakeCurrent failed");
303            }
304
305            return mEglContext.getGL();
306        }
307
308        EGLContext createContext(EGL10 egl, EGLDisplay eglDisplay, EGLConfig eglConfig) {
309            int[] attrib_list = { EGL_CONTEXT_CLIENT_VERSION, mGlVersion, EGL10.EGL_NONE };
310
311            return egl.eglCreateContext(eglDisplay, eglConfig, EGL10.EGL_NO_CONTEXT,
312                    mGlVersion != 0 ? attrib_list : null);
313        }
314
315        @Override
316        void initializeIfNeeded(int width, int height, View.AttachInfo attachInfo,
317                SurfaceHolder holder) {
318
319            if (isRequested()) {
320                checkErrors();
321                super.initializeIfNeeded(width, height, attachInfo, holder);
322            }
323        }
324
325        @Override
326        void destroy() {
327            if (!isEnabled()) return;
328
329            mEgl.eglMakeCurrent(mEglDisplay, EGL10.EGL_NO_SURFACE,
330                    EGL10.EGL_NO_SURFACE, EGL10.EGL_NO_CONTEXT);
331            mEgl.eglDestroyContext(mEglDisplay, mEglContext);
332            mEgl.eglDestroySurface(mEglDisplay, mEglSurface);
333            mEgl.eglTerminate(mEglDisplay);
334
335            mEglContext = null;
336            mEglSurface = null;
337            mEglDisplay = null;
338            mEgl = null;
339            mGl = null;
340            mCanvas = null;
341
342            setEnabled(false);
343        }
344
345        @Override
346        void setup(int width, int height, View.AttachInfo attachInfo) {
347            final float scale = attachInfo.mApplicationScale;
348            mCanvas.setViewport((int) (width * scale + 0.5f), (int) (height * scale + 0.5f));
349        }
350
351        boolean canDraw() {
352            return mGl != null && mCanvas != null;
353        }
354
355        void onPreDraw() {
356        }
357
358        /**
359         * Defines the EGL configuration for this renderer. The default configuration
360         * is RGBX, no depth, no stencil.
361         *
362         * @return An {@link android.view.HardwareRenderer.GlRenderer.EglConfigChooser}.
363         * @param glVersion
364         */
365        EglConfigChooser getConfigChooser(int glVersion) {
366            return new ComponentSizeChooser(glVersion, 8, 8, 8, mTranslucent ? 8 : 0, 0, 0);
367        }
368
369        @Override
370        void draw(View view, View.AttachInfo attachInfo, int yOffset) {
371            if (canDraw()) {
372                attachInfo.mDrawingTime = SystemClock.uptimeMillis();
373                attachInfo.mIgnoreDirtyState = true;
374                view.mPrivateFlags |= View.DRAWN;
375
376                // TODO: Don't check the current context when we have one per UI thread
377                // TODO: Use a threadlocal flag to know whether the surface has changed
378                if (mEgl.eglGetCurrentContext() != mEglContext ||
379                        mEgl.eglGetCurrentSurface(EGL10.EGL_DRAW) != mEglSurface) {
380                    if (!mEgl.eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface, mEglContext)) {
381                        throw new RuntimeException("eglMakeCurrent failed");
382                    }
383                }
384
385                onPreDraw();
386
387                Canvas canvas = mCanvas;
388                int saveCount = canvas.save();
389                canvas.translate(0, -yOffset);
390
391                try {
392                    view.draw(canvas);
393                } finally {
394                    canvas.restoreToCount(saveCount);
395                }
396
397                attachInfo.mIgnoreDirtyState = false;
398
399                mEgl.eglSwapBuffers(mEglDisplay, mEglSurface);
400                checkErrors();
401            }
402        }
403
404        static abstract class EglConfigChooser {
405            final int[] mConfigSpec;
406            private final int mGlVersion;
407
408            EglConfigChooser(int glVersion, int[] configSpec) {
409                mGlVersion = glVersion;
410                mConfigSpec = filterConfigSpec(configSpec);
411            }
412
413            EGLConfig chooseConfig(EGL10 egl, EGLDisplay display) {
414                int[] index = new int[1];
415                if (!egl.eglChooseConfig(display, mConfigSpec, null, 0, index)) {
416                    throw new IllegalArgumentException("eglChooseConfig failed");
417                }
418
419                int numConfigs = index[0];
420                if (numConfigs <= 0) {
421                    throw new IllegalArgumentException("No configs match configSpec");
422                }
423
424                EGLConfig[] configs = new EGLConfig[numConfigs];
425                if (!egl.eglChooseConfig(display, mConfigSpec, configs, numConfigs, index)) {
426                    throw new IllegalArgumentException("eglChooseConfig failed");
427                }
428
429                EGLConfig config = chooseConfig(egl, display, configs);
430                if (config == null) {
431                    throw new IllegalArgumentException("No config chosen");
432                }
433
434                return config;
435            }
436
437            abstract EGLConfig chooseConfig(EGL10 egl, EGLDisplay display, EGLConfig[] configs);
438
439            private int[] filterConfigSpec(int[] configSpec) {
440                if (mGlVersion != 2) {
441                    return configSpec;
442                }
443                /* We know none of the subclasses define EGL_RENDERABLE_TYPE.
444                 * And we know the configSpec is well formed.
445                 */
446                int len = configSpec.length;
447                int[] newConfigSpec = new int[len + 2];
448                System.arraycopy(configSpec, 0, newConfigSpec, 0, len - 1);
449                newConfigSpec[len - 1] = EGL10.EGL_RENDERABLE_TYPE;
450                newConfigSpec[len] = 4; /* EGL_OPENGL_ES2_BIT */
451                newConfigSpec[len + 1] = EGL10.EGL_NONE;
452                return newConfigSpec;
453            }
454        }
455
456        /**
457         * Choose a configuration with exactly the specified r,g,b,a sizes,
458         * and at least the specified depth and stencil sizes.
459         */
460        static class ComponentSizeChooser extends EglConfigChooser {
461            private int[] mValue;
462
463            private int mRedSize;
464            private int mGreenSize;
465            private int mBlueSize;
466            private int mAlphaSize;
467            private int mDepthSize;
468            private int mStencilSize;
469
470            ComponentSizeChooser(int glVersion, int redSize, int greenSize, int blueSize,
471                    int alphaSize, int depthSize, int stencilSize) {
472                super(glVersion, new int[] {
473                        EGL10.EGL_RED_SIZE, redSize,
474                        EGL10.EGL_GREEN_SIZE, greenSize,
475                        EGL10.EGL_BLUE_SIZE, blueSize,
476                        EGL10.EGL_ALPHA_SIZE, alphaSize,
477                        EGL10.EGL_DEPTH_SIZE, depthSize,
478                        EGL10.EGL_STENCIL_SIZE, stencilSize,
479                        EGL10.EGL_NONE });
480                mValue = new int[1];
481                mRedSize = redSize;
482                mGreenSize = greenSize;
483                mBlueSize = blueSize;
484                mAlphaSize = alphaSize;
485                mDepthSize = depthSize;
486                mStencilSize = stencilSize;
487           }
488
489            @Override
490            EGLConfig chooseConfig(EGL10 egl, EGLDisplay display, EGLConfig[] configs) {
491                for (EGLConfig config : configs) {
492                    int d = findConfigAttrib(egl, display, config, EGL10.EGL_DEPTH_SIZE, 0);
493                    int s = findConfigAttrib(egl, display, config, EGL10.EGL_STENCIL_SIZE, 0);
494                    if (d >= mDepthSize && s >= mStencilSize) {
495                        int r = findConfigAttrib(egl, display, config, EGL10.EGL_RED_SIZE, 0);
496                        int g = findConfigAttrib(egl, display, config, EGL10.EGL_GREEN_SIZE, 0);
497                        int b = findConfigAttrib(egl, display, config, EGL10.EGL_BLUE_SIZE, 0);
498                        int a = findConfigAttrib(egl, display, config, EGL10.EGL_ALPHA_SIZE, 0);
499                        if (r == mRedSize && g == mGreenSize && b == mBlueSize && a >= mAlphaSize) {
500                            return config;
501                        }
502                    }
503                }
504                return null;
505            }
506
507            private int findConfigAttrib(EGL10 egl, EGLDisplay display, EGLConfig config,
508                    int attribute, int defaultValue) {
509                if (egl.eglGetConfigAttrib(display, config, attribute, mValue)) {
510                    return mValue[0];
511                }
512
513                return defaultValue;
514            }
515        }
516    }
517
518    /**
519     * Hardware renderer using OpenGL ES 2.0.
520     */
521    static class Gl20Renderer extends GlRenderer {
522        private GLES20Canvas mGlCanvas;
523
524        Gl20Renderer(boolean translucent) {
525            super(2, translucent);
526        }
527
528        @Override
529        Canvas createCanvas() {
530            return mGlCanvas = new GLES20Canvas(mGl, mTranslucent);
531        }
532
533        @Override
534        void onPreDraw() {
535            mGlCanvas.onPreDraw();
536        }
537
538        static HardwareRenderer create(boolean translucent) {
539            if (GLES20Canvas.isAvailable()) {
540                return new Gl20Renderer(translucent);
541            }
542            return null;
543        }
544    }
545}
546