HardwareRenderer.java revision ce0537b80087a6225273040a987414b1dd081aa0
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[] num_config = new int[1];
417                if (!egl.eglChooseConfig(display, mConfigSpec, null, 0,
418                        num_config)) {
419                    throw new IllegalArgumentException("eglChooseConfig failed");
420                }
421
422                int numConfigs = num_config[0];
423
424                if (numConfigs <= 0) {
425                    throw new IllegalArgumentException(
426                            "No configs match configSpec");
427                }
428
429                EGLConfig[] configs = new EGLConfig[numConfigs];
430                if (!egl.eglChooseConfig(display, mConfigSpec, configs, numConfigs,
431                        num_config)) {
432                    throw new IllegalArgumentException("eglChooseConfig#2 failed");
433                }
434                EGLConfig config = chooseConfig(egl, display, configs);
435                if (config == null) {
436                    throw new IllegalArgumentException("No config chosen");
437                }
438                return config;
439            }
440
441            abstract EGLConfig chooseConfig(EGL10 egl, EGLDisplay display,
442                    EGLConfig[] configs);
443
444            private int[] filterConfigSpec(int[] configSpec) {
445                if (mGlVersion != 2) {
446                    return configSpec;
447                }
448                /* We know none of the subclasses define EGL_RENDERABLE_TYPE.
449                 * And we know the configSpec is well formed.
450                 */
451                int len = configSpec.length;
452                int[] newConfigSpec = new int[len + 2];
453                System.arraycopy(configSpec, 0, newConfigSpec, 0, len-1);
454                newConfigSpec[len-1] = EGL10.EGL_RENDERABLE_TYPE;
455                newConfigSpec[len] = 4; /* EGL_OPENGL_ES2_BIT */
456                newConfigSpec[len+1] = EGL10.EGL_NONE;
457                return newConfigSpec;
458            }
459        }
460
461        /**
462         * Choose a configuration with exactly the specified r,g,b,a sizes,
463         * and at least the specified depth and stencil sizes.
464         */
465        static class ComponentSizeChooser extends EglConfigChooser {
466            private int[] mValue;
467
468            private int mRedSize;
469            private int mGreenSize;
470            private int mBlueSize;
471            private int mAlphaSize;
472            private int mDepthSize;
473            private int mStencilSize;
474
475            ComponentSizeChooser(int glVersion, int redSize, int greenSize, int blueSize,
476                    int alphaSize, int depthSize, int stencilSize) {
477                super(glVersion, new int[] {
478                        EGL10.EGL_RED_SIZE, redSize,
479                        EGL10.EGL_GREEN_SIZE, greenSize,
480                        EGL10.EGL_BLUE_SIZE, blueSize,
481                        EGL10.EGL_ALPHA_SIZE, alphaSize,
482                        EGL10.EGL_DEPTH_SIZE, depthSize,
483                        EGL10.EGL_STENCIL_SIZE, stencilSize,
484                        EGL10.EGL_NONE });
485                mValue = new int[1];
486                mRedSize = redSize;
487                mGreenSize = greenSize;
488                mBlueSize = blueSize;
489                mAlphaSize = alphaSize;
490                mDepthSize = depthSize;
491                mStencilSize = stencilSize;
492           }
493
494            @Override
495            EGLConfig chooseConfig(EGL10 egl, EGLDisplay display, EGLConfig[] configs) {
496                for (EGLConfig config : configs) {
497                    int d = findConfigAttrib(egl, display, config, EGL10.EGL_DEPTH_SIZE, 0);
498                    int s = findConfigAttrib(egl, display, config, EGL10.EGL_STENCIL_SIZE, 0);
499                    if ((d >= mDepthSize) && (s >= mStencilSize)) {
500                        int r = findConfigAttrib(egl, display, config, EGL10.EGL_RED_SIZE, 0);
501                        int g = findConfigAttrib(egl, display, config, EGL10.EGL_GREEN_SIZE, 0);
502                        int b = findConfigAttrib(egl, display, config, EGL10.EGL_BLUE_SIZE, 0);
503                        int a = findConfigAttrib(egl, display, config, EGL10.EGL_ALPHA_SIZE, 0);
504                        if ((r == mRedSize) && (g == mGreenSize) && (b == mBlueSize) &&
505                                (a == mAlphaSize)) {
506                            return config;
507                        }
508                    }
509                }
510                return null;
511            }
512
513            private int findConfigAttrib(EGL10 egl, EGLDisplay display,
514                    EGLConfig config, int attribute, int defaultValue) {
515
516                if (egl.eglGetConfigAttrib(display, config, attribute, mValue)) {
517                    return mValue[0];
518                }
519
520                return defaultValue;
521            }
522        }
523    }
524
525    /**
526     * Hardware renderer using OpenGL ES 2.0.
527     */
528    static class Gl20Renderer extends GlRenderer {
529        private GLES20Canvas mGlCanvas;
530
531        Gl20Renderer(boolean translucent) {
532            super(2, translucent);
533        }
534
535        @Override
536        Canvas createCanvas() {
537            return mGlCanvas = new GLES20Canvas(mGl, mTranslucent);
538        }
539
540        @Override
541        void onPreDraw() {
542            mGlCanvas.onPreDraw();
543        }
544    }
545
546    /**
547     * Hardware renderer using OpenGL ES 1.0.
548     */
549    @SuppressWarnings({"deprecation"})
550    static class Gl10Renderer extends GlRenderer {
551        Gl10Renderer(boolean translucent) {
552            super(1, translucent);
553        }
554
555        @Override
556        Canvas createCanvas() {
557            return new Canvas(mGl);
558        }
559
560        @Override
561        void destroy() {
562            if (isEnabled()) {
563                nativeAbandonGlCaches();
564            }
565
566            super.destroy();
567        }
568
569        @Override
570        void onPreDraw() {
571            GL11 gl = (GL11) mGl;
572            gl.glDisable(GL_SCISSOR_TEST);
573            gl.glClearColor(0, 0, 0, 0);
574            gl.glClear(GL_COLOR_BUFFER_BIT);
575            gl.glEnable(GL_SCISSOR_TEST);
576        }
577    }
578
579    // Inform Skia to just abandon its texture cache IDs doesn't call glDeleteTextures
580    // Used only by the native Skia OpenGL ES 1.x implementation
581    private static native void nativeAbandonGlCaches();
582}
583