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