HardwareRenderer.java revision 2a83f001fdb189f945e82e81e717ba204824b112
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(); 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 Canvas canvas = mCanvas; 510 int saveCount = canvas.save(); 511 callbacks.onHardwarePreDraw(canvas); 512 513 try { 514 view.draw(canvas); 515 } finally { 516 callbacks.onHardwarePostDraw(canvas); 517 canvas.restoreToCount(saveCount); 518 } 519 520 onPostDraw(); 521 522 if (ViewDebug.DEBUG_PROFILE_DRAWING) { 523 EventLog.writeEvent(60000, SystemClock.elapsedRealtime() - startTime); 524 } 525 526 attachInfo.mIgnoreDirtyState = false; 527 528 sEgl.eglSwapBuffers(sEglDisplay, mEglSurface); 529 checkEglErrors(); 530 } 531 } 532 } 533 534 private boolean checkCurrent() { 535 // TODO: Don't check the current context when we have one per UI thread 536 // TODO: Use a threadlocal flag to know whether the surface has changed 537 if (sEgl.eglGetCurrentContext() != sEglContext || 538 sEgl.eglGetCurrentSurface(EGL10.EGL_DRAW) != mEglSurface) { 539 if (!sEgl.eglMakeCurrent(sEglDisplay, mEglSurface, mEglSurface, sEglContext)) { 540 fallback(true); 541 Log.e(LOG_TAG, "eglMakeCurrent failed " + 542 getEGLErrorString(sEgl.eglGetError())); 543 return false; 544 } 545 } 546 return true; 547 } 548 549 static abstract class EglConfigChooser { 550 final int[] mConfigSpec; 551 private final int mGlVersion; 552 553 EglConfigChooser(int glVersion, int[] configSpec) { 554 mGlVersion = glVersion; 555 mConfigSpec = filterConfigSpec(configSpec); 556 } 557 558 EGLConfig chooseConfig(EGL10 egl, EGLDisplay display) { 559 int[] index = new int[1]; 560 if (!egl.eglChooseConfig(display, mConfigSpec, null, 0, index)) { 561 throw new IllegalArgumentException("eglChooseConfig failed " 562 + getEGLErrorString(egl.eglGetError())); 563 } 564 565 int numConfigs = index[0]; 566 if (numConfigs <= 0) { 567 throw new IllegalArgumentException("No configs match configSpec"); 568 } 569 570 EGLConfig[] configs = new EGLConfig[numConfigs]; 571 if (!egl.eglChooseConfig(display, mConfigSpec, configs, numConfigs, index)) { 572 throw new IllegalArgumentException("eglChooseConfig failed " 573 + getEGLErrorString(egl.eglGetError())); 574 } 575 576 EGLConfig config = chooseConfig(egl, display, configs); 577 if (config == null) { 578 throw new IllegalArgumentException("No config chosen"); 579 } 580 581 return config; 582 } 583 584 abstract EGLConfig chooseConfig(EGL10 egl, EGLDisplay display, EGLConfig[] configs); 585 586 private int[] filterConfigSpec(int[] configSpec) { 587 if (mGlVersion != 2) { 588 return configSpec; 589 } 590 /* We know none of the subclasses define EGL_RENDERABLE_TYPE. 591 * And we know the configSpec is well formed. 592 */ 593 int len = configSpec.length; 594 int[] newConfigSpec = new int[len + 2]; 595 System.arraycopy(configSpec, 0, newConfigSpec, 0, len - 1); 596 newConfigSpec[len - 1] = EGL10.EGL_RENDERABLE_TYPE; 597 newConfigSpec[len] = 4; /* EGL_OPENGL_ES2_BIT */ 598 newConfigSpec[len + 1] = EGL10.EGL_NONE; 599 return newConfigSpec; 600 } 601 } 602 603 /** 604 * Choose a configuration with exactly the specified r,g,b,a sizes, 605 * and at least the specified depth and stencil sizes. 606 */ 607 static class ComponentSizeChooser extends EglConfigChooser { 608 private int[] mValue; 609 610 private int mRedSize; 611 private int mGreenSize; 612 private int mBlueSize; 613 private int mAlphaSize; 614 private int mDepthSize; 615 private int mStencilSize; 616 617 ComponentSizeChooser(int glVersion, int redSize, int greenSize, int blueSize, 618 int alphaSize, int depthSize, int stencilSize) { 619 super(glVersion, new int[] { 620 EGL10.EGL_RED_SIZE, redSize, 621 EGL10.EGL_GREEN_SIZE, greenSize, 622 EGL10.EGL_BLUE_SIZE, blueSize, 623 EGL10.EGL_ALPHA_SIZE, alphaSize, 624 EGL10.EGL_DEPTH_SIZE, depthSize, 625 EGL10.EGL_STENCIL_SIZE, stencilSize, 626 EGL10.EGL_NONE }); 627 mValue = new int[1]; 628 mRedSize = redSize; 629 mGreenSize = greenSize; 630 mBlueSize = blueSize; 631 mAlphaSize = alphaSize; 632 mDepthSize = depthSize; 633 mStencilSize = stencilSize; 634 } 635 636 @Override 637 EGLConfig chooseConfig(EGL10 egl, EGLDisplay display, EGLConfig[] configs) { 638 for (EGLConfig config : configs) { 639 int d = findConfigAttrib(egl, display, config, EGL10.EGL_DEPTH_SIZE, 0); 640 int s = findConfigAttrib(egl, display, config, EGL10.EGL_STENCIL_SIZE, 0); 641 if (d >= mDepthSize && s >= mStencilSize) { 642 int r = findConfigAttrib(egl, display, config, EGL10.EGL_RED_SIZE, 0); 643 int g = findConfigAttrib(egl, display, config, EGL10.EGL_GREEN_SIZE, 0); 644 int b = findConfigAttrib(egl, display, config, EGL10.EGL_BLUE_SIZE, 0); 645 int a = findConfigAttrib(egl, display, config, EGL10.EGL_ALPHA_SIZE, 0); 646 if (r >= mRedSize && g >= mGreenSize && b >= mBlueSize && a >= mAlphaSize) { 647 return config; 648 } 649 } 650 } 651 return null; 652 } 653 654 private int findConfigAttrib(EGL10 egl, EGLDisplay display, EGLConfig config, 655 int attribute, int defaultValue) { 656 if (egl.eglGetConfigAttrib(display, config, attribute, mValue)) { 657 return mValue[0]; 658 } 659 660 return defaultValue; 661 } 662 } 663 } 664 665 /** 666 * Hardware renderer using OpenGL ES 2.0. 667 */ 668 static class Gl20Renderer extends GlRenderer { 669 private GLES20Canvas mGlCanvas; 670 671 Gl20Renderer(boolean translucent) { 672 super(2, translucent); 673 } 674 675 @Override 676 GLES20Canvas createCanvas() { 677 return mGlCanvas = new GLES20Canvas(mTranslucent); 678 } 679 680 @Override 681 boolean canDraw() { 682 return super.canDraw() && mGlCanvas != null; 683 } 684 685 @Override 686 void onPreDraw() { 687 mGlCanvas.onPreDraw(); 688 } 689 690 @Override 691 void onPostDraw() { 692 mGlCanvas.onPostDraw(); 693 } 694 695 @Override 696 void destroy(boolean full) { 697 try { 698 super.destroy(full); 699 } finally { 700 if (full && mGlCanvas != null) { 701 mGlCanvas = null; 702 } 703 } 704 } 705 706 @Override 707 DisplayList createDisplayList() { 708 return new GLES20DisplayList(); 709 } 710 711 @Override 712 HardwareLayer createHardwareLayer(int width, int height, boolean isOpaque) { 713 return new GLES20Layer(width, height, isOpaque); 714 } 715 716 static HardwareRenderer create(boolean translucent) { 717 if (GLES20Canvas.isAvailable()) { 718 return new Gl20Renderer(translucent); 719 } 720 return null; 721 } 722 } 723} 724