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