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