HardwareRenderer.java revision 9477c6e6581ce97976250951f33e1297604ac777
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.ComponentCallbacks2;
21import android.graphics.Paint;
22import android.graphics.Rect;
23import android.graphics.SurfaceTexture;
24import android.opengl.GLUtils;
25import android.opengl.ManagedEGLContext;
26import android.os.Handler;
27import android.os.Looper;
28import android.os.SystemClock;
29import android.os.SystemProperties;
30import android.util.Log;
31import com.google.android.gles_jni.EGLImpl;
32
33import javax.microedition.khronos.egl.EGL10;
34import javax.microedition.khronos.egl.EGL11;
35import javax.microedition.khronos.egl.EGLConfig;
36import javax.microedition.khronos.egl.EGLContext;
37import javax.microedition.khronos.egl.EGLDisplay;
38import javax.microedition.khronos.egl.EGLSurface;
39import javax.microedition.khronos.opengles.GL;
40
41import java.io.File;
42
43import static javax.microedition.khronos.egl.EGL10.*;
44
45/**
46 * Interface for rendering a ViewAncestor using hardware acceleration.
47 *
48 * @hide
49 */
50public abstract class HardwareRenderer {
51    static final String LOG_TAG = "HardwareRenderer";
52
53    /**
54     * Name of the file that holds the shaders cache.
55     */
56    private static final String CACHE_PATH_SHADERS = "com.android.opengl.shaders_cache";
57
58    /**
59     * Turn on to only refresh the parts of the screen that need updating.
60     * When turned on the property defined by {@link #RENDER_DIRTY_REGIONS_PROPERTY}
61     * must also have the value "true".
62     */
63    public static final boolean RENDER_DIRTY_REGIONS = true;
64
65    /**
66     * System property used to enable or disable dirty regions invalidation.
67     * This property is only queried if {@link #RENDER_DIRTY_REGIONS} is true.
68     * The default value of this property is assumed to be true.
69     *
70     * Possible values:
71     * "true", to enable partial invalidates
72     * "false", to disable partial invalidates
73     */
74    static final String RENDER_DIRTY_REGIONS_PROPERTY = "hwui.render_dirty_regions";
75
76    /**
77     * System property used to enable or disable vsync.
78     * The default value of this property is assumed to be false.
79     *
80     * Possible values:
81     * "true", to disable vsync
82     * "false", to enable vsync
83     */
84    static final String DISABLE_VSYNC_PROPERTY = "hwui.disable_vsync";
85
86    /**
87     * System property used to debug EGL configuration choice.
88     *
89     * Possible values:
90     * "choice", print the chosen configuration only
91     * "all", print all possible configurations
92     */
93    static final String PRINT_CONFIG_PROPERTY = "hwui.print_config";
94
95    /**
96     * Turn on to draw dirty regions every other frame.
97     */
98    private static final boolean DEBUG_DIRTY_REGION = false;
99
100    /**
101     * A process can set this flag to false to prevent the use of hardware
102     * rendering.
103     *
104     * @hide
105     */
106    public static boolean sRendererDisabled = false;
107
108    /**
109     * Further hardware renderer disabling for the system process.
110     *
111     * @hide
112     */
113    public static boolean sSystemRendererDisabled = false;
114
115    private boolean mEnabled;
116    private boolean mRequested = true;
117
118    /**
119     * Invoke this method to disable hardware rendering in the current process.
120     *
121     * @hide
122     */
123    public static void disable(boolean system) {
124        sRendererDisabled = true;
125        if (system) {
126            sSystemRendererDisabled = true;
127        }
128    }
129
130    /**
131     * Indicates whether hardware acceleration is available under any form for
132     * the view hierarchy.
133     *
134     * @return True if the view hierarchy can potentially be hardware accelerated,
135     *         false otherwise
136     */
137    public static boolean isAvailable() {
138        return GLES20Canvas.isAvailable();
139    }
140
141    /**
142     * Destroys the hardware rendering context.
143     *
144     * @param full If true, destroys all associated resources.
145     */
146    abstract void destroy(boolean full);
147
148    /**
149     * Initializes the hardware renderer for the specified surface.
150     *
151     * @param holder The holder for the surface to hardware accelerate.
152     *
153     * @return True if the initialization was successful, false otherwise.
154     */
155    abstract boolean initialize(SurfaceHolder holder) throws Surface.OutOfResourcesException;
156
157    /**
158     * Updates the hardware renderer for the specified surface.
159     *
160     * @param holder The holder for the surface to hardware accelerate
161     */
162    abstract void updateSurface(SurfaceHolder holder) throws Surface.OutOfResourcesException;
163
164    /**
165     * Destroys the layers used by the specified view hierarchy.
166     *
167     * @param view The root of the view hierarchy
168     */
169    abstract void destroyLayers(View view);
170
171    /**
172     * Destroys all hardware rendering resources associated with the specified
173     * view hierarchy.
174     *
175     * @param view The root of the view hierarchy
176     */
177    abstract void destroyHardwareResources(View view);
178
179    /**
180     * This method should be invoked whenever the current hardware renderer
181     * context should be reset.
182     *
183     * @param holder The holder for the surface to hardware accelerate
184     */
185    abstract void invalidate(SurfaceHolder holder);
186
187    /**
188     * This method should be invoked to ensure the hardware renderer is in
189     * valid state (for instance, to ensure the correct EGL context is bound
190     * to the current thread.)
191     *
192     * @return true if the renderer is now valid, false otherwise
193     */
194    abstract boolean validate();
195
196    /**
197     * Setup the hardware renderer for drawing. This is called whenever the
198     * size of the target surface changes or when the surface is first created.
199     *
200     * @param width Width of the drawing surface.
201     * @param height Height of the drawing surface.
202     */
203    abstract void setup(int width, int height);
204
205    /**
206     * Gets the current width of the surface. This is the width that the surface
207     * was last set to in a call to {@link #setup(int, int)}.
208     *
209     * @return the current width of the surface
210     */
211    abstract int getWidth();
212
213    /**
214     * Gets the current height of the surface. This is the height that the surface
215     * was last set to in a call to {@link #setup(int, int)}.
216     *
217     * @return the current width of the surface
218     */
219    abstract int getHeight();
220
221    /**
222     * Gets the current canvas associated with this HardwareRenderer.
223     *
224     * @return the current HardwareCanvas
225     */
226    abstract HardwareCanvas getCanvas();
227
228    /**
229     * Sets the directory to use as a persistent storage for hardware rendering
230     * resources.
231     *
232     * @param cacheDir A directory the current process can write to
233     */
234    public static void setupDiskCache(File cacheDir) {
235        nSetupShadersDiskCache(new File(cacheDir, CACHE_PATH_SHADERS).getAbsolutePath());
236    }
237
238    private static native void nSetupShadersDiskCache(String cacheFile);
239
240    /**
241     * Interface used to receive callbacks whenever a view is drawn by
242     * a hardware renderer instance.
243     */
244    interface HardwareDrawCallbacks {
245        /**
246         * Invoked before a view is drawn by a hardware renderer.
247         *
248         * @param canvas The Canvas used to render the view.
249         */
250        void onHardwarePreDraw(HardwareCanvas canvas);
251
252        /**
253         * Invoked after a view is drawn by a hardware renderer.
254         *
255         * @param canvas The Canvas used to render the view.
256         */
257        void onHardwarePostDraw(HardwareCanvas canvas);
258    }
259
260    /**
261     * Draws the specified view.
262     *
263     * @param view The view to draw.
264     * @param attachInfo AttachInfo tied to the specified view.
265     * @param callbacks Callbacks invoked when drawing happens.
266     * @param dirty The dirty rectangle to update, can be null.
267     *
268     * @return true if the dirty rect was ignored, false otherwise
269     */
270    abstract boolean draw(View view, View.AttachInfo attachInfo, HardwareDrawCallbacks callbacks,
271            Rect dirty);
272
273    /**
274     * Creates a new display list that can be used to record batches of
275     * drawing operations.
276     *
277     * @return A new display list.
278     */
279    abstract DisplayList createDisplayList();
280
281    /**
282     * Creates a new hardware layer. A hardware layer built by calling this
283     * method will be treated as a texture layer, instead of as a render target.
284     *
285     * @param isOpaque Whether the layer should be opaque or not
286     *
287     * @return A hardware layer
288     */
289    abstract HardwareLayer createHardwareLayer(boolean isOpaque);
290
291    /**
292     * Creates a new hardware layer.
293     *
294     * @param width The minimum width of the layer
295     * @param height The minimum height of the layer
296     * @param isOpaque Whether the layer should be opaque or not
297     *
298     * @return A hardware layer
299     */
300    abstract HardwareLayer createHardwareLayer(int width, int height, boolean isOpaque);
301
302    /**
303     * Creates a new {@link SurfaceTexture} that can be used to render into the
304     * specified hardware layer.
305     *
306     *
307     * @param layer The layer to render into using a {@link android.graphics.SurfaceTexture}
308     *
309     * @return A {@link SurfaceTexture}
310     */
311    abstract SurfaceTexture createSurfaceTexture(HardwareLayer layer);
312
313    /**
314     * Initializes the hardware renderer for the specified surface and setup the
315     * renderer for drawing, if needed. This is invoked when the ViewAncestor has
316     * potentially lost the hardware renderer. The hardware renderer should be
317     * reinitialized and setup when the render {@link #isRequested()} and
318     * {@link #isEnabled()}.
319     *
320     * @param width The width of the drawing surface.
321     * @param height The height of the drawing surface.
322     * @param attachInfo The
323     * @param holder
324     */
325    void initializeIfNeeded(int width, int height, View.AttachInfo attachInfo,
326            SurfaceHolder holder) throws Surface.OutOfResourcesException {
327        if (isRequested()) {
328            // We lost the gl context, so recreate it.
329            if (!isEnabled()) {
330                if (initialize(holder)) {
331                    setup(width, height);
332                }
333            }
334        }
335    }
336
337    /**
338     * Creates a hardware renderer using OpenGL.
339     *
340     * @param glVersion The version of OpenGL to use (1 for OpenGL 1, 11 for OpenGL 1.1, etc.)
341     * @param translucent True if the surface is translucent, false otherwise
342     *
343     * @return A hardware renderer backed by OpenGL.
344     */
345    static HardwareRenderer createGlRenderer(int glVersion, boolean translucent) {
346        switch (glVersion) {
347            case 2:
348                return Gl20Renderer.create(translucent);
349        }
350        throw new IllegalArgumentException("Unknown GL version: " + glVersion);
351    }
352
353    /**
354     * Invoke this method when the system is running out of memory. This
355     * method will attempt to recover as much memory as possible, based on
356     * the specified hint.
357     *
358     * @param level Hint about the amount of memory that should be trimmed,
359     *              see {@link android.content.ComponentCallbacks}
360     */
361    static void trimMemory(int level) {
362        Gl20Renderer.trimMemory(level);
363    }
364
365    /**
366     * Indicates whether hardware acceleration is currently enabled.
367     *
368     * @return True if hardware acceleration is in use, false otherwise.
369     */
370    boolean isEnabled() {
371        return mEnabled;
372    }
373
374    /**
375     * Indicates whether hardware acceleration is currently enabled.
376     *
377     * @param enabled True if the hardware renderer is in use, false otherwise.
378     */
379    void setEnabled(boolean enabled) {
380        mEnabled = enabled;
381    }
382
383    /**
384     * Indicates whether hardware acceleration is currently request but not
385     * necessarily enabled yet.
386     *
387     * @return True if requested, false otherwise.
388     */
389    boolean isRequested() {
390        return mRequested;
391    }
392
393    /**
394     * Indicates whether hardware acceleration is currently requested but not
395     * necessarily enabled yet.
396     *
397     * @return True to request hardware acceleration, false otherwise.
398     */
399    void setRequested(boolean requested) {
400        mRequested = requested;
401    }
402
403    @SuppressWarnings({"deprecation"})
404    static abstract class GlRenderer extends HardwareRenderer {
405        // These values are not exposed in our EGL APIs
406        static final int EGL_CONTEXT_CLIENT_VERSION = 0x3098;
407        static final int EGL_OPENGL_ES2_BIT = 4;
408        static final int EGL_SURFACE_TYPE = 0x3033;
409        static final int EGL_SWAP_BEHAVIOR_PRESERVED_BIT = 0x0400;
410
411        static final int SURFACE_STATE_ERROR = 0;
412        static final int SURFACE_STATE_SUCCESS = 1;
413        static final int SURFACE_STATE_UPDATED = 2;
414
415        static EGL10 sEgl;
416        static EGLDisplay sEglDisplay;
417        static EGLConfig sEglConfig;
418        static final Object[] sEglLock = new Object[0];
419        int mWidth = -1, mHeight = -1;
420
421        static final ThreadLocal<Gl20Renderer.Gl20RendererEglContext> sEglContextStorage
422                = new ThreadLocal<Gl20Renderer.Gl20RendererEglContext>();
423
424        EGLContext mEglContext;
425        Thread mEglThread;
426
427        EGLSurface mEglSurface;
428
429        GL mGl;
430        HardwareCanvas mCanvas;
431        int mFrameCount;
432        Paint mDebugPaint;
433
434        static boolean sDirtyRegions;
435        static final boolean sDirtyRegionsRequested;
436        static {
437            String dirtyProperty = SystemProperties.get(RENDER_DIRTY_REGIONS_PROPERTY, "true");
438            //noinspection PointlessBooleanExpression,ConstantConditions
439            sDirtyRegions = RENDER_DIRTY_REGIONS && "true".equalsIgnoreCase(dirtyProperty);
440            sDirtyRegionsRequested = sDirtyRegions;
441        }
442
443        boolean mDirtyRegionsEnabled;
444        boolean mUpdateDirtyRegions;
445
446        final boolean mVsyncDisabled;
447
448        final int mGlVersion;
449        final boolean mTranslucent;
450
451        private boolean mDestroyed;
452
453        private final Rect mRedrawClip = new Rect();
454
455        GlRenderer(int glVersion, boolean translucent) {
456            mGlVersion = glVersion;
457            mTranslucent = translucent;
458
459            final String vsyncProperty = SystemProperties.get(DISABLE_VSYNC_PROPERTY, "false");
460            mVsyncDisabled = "true".equalsIgnoreCase(vsyncProperty);
461            if (mVsyncDisabled) {
462                Log.d(LOG_TAG, "Disabling v-sync");
463            }
464        }
465
466        /**
467         * Indicates whether this renderer instance can track and update dirty regions.
468         */
469        boolean hasDirtyRegions() {
470            return mDirtyRegionsEnabled;
471        }
472
473        /**
474         * Checks for OpenGL errors. If an error has occured, {@link #destroy(boolean)}
475         * is invoked and the requested flag is turned off. The error code is
476         * also logged as a warning.
477         */
478        void checkEglErrors() {
479            if (isEnabled()) {
480                int error = sEgl.eglGetError();
481                if (error != EGL_SUCCESS) {
482                    // something bad has happened revert to
483                    // normal rendering.
484                    Log.w(LOG_TAG, "EGL error: " + GLUtils.getEGLErrorString(error));
485                    fallback(error != EGL11.EGL_CONTEXT_LOST);
486                }
487            }
488        }
489
490        private void fallback(boolean fallback) {
491            destroy(true);
492            if (fallback) {
493                // we'll try again if it was context lost
494                setRequested(false);
495                Log.w(LOG_TAG, "Mountain View, we've had a problem here. "
496                        + "Switching back to software rendering.");
497            }
498        }
499
500        @Override
501        boolean initialize(SurfaceHolder holder) throws Surface.OutOfResourcesException {
502            if (isRequested() && !isEnabled()) {
503                initializeEgl();
504                mGl = createEglSurface(holder);
505                mDestroyed = false;
506
507                if (mGl != null) {
508                    int err = sEgl.eglGetError();
509                    if (err != EGL_SUCCESS) {
510                        destroy(true);
511                        setRequested(false);
512                    } else {
513                        if (mCanvas == null) {
514                            mCanvas = createCanvas();
515                        }
516                        if (mCanvas != null) {
517                            setEnabled(true);
518                        } else {
519                            Log.w(LOG_TAG, "Hardware accelerated Canvas could not be created");
520                        }
521                    }
522
523                    return mCanvas != null;
524                }
525            }
526            return false;
527        }
528
529        @Override
530        void updateSurface(SurfaceHolder holder) throws Surface.OutOfResourcesException {
531            if (isRequested() && isEnabled()) {
532                createEglSurface(holder);
533            }
534        }
535
536        abstract GLES20Canvas createCanvas();
537
538        abstract int[] getConfig(boolean dirtyRegions);
539
540        void initializeEgl() {
541            synchronized (sEglLock) {
542                if (sEgl == null && sEglConfig == null) {
543                    sEgl = (EGL10) EGLContext.getEGL();
544
545                    // Get to the default display.
546                    sEglDisplay = sEgl.eglGetDisplay(EGL_DEFAULT_DISPLAY);
547
548                    if (sEglDisplay == EGL_NO_DISPLAY) {
549                        throw new RuntimeException("eglGetDisplay failed "
550                                + GLUtils.getEGLErrorString(sEgl.eglGetError()));
551                    }
552
553                    // We can now initialize EGL for that display
554                    int[] version = new int[2];
555                    if (!sEgl.eglInitialize(sEglDisplay, version)) {
556                        throw new RuntimeException("eglInitialize failed " +
557                                GLUtils.getEGLErrorString(sEgl.eglGetError()));
558                    }
559
560                    sEglConfig = chooseEglConfig();
561                    if (sEglConfig == null) {
562                        // We tried to use EGL_SWAP_BEHAVIOR_PRESERVED_BIT, try again without
563                        if (sDirtyRegions) {
564                            sDirtyRegions = false;
565                            sEglConfig = chooseEglConfig();
566                            if (sEglConfig == null) {
567                                throw new RuntimeException("eglConfig not initialized");
568                            }
569                        } else {
570                            throw new RuntimeException("eglConfig not initialized");
571                        }
572                    }
573                }
574            }
575
576            Gl20Renderer.Gl20RendererEglContext managedContext = sEglContextStorage.get();
577            mEglContext = managedContext != null ? managedContext.getContext() : null;
578            mEglThread = Thread.currentThread();
579
580            if (mEglContext == null) {
581                mEglContext = createContext(sEgl, sEglDisplay, sEglConfig);
582                sEglContextStorage.set(new Gl20Renderer.Gl20RendererEglContext(mEglContext));
583            }
584        }
585
586        private EGLConfig chooseEglConfig() {
587            EGLConfig[] configs = new EGLConfig[1];
588            int[] configsCount = new int[1];
589            int[] configSpec = getConfig(sDirtyRegions);
590
591            // Debug
592            final String debug = SystemProperties.get(PRINT_CONFIG_PROPERTY, "");
593            if ("all".equalsIgnoreCase(debug)) {
594                sEgl.eglChooseConfig(sEglDisplay, configSpec, null, 0, configsCount);
595
596                EGLConfig[] debugConfigs = new EGLConfig[configsCount[0]];
597                sEgl.eglChooseConfig(sEglDisplay, configSpec, debugConfigs,
598                        configsCount[0], configsCount);
599
600                for (EGLConfig config : debugConfigs) {
601                    printConfig(config);
602                }
603            }
604
605            if (!sEgl.eglChooseConfig(sEglDisplay, configSpec, configs, 1, configsCount)) {
606                throw new IllegalArgumentException("eglChooseConfig failed " +
607                        GLUtils.getEGLErrorString(sEgl.eglGetError()));
608            } else if (configsCount[0] > 0) {
609                if ("choice".equalsIgnoreCase(debug)) {
610                    printConfig(configs[0]);
611                }
612                return configs[0];
613            }
614
615            return null;
616        }
617
618        private void printConfig(EGLConfig config) {
619            int[] value = new int[1];
620
621            Log.d(LOG_TAG, "EGL configuration " + config + ":");
622
623            sEgl.eglGetConfigAttrib(sEglDisplay, config, EGL_RED_SIZE, value);
624            Log.d(LOG_TAG, "  RED_SIZE = " + value[0]);
625
626            sEgl.eglGetConfigAttrib(sEglDisplay, config, EGL_GREEN_SIZE, value);
627            Log.d(LOG_TAG, "  GREEN_SIZE = " + value[0]);
628
629            sEgl.eglGetConfigAttrib(sEglDisplay, config, EGL_BLUE_SIZE, value);
630            Log.d(LOG_TAG, "  BLUE_SIZE = " + value[0]);
631
632            sEgl.eglGetConfigAttrib(sEglDisplay, config, EGL_ALPHA_SIZE, value);
633            Log.d(LOG_TAG, "  ALPHA_SIZE = " + value[0]);
634
635            sEgl.eglGetConfigAttrib(sEglDisplay, config, EGL_DEPTH_SIZE, value);
636            Log.d(LOG_TAG, "  DEPTH_SIZE = " + value[0]);
637
638            sEgl.eglGetConfigAttrib(sEglDisplay, config, EGL_STENCIL_SIZE, value);
639            Log.d(LOG_TAG, "  STENCIL_SIZE = " + value[0]);
640
641            sEgl.eglGetConfigAttrib(sEglDisplay, config, EGL_SURFACE_TYPE, value);
642            Log.d(LOG_TAG, "  SURFACE_TYPE = 0x" + Integer.toHexString(value[0]));
643        }
644
645        GL createEglSurface(SurfaceHolder holder) throws Surface.OutOfResourcesException {
646            // Check preconditions.
647            if (sEgl == null) {
648                throw new RuntimeException("egl not initialized");
649            }
650            if (sEglDisplay == null) {
651                throw new RuntimeException("eglDisplay not initialized");
652            }
653            if (sEglConfig == null) {
654                throw new RuntimeException("eglConfig not initialized");
655            }
656            if (Thread.currentThread() != mEglThread) {
657                throw new IllegalStateException("HardwareRenderer cannot be used "
658                        + "from multiple threads");
659            }
660
661            // In case we need to destroy an existing surface
662            destroySurface();
663
664            // Create an EGL surface we can render into.
665            if (!createSurface(holder)) {
666                return null;
667            }
668
669            /*
670             * Before we can issue GL commands, we need to make sure
671             * the context is current and bound to a surface.
672             */
673            if (!sEgl.eglMakeCurrent(sEglDisplay, mEglSurface, mEglSurface, mEglContext)) {
674                throw new Surface.OutOfResourcesException("eglMakeCurrent failed "
675                        + GLUtils.getEGLErrorString(sEgl.eglGetError()));
676            }
677
678            initCaches();
679
680            enableDirtyRegions();
681
682            return mEglContext.getGL();
683        }
684
685        private void enableDirtyRegions() {
686            // If mDirtyRegions is set, this means we have an EGL configuration
687            // with EGL_SWAP_BEHAVIOR_PRESERVED_BIT set
688            if (sDirtyRegions) {
689                if (!(mDirtyRegionsEnabled = GLES20Canvas.preserveBackBuffer())) {
690                    Log.w(LOG_TAG, "Backbuffer cannot be preserved");
691                }
692            } else if (sDirtyRegionsRequested) {
693                // If mDirtyRegions is not set, our EGL configuration does not
694                // have EGL_SWAP_BEHAVIOR_PRESERVED_BIT; however, the default
695                // swap behavior might be EGL_BUFFER_PRESERVED, which means we
696                // want to set mDirtyRegions. We try to do this only if dirty
697                // regions were initially requested as part of the device
698                // configuration (see RENDER_DIRTY_REGIONS)
699                mDirtyRegionsEnabled = GLES20Canvas.isBackBufferPreserved();
700            }
701        }
702
703        abstract void initCaches();
704
705        EGLContext createContext(EGL10 egl, EGLDisplay eglDisplay, EGLConfig eglConfig) {
706            int[] attribs = { EGL_CONTEXT_CLIENT_VERSION, mGlVersion, EGL_NONE };
707
708            return egl.eglCreateContext(eglDisplay, eglConfig, EGL_NO_CONTEXT,
709                    mGlVersion != 0 ? attribs : null);
710        }
711
712        @Override
713        void destroy(boolean full) {
714            if (full && mCanvas != null) {
715                mCanvas = null;
716            }
717
718            if (!isEnabled() || mDestroyed) {
719                setEnabled(false);
720                return;
721            }
722
723            destroySurface();
724            setEnabled(false);
725
726            mDestroyed = true;
727            mGl = null;
728        }
729
730        void destroySurface() {
731            if (mEglSurface != null && mEglSurface != EGL_NO_SURFACE) {
732                sEgl.eglMakeCurrent(sEglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
733                sEgl.eglDestroySurface(sEglDisplay, mEglSurface);
734                mEglSurface = null;
735            }
736        }
737
738        @Override
739        void invalidate(SurfaceHolder holder) {
740            // Cancels any existing buffer to ensure we'll get a buffer
741            // of the right size before we call eglSwapBuffers
742            sEgl.eglMakeCurrent(sEglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
743
744            if (mEglSurface != null && mEglSurface != EGL_NO_SURFACE) {
745                sEgl.eglDestroySurface(sEglDisplay, mEglSurface);
746                mEglSurface = null;
747                setEnabled(false);
748            }
749
750            if (holder.getSurface().isValid()) {
751                if (!createSurface(holder)) {
752                    return;
753                }
754
755                mUpdateDirtyRegions = true;
756
757                if (mCanvas != null) {
758                    setEnabled(true);
759                }
760            }
761        }
762
763        private boolean createSurface(SurfaceHolder holder) {
764            mEglSurface = sEgl.eglCreateWindowSurface(sEglDisplay, sEglConfig, holder, null);
765
766            if (mEglSurface == null || mEglSurface == EGL_NO_SURFACE) {
767                int error = sEgl.eglGetError();
768                if (error == EGL_BAD_NATIVE_WINDOW) {
769                    Log.e(LOG_TAG, "createWindowSurface returned EGL_BAD_NATIVE_WINDOW.");
770                    return false;
771                }
772                throw new RuntimeException("createWindowSurface failed "
773                        + GLUtils.getEGLErrorString(error));
774            }
775            return true;
776        }
777
778        @Override
779        boolean validate() {
780            return checkCurrent() != SURFACE_STATE_ERROR;
781        }
782
783        @Override
784        void setup(int width, int height) {
785            if (validate()) {
786                mCanvas.setViewport(width, height);
787                mWidth = width;
788                mHeight = height;
789            }
790        }
791
792        @Override
793        int getWidth() {
794            return mWidth;
795        }
796
797        @Override
798        int getHeight() {
799            return mHeight;
800        }
801
802        @Override
803        HardwareCanvas getCanvas() {
804            return mCanvas;
805        }
806
807        boolean canDraw() {
808            return mGl != null && mCanvas != null;
809        }
810
811        void onPreDraw(Rect dirty) {
812        }
813
814        void onPostDraw() {
815        }
816
817        @Override
818        boolean draw(View view, View.AttachInfo attachInfo, HardwareDrawCallbacks callbacks,
819                Rect dirty) {
820            if (canDraw()) {
821                if (!hasDirtyRegions()) {
822                    dirty = null;
823                }
824                attachInfo.mIgnoreDirtyState = true;
825                attachInfo.mDrawingTime = SystemClock.uptimeMillis();
826
827                view.mPrivateFlags |= View.DRAWN;
828
829                final int surfaceState = checkCurrent();
830                if (surfaceState != SURFACE_STATE_ERROR) {
831                    // We had to change the current surface and/or context, redraw everything
832                    if (surfaceState == SURFACE_STATE_UPDATED) {
833                        dirty = null;
834                    }
835
836                    onPreDraw(dirty);
837
838                    HardwareCanvas canvas = mCanvas;
839                    attachInfo.mHardwareCanvas = canvas;
840
841                    int saveCount = canvas.save();
842                    callbacks.onHardwarePreDraw(canvas);
843
844                    try {
845                        view.mRecreateDisplayList =
846                                (view.mPrivateFlags & View.INVALIDATED) == View.INVALIDATED;
847                        view.mPrivateFlags &= ~View.INVALIDATED;
848
849                        final long getDisplayListStartTime;
850                        if (ViewDebug.DEBUG_LATENCY) {
851                            getDisplayListStartTime = System.nanoTime();
852                        }
853
854                        DisplayList displayList = view.getDisplayList();
855
856                        if (ViewDebug.DEBUG_LATENCY) {
857                            long now = System.nanoTime();
858                            Log.d(ViewDebug.DEBUG_LATENCY_TAG, "- getDisplayList() took "
859                                    + ((now - getDisplayListStartTime) * 0.000001f) + "ms");
860                        }
861
862                        if (displayList != null) {
863                            final long drawDisplayListStartTime;
864                            if (ViewDebug.DEBUG_LATENCY) {
865                                drawDisplayListStartTime = System.nanoTime();
866                            }
867
868                            boolean invalidateNeeded = canvas.drawDisplayList(
869                                    displayList, view.getWidth(),
870                                    view.getHeight(), mRedrawClip);
871
872                            if (ViewDebug.DEBUG_LATENCY) {
873                                long now = System.nanoTime();
874                                Log.d(ViewDebug.DEBUG_LATENCY_TAG, "- drawDisplayList() took "
875                                        + ((now - drawDisplayListStartTime) * 0.000001f)
876                                        + "ms, invalidateNeeded=" + invalidateNeeded + ".");
877                            }
878
879                            if (invalidateNeeded) {
880                                if (mRedrawClip.isEmpty() || view.getParent() == null) {
881                                    view.invalidate();
882                                } else {
883                                    view.getParent().invalidateChild(view, mRedrawClip);
884                                }
885                                mRedrawClip.setEmpty();
886                            }
887                        } else {
888                            // Shouldn't reach here
889                            view.draw(canvas);
890                        }
891
892                        if (DEBUG_DIRTY_REGION) {
893                            if (mDebugPaint == null) {
894                                mDebugPaint = new Paint();
895                                mDebugPaint.setColor(0x7fff0000);
896                            }
897                            if (dirty != null && (mFrameCount++ & 1) == 0) {
898                                canvas.drawRect(dirty, mDebugPaint);
899                            }
900                        }
901                    } finally {
902                        callbacks.onHardwarePostDraw(canvas);
903                        canvas.restoreToCount(saveCount);
904                        view.mRecreateDisplayList = false;
905                    }
906
907                    onPostDraw();
908
909                    attachInfo.mIgnoreDirtyState = false;
910
911                    final long eglSwapBuffersStartTime;
912                    if (ViewDebug.DEBUG_LATENCY) {
913                        eglSwapBuffersStartTime = System.nanoTime();
914                    }
915
916                    sEgl.eglSwapBuffers(sEglDisplay, mEglSurface);
917
918                    if (ViewDebug.DEBUG_LATENCY) {
919                        long now = System.nanoTime();
920                        Log.d(ViewDebug.DEBUG_LATENCY_TAG, "- eglSwapBuffers() took "
921                                + ((now - eglSwapBuffersStartTime) * 0.000001f) + "ms");
922                    }
923
924                    checkEglErrors();
925
926                    return dirty == null;
927                }
928            }
929
930            return false;
931        }
932
933        /**
934         * Ensures the current EGL context is the one we expect.
935         *
936         * @return {@link #SURFACE_STATE_ERROR} if the correct EGL context cannot be made current,
937         *         {@link #SURFACE_STATE_UPDATED} if the EGL context was changed or
938         *         {@link #SURFACE_STATE_SUCCESS} if the EGL context was the correct one
939         */
940        int checkCurrent() {
941            if (mEglThread != Thread.currentThread()) {
942                throw new IllegalStateException("Hardware acceleration can only be used with a " +
943                        "single UI thread.\nOriginal thread: " + mEglThread + "\n" +
944                        "Current thread: " + Thread.currentThread());
945            }
946
947            if (!mEglContext.equals(sEgl.eglGetCurrentContext()) ||
948                    !mEglSurface.equals(sEgl.eglGetCurrentSurface(EGL_DRAW))) {
949                if (!sEgl.eglMakeCurrent(sEglDisplay, mEglSurface, mEglSurface, mEglContext)) {
950                    Log.e(LOG_TAG, "eglMakeCurrent failed " +
951                            GLUtils.getEGLErrorString(sEgl.eglGetError()));
952                    fallback(true);
953                    return SURFACE_STATE_ERROR;
954                } else {
955                    if (mUpdateDirtyRegions) {
956                        enableDirtyRegions();
957                        mUpdateDirtyRegions = false;
958                    }
959                    return SURFACE_STATE_UPDATED;
960                }
961            }
962            return SURFACE_STATE_SUCCESS;
963        }
964    }
965
966    /**
967     * Hardware renderer using OpenGL ES 2.0.
968     */
969    static class Gl20Renderer extends GlRenderer {
970        private GLES20Canvas mGlCanvas;
971
972        private static EGLSurface sPbuffer;
973        private static final Object[] sPbufferLock = new Object[0];
974
975        static class Gl20RendererEglContext extends ManagedEGLContext {
976            final Handler mHandler = new Handler();
977
978            public Gl20RendererEglContext(EGLContext context) {
979                super(context);
980            }
981
982            @Override
983            public void onTerminate(final EGLContext eglContext) {
984                // Make sure we do this on the correct thread.
985                if (mHandler.getLooper() != Looper.myLooper()) {
986                    mHandler.post(new Runnable() {
987                        @Override public void run() {
988                            onTerminate(eglContext);
989                        }
990                    });
991                    return;
992                }
993
994                synchronized (sEglLock) {
995                    if (sEgl == null) return;
996
997                    if (EGLImpl.getInitCount(sEglDisplay) == 1) {
998                        usePbufferSurface(eglContext);
999                        GLES20Canvas.terminateCaches();
1000
1001                        sEgl.eglDestroyContext(sEglDisplay, eglContext);
1002                        sEglContextStorage.remove();
1003
1004                        sEgl.eglDestroySurface(sEglDisplay, sPbuffer);
1005                        sEgl.eglMakeCurrent(sEglDisplay, EGL_NO_SURFACE,
1006                                EGL_NO_SURFACE, EGL_NO_CONTEXT);
1007
1008                        sEgl.eglReleaseThread();
1009                        sEgl.eglTerminate(sEglDisplay);
1010
1011                        sEgl = null;
1012                        sEglDisplay = null;
1013                        sEglConfig = null;
1014                        sPbuffer = null;
1015                        sEglContextStorage.set(null);
1016                    }
1017                }
1018            }
1019        }
1020
1021        Gl20Renderer(boolean translucent) {
1022            super(2, translucent);
1023        }
1024
1025        @Override
1026        GLES20Canvas createCanvas() {
1027            return mGlCanvas = new GLES20Canvas(mTranslucent);
1028        }
1029
1030        @Override
1031        int[] getConfig(boolean dirtyRegions) {
1032            return new int[] {
1033                    EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
1034                    EGL_RED_SIZE, 8,
1035                    EGL_GREEN_SIZE, 8,
1036                    EGL_BLUE_SIZE, 8,
1037                    EGL_ALPHA_SIZE, 8,
1038                    EGL_DEPTH_SIZE, 0,
1039                    EGL_STENCIL_SIZE, 0,
1040                    EGL_SURFACE_TYPE, EGL_WINDOW_BIT |
1041                            (dirtyRegions ? EGL_SWAP_BEHAVIOR_PRESERVED_BIT : 0),
1042                    EGL_NONE
1043            };
1044        }
1045
1046        @Override
1047        void initCaches() {
1048            GLES20Canvas.initCaches();
1049        }
1050
1051        @Override
1052        boolean canDraw() {
1053            return super.canDraw() && mGlCanvas != null;
1054        }
1055
1056        @Override
1057        void onPreDraw(Rect dirty) {
1058            mGlCanvas.onPreDraw(dirty);
1059        }
1060
1061        @Override
1062        void onPostDraw() {
1063            mGlCanvas.onPostDraw();
1064        }
1065
1066        @Override
1067        void destroy(boolean full) {
1068            try {
1069                super.destroy(full);
1070            } finally {
1071                if (full && mGlCanvas != null) {
1072                    mGlCanvas = null;
1073                }
1074            }
1075        }
1076
1077        @Override
1078        void setup(int width, int height) {
1079            super.setup(width, height);
1080            if (mVsyncDisabled) {
1081                GLES20Canvas.disableVsync();
1082            }
1083        }
1084
1085        @Override
1086        DisplayList createDisplayList() {
1087            return new GLES20DisplayList();
1088        }
1089
1090        @Override
1091        HardwareLayer createHardwareLayer(boolean isOpaque) {
1092            return new GLES20TextureLayer(isOpaque);
1093        }
1094
1095        @Override
1096        HardwareLayer createHardwareLayer(int width, int height, boolean isOpaque) {
1097            return new GLES20RenderLayer(width, height, isOpaque);
1098        }
1099
1100        @Override
1101        SurfaceTexture createSurfaceTexture(HardwareLayer layer) {
1102            return ((GLES20TextureLayer) layer).getSurfaceTexture();
1103        }
1104
1105        @Override
1106        void destroyLayers(View view) {
1107            if (view != null && isEnabled() && checkCurrent() != SURFACE_STATE_ERROR) {
1108                destroyHardwareLayer(view);
1109                GLES20Canvas.flushCaches(GLES20Canvas.FLUSH_CACHES_LAYERS);
1110            }
1111        }
1112
1113        private static void destroyHardwareLayer(View view) {
1114            view.destroyLayer();
1115
1116            if (view instanceof ViewGroup) {
1117                ViewGroup group = (ViewGroup) view;
1118
1119                int count = group.getChildCount();
1120                for (int i = 0; i < count; i++) {
1121                    destroyHardwareLayer(group.getChildAt(i));
1122                }
1123            }
1124        }
1125
1126        @Override
1127        void destroyHardwareResources(View view) {
1128            if (view != null) {
1129                boolean needsContext = true;
1130                if (isEnabled() && checkCurrent() != SURFACE_STATE_ERROR) needsContext = false;
1131
1132                if (needsContext) {
1133                    Gl20RendererEglContext managedContext = sEglContextStorage.get();
1134                    if (managedContext == null) return;
1135                    usePbufferSurface(managedContext.getContext());
1136                }
1137
1138                destroyResources(view);
1139                GLES20Canvas.flushCaches(GLES20Canvas.FLUSH_CACHES_LAYERS);
1140            }
1141        }
1142
1143        private static void destroyResources(View view) {
1144            view.destroyHardwareResources();
1145
1146            if (view instanceof ViewGroup) {
1147                ViewGroup group = (ViewGroup) view;
1148
1149                int count = group.getChildCount();
1150                for (int i = 0; i < count; i++) {
1151                    destroyResources(group.getChildAt(i));
1152                }
1153            }
1154        }
1155
1156        static HardwareRenderer create(boolean translucent) {
1157            if (GLES20Canvas.isAvailable()) {
1158                return new Gl20Renderer(translucent);
1159            }
1160            return null;
1161        }
1162
1163        static void trimMemory(int level) {
1164            if (sEgl == null || sEglConfig == null) return;
1165
1166            Gl20RendererEglContext managedContext = sEglContextStorage.get();
1167            // We do not have OpenGL objects
1168            if (managedContext == null) {
1169                return;
1170            } else {
1171                usePbufferSurface(managedContext.getContext());
1172            }
1173
1174            switch (level) {
1175                case ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN:
1176                case ComponentCallbacks2.TRIM_MEMORY_BACKGROUND:
1177                case ComponentCallbacks2.TRIM_MEMORY_MODERATE:
1178                    GLES20Canvas.flushCaches(GLES20Canvas.FLUSH_CACHES_MODERATE);
1179                    break;
1180                case ComponentCallbacks2.TRIM_MEMORY_COMPLETE:
1181                    GLES20Canvas.flushCaches(GLES20Canvas.FLUSH_CACHES_FULL);
1182                    break;
1183            }
1184        }
1185
1186        private static void usePbufferSurface(EGLContext eglContext) {
1187            synchronized (sPbufferLock) {
1188                // Create a temporary 1x1 pbuffer so we have a context
1189                // to clear our OpenGL objects
1190                if (sPbuffer == null) {
1191                    sPbuffer = sEgl.eglCreatePbufferSurface(sEglDisplay, sEglConfig, new int[] {
1192                            EGL_WIDTH, 1, EGL_HEIGHT, 1, EGL_NONE
1193                    });
1194                }
1195            }
1196            sEgl.eglMakeCurrent(sEglDisplay, sPbuffer, sPbuffer, eglContext);
1197        }
1198    }
1199}
1200