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