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