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