ImageWallpaper.java revision c61d70ec46bc44344a419a0a15ccbecd1f8f1a2d
1/* 2 * Copyright (C) 2009 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 17package com.android.systemui; 18 19import android.app.ActivityManager; 20import android.app.WallpaperManager; 21import android.content.BroadcastReceiver; 22import android.content.ComponentCallbacks2; 23import android.content.Context; 24import android.content.Intent; 25import android.graphics.Bitmap; 26import android.graphics.Canvas; 27import android.graphics.Rect; 28import android.graphics.Region.Op; 29import android.opengl.GLUtils; 30import android.os.SystemProperties; 31import android.renderscript.Matrix4f; 32import android.service.wallpaper.WallpaperService; 33import android.util.Log; 34import android.view.MotionEvent; 35import android.view.SurfaceHolder; 36 37import javax.microedition.khronos.egl.EGL10; 38import javax.microedition.khronos.egl.EGLConfig; 39import javax.microedition.khronos.egl.EGLContext; 40import javax.microedition.khronos.egl.EGLDisplay; 41import javax.microedition.khronos.egl.EGLSurface; 42import javax.microedition.khronos.opengles.GL; 43import java.io.IOException; 44import java.nio.ByteBuffer; 45import java.nio.ByteOrder; 46import java.nio.FloatBuffer; 47 48import static android.opengl.GLES20.*; 49import static javax.microedition.khronos.egl.EGL10.*; 50 51/** 52 * Default built-in wallpaper that simply shows a static image. 53 */ 54@SuppressWarnings({"UnusedDeclaration"}) 55public class ImageWallpaper extends WallpaperService { 56 private static final String TAG = "ImageWallpaper"; 57 private static final String GL_LOG_TAG = "ImageWallpaperGL"; 58 private static final boolean DEBUG = false; 59 private static final String PROPERTY_KERNEL_QEMU = "ro.kernel.qemu"; 60 61 static final boolean FIXED_SIZED_SURFACE = true; 62 static final boolean USE_OPENGL = true; 63 64 WallpaperManager mWallpaperManager; 65 66 DrawableEngine mEngine; 67 68 boolean mIsHwAccelerated; 69 70 @Override 71 public void onCreate() { 72 super.onCreate(); 73 mWallpaperManager = (WallpaperManager) getSystemService(WALLPAPER_SERVICE); 74 75 //noinspection PointlessBooleanExpression,ConstantConditions 76 if (FIXED_SIZED_SURFACE && USE_OPENGL) { 77 if (!isEmulator()) { 78 mIsHwAccelerated = ActivityManager.isHighEndGfx(); 79 } 80 } 81 } 82 83 @Override 84 public void onTrimMemory(int level) { 85 if (mEngine != null) { 86 mEngine.trimMemory(level); 87 } 88 } 89 90 private static boolean isEmulator() { 91 return "1".equals(SystemProperties.get(PROPERTY_KERNEL_QEMU, "0")); 92 } 93 94 public Engine onCreateEngine() { 95 mEngine = new DrawableEngine(); 96 return mEngine; 97 } 98 99 class DrawableEngine extends Engine { 100 static final int EGL_CONTEXT_CLIENT_VERSION = 0x3098; 101 static final int EGL_OPENGL_ES2_BIT = 4; 102 103 private final Object mLock = new Object[0]; 104 105 // TODO: Not currently used, keeping around until we know we don't need it 106 @SuppressWarnings({"UnusedDeclaration"}) 107 private WallpaperObserver mReceiver; 108 109 Bitmap mBackground; 110 int mBackgroundWidth = -1, mBackgroundHeight = -1; 111 float mXOffset; 112 float mYOffset; 113 114 boolean mVisible = true; 115 boolean mRedrawNeeded; 116 boolean mOffsetsChanged; 117 int mLastXTranslation; 118 int mLastYTranslation; 119 120 private EGL10 mEgl; 121 private EGLDisplay mEglDisplay; 122 private EGLConfig mEglConfig; 123 private EGLContext mEglContext; 124 private EGLSurface mEglSurface; 125 private GL mGL; 126 127 private static final String sSimpleVS = 128 "attribute vec4 position;\n" + 129 "attribute vec2 texCoords;\n" + 130 "varying vec2 outTexCoords;\n" + 131 "uniform mat4 projection;\n" + 132 "\nvoid main(void) {\n" + 133 " outTexCoords = texCoords;\n" + 134 " gl_Position = projection * position;\n" + 135 "}\n\n"; 136 private static final String sSimpleFS = 137 "precision mediump float;\n\n" + 138 "varying vec2 outTexCoords;\n" + 139 "uniform sampler2D texture;\n" + 140 "\nvoid main(void) {\n" + 141 " gl_FragColor = texture2D(texture, outTexCoords);\n" + 142 "}\n\n"; 143 144 private static final int FLOAT_SIZE_BYTES = 4; 145 private static final int TRIANGLE_VERTICES_DATA_STRIDE_BYTES = 5 * FLOAT_SIZE_BYTES; 146 private static final int TRIANGLE_VERTICES_DATA_POS_OFFSET = 0; 147 private static final int TRIANGLE_VERTICES_DATA_UV_OFFSET = 3; 148 149 class WallpaperObserver extends BroadcastReceiver { 150 public void onReceive(Context context, Intent intent) { 151 if (DEBUG) { 152 Log.d(TAG, "onReceive"); 153 } 154 155 synchronized (mLock) { 156 mBackgroundWidth = mBackgroundHeight = -1; 157 mBackground = null; 158 mRedrawNeeded = true; 159 drawFrameLocked(); 160 } 161 } 162 } 163 164 public DrawableEngine() { 165 super(); 166 setFixedSizeAllowed(true); 167 } 168 169 public void trimMemory(int level) { 170 if (level >= ComponentCallbacks2.TRIM_MEMORY_RUNNING_LOW && 171 mBackground != null && mIsHwAccelerated) { 172 mBackground.recycle(); 173 mBackground = null; 174 mWallpaperManager.forgetLoadedWallpaper(); 175 } 176 } 177 178 @Override 179 public void onCreate(SurfaceHolder surfaceHolder) { 180 if (DEBUG) { 181 Log.d(TAG, "onCreate"); 182 } 183 184 super.onCreate(surfaceHolder); 185 186 // TODO: Don't need this currently because the wallpaper service 187 // will restart the image wallpaper whenever the image changes. 188 //IntentFilter filter = new IntentFilter(Intent.ACTION_WALLPAPER_CHANGED); 189 //mReceiver = new WallpaperObserver(); 190 //registerReceiver(mReceiver, filter, null, mHandler); 191 192 updateSurfaceSize(surfaceHolder); 193 194 setOffsetNotificationsEnabled(false); 195 } 196 197 @Override 198 public void onDestroy() { 199 super.onDestroy(); 200 if (mReceiver != null) { 201 unregisterReceiver(mReceiver); 202 } 203 } 204 205 @Override 206 public void onDesiredSizeChanged(int desiredWidth, int desiredHeight) { 207 super.onDesiredSizeChanged(desiredWidth, desiredHeight); 208 SurfaceHolder surfaceHolder = getSurfaceHolder(); 209 if (surfaceHolder != null) { 210 updateSurfaceSize(surfaceHolder); 211 } 212 } 213 214 void updateSurfaceSize(SurfaceHolder surfaceHolder) { 215 if (FIXED_SIZED_SURFACE) { 216 // Used a fixed size surface, because we are special. We can do 217 // this because we know the current design of window animations doesn't 218 // cause this to break. 219 surfaceHolder.setFixedSize(getDesiredMinimumWidth(), getDesiredMinimumHeight()); 220 } else { 221 surfaceHolder.setSizeFromLayout(); 222 } 223 } 224 225 @Override 226 public void onVisibilityChanged(boolean visible) { 227 if (DEBUG) { 228 Log.d(TAG, "onVisibilityChanged: visible=" + visible); 229 } 230 231 synchronized (mLock) { 232 if (mVisible != visible) { 233 if (DEBUG) { 234 Log.d(TAG, "Visibility changed to visible=" + visible); 235 } 236 mVisible = visible; 237 drawFrameLocked(); 238 } 239 } 240 } 241 242 @Override 243 public void onTouchEvent(MotionEvent event) { 244 super.onTouchEvent(event); 245 } 246 247 @Override 248 public void onOffsetsChanged(float xOffset, float yOffset, 249 float xOffsetStep, float yOffsetStep, 250 int xPixels, int yPixels) { 251 if (DEBUG) { 252 Log.d(TAG, "onOffsetsChanged: xOffset=" + xOffset + ", yOffset=" + yOffset 253 + ", xOffsetStep=" + xOffsetStep + ", yOffsetStep=" + yOffsetStep 254 + ", xPixels=" + xPixels + ", yPixels=" + yPixels); 255 } 256 257 synchronized (mLock) { 258 if (mXOffset != xOffset || mYOffset != yOffset) { 259 if (DEBUG) { 260 Log.d(TAG, "Offsets changed to (" + xOffset + "," + yOffset + ")."); 261 } 262 mXOffset = xOffset; 263 mYOffset = yOffset; 264 mOffsetsChanged = true; 265 } 266 drawFrameLocked(); 267 } 268 } 269 270 @Override 271 public void onSurfaceChanged(SurfaceHolder holder, int format, int width, int height) { 272 if (DEBUG) { 273 Log.d(TAG, "onSurfaceChanged: width=" + width + ", height=" + height); 274 } 275 276 super.onSurfaceChanged(holder, format, width, height); 277 278 synchronized (mLock) { 279 mRedrawNeeded = true; 280 drawFrameLocked(); 281 } 282 } 283 284 @Override 285 public void onSurfaceRedrawNeeded(SurfaceHolder holder) { 286 if (DEBUG) { 287 Log.d(TAG, "onSurfaceRedrawNeeded:"); 288 } 289 super.onSurfaceRedrawNeeded(holder); 290 291 synchronized (mLock) { 292 mRedrawNeeded = true; 293 drawFrameLocked(); 294 } 295 } 296 297 void drawFrameLocked() { 298 if (!mVisible) { 299 if (DEBUG) { 300 Log.d(TAG, "Suppressed drawFrame since wallpaper is not visible."); 301 } 302 return; 303 } 304 if (!mRedrawNeeded && !mOffsetsChanged) { 305 if (DEBUG) { 306 Log.d(TAG, "Suppressed drawFrame since redraw is not needed " 307 + "and offsets have not changed."); 308 } 309 return; 310 } 311 312 // If we don't yet know the size of the wallpaper bitmap, 313 // we need to get it now. 314 boolean updateWallpaper = mBackgroundWidth < 0 || mBackgroundHeight < 0 ; 315 316 // If we somehow got to this point after we have last flushed 317 // the wallpaper, well we really need it to draw again. So 318 // seems like we need to reload it. Ouch. 319 updateWallpaper = updateWallpaper || mBackground == null; 320 321 if (updateWallpaper) { 322 updateWallpaperLocked(); 323 } 324 325 SurfaceHolder sh = getSurfaceHolder(); 326 final Rect frame = sh.getSurfaceFrame(); 327 final int dw = frame.width(); 328 final int dh = frame.height(); 329 final int availw = dw - mBackgroundWidth; 330 final int availh = dh - mBackgroundHeight; 331 int xPixels = availw < 0 ? (int)(availw * mXOffset + .5f) : (availw / 2); 332 int yPixels = availh < 0 ? (int)(availh * mYOffset + .5f) : (availh / 2); 333 334 mOffsetsChanged = false; 335 if (!mRedrawNeeded && xPixels == mLastXTranslation && yPixels == mLastYTranslation) { 336 if (DEBUG) { 337 Log.d(TAG, "Suppressed drawFrame since the image has not " 338 + "actually moved an integral number of pixels."); 339 } 340 return; 341 } 342 mRedrawNeeded = false; 343 mLastXTranslation = xPixels; 344 mLastYTranslation = yPixels; 345 346 if (mIsHwAccelerated) { 347 if (!drawWallpaperWithOpenGL(sh, availw, availh, xPixels, yPixels)) { 348 drawWallpaperWithCanvas(sh, availw, availh, xPixels, yPixels); 349 } 350 } else { 351 drawWallpaperWithCanvas(sh, availw, availh, xPixels, yPixels); 352 if (FIXED_SIZED_SURFACE) { 353 // If the surface is fixed-size, we should only need to 354 // draw it once and then we'll let the window manager 355 // position it appropriately. As such, we no longer needed 356 // the loaded bitmap. Yay! 357 // hw-accelerated path retains bitmap for faster rotation 358 mBackground = null; 359 mWallpaperManager.forgetLoadedWallpaper(); 360 } 361 } 362 363 } 364 365 void updateWallpaperLocked() { 366 Throwable exception = null; 367 try { 368 mBackground = mWallpaperManager.getBitmap(); 369 } catch (RuntimeException e) { 370 exception = e; 371 } catch (OutOfMemoryError e) { 372 exception = e; 373 } 374 375 if (exception != null) { 376 mBackground = null; 377 // Note that if we do fail at this, and the default wallpaper can't 378 // be loaded, we will go into a cycle. Don't do a build where the 379 // default wallpaper can't be loaded. 380 Log.w(TAG, "Unable to load wallpaper!", exception); 381 try { 382 mWallpaperManager.clear(); 383 } catch (IOException ex) { 384 // now we're really screwed. 385 Log.w(TAG, "Unable reset to default wallpaper!", ex); 386 } 387 } 388 389 mBackgroundWidth = mBackground != null ? mBackground.getWidth() : 0; 390 mBackgroundHeight = mBackground != null ? mBackground.getHeight() : 0; 391 } 392 393 private void drawWallpaperWithCanvas(SurfaceHolder sh, int w, int h, int x, int y) { 394 Canvas c = sh.lockCanvas(); 395 if (c != null) { 396 try { 397 if (DEBUG) { 398 Log.d(TAG, "Redrawing: x=" + x + ", y=" + y); 399 } 400 401 c.translate(x, y); 402 if (w < 0 || h < 0) { 403 c.save(Canvas.CLIP_SAVE_FLAG); 404 c.clipRect(0, 0, mBackgroundWidth, mBackgroundHeight, Op.DIFFERENCE); 405 c.drawColor(0xff000000); 406 c.restore(); 407 } 408 if (mBackground != null) { 409 c.drawBitmap(mBackground, 0, 0, null); 410 } 411 } finally { 412 sh.unlockCanvasAndPost(c); 413 } 414 } 415 } 416 417 private boolean drawWallpaperWithOpenGL(SurfaceHolder sh, int w, int h, int left, int top) { 418 if (!initGL(sh)) return false; 419 420 final float right = left + mBackgroundWidth; 421 final float bottom = top + mBackgroundHeight; 422 423 final Rect frame = sh.getSurfaceFrame(); 424 425 final Matrix4f ortho = new Matrix4f(); 426 ortho.loadOrtho(0.0f, frame.width(), frame.height(), 0.0f, -1.0f, 1.0f); 427 428 final FloatBuffer triangleVertices = createMesh(left, top, right, bottom); 429 430 final int texture = loadTexture(mBackground); 431 final int program = buildProgram(sSimpleVS, sSimpleFS); 432 433 final int attribPosition = glGetAttribLocation(program, "position"); 434 final int attribTexCoords = glGetAttribLocation(program, "texCoords"); 435 final int uniformTexture = glGetUniformLocation(program, "texture"); 436 final int uniformProjection = glGetUniformLocation(program, "projection"); 437 438 checkGlError(); 439 440 glViewport(0, 0, frame.width(), frame.height()); 441 glBindTexture(GL_TEXTURE_2D, texture); 442 443 glUseProgram(program); 444 glEnableVertexAttribArray(attribPosition); 445 glEnableVertexAttribArray(attribTexCoords); 446 glUniform1i(uniformTexture, 0); 447 glUniformMatrix4fv(uniformProjection, 1, false, ortho.getArray(), 0); 448 449 checkGlError(); 450 451 if (w < 0 || h < 0) { 452 glClearColor(0.0f, 0.0f, 0.0f, 0.0f); 453 glClear(GL_COLOR_BUFFER_BIT); 454 } 455 456 // drawQuad 457 triangleVertices.position(TRIANGLE_VERTICES_DATA_POS_OFFSET); 458 glVertexAttribPointer(attribPosition, 3, GL_FLOAT, false, 459 TRIANGLE_VERTICES_DATA_STRIDE_BYTES, triangleVertices); 460 461 triangleVertices.position(TRIANGLE_VERTICES_DATA_UV_OFFSET); 462 glVertexAttribPointer(attribTexCoords, 3, GL_FLOAT, false, 463 TRIANGLE_VERTICES_DATA_STRIDE_BYTES, triangleVertices); 464 465 glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); 466 467 if (!mEgl.eglSwapBuffers(mEglDisplay, mEglSurface)) { 468 throw new RuntimeException("Cannot swap buffers"); 469 } 470 checkEglError(); 471 472 finishGL(); 473 474 return true; 475 } 476 477 private FloatBuffer createMesh(int left, int top, float right, float bottom) { 478 final float[] verticesData = { 479 // X, Y, Z, U, V 480 left, bottom, 0.0f, 0.0f, 1.0f, 481 right, bottom, 0.0f, 1.0f, 1.0f, 482 left, top, 0.0f, 0.0f, 0.0f, 483 right, top, 0.0f, 1.0f, 0.0f, 484 }; 485 486 final int bytes = verticesData.length * FLOAT_SIZE_BYTES; 487 final FloatBuffer triangleVertices = ByteBuffer.allocateDirect(bytes).order( 488 ByteOrder.nativeOrder()).asFloatBuffer(); 489 triangleVertices.put(verticesData).position(0); 490 return triangleVertices; 491 } 492 493 private int loadTexture(Bitmap bitmap) { 494 int[] textures = new int[1]; 495 496 glActiveTexture(GL_TEXTURE0); 497 glGenTextures(1, textures, 0); 498 checkGlError(); 499 500 int texture = textures[0]; 501 glBindTexture(GL_TEXTURE_2D, texture); 502 checkGlError(); 503 504 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); 505 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); 506 507 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); 508 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); 509 510 GLUtils.texImage2D(GL_TEXTURE_2D, 0, GL_RGBA, bitmap, GL_UNSIGNED_BYTE, 0); 511 checkGlError(); 512 513 return texture; 514 } 515 516 private int buildProgram(String vertex, String fragment) { 517 int vertexShader = buildShader(vertex, GL_VERTEX_SHADER); 518 if (vertexShader == 0) return 0; 519 520 int fragmentShader = buildShader(fragment, GL_FRAGMENT_SHADER); 521 if (fragmentShader == 0) return 0; 522 523 int program = glCreateProgram(); 524 glAttachShader(program, vertexShader); 525 checkGlError(); 526 527 glAttachShader(program, fragmentShader); 528 checkGlError(); 529 530 glLinkProgram(program); 531 checkGlError(); 532 533 int[] status = new int[1]; 534 glGetProgramiv(program, GL_LINK_STATUS, status, 0); 535 if (status[0] != GL_TRUE) { 536 String error = glGetProgramInfoLog(program); 537 Log.d(GL_LOG_TAG, "Error while linking program:\n" + error); 538 glDeleteShader(vertexShader); 539 glDeleteShader(fragmentShader); 540 glDeleteProgram(program); 541 return 0; 542 } 543 544 return program; 545 } 546 547 private int buildShader(String source, int type) { 548 int shader = glCreateShader(type); 549 550 glShaderSource(shader, source); 551 checkGlError(); 552 553 glCompileShader(shader); 554 checkGlError(); 555 556 int[] status = new int[1]; 557 glGetShaderiv(shader, GL_COMPILE_STATUS, status, 0); 558 if (status[0] != GL_TRUE) { 559 String error = glGetShaderInfoLog(shader); 560 Log.d(GL_LOG_TAG, "Error while compiling shader:\n" + error); 561 glDeleteShader(shader); 562 return 0; 563 } 564 565 return shader; 566 } 567 568 private void checkEglError() { 569 int error = mEgl.eglGetError(); 570 if (error != EGL_SUCCESS) { 571 Log.w(GL_LOG_TAG, "EGL error = " + GLUtils.getEGLErrorString(error)); 572 } 573 } 574 575 private void checkGlError() { 576 int error = glGetError(); 577 if (error != GL_NO_ERROR) { 578 Log.w(GL_LOG_TAG, "GL error = 0x" + Integer.toHexString(error), new Throwable()); 579 } 580 } 581 582 private void finishGL() { 583 mEgl.eglMakeCurrent(mEglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); 584 mEgl.eglDestroySurface(mEglDisplay, mEglSurface); 585 mEgl.eglDestroyContext(mEglDisplay, mEglContext); 586 } 587 588 private boolean initGL(SurfaceHolder surfaceHolder) { 589 mEgl = (EGL10) EGLContext.getEGL(); 590 591 mEglDisplay = mEgl.eglGetDisplay(EGL_DEFAULT_DISPLAY); 592 if (mEglDisplay == EGL_NO_DISPLAY) { 593 throw new RuntimeException("eglGetDisplay failed " + 594 GLUtils.getEGLErrorString(mEgl.eglGetError())); 595 } 596 597 int[] version = new int[2]; 598 if (!mEgl.eglInitialize(mEglDisplay, version)) { 599 throw new RuntimeException("eglInitialize failed " + 600 GLUtils.getEGLErrorString(mEgl.eglGetError())); 601 } 602 603 mEglConfig = chooseEglConfig(); 604 if (mEglConfig == null) { 605 throw new RuntimeException("eglConfig not initialized"); 606 } 607 608 mEglContext = createContext(mEgl, mEglDisplay, mEglConfig); 609 610 mEglSurface = mEgl.eglCreateWindowSurface(mEglDisplay, mEglConfig, surfaceHolder, null); 611 612 if (mEglSurface == null || mEglSurface == EGL_NO_SURFACE) { 613 int error = mEgl.eglGetError(); 614 if (error == EGL_BAD_NATIVE_WINDOW) { 615 Log.e(GL_LOG_TAG, "createWindowSurface returned EGL_BAD_NATIVE_WINDOW."); 616 return false; 617 } 618 throw new RuntimeException("createWindowSurface failed " + 619 GLUtils.getEGLErrorString(error)); 620 } 621 622 if (!mEgl.eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface, mEglContext)) { 623 throw new RuntimeException("eglMakeCurrent failed " + 624 GLUtils.getEGLErrorString(mEgl.eglGetError())); 625 } 626 627 mGL = mEglContext.getGL(); 628 629 return true; 630 } 631 632 633 EGLContext createContext(EGL10 egl, EGLDisplay eglDisplay, EGLConfig eglConfig) { 634 int[] attrib_list = { EGL_CONTEXT_CLIENT_VERSION, 2, EGL_NONE }; 635 return egl.eglCreateContext(eglDisplay, eglConfig, EGL_NO_CONTEXT, attrib_list); 636 } 637 638 private EGLConfig chooseEglConfig() { 639 int[] configsCount = new int[1]; 640 EGLConfig[] configs = new EGLConfig[1]; 641 int[] configSpec = getConfig(); 642 if (!mEgl.eglChooseConfig(mEglDisplay, configSpec, configs, 1, configsCount)) { 643 throw new IllegalArgumentException("eglChooseConfig failed " + 644 GLUtils.getEGLErrorString(mEgl.eglGetError())); 645 } else if (configsCount[0] > 0) { 646 return configs[0]; 647 } 648 return null; 649 } 650 651 private int[] getConfig() { 652 return new int[] { 653 EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, 654 EGL_RED_SIZE, 8, 655 EGL_GREEN_SIZE, 8, 656 EGL_BLUE_SIZE, 8, 657 EGL_ALPHA_SIZE, 0, 658 EGL_DEPTH_SIZE, 0, 659 EGL_STENCIL_SIZE, 0, 660 EGL_NONE 661 }; 662 } 663 } 664} 665