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