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