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