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