1/* 2 * Copyright (C) 2013 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.photos.views; 18 19import android.content.Context; 20import android.graphics.SurfaceTexture; 21import android.opengl.GLSurfaceView.Renderer; 22import android.opengl.GLUtils; 23import android.util.Log; 24import android.view.TextureView; 25import android.view.TextureView.SurfaceTextureListener; 26 27import javax.microedition.khronos.egl.EGL10; 28import javax.microedition.khronos.egl.EGLConfig; 29import javax.microedition.khronos.egl.EGLContext; 30import javax.microedition.khronos.egl.EGLDisplay; 31import javax.microedition.khronos.egl.EGLSurface; 32import javax.microedition.khronos.opengles.GL10; 33 34/** 35 * A TextureView that supports blocking rendering for synchronous drawing 36 */ 37public class BlockingGLTextureView extends TextureView 38 implements SurfaceTextureListener { 39 40 private RenderThread mRenderThread; 41 42 public BlockingGLTextureView(Context context) { 43 super(context); 44 setSurfaceTextureListener(this); 45 } 46 47 public void setRenderer(Renderer renderer) { 48 if (mRenderThread != null) { 49 throw new IllegalArgumentException("Renderer already set"); 50 } 51 mRenderThread = new RenderThread(renderer); 52 } 53 54 public void render() { 55 mRenderThread.render(); 56 } 57 58 public void destroy() { 59 if (mRenderThread != null) { 60 mRenderThread.finish(); 61 mRenderThread = null; 62 } 63 } 64 65 @Override 66 public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, 67 int height) { 68 mRenderThread.setSurface(surface); 69 mRenderThread.setSize(width, height); 70 } 71 72 @Override 73 public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, 74 int height) { 75 mRenderThread.setSize(width, height); 76 } 77 78 @Override 79 public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) { 80 if (mRenderThread != null) { 81 mRenderThread.setSurface(null); 82 } 83 return false; 84 } 85 86 @Override 87 public void onSurfaceTextureUpdated(SurfaceTexture surface) { 88 } 89 90 @Override 91 protected void finalize() throws Throwable { 92 try { 93 destroy(); 94 } catch (Throwable t) { 95 // Ignore 96 } 97 super.finalize(); 98 } 99 100 /** 101 * An EGL helper class. 102 */ 103 104 private static class EglHelper { 105 private static final int EGL_CONTEXT_CLIENT_VERSION = 0x3098; 106 private static final int EGL_OPENGL_ES2_BIT = 4; 107 108 EGL10 mEgl; 109 EGLDisplay mEglDisplay; 110 EGLSurface mEglSurface; 111 EGLConfig mEglConfig; 112 EGLContext mEglContext; 113 114 private EGLConfig chooseEglConfig() { 115 int[] configsCount = new int[1]; 116 EGLConfig[] configs = new EGLConfig[1]; 117 int[] configSpec = getConfig(); 118 if (!mEgl.eglChooseConfig(mEglDisplay, configSpec, configs, 1, configsCount)) { 119 throw new IllegalArgumentException("eglChooseConfig failed " + 120 GLUtils.getEGLErrorString(mEgl.eglGetError())); 121 } else if (configsCount[0] > 0) { 122 return configs[0]; 123 } 124 return null; 125 } 126 127 private static int[] getConfig() { 128 return new int[] { 129 EGL10.EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, 130 EGL10.EGL_RED_SIZE, 8, 131 EGL10.EGL_GREEN_SIZE, 8, 132 EGL10.EGL_BLUE_SIZE, 8, 133 EGL10.EGL_ALPHA_SIZE, 8, 134 EGL10.EGL_DEPTH_SIZE, 0, 135 EGL10.EGL_STENCIL_SIZE, 0, 136 EGL10.EGL_NONE 137 }; 138 } 139 140 EGLContext createContext(EGL10 egl, EGLDisplay eglDisplay, EGLConfig eglConfig) { 141 int[] attribList = { EGL_CONTEXT_CLIENT_VERSION, 2, EGL10.EGL_NONE }; 142 return egl.eglCreateContext(eglDisplay, eglConfig, EGL10.EGL_NO_CONTEXT, attribList); 143 } 144 145 /** 146 * Initialize EGL for a given configuration spec. 147 */ 148 public void start() { 149 /* 150 * Get an EGL instance 151 */ 152 mEgl = (EGL10) EGLContext.getEGL(); 153 154 /* 155 * Get to the default display. 156 */ 157 mEglDisplay = mEgl.eglGetDisplay(EGL10.EGL_DEFAULT_DISPLAY); 158 159 if (mEglDisplay == EGL10.EGL_NO_DISPLAY) { 160 throw new RuntimeException("eglGetDisplay failed"); 161 } 162 163 /* 164 * We can now initialize EGL for that display 165 */ 166 int[] version = new int[2]; 167 if (!mEgl.eglInitialize(mEglDisplay, version)) { 168 throw new RuntimeException("eglInitialize failed"); 169 } 170 mEglConfig = chooseEglConfig(); 171 172 /* 173 * Create an EGL context. We want to do this as rarely as we can, because an 174 * EGL context is a somewhat heavy object. 175 */ 176 mEglContext = createContext(mEgl, mEglDisplay, mEglConfig); 177 178 if (mEglContext == null || mEglContext == EGL10.EGL_NO_CONTEXT) { 179 mEglContext = null; 180 throwEglException("createContext"); 181 } 182 183 mEglSurface = null; 184 } 185 186 /** 187 * Create an egl surface for the current SurfaceTexture surface. If a surface 188 * already exists, destroy it before creating the new surface. 189 * 190 * @return true if the surface was created successfully. 191 */ 192 public boolean createSurface(SurfaceTexture surface) { 193 /* 194 * Check preconditions. 195 */ 196 if (mEgl == null) { 197 throw new RuntimeException("egl not initialized"); 198 } 199 if (mEglDisplay == null) { 200 throw new RuntimeException("eglDisplay not initialized"); 201 } 202 if (mEglConfig == null) { 203 throw new RuntimeException("mEglConfig not initialized"); 204 } 205 206 /* 207 * The window size has changed, so we need to create a new 208 * surface. 209 */ 210 destroySurfaceImp(); 211 212 /* 213 * Create an EGL surface we can render into. 214 */ 215 if (surface != null) { 216 mEglSurface = mEgl.eglCreateWindowSurface(mEglDisplay, mEglConfig, surface, null); 217 } else { 218 mEglSurface = null; 219 } 220 221 if (mEglSurface == null || mEglSurface == EGL10.EGL_NO_SURFACE) { 222 int error = mEgl.eglGetError(); 223 if (error == EGL10.EGL_BAD_NATIVE_WINDOW) { 224 Log.e("EglHelper", "createWindowSurface returned EGL_BAD_NATIVE_WINDOW."); 225 } 226 return false; 227 } 228 229 /* 230 * Before we can issue GL commands, we need to make sure 231 * the context is current and bound to a surface. 232 */ 233 if (!mEgl.eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface, mEglContext)) { 234 /* 235 * Could not make the context current, probably because the underlying 236 * SurfaceView surface has been destroyed. 237 */ 238 logEglErrorAsWarning("EGLHelper", "eglMakeCurrent", mEgl.eglGetError()); 239 return false; 240 } 241 242 return true; 243 } 244 245 /** 246 * Create a GL object for the current EGL context. 247 */ 248 public GL10 createGL() { 249 return (GL10) mEglContext.getGL(); 250 } 251 252 /** 253 * Display the current render surface. 254 * @return the EGL error code from eglSwapBuffers. 255 */ 256 public int swap() { 257 if (!mEgl.eglSwapBuffers(mEglDisplay, mEglSurface)) { 258 return mEgl.eglGetError(); 259 } 260 return EGL10.EGL_SUCCESS; 261 } 262 263 public void destroySurface() { 264 destroySurfaceImp(); 265 } 266 267 private void destroySurfaceImp() { 268 if (mEglSurface != null && mEglSurface != EGL10.EGL_NO_SURFACE) { 269 mEgl.eglMakeCurrent(mEglDisplay, EGL10.EGL_NO_SURFACE, 270 EGL10.EGL_NO_SURFACE, 271 EGL10.EGL_NO_CONTEXT); 272 mEgl.eglDestroySurface(mEglDisplay, mEglSurface); 273 mEglSurface = null; 274 } 275 } 276 277 public void finish() { 278 if (mEglContext != null) { 279 mEgl.eglDestroyContext(mEglDisplay, mEglContext); 280 mEglContext = null; 281 } 282 if (mEglDisplay != null) { 283 mEgl.eglTerminate(mEglDisplay); 284 mEglDisplay = null; 285 } 286 } 287 288 private void throwEglException(String function) { 289 throwEglException(function, mEgl.eglGetError()); 290 } 291 292 public static void throwEglException(String function, int error) { 293 String message = formatEglError(function, error); 294 throw new RuntimeException(message); 295 } 296 297 public static void logEglErrorAsWarning(String tag, String function, int error) { 298 Log.w(tag, formatEglError(function, error)); 299 } 300 301 public static String formatEglError(String function, int error) { 302 return function + " failed: " + error; 303 } 304 305 } 306 307 private static class RenderThread extends Thread { 308 private static final int INVALID = -1; 309 private static final int RENDER = 1; 310 private static final int CHANGE_SURFACE = 2; 311 private static final int RESIZE_SURFACE = 3; 312 private static final int FINISH = 4; 313 314 private EglHelper mEglHelper = new EglHelper(); 315 316 private Object mLock = new Object(); 317 private int mExecMsgId = INVALID; 318 private SurfaceTexture mSurface; 319 private Renderer mRenderer; 320 private int mWidth, mHeight; 321 322 private boolean mFinished = false; 323 private GL10 mGL; 324 325 public RenderThread(Renderer renderer) { 326 super("RenderThread"); 327 mRenderer = renderer; 328 start(); 329 } 330 331 private void checkRenderer() { 332 if (mRenderer == null) { 333 throw new IllegalArgumentException("Renderer is null!"); 334 } 335 } 336 337 private void checkSurface() { 338 if (mSurface == null) { 339 throw new IllegalArgumentException("surface is null!"); 340 } 341 } 342 343 public void setSurface(SurfaceTexture surface) { 344 // If the surface is null we're being torn down, don't need a 345 // renderer then 346 if (surface != null) { 347 checkRenderer(); 348 } 349 mSurface = surface; 350 exec(CHANGE_SURFACE); 351 } 352 353 public void setSize(int width, int height) { 354 checkRenderer(); 355 checkSurface(); 356 mWidth = width; 357 mHeight = height; 358 exec(RESIZE_SURFACE); 359 } 360 361 public void render() { 362 checkRenderer(); 363 if (mSurface != null) { 364 exec(RENDER); 365 mSurface.updateTexImage(); 366 } 367 } 368 369 public void finish() { 370 mSurface = null; 371 exec(FINISH); 372 try { 373 join(); 374 } catch (InterruptedException e) { 375 // Ignore 376 } 377 } 378 379 private void exec(int msgid) { 380 synchronized (mLock) { 381 if (mExecMsgId != INVALID) { 382 throw new IllegalArgumentException( 383 "Message already set - multithreaded access?"); 384 } 385 mExecMsgId = msgid; 386 mLock.notify(); 387 try { 388 mLock.wait(); 389 } catch (InterruptedException e) { 390 // Ignore 391 } 392 } 393 } 394 395 private void handleMessageLocked(int what) { 396 switch (what) { 397 case CHANGE_SURFACE: 398 if (mEglHelper.createSurface(mSurface)) { 399 mGL = mEglHelper.createGL(); 400 mRenderer.onSurfaceCreated(mGL, mEglHelper.mEglConfig); 401 } 402 break; 403 case RESIZE_SURFACE: 404 mRenderer.onSurfaceChanged(mGL, mWidth, mHeight); 405 break; 406 case RENDER: 407 mRenderer.onDrawFrame(mGL); 408 mEglHelper.swap(); 409 break; 410 case FINISH: 411 mEglHelper.destroySurface(); 412 mEglHelper.finish(); 413 mFinished = true; 414 break; 415 } 416 } 417 418 @Override 419 public void run() { 420 synchronized (mLock) { 421 mEglHelper.start(); 422 while (!mFinished) { 423 while (mExecMsgId == INVALID) { 424 try { 425 mLock.wait(); 426 } catch (InterruptedException e) { 427 // Ignore 428 } 429 } 430 handleMessageLocked(mExecMsgId); 431 mExecMsgId = INVALID; 432 mLock.notify(); 433 } 434 mExecMsgId = FINISH; 435 } 436 } 437 } 438} 439