HardwareRenderer.java revision 4caa4ed120a86e855d4d3b6b455c6fb9ca22e365
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.Log; 23 24import javax.microedition.khronos.egl.EGL10; 25import javax.microedition.khronos.egl.EGL11; 26import javax.microedition.khronos.egl.EGLConfig; 27import javax.microedition.khronos.egl.EGLContext; 28import javax.microedition.khronos.egl.EGLDisplay; 29import javax.microedition.khronos.egl.EGLSurface; 30import javax.microedition.khronos.opengles.GL; 31 32/** 33 * Interface for rendering a ViewRoot using hardware acceleration. 34 * 35 * @hide 36 */ 37public abstract class HardwareRenderer { 38 private static final String LOG_TAG = "HardwareRenderer"; 39 40 private boolean mEnabled; 41 private boolean mRequested = true; 42 43 /** 44 * Indicates whether hardware acceleration is available under any form for 45 * the view hierarchy. 46 * 47 * @return True if the view hierarchy can potentially be hardware accelerated, 48 * false otherwise 49 */ 50 public static boolean isAvailable() { 51 return GLES20Canvas.isAvailable(); 52 } 53 54 /** 55 * Destroys the hardware rendering context. 56 * 57 * @param full If true, destroys all associated resources. 58 */ 59 abstract void destroy(boolean full); 60 61 /** 62 * Initializes the hardware renderer for the specified surface. 63 * 64 * @param holder The holder for the surface to hardware accelerate. 65 * 66 * @return True if the initialization was successful, false otherwise. 67 */ 68 abstract boolean initialize(SurfaceHolder holder); 69 70 /** 71 * Setup the hardware renderer for drawing. This is called for every 72 * frame to draw. 73 * 74 * @param width Width of the drawing surface. 75 * @param height Height of the drawing surface. 76 */ 77 abstract void setup(int width, int height); 78 79 /** 80 * Draws the specified view. 81 * 82 * @param view The view to draw. 83 * @param attachInfo AttachInfo tied to the specified view. 84 */ 85 abstract void draw(View view, View.AttachInfo attachInfo, int yOffset); 86 87 /** 88 * Initializes the hardware renderer for the specified surface and setup the 89 * renderer for drawing, if needed. This is invoked when the ViewRoot has 90 * potentially lost the hardware renderer. The hardware renderer should be 91 * reinitialized and setup when the render {@link #isRequested()} and 92 * {@link #isEnabled()}. 93 * 94 * @param width The width of the drawing surface. 95 * @param height The height of the drawing surface. 96 * @param attachInfo The 97 * @param holder 98 */ 99 void initializeIfNeeded(int width, int height, View.AttachInfo attachInfo, 100 SurfaceHolder holder) { 101 if (isRequested()) { 102 // We lost the gl context, so recreate it. 103 if (!isEnabled()) { 104 if (initialize(holder)) { 105 setup(width, height); 106 } 107 } 108 } 109 } 110 111 /** 112 * Creates a hardware renderer using OpenGL. 113 * 114 * @param glVersion The version of OpenGL to use (1 for OpenGL 1, 11 for OpenGL 1.1, etc.) 115 * @param translucent True if the surface is translucent, false otherwise 116 * 117 * @return A hardware renderer backed by OpenGL. 118 */ 119 static HardwareRenderer createGlRenderer(int glVersion, boolean translucent) { 120 switch (glVersion) { 121 case 2: 122 return Gl20Renderer.create(translucent); 123 } 124 throw new IllegalArgumentException("Unknown GL version: " + glVersion); 125 } 126 127 /** 128 * Indicates whether hardware acceleration is currently enabled. 129 * 130 * @return True if hardware acceleration is in use, false otherwise. 131 */ 132 boolean isEnabled() { 133 return mEnabled; 134 } 135 136 /** 137 * Indicates whether hardware acceleration is currently enabled. 138 * 139 * @param enabled True if the hardware renderer is in use, false otherwise. 140 */ 141 void setEnabled(boolean enabled) { 142 mEnabled = enabled; 143 } 144 145 /** 146 * Indicates whether hardware acceleration is currently request but not 147 * necessarily enabled yet. 148 * 149 * @return True if requested, false otherwise. 150 */ 151 boolean isRequested() { 152 return mRequested; 153 } 154 155 /** 156 * Indicates whether hardware acceleration is currently request but not 157 * necessarily enabled yet. 158 * 159 * @return True to request hardware acceleration, false otherwise. 160 */ 161 void setRequested(boolean requested) { 162 mRequested = requested; 163 } 164 165 @SuppressWarnings({"deprecation"}) 166 static abstract class GlRenderer extends HardwareRenderer { 167 private static final int EGL_CONTEXT_CLIENT_VERSION = 0x3098; 168 169 static EGLContext sEglContext; 170 static EGL10 sEgl; 171 static EGLDisplay sEglDisplay; 172 static EGLConfig sEglConfig; 173 174 private static Thread sEglThread; 175 176 EGLSurface mEglSurface; 177 178 GL mGl; 179 GLES20Canvas mCanvas; 180 181 final int mGlVersion; 182 final boolean mTranslucent; 183 184 private boolean mDestroyed; 185 186 GlRenderer(int glVersion, boolean translucent) { 187 mGlVersion = glVersion; 188 mTranslucent = translucent; 189 } 190 191 /** 192 * Checks for OpenGL errors. If an error has occured, {@link #destroy(boolean)} 193 * is invoked and the requested flag is turned off. The error code is 194 * also logged as a warning. 195 */ 196 void checkErrors() { 197 if (isEnabled()) { 198 int error = sEgl.eglGetError(); 199 if (error != EGL10.EGL_SUCCESS) { 200 // something bad has happened revert to 201 // normal rendering. 202 destroy(true); 203 if (error != EGL11.EGL_CONTEXT_LOST) { 204 // we'll try again if it was context lost 205 setRequested(false); 206 } 207 Log.w(LOG_TAG, "OpenGL error: " + error); 208 } 209 } 210 } 211 212 @Override 213 boolean initialize(SurfaceHolder holder) { 214 if (isRequested() && !isEnabled()) { 215 initializeEgl(); 216 mGl = createEglSurface(holder); 217 mDestroyed = false; 218 219 if (mGl != null) { 220 int err = sEgl.eglGetError(); 221 if (err != EGL10.EGL_SUCCESS) { 222 destroy(true); 223 setRequested(false); 224 } else { 225 if (mCanvas == null) { 226 mCanvas = createCanvas(); 227 } 228 if (mCanvas != null) { 229 setEnabled(true); 230 } else { 231 Log.w(LOG_TAG, "Hardware accelerated Canvas could not be created"); 232 } 233 } 234 235 return mCanvas != null; 236 } 237 } 238 return false; 239 } 240 241 abstract GLES20Canvas createCanvas(); 242 243 void initializeEgl() { 244 if (sEglContext != null) return; 245 246 sEglThread = Thread.currentThread(); 247 sEgl = (EGL10) EGLContext.getEGL(); 248 249 // Get to the default display. 250 sEglDisplay = sEgl.eglGetDisplay(EGL10.EGL_DEFAULT_DISPLAY); 251 252 if (sEglDisplay == EGL10.EGL_NO_DISPLAY) { 253 throw new RuntimeException("eglGetDisplay failed"); 254 } 255 256 // We can now initialize EGL for that display 257 int[] version = new int[2]; 258 if (!sEgl.eglInitialize(sEglDisplay, version)) { 259 throw new RuntimeException("eglInitialize failed"); 260 } 261 sEglConfig = getConfigChooser(mGlVersion).chooseConfig(sEgl, sEglDisplay); 262 263 /* 264 * Create an EGL context. We want to do this as rarely as we can, because an 265 * EGL context is a somewhat heavy object. 266 */ 267 sEglContext = createContext(sEgl, sEglDisplay, sEglConfig); 268 } 269 270 GL createEglSurface(SurfaceHolder holder) { 271 // Check preconditions. 272 if (sEgl == null) { 273 throw new RuntimeException("egl not initialized"); 274 } 275 if (sEglDisplay == null) { 276 throw new RuntimeException("eglDisplay not initialized"); 277 } 278 if (sEglConfig == null) { 279 throw new RuntimeException("mEglConfig not initialized"); 280 } 281 if (Thread.currentThread() != sEglThread) { 282 throw new IllegalStateException("HardwareRenderer cannot be used " 283 + "from multiple threads"); 284 } 285 286 /* 287 * The window size has changed, so we need to create a new 288 * surface. 289 */ 290 if (mEglSurface != null && mEglSurface != EGL10.EGL_NO_SURFACE) { 291 /* 292 * Unbind and destroy the old EGL surface, if 293 * there is one. 294 */ 295 sEgl.eglMakeCurrent(sEglDisplay, EGL10.EGL_NO_SURFACE, 296 EGL10.EGL_NO_SURFACE, EGL10.EGL_NO_CONTEXT); 297 sEgl.eglDestroySurface(sEglDisplay, mEglSurface); 298 } 299 300 // Create an EGL surface we can render into. 301 mEglSurface = sEgl.eglCreateWindowSurface(sEglDisplay, sEglConfig, holder, null); 302 303 if (mEglSurface == null || mEglSurface == EGL10.EGL_NO_SURFACE) { 304 int error = sEgl.eglGetError(); 305 if (error == EGL10.EGL_BAD_NATIVE_WINDOW) { 306 Log.e("EglHelper", "createWindowSurface returned EGL_BAD_NATIVE_WINDOW."); 307 return null; 308 } 309 throw new RuntimeException("createWindowSurface failed"); 310 } 311 312 /* 313 * Before we can issue GL commands, we need to make sure 314 * the context is current and bound to a surface. 315 */ 316 if (!sEgl.eglMakeCurrent(sEglDisplay, mEglSurface, mEglSurface, sEglContext)) { 317 throw new RuntimeException("eglMakeCurrent failed"); 318 } 319 320 return sEglContext.getGL(); 321 } 322 323 EGLContext createContext(EGL10 egl, EGLDisplay eglDisplay, EGLConfig eglConfig) { 324 int[] attrib_list = { EGL_CONTEXT_CLIENT_VERSION, mGlVersion, EGL10.EGL_NONE }; 325 326 return egl.eglCreateContext(eglDisplay, eglConfig, EGL10.EGL_NO_CONTEXT, 327 mGlVersion != 0 ? attrib_list : null); 328 } 329 330 @Override 331 void initializeIfNeeded(int width, int height, View.AttachInfo attachInfo, 332 SurfaceHolder holder) { 333 if (isRequested()) { 334 checkErrors(); 335 super.initializeIfNeeded(width, height, attachInfo, holder); 336 } 337 } 338 339 @Override 340 void destroy(boolean full) { 341 if (full && mCanvas != null) { 342 mCanvas.destroy(); 343 mCanvas = null; 344 } 345 346 if (!isEnabled() || mDestroyed) return; 347 348 mDestroyed = true; 349 350 sEgl.eglMakeCurrent(sEglDisplay, EGL10.EGL_NO_SURFACE, 351 EGL10.EGL_NO_SURFACE, EGL10.EGL_NO_CONTEXT); 352 sEgl.eglDestroySurface(sEglDisplay, mEglSurface); 353 354 mEglSurface = null; 355 mGl = null; 356 357 setEnabled(false); 358 } 359 360 @Override 361 void setup(int width, int height) { 362 mCanvas.setViewport(width, height); 363 } 364 365 boolean canDraw() { 366 return mGl != null && mCanvas != null; 367 } 368 369 void onPreDraw() { 370 } 371 372 /** 373 * Defines the EGL configuration for this renderer. The default configuration 374 * is RGBX, no depth, no stencil. 375 * 376 * @return An {@link android.view.HardwareRenderer.GlRenderer.EglConfigChooser}. 377 * @param glVersion 378 */ 379 EglConfigChooser getConfigChooser(int glVersion) { 380 // TODO: Check for mTranslucent here, which means at least 2 EGL contexts 381 return new ComponentSizeChooser(glVersion, 8, 8, 8, 8, 0, 0); 382 } 383 384 @Override 385 void draw(View view, View.AttachInfo attachInfo, int yOffset) { 386 if (canDraw()) { 387 attachInfo.mDrawingTime = SystemClock.uptimeMillis(); 388 attachInfo.mIgnoreDirtyState = true; 389 view.mPrivateFlags |= View.DRAWN; 390 391 checkCurrent(); 392 393 onPreDraw(); 394 395 Canvas canvas = mCanvas; 396 int saveCount = canvas.save(); 397 canvas.translate(0, -yOffset); 398 399 try { 400 view.draw(canvas); 401 } finally { 402 canvas.restoreToCount(saveCount); 403 } 404 405 attachInfo.mIgnoreDirtyState = false; 406 407 sEgl.eglSwapBuffers(sEglDisplay, mEglSurface); 408 checkErrors(); 409 } 410 } 411 412 private void checkCurrent() { 413 // TODO: Don't check the current context when we have one per UI thread 414 // TODO: Use a threadlocal flag to know whether the surface has changed 415 if (sEgl.eglGetCurrentContext() != sEglContext || 416 sEgl.eglGetCurrentSurface(EGL10.EGL_DRAW) != mEglSurface) { 417 if (!sEgl.eglMakeCurrent(sEglDisplay, mEglSurface, mEglSurface, sEglContext)) { 418 throw new RuntimeException("eglMakeCurrent failed"); 419 } 420 } 421 } 422 423 static abstract class EglConfigChooser { 424 final int[] mConfigSpec; 425 private final int mGlVersion; 426 427 EglConfigChooser(int glVersion, int[] configSpec) { 428 mGlVersion = glVersion; 429 mConfigSpec = filterConfigSpec(configSpec); 430 } 431 432 EGLConfig chooseConfig(EGL10 egl, EGLDisplay display) { 433 int[] index = new int[1]; 434 if (!egl.eglChooseConfig(display, mConfigSpec, null, 0, index)) { 435 throw new IllegalArgumentException("eglChooseConfig failed"); 436 } 437 438 int numConfigs = index[0]; 439 if (numConfigs <= 0) { 440 throw new IllegalArgumentException("No configs match configSpec"); 441 } 442 443 EGLConfig[] configs = new EGLConfig[numConfigs]; 444 if (!egl.eglChooseConfig(display, mConfigSpec, configs, numConfigs, index)) { 445 throw new IllegalArgumentException("eglChooseConfig failed"); 446 } 447 448 EGLConfig config = chooseConfig(egl, display, configs); 449 if (config == null) { 450 throw new IllegalArgumentException("No config chosen"); 451 } 452 453 return config; 454 } 455 456 abstract EGLConfig chooseConfig(EGL10 egl, EGLDisplay display, EGLConfig[] configs); 457 458 private int[] filterConfigSpec(int[] configSpec) { 459 if (mGlVersion != 2) { 460 return configSpec; 461 } 462 /* We know none of the subclasses define EGL_RENDERABLE_TYPE. 463 * And we know the configSpec is well formed. 464 */ 465 int len = configSpec.length; 466 int[] newConfigSpec = new int[len + 2]; 467 System.arraycopy(configSpec, 0, newConfigSpec, 0, len - 1); 468 newConfigSpec[len - 1] = EGL10.EGL_RENDERABLE_TYPE; 469 newConfigSpec[len] = 4; /* EGL_OPENGL_ES2_BIT */ 470 newConfigSpec[len + 1] = EGL10.EGL_NONE; 471 return newConfigSpec; 472 } 473 } 474 475 /** 476 * Choose a configuration with exactly the specified r,g,b,a sizes, 477 * and at least the specified depth and stencil sizes. 478 */ 479 static class ComponentSizeChooser extends EglConfigChooser { 480 private int[] mValue; 481 482 private int mRedSize; 483 private int mGreenSize; 484 private int mBlueSize; 485 private int mAlphaSize; 486 private int mDepthSize; 487 private int mStencilSize; 488 489 ComponentSizeChooser(int glVersion, int redSize, int greenSize, int blueSize, 490 int alphaSize, int depthSize, int stencilSize) { 491 super(glVersion, new int[] { 492 EGL10.EGL_RED_SIZE, redSize, 493 EGL10.EGL_GREEN_SIZE, greenSize, 494 EGL10.EGL_BLUE_SIZE, blueSize, 495 EGL10.EGL_ALPHA_SIZE, alphaSize, 496 EGL10.EGL_DEPTH_SIZE, depthSize, 497 EGL10.EGL_STENCIL_SIZE, stencilSize, 498 EGL10.EGL_NONE }); 499 mValue = new int[1]; 500 mRedSize = redSize; 501 mGreenSize = greenSize; 502 mBlueSize = blueSize; 503 mAlphaSize = alphaSize; 504 mDepthSize = depthSize; 505 mStencilSize = stencilSize; 506 } 507 508 @Override 509 EGLConfig chooseConfig(EGL10 egl, EGLDisplay display, EGLConfig[] configs) { 510 for (EGLConfig config : configs) { 511 int d = findConfigAttrib(egl, display, config, EGL10.EGL_DEPTH_SIZE, 0); 512 int s = findConfigAttrib(egl, display, config, EGL10.EGL_STENCIL_SIZE, 0); 513 if (d >= mDepthSize && s >= mStencilSize) { 514 int r = findConfigAttrib(egl, display, config, EGL10.EGL_RED_SIZE, 0); 515 int g = findConfigAttrib(egl, display, config, EGL10.EGL_GREEN_SIZE, 0); 516 int b = findConfigAttrib(egl, display, config, EGL10.EGL_BLUE_SIZE, 0); 517 int a = findConfigAttrib(egl, display, config, EGL10.EGL_ALPHA_SIZE, 0); 518 if (r >= mRedSize && g >= mGreenSize && b >= mBlueSize && a >= mAlphaSize) { 519 return config; 520 } 521 } 522 } 523 return null; 524 } 525 526 private int findConfigAttrib(EGL10 egl, EGLDisplay display, EGLConfig config, 527 int attribute, int defaultValue) { 528 if (egl.eglGetConfigAttrib(display, config, attribute, mValue)) { 529 return mValue[0]; 530 } 531 532 return defaultValue; 533 } 534 } 535 } 536 537 /** 538 * Hardware renderer using OpenGL ES 2.0. 539 */ 540 static class Gl20Renderer extends GlRenderer { 541 private GLES20Canvas mGlCanvas; 542 543 Gl20Renderer(boolean translucent) { 544 super(2, translucent); 545 } 546 547 @Override 548 GLES20Canvas createCanvas() { 549 // TODO: Pass mTranslucent instead of true 550 return mGlCanvas = new GLES20Canvas(mGl, true); 551 } 552 553 @Override 554 void onPreDraw() { 555 mGlCanvas.onPreDraw(); 556 } 557 558 static HardwareRenderer create(boolean translucent) { 559 if (GLES20Canvas.isAvailable()) { 560 return new Gl20Renderer(translucent); 561 } 562 return null; 563 } 564 } 565} 566