HardwareRenderer.java revision daf98e941e140e8739458126640183b9f296a2ab
1/*
2 * Copyright (C) 2010 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17
18package android.view;
19
20import android.graphics.Canvas;
21import android.os.SystemClock;
22import android.util.EventLog;
23import android.util.Log;
24
25import javax.microedition.khronos.egl.EGL10;
26import javax.microedition.khronos.egl.EGL11;
27import javax.microedition.khronos.egl.EGLConfig;
28import javax.microedition.khronos.egl.EGLContext;
29import javax.microedition.khronos.egl.EGLDisplay;
30import javax.microedition.khronos.egl.EGLSurface;
31import javax.microedition.khronos.opengles.GL;
32
33/**
34 * Interface for rendering a ViewRoot using hardware acceleration.
35 *
36 * @hide
37 */
38public abstract class HardwareRenderer {
39    static final String LOG_TAG = "HardwareRenderer";
40
41    /**
42     * A process can set this flag to false to prevent the use of hardware
43     * rendering.
44     *
45     * @hide
46     */
47    public static boolean sRendererDisabled = false;
48
49    private boolean mEnabled;
50    private boolean mRequested = true;
51
52    /**
53     * Invoke this method to disable hardware rendering in the current process.
54     *
55     * @hide
56     */
57    public static void disable() {
58        sRendererDisabled = true;
59    }
60
61    /**
62     * Indicates whether hardware acceleration is available under any form for
63     * the view hierarchy.
64     *
65     * @return True if the view hierarchy can potentially be hardware accelerated,
66     *         false otherwise
67     */
68    public static boolean isAvailable() {
69        return GLES20Canvas.isAvailable();
70    }
71
72    /**
73     * Destroys the hardware rendering context.
74     *
75     * @param full If true, destroys all associated resources.
76     */
77    abstract void destroy(boolean full);
78
79    /**
80     * Initializes the hardware renderer for the specified surface.
81     *
82     * @param holder The holder for the surface to hardware accelerate.
83     *
84     * @return True if the initialization was successful, false otherwise.
85     */
86    abstract boolean initialize(SurfaceHolder holder);
87
88    /**
89     * Updates the hardware renderer for the specified surface.
90     *
91     * @param holder The holder for the surface to hardware accelerate.
92     */
93    abstract void updateSurface(SurfaceHolder holder);
94
95    /**
96     * Setup the hardware renderer for drawing. This is called for every
97     * frame to draw.
98     *
99     * @param width Width of the drawing surface.
100     * @param height Height of the drawing surface.
101     */
102    abstract void setup(int width, int height);
103
104    interface HardwareDrawCallbacks {
105        void onHardwarePreDraw(Canvas canvas);
106        void onHardwarePostDraw(Canvas canvas);
107    }
108
109    /**
110     * Draws the specified view.
111     *
112     * @param view The view to draw.
113     * @param attachInfo AttachInfo tied to the specified view.
114     */
115    abstract void draw(View view, View.AttachInfo attachInfo, HardwareDrawCallbacks callbacks);
116
117    /**
118     * Creates a new display list that can be used to record batches of
119     * drawing operations.
120     *
121     * @return A new display list.
122     */
123    abstract DisplayList createDisplayList(View v);
124
125    /**
126     * Creates a new hardware layer.
127     *
128     * @param width The minimum width of the layer
129     * @param height The minimum height of the layer
130     * @param isOpaque Whether the layer should be opaque or not
131     *
132     * @return A hardware layer
133     */
134    abstract HardwareLayer createHardwareLayer(int width, int height, boolean isOpaque);
135
136    /**
137     * Initializes the hardware renderer for the specified surface and setup the
138     * renderer for drawing, if needed. This is invoked when the ViewRoot has
139     * potentially lost the hardware renderer. The hardware renderer should be
140     * reinitialized and setup when the render {@link #isRequested()} and
141     * {@link #isEnabled()}.
142     *
143     * @param width The width of the drawing surface.
144     * @param height The height of the drawing surface.
145     * @param attachInfo The
146     * @param holder
147     */
148    void initializeIfNeeded(int width, int height, View.AttachInfo attachInfo,
149            SurfaceHolder holder) {
150        if (isRequested()) {
151            // We lost the gl context, so recreate it.
152            if (!isEnabled()) {
153                if (initialize(holder)) {
154                    setup(width, height);
155                }
156            }
157        }
158    }
159
160    /**
161     * Creates a hardware renderer using OpenGL.
162     *
163     * @param glVersion The version of OpenGL to use (1 for OpenGL 1, 11 for OpenGL 1.1, etc.)
164     * @param translucent True if the surface is translucent, false otherwise
165     *
166     * @return A hardware renderer backed by OpenGL.
167     */
168    static HardwareRenderer createGlRenderer(int glVersion, boolean translucent) {
169        switch (glVersion) {
170            case 2:
171                return Gl20Renderer.create(translucent);
172        }
173        throw new IllegalArgumentException("Unknown GL version: " + glVersion);
174    }
175
176    /**
177     * Indicates whether hardware acceleration is currently enabled.
178     *
179     * @return True if hardware acceleration is in use, false otherwise.
180     */
181    boolean isEnabled() {
182        return mEnabled;
183    }
184
185    /**
186     * Indicates whether hardware acceleration is currently enabled.
187     *
188     * @param enabled True if the hardware renderer is in use, false otherwise.
189     */
190    void setEnabled(boolean enabled) {
191        mEnabled = enabled;
192    }
193
194    /**
195     * Indicates whether hardware acceleration is currently request but not
196     * necessarily enabled yet.
197     *
198     * @return True if requested, false otherwise.
199     */
200    boolean isRequested() {
201        return mRequested;
202    }
203
204    /**
205     * Indicates whether hardware acceleration is currently requested but not
206     * necessarily enabled yet.
207     *
208     * @return True to request hardware acceleration, false otherwise.
209     */
210    void setRequested(boolean requested) {
211        mRequested = requested;
212    }
213
214    @SuppressWarnings({"deprecation"})
215    static abstract class GlRenderer extends HardwareRenderer {
216        private static final int EGL_CONTEXT_CLIENT_VERSION = 0x3098;
217
218        static EGLContext sEglContext;
219        static EGL10 sEgl;
220        static EGLDisplay sEglDisplay;
221        static EGLConfig sEglConfig;
222
223        private static Thread sEglThread;
224
225        EGLSurface mEglSurface;
226
227        GL mGl;
228        HardwareCanvas mCanvas;
229
230        final int mGlVersion;
231        final boolean mTranslucent;
232
233        private boolean mDestroyed;
234
235        GlRenderer(int glVersion, boolean translucent) {
236            mGlVersion = glVersion;
237            mTranslucent = translucent;
238        }
239
240        /**
241         * Return a string for the EGL error code, or the hex representation
242         * if the error is unknown.
243         *
244         * @param error The EGL error to convert into a String.
245         *
246         * @return An error string correponding to the EGL error code.
247         */
248        static String getEGLErrorString(int error) {
249            switch (error) {
250                case EGL10.EGL_SUCCESS:
251                    return "EGL_SUCCESS";
252                case EGL10.EGL_NOT_INITIALIZED:
253                    return "EGL_NOT_INITIALIZED";
254                case EGL10.EGL_BAD_ACCESS:
255                    return "EGL_BAD_ACCESS";
256                case EGL10.EGL_BAD_ALLOC:
257                    return "EGL_BAD_ALLOC";
258                case EGL10.EGL_BAD_ATTRIBUTE:
259                    return "EGL_BAD_ATTRIBUTE";
260                case EGL10.EGL_BAD_CONFIG:
261                    return "EGL_BAD_CONFIG";
262                case EGL10.EGL_BAD_CONTEXT:
263                    return "EGL_BAD_CONTEXT";
264                case EGL10.EGL_BAD_CURRENT_SURFACE:
265                    return "EGL_BAD_CURRENT_SURFACE";
266                case EGL10.EGL_BAD_DISPLAY:
267                    return "EGL_BAD_DISPLAY";
268                case EGL10.EGL_BAD_MATCH:
269                    return "EGL_BAD_MATCH";
270                case EGL10.EGL_BAD_NATIVE_PIXMAP:
271                    return "EGL_BAD_NATIVE_PIXMAP";
272                case EGL10.EGL_BAD_NATIVE_WINDOW:
273                    return "EGL_BAD_NATIVE_WINDOW";
274                case EGL10.EGL_BAD_PARAMETER:
275                    return "EGL_BAD_PARAMETER";
276                case EGL10.EGL_BAD_SURFACE:
277                    return "EGL_BAD_SURFACE";
278                case EGL11.EGL_CONTEXT_LOST:
279                    return "EGL_CONTEXT_LOST";
280                default:
281                    return "0x" + Integer.toHexString(error);
282            }
283        }
284
285        /**
286         * Checks for OpenGL errors. If an error has occured, {@link #destroy(boolean)}
287         * is invoked and the requested flag is turned off. The error code is
288         * also logged as a warning.
289         */
290        void checkEglErrors() {
291            if (isEnabled()) {
292                int error = sEgl.eglGetError();
293                if (error != EGL10.EGL_SUCCESS) {
294                    // something bad has happened revert to
295                    // normal rendering.
296                    fallback(error != EGL11.EGL_CONTEXT_LOST);
297                    Log.w(LOG_TAG, "EGL error: " + getEGLErrorString(error));
298                }
299            }
300        }
301
302        private void fallback(boolean fallback) {
303            destroy(true);
304            if (fallback) {
305                // we'll try again if it was context lost
306                setRequested(false);
307                Log.w(LOG_TAG, "Mountain View, we've had a problem here. "
308                        + "Switching back to software rendering.");
309            }
310        }
311
312        @Override
313        boolean initialize(SurfaceHolder holder) {
314            if (isRequested() && !isEnabled()) {
315                initializeEgl();
316                mGl = createEglSurface(holder);
317                mDestroyed = false;
318
319                if (mGl != null) {
320                    int err = sEgl.eglGetError();
321                    if (err != EGL10.EGL_SUCCESS) {
322                        destroy(true);
323                        setRequested(false);
324                    } else {
325                        if (mCanvas == null) {
326                            mCanvas = createCanvas();
327                        }
328                        if (mCanvas != null) {
329                            setEnabled(true);
330                        } else {
331                            Log.w(LOG_TAG, "Hardware accelerated Canvas could not be created");
332                        }
333                    }
334
335                    return mCanvas != null;
336                }
337            }
338            return false;
339        }
340
341        @Override
342        void updateSurface(SurfaceHolder holder) {
343            if (isRequested() && isEnabled()) {
344                createEglSurface(holder);
345            }
346        }
347
348        abstract GLES20Canvas createCanvas();
349
350        void initializeEgl() {
351            if (sEglContext != null) return;
352
353            sEglThread = Thread.currentThread();
354            sEgl = (EGL10) EGLContext.getEGL();
355
356            // Get to the default display.
357            sEglDisplay = sEgl.eglGetDisplay(EGL10.EGL_DEFAULT_DISPLAY);
358
359            if (sEglDisplay == EGL10.EGL_NO_DISPLAY) {
360                throw new RuntimeException("eglGetDisplay failed "
361                        + getEGLErrorString(sEgl.eglGetError()));
362            }
363
364            // We can now initialize EGL for that display
365            int[] version = new int[2];
366            if (!sEgl.eglInitialize(sEglDisplay, version)) {
367                throw new RuntimeException("eglInitialize failed "
368                        + getEGLErrorString(sEgl.eglGetError()));
369            }
370            sEglConfig = getConfigChooser(mGlVersion).chooseConfig(sEgl, sEglDisplay);
371
372            /*
373            * Create an EGL context. We want to do this as rarely as we can, because an
374            * EGL context is a somewhat heavy object.
375            */
376            sEglContext = createContext(sEgl, sEglDisplay, sEglConfig);
377        }
378
379        GL createEglSurface(SurfaceHolder holder) {
380            // Check preconditions.
381            if (sEgl == null) {
382                throw new RuntimeException("egl not initialized");
383            }
384            if (sEglDisplay == null) {
385                throw new RuntimeException("eglDisplay not initialized");
386            }
387            if (sEglConfig == null) {
388                throw new RuntimeException("mEglConfig not initialized");
389            }
390            if (Thread.currentThread() != sEglThread) {
391                throw new IllegalStateException("HardwareRenderer cannot be used "
392                        + "from multiple threads");
393            }
394
395            /*
396             *  The window size has changed, so we need to create a new
397             *  surface.
398             */
399            if (mEglSurface != null && mEglSurface != EGL10.EGL_NO_SURFACE) {
400                /*
401                 * Unbind and destroy the old EGL surface, if
402                 * there is one.
403                 */
404                sEgl.eglMakeCurrent(sEglDisplay, EGL10.EGL_NO_SURFACE,
405                        EGL10.EGL_NO_SURFACE, EGL10.EGL_NO_CONTEXT);
406                sEgl.eglDestroySurface(sEglDisplay, mEglSurface);
407            }
408
409            // Create an EGL surface we can render into.
410            mEglSurface = sEgl.eglCreateWindowSurface(sEglDisplay, sEglConfig, holder, null);
411
412            if (mEglSurface == null || mEglSurface == EGL10.EGL_NO_SURFACE) {
413                int error = sEgl.eglGetError();
414                if (error == EGL10.EGL_BAD_NATIVE_WINDOW) {
415                    Log.e("EglHelper", "createWindowSurface returned EGL_BAD_NATIVE_WINDOW.");
416                    return null;
417                }
418                throw new RuntimeException("createWindowSurface failed "
419                        + getEGLErrorString(error));
420            }
421
422            /*
423             * Before we can issue GL commands, we need to make sure
424             * the context is current and bound to a surface.
425             */
426            if (!sEgl.eglMakeCurrent(sEglDisplay, mEglSurface, mEglSurface, sEglContext)) {
427                throw new RuntimeException("eglMakeCurrent failed "
428                        + getEGLErrorString(sEgl.eglGetError()));
429            }
430
431            return sEglContext.getGL();
432        }
433
434        EGLContext createContext(EGL10 egl, EGLDisplay eglDisplay, EGLConfig eglConfig) {
435            int[] attrib_list = { EGL_CONTEXT_CLIENT_VERSION, mGlVersion, EGL10.EGL_NONE };
436
437            return egl.eglCreateContext(eglDisplay, eglConfig, EGL10.EGL_NO_CONTEXT,
438                    mGlVersion != 0 ? attrib_list : null);
439        }
440
441        @Override
442        void initializeIfNeeded(int width, int height, View.AttachInfo attachInfo,
443                SurfaceHolder holder) {
444            if (isRequested()) {
445                checkEglErrors();
446                super.initializeIfNeeded(width, height, attachInfo, holder);
447            }
448        }
449
450        @Override
451        void destroy(boolean full) {
452            if (full && mCanvas != null) {
453                mCanvas = null;
454            }
455
456            if (!isEnabled() || mDestroyed) return;
457
458            mDestroyed = true;
459
460            sEgl.eglMakeCurrent(sEglDisplay, EGL10.EGL_NO_SURFACE,
461                    EGL10.EGL_NO_SURFACE, EGL10.EGL_NO_CONTEXT);
462            sEgl.eglDestroySurface(sEglDisplay, mEglSurface);
463
464            mEglSurface = null;
465            mGl = null;
466
467            setEnabled(false);
468        }
469
470        @Override
471        void setup(int width, int height) {
472            mCanvas.setViewport(width, height);
473        }
474
475        boolean canDraw() {
476            return mGl != null && mCanvas != null;
477        }
478
479        void onPreDraw() {
480        }
481
482        void onPostDraw() {
483        }
484
485        /**
486         * Defines the EGL configuration for this renderer.
487         *
488         * @return An {@link android.view.HardwareRenderer.GlRenderer.EglConfigChooser}.
489         */
490        EglConfigChooser getConfigChooser(int glVersion) {
491            return new ComponentSizeChooser(glVersion, 8, 8, 8, 8, 0, 0);
492        }
493
494        @Override
495        void draw(View view, View.AttachInfo attachInfo, HardwareDrawCallbacks callbacks) {
496            if (canDraw()) {
497                attachInfo.mDrawingTime = SystemClock.uptimeMillis();
498                attachInfo.mIgnoreDirtyState = true;
499                view.mPrivateFlags |= View.DRAWN;
500
501                long startTime;
502                if (ViewDebug.DEBUG_PROFILE_DRAWING) {
503                    startTime = SystemClock.elapsedRealtime();
504                }
505
506                if (checkCurrent()) {
507                    onPreDraw();
508
509                    HardwareCanvas canvas = mCanvas;
510                    attachInfo.mHardwareCanvas = canvas;
511                    int saveCount = canvas.save();
512                    callbacks.onHardwarePreDraw(canvas);
513
514                    try {
515                        view.mRecreateDisplayList =
516                                (view.mPrivateFlags & View.INVALIDATED) == View.INVALIDATED;
517                        view.mPrivateFlags &= ~View.INVALIDATED;
518                        DisplayList displayList = view.getDisplayList();
519                        if (displayList != null) {
520                            if (canvas.drawDisplayList(displayList)) {
521                                view.invalidate();
522                            }
523                        } else {
524                            // Shouldn't reach here
525                            view.draw(canvas);
526                        }
527                    } finally {
528                        callbacks.onHardwarePostDraw(canvas);
529                        canvas.restoreToCount(saveCount);
530                        view.mRecreateDisplayList = false;
531                    }
532
533                    onPostDraw();
534
535                    if (ViewDebug.DEBUG_PROFILE_DRAWING) {
536                        EventLog.writeEvent(60000, SystemClock.elapsedRealtime() - startTime);
537                    }
538
539                    attachInfo.mIgnoreDirtyState = false;
540
541                    sEgl.eglSwapBuffers(sEglDisplay, mEglSurface);
542                    checkEglErrors();
543                }
544            }
545        }
546
547        private boolean checkCurrent() {
548            // TODO: Don't check the current context when we have one per UI thread
549            // TODO: Use a threadlocal flag to know whether the surface has changed
550            if (sEgl.eglGetCurrentContext() != sEglContext ||
551                    sEgl.eglGetCurrentSurface(EGL10.EGL_DRAW) != mEglSurface) {
552                if (!sEgl.eglMakeCurrent(sEglDisplay, mEglSurface, mEglSurface, sEglContext)) {
553                    fallback(true);
554                    Log.e(LOG_TAG, "eglMakeCurrent failed " +
555                            getEGLErrorString(sEgl.eglGetError()));
556                    return false;
557                }
558            }
559            return true;
560        }
561
562        static abstract class EglConfigChooser {
563            final int[] mConfigSpec;
564            private final int mGlVersion;
565
566            EglConfigChooser(int glVersion, int[] configSpec) {
567                mGlVersion = glVersion;
568                mConfigSpec = filterConfigSpec(configSpec);
569            }
570
571            EGLConfig chooseConfig(EGL10 egl, EGLDisplay display) {
572                int[] index = new int[1];
573                if (!egl.eglChooseConfig(display, mConfigSpec, null, 0, index)) {
574                    throw new IllegalArgumentException("eglChooseConfig failed "
575                            + getEGLErrorString(egl.eglGetError()));
576                }
577
578                int numConfigs = index[0];
579                if (numConfigs <= 0) {
580                    throw new IllegalArgumentException("No configs match configSpec");
581                }
582
583                EGLConfig[] configs = new EGLConfig[numConfigs];
584                if (!egl.eglChooseConfig(display, mConfigSpec, configs, numConfigs, index)) {
585                    throw new IllegalArgumentException("eglChooseConfig failed "
586                            + getEGLErrorString(egl.eglGetError()));
587                }
588
589                EGLConfig config = chooseConfig(egl, display, configs);
590                if (config == null) {
591                    throw new IllegalArgumentException("No config chosen");
592                }
593
594                return config;
595            }
596
597            abstract EGLConfig chooseConfig(EGL10 egl, EGLDisplay display, EGLConfig[] configs);
598
599            private int[] filterConfigSpec(int[] configSpec) {
600                if (mGlVersion != 2) {
601                    return configSpec;
602                }
603                /* We know none of the subclasses define EGL_RENDERABLE_TYPE.
604                 * And we know the configSpec is well formed.
605                 */
606                int len = configSpec.length;
607                int[] newConfigSpec = new int[len + 2];
608                System.arraycopy(configSpec, 0, newConfigSpec, 0, len - 1);
609                newConfigSpec[len - 1] = EGL10.EGL_RENDERABLE_TYPE;
610                newConfigSpec[len] = 4; /* EGL_OPENGL_ES2_BIT */
611                newConfigSpec[len + 1] = EGL10.EGL_NONE;
612                return newConfigSpec;
613            }
614        }
615
616        /**
617         * Choose a configuration with exactly the specified r,g,b,a sizes,
618         * and at least the specified depth and stencil sizes.
619         */
620        static class ComponentSizeChooser extends EglConfigChooser {
621            private int[] mValue;
622
623            private int mRedSize;
624            private int mGreenSize;
625            private int mBlueSize;
626            private int mAlphaSize;
627            private int mDepthSize;
628            private int mStencilSize;
629
630            ComponentSizeChooser(int glVersion, int redSize, int greenSize, int blueSize,
631                    int alphaSize, int depthSize, int stencilSize) {
632                super(glVersion, new int[] {
633                        EGL10.EGL_RED_SIZE, redSize,
634                        EGL10.EGL_GREEN_SIZE, greenSize,
635                        EGL10.EGL_BLUE_SIZE, blueSize,
636                        EGL10.EGL_ALPHA_SIZE, alphaSize,
637                        EGL10.EGL_DEPTH_SIZE, depthSize,
638                        EGL10.EGL_STENCIL_SIZE, stencilSize,
639                        EGL10.EGL_NONE });
640                mValue = new int[1];
641                mRedSize = redSize;
642                mGreenSize = greenSize;
643                mBlueSize = blueSize;
644                mAlphaSize = alphaSize;
645                mDepthSize = depthSize;
646                mStencilSize = stencilSize;
647           }
648
649            @Override
650            EGLConfig chooseConfig(EGL10 egl, EGLDisplay display, EGLConfig[] configs) {
651                for (EGLConfig config : configs) {
652                    int d = findConfigAttrib(egl, display, config, EGL10.EGL_DEPTH_SIZE, 0);
653                    int s = findConfigAttrib(egl, display, config, EGL10.EGL_STENCIL_SIZE, 0);
654                    if (d >= mDepthSize && s >= mStencilSize) {
655                        int r = findConfigAttrib(egl, display, config, EGL10.EGL_RED_SIZE, 0);
656                        int g = findConfigAttrib(egl, display, config, EGL10.EGL_GREEN_SIZE, 0);
657                        int b = findConfigAttrib(egl, display, config, EGL10.EGL_BLUE_SIZE, 0);
658                        int a = findConfigAttrib(egl, display, config, EGL10.EGL_ALPHA_SIZE, 0);
659                        if (r >= mRedSize && g >= mGreenSize && b >= mBlueSize && a >= mAlphaSize) {
660                            return config;
661                        }
662                    }
663                }
664                return null;
665            }
666
667            private int findConfigAttrib(EGL10 egl, EGLDisplay display, EGLConfig config,
668                    int attribute, int defaultValue) {
669                if (egl.eglGetConfigAttrib(display, config, attribute, mValue)) {
670                    return mValue[0];
671                }
672
673                return defaultValue;
674            }
675        }
676    }
677
678    /**
679     * Hardware renderer using OpenGL ES 2.0.
680     */
681    static class Gl20Renderer extends GlRenderer {
682        private GLES20Canvas mGlCanvas;
683
684        Gl20Renderer(boolean translucent) {
685            super(2, translucent);
686        }
687
688        @Override
689        GLES20Canvas createCanvas() {
690            return mGlCanvas = new GLES20Canvas(mTranslucent);
691        }
692
693        @Override
694        boolean canDraw() {
695            return super.canDraw() && mGlCanvas != null;
696        }
697
698        @Override
699        void onPreDraw() {
700            mGlCanvas.onPreDraw();
701        }
702
703        @Override
704        void onPostDraw() {
705            mGlCanvas.onPostDraw();
706        }
707
708        @Override
709        void destroy(boolean full) {
710            try {
711                super.destroy(full);
712            } finally {
713                if (full && mGlCanvas != null) {
714                    mGlCanvas = null;
715                }
716            }
717        }
718
719        @Override
720        DisplayList createDisplayList(View v) {
721            return new GLES20DisplayList(v);
722        }
723
724        @Override
725        HardwareLayer createHardwareLayer(int width, int height, boolean isOpaque) {
726            return new GLES20Layer(width, height, isOpaque);
727        }
728
729        static HardwareRenderer create(boolean translucent) {
730            if (GLES20Canvas.isAvailable()) {
731                return new Gl20Renderer(translucent);
732            }
733            return null;
734        }
735    }
736}
737