HardwareRenderer.java revision f86ef57f8bcd8ba43ce222ec6a8b4f67d3600640
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 density The density of the application
80     * @param scalingRequired Whether drawing should be scaled.
81     */
82    abstract void draw(View view, View.AttachInfo attachInfo,
83            CompatibilityInfo.Translator translator, int yoff, int density,
84            boolean scalingRequired);
85
86    /**
87     * Initializes the hardware renderer for the specified surface and setup the
88     * renderer for drawing, if needed. This is invoked when the ViewRoot has
89     * potentially lost the hardware renderer. The hardware renderer should be
90     * reinitialized and setup when the render {@link #isRequested()} and
91     * {@link #isEnabled()}.
92     *
93     * @param width The width of the drawing surface.
94     * @param height The height of the drawing surface.
95     * @param attachInfo The
96     * @param holder
97     */
98    void initializeIfNeeded(int width, int height, View.AttachInfo attachInfo,
99            SurfaceHolder holder) {
100
101        if (isRequested()) {
102            // We lost the gl context, so recreate it.
103            if (!isEnabled()) {
104                if (initialize(holder)) {
105                    setup(width, height, attachInfo);
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 1:
122                return new Gl10Renderer(translucent);
123            case 2:
124                return new Gl20Renderer(translucent);
125        }
126        throw new IllegalArgumentException("Unknown GL version: " + glVersion);
127    }
128
129    /**
130     * Indicates whether hardware acceleration is currently enabled.
131     *
132     * @return True if hardware acceleration is in use, false otherwise.
133     */
134    boolean isEnabled() {
135        return mEnabled;
136    }
137
138    /**
139     * Indicates whether hardware acceleration is currently enabled.
140     *
141     * @param enabled True if the hardware renderer is in use, false otherwise.
142     */
143    void setEnabled(boolean enabled) {
144        mEnabled = enabled;
145    }
146
147    /**
148     * Indicates whether hardware acceleration is currently request but not
149     * necessarily enabled yet.
150     *
151     * @return True if requested, false otherwise.
152     */
153    boolean isRequested() {
154        return mRequested;
155    }
156
157    /**
158     * Indicates whether hardware acceleration is currently request but not
159     * necessarily enabled yet.
160     *
161     * @return True to request hardware acceleration, false otherwise.
162     */
163    void setRequested(boolean requested) {
164        mRequested = requested;
165    }
166
167    @SuppressWarnings({"deprecation"})
168    static abstract class GlRenderer extends HardwareRenderer {
169        private static final int EGL_CONTEXT_CLIENT_VERSION = 0x3098;
170
171        EGL10 mEgl;
172        EGLDisplay mEglDisplay;
173        EGLContext mEglContext;
174        EGLSurface mEglSurface;
175        EGLConfig mEglConfig;
176
177        GL mGl;
178        Canvas mCanvas;
179
180        final int mGlVersion;
181        final boolean mTranslucent;
182
183        GlRenderer(int glVersion, boolean translucent) {
184            mGlVersion = glVersion;
185            mTranslucent = translucent;
186        }
187
188        /**
189         * Checks for OpenGL errors. If an error has occured, {@link #destroy()}
190         * is invoked and the requested flag is turned off. The error code is
191         * also logged as a warning.
192         */
193        void checkErrors() {
194            if (isEnabled()) {
195                int error = mEgl.eglGetError();
196                if (error != EGL10.EGL_SUCCESS) {
197                    // something bad has happened revert to
198                    // normal rendering.
199                    destroy();
200                    if (error != EGL11.EGL_CONTEXT_LOST) {
201                        // we'll try again if it was context lost
202                        setRequested(false);
203                    }
204                    Log.w(LOG_TAG, "OpenGL error: " + error);
205                }
206            }
207        }
208
209        @Override
210        boolean initialize(SurfaceHolder holder) {
211            if (isRequested() && !isEnabled()) {
212                initializeEgl();
213                mGl = createEglSurface(holder);
214
215                if (mGl != null) {
216                    int err = mEgl.eglGetError();
217                    if (err != EGL10.EGL_SUCCESS) {
218                        destroy();
219                        setRequested(false);
220                    } else {
221                        mCanvas = createCanvas();
222                        if (mCanvas != null) {
223                            setEnabled(true);
224                        } else {
225                            Log.w(LOG_TAG, "Hardware accelerated Canvas could not be created");
226                        }
227                    }
228
229                    return mCanvas != null;
230                }
231            }
232            return false;
233        }
234
235        abstract Canvas createCanvas();
236
237        void initializeEgl() {
238            mEgl = (EGL10) EGLContext.getEGL();
239
240            // Get to the default display.
241            mEglDisplay = mEgl.eglGetDisplay(EGL10.EGL_DEFAULT_DISPLAY);
242
243            if (mEglDisplay == EGL10.EGL_NO_DISPLAY) {
244                throw new RuntimeException("eglGetDisplay failed");
245            }
246
247            // We can now initialize EGL for that display
248            int[] version = new int[2];
249            if (!mEgl.eglInitialize(mEglDisplay, version)) {
250                throw new RuntimeException("eglInitialize failed");
251            }
252            mEglConfig = getConfigChooser(mGlVersion).chooseConfig(mEgl, mEglDisplay);
253
254            /*
255            * Create an EGL context. We want to do this as rarely as we can, because an
256            * EGL context is a somewhat heavy object.
257            */
258            mEglContext = createContext(mEgl, mEglDisplay, mEglConfig);
259        }
260
261        GL createEglSurface(SurfaceHolder holder) {
262            // Check preconditions.
263            if (mEgl == null) {
264                throw new RuntimeException("egl not initialized");
265            }
266            if (mEglDisplay == null) {
267                throw new RuntimeException("eglDisplay not initialized");
268            }
269            if (mEglConfig == null) {
270                throw new RuntimeException("mEglConfig not initialized");
271            }
272
273            /*
274             *  The window size has changed, so we need to create a new
275             *  surface.
276             */
277            if (mEglSurface != null && mEglSurface != EGL10.EGL_NO_SURFACE) {
278
279                /*
280                 * Unbind and destroy the old EGL surface, if
281                 * there is one.
282                 */
283                mEgl.eglMakeCurrent(mEglDisplay, EGL10.EGL_NO_SURFACE,
284                        EGL10.EGL_NO_SURFACE, EGL10.EGL_NO_CONTEXT);
285                mEgl.eglDestroySurface(mEglDisplay, mEglSurface);
286            }
287
288            // Create an EGL surface we can render into.
289            mEglSurface = mEgl.eglCreateWindowSurface(mEglDisplay, mEglConfig, holder, null);
290
291            if (mEglSurface == null || mEglSurface == EGL10.EGL_NO_SURFACE) {
292                int error = mEgl.eglGetError();
293                if (error == EGL10.EGL_BAD_NATIVE_WINDOW) {
294                    Log.e("EglHelper", "createWindowSurface returned EGL_BAD_NATIVE_WINDOW.");
295                    return null;
296                }
297                throw new RuntimeException("createWindowSurface failed");
298            }
299
300            /*
301             * Before we can issue GL commands, we need to make sure
302             * the context is current and bound to a surface.
303             */
304            if (!mEgl.eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface, mEglContext)) {
305                throw new RuntimeException("eglMakeCurrent failed");
306
307            }
308
309            return mEglContext.getGL();
310        }
311
312        EGLContext createContext(EGL10 egl, EGLDisplay eglDisplay, EGLConfig eglConfig) {
313            int[] attrib_list = { EGL_CONTEXT_CLIENT_VERSION, mGlVersion, EGL10.EGL_NONE };
314
315            return egl.eglCreateContext(eglDisplay, eglConfig, EGL10.EGL_NO_CONTEXT,
316                    mGlVersion != 0 ? attrib_list : null);
317        }
318
319        @Override
320        void initializeIfNeeded(int width, int height, View.AttachInfo attachInfo,
321                SurfaceHolder holder) {
322
323            if (isRequested()) {
324                checkErrors();
325                super.initializeIfNeeded(width, height, attachInfo, holder);
326            }
327        }
328
329        @Override
330        void destroy() {
331            if (!isEnabled()) return;
332
333            mEgl.eglMakeCurrent(mEglDisplay, EGL10.EGL_NO_SURFACE,
334                    EGL10.EGL_NO_SURFACE, EGL10.EGL_NO_CONTEXT);
335            mEgl.eglDestroyContext(mEglDisplay, mEglContext);
336            mEgl.eglDestroySurface(mEglDisplay, mEglSurface);
337            mEgl.eglTerminate(mEglDisplay);
338
339            mEglContext = null;
340            mEglSurface = null;
341            mEglDisplay = null;
342            mEgl = null;
343            mGl = null;
344            mCanvas = null;
345
346            setEnabled(false);
347        }
348
349        @Override
350        void setup(int width, int height, View.AttachInfo attachInfo) {
351            final float scale = attachInfo.mApplicationScale;
352            mCanvas.setViewport((int) (width * scale + 0.5f), (int) (height * scale + 0.5f));
353        }
354
355        boolean canDraw() {
356            return mGl != null && mCanvas != null;
357        }
358
359        void onPreDraw() {
360        }
361
362        /**
363         * Defines the EGL configuration for this renderer. The default configuration
364         * is RGBX, no depth, no stencil.
365         *
366         * @return An {@link android.view.HardwareRenderer.GlRenderer.EglConfigChooser}.
367         * @param glVersion
368         */
369        EglConfigChooser getConfigChooser(int glVersion) {
370            return new ComponentSizeChooser(glVersion, 8, 8, 8, mTranslucent ? 8 : 0, 0, 0);
371        }
372
373        @Override
374        void draw(View view, View.AttachInfo attachInfo, CompatibilityInfo.Translator translator,
375                int yoff, int density, boolean scalingRequired) {
376
377            if (canDraw()) {
378                attachInfo.mDrawingTime = SystemClock.uptimeMillis();
379                attachInfo.mIgnoreDirtyState = true;
380                view.mPrivateFlags |= View.DRAWN;
381
382                onPreDraw();
383
384                Canvas canvas = mCanvas;
385                int saveCount = canvas.save(Canvas.MATRIX_SAVE_FLAG);
386                try {
387                    canvas.translate(0, -yoff);
388                    if (translator != null) {
389                        translator.translateCanvas(canvas);
390                    }
391                    canvas.setDensity(density);
392                    canvas.setScreenDensity(scalingRequired ? DisplayMetrics.DENSITY_DEVICE : 0);
393
394                    view.draw(canvas);
395                } finally {
396                    canvas.restoreToCount(saveCount);
397                }
398
399                attachInfo.mIgnoreDirtyState = false;
400
401                mEgl.eglSwapBuffers(mEglDisplay, mEglSurface);
402                checkErrors();
403            }
404        }
405
406        static abstract class EglConfigChooser {
407            final int[] mConfigSpec;
408            private final int mGlVersion;
409
410            EglConfigChooser(int glVersion, int[] configSpec) {
411                mGlVersion = glVersion;
412                mConfigSpec = filterConfigSpec(configSpec);
413            }
414
415            EGLConfig chooseConfig(EGL10 egl, EGLDisplay display) {
416                int[] index = new int[1];
417                if (!egl.eglChooseConfig(display, mConfigSpec, null, 0, index)) {
418                    throw new IllegalArgumentException("eglChooseConfig failed");
419                }
420
421                int numConfigs = index[0];
422                if (numConfigs <= 0) {
423                    throw new IllegalArgumentException("No configs match configSpec");
424                }
425
426                EGLConfig[] configs = new EGLConfig[numConfigs];
427                if (!egl.eglChooseConfig(display, mConfigSpec, configs, numConfigs, index)) {
428                    throw new IllegalArgumentException("eglChooseConfig failed");
429                }
430
431                EGLConfig config = chooseConfig(egl, display, configs);
432                if (config == null) {
433                    throw new IllegalArgumentException("No config chosen");
434                }
435
436                return config;
437            }
438
439            abstract EGLConfig chooseConfig(EGL10 egl, EGLDisplay display, 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 && a >= mAlphaSize) {
502                            return config;
503                        }
504                    }
505                }
506                return null;
507            }
508
509            private int findConfigAttrib(EGL10 egl, EGLDisplay display, EGLConfig config,
510                    int attribute, int defaultValue) {
511                if (egl.eglGetConfigAttrib(display, config, attribute, mValue)) {
512                    return mValue[0];
513                }
514
515                return defaultValue;
516            }
517        }
518    }
519
520    /**
521     * Hardware renderer using OpenGL ES 2.0.
522     */
523    static class Gl20Renderer extends GlRenderer {
524        private GLES20Canvas mGlCanvas;
525
526        Gl20Renderer(boolean translucent) {
527            super(2, translucent);
528        }
529
530        @Override
531        Canvas createCanvas() {
532            return mGlCanvas = new GLES20Canvas(mGl, mTranslucent);
533        }
534
535        @Override
536        void onPreDraw() {
537            mGlCanvas.onPreDraw();
538        }
539    }
540
541    /**
542     * Hardware renderer using OpenGL ES 1.0.
543     */
544    @SuppressWarnings({"deprecation"})
545    static class Gl10Renderer extends GlRenderer {
546        Gl10Renderer(boolean translucent) {
547            super(1, translucent);
548        }
549
550        @Override
551        Canvas createCanvas() {
552            return new Canvas(mGl);
553        }
554
555        @Override
556        void destroy() {
557            if (isEnabled()) {
558                nativeAbandonGlCaches();
559            }
560
561            super.destroy();
562        }
563
564        @Override
565        void onPreDraw() {
566            GL11 gl = (GL11) mGl;
567            gl.glDisable(GL_SCISSOR_TEST);
568            gl.glClearColor(0, 0, 0, 0);
569            gl.glClear(GL_COLOR_BUFFER_BIT);
570            gl.glEnable(GL_SCISSOR_TEST);
571        }
572    }
573
574    // Inform Skia to just abandon its texture cache IDs doesn't call glDeleteTextures
575    // Used only by the native Skia OpenGL ES 1.x implementation
576    private static native void nativeAbandonGlCaches();
577}
578