| /* |
| * Copyright (C) 2013 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package com.android.photos.views; |
| |
| import android.content.Context; |
| import android.graphics.SurfaceTexture; |
| import android.opengl.GLSurfaceView.Renderer; |
| import android.opengl.GLUtils; |
| import android.util.Log; |
| import android.view.TextureView; |
| import android.view.TextureView.SurfaceTextureListener; |
| |
| import javax.microedition.khronos.egl.EGL10; |
| import javax.microedition.khronos.egl.EGLConfig; |
| import javax.microedition.khronos.egl.EGLContext; |
| import javax.microedition.khronos.egl.EGLDisplay; |
| import javax.microedition.khronos.egl.EGLSurface; |
| import javax.microedition.khronos.opengles.GL10; |
| |
| /** |
| * A TextureView that supports blocking rendering for synchronous drawing |
| */ |
| public class BlockingGLTextureView extends TextureView |
| implements SurfaceTextureListener { |
| |
| private RenderThread mRenderThread; |
| |
| public BlockingGLTextureView(Context context) { |
| super(context); |
| setSurfaceTextureListener(this); |
| } |
| |
| public void setRenderer(Renderer renderer) { |
| if (mRenderThread != null) { |
| throw new IllegalArgumentException("Renderer already set"); |
| } |
| mRenderThread = new RenderThread(renderer); |
| } |
| |
| public void render() { |
| mRenderThread.render(); |
| } |
| |
| public void destroy() { |
| if (mRenderThread != null) { |
| mRenderThread.finish(); |
| mRenderThread = null; |
| } |
| } |
| |
| @Override |
| public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, |
| int height) { |
| mRenderThread.setSurface(surface); |
| mRenderThread.setSize(width, height); |
| } |
| |
| @Override |
| public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, |
| int height) { |
| mRenderThread.setSize(width, height); |
| } |
| |
| @Override |
| public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) { |
| if (mRenderThread != null) { |
| mRenderThread.setSurface(null); |
| } |
| return false; |
| } |
| |
| @Override |
| public void onSurfaceTextureUpdated(SurfaceTexture surface) { |
| } |
| |
| @Override |
| protected void finalize() throws Throwable { |
| try { |
| destroy(); |
| } catch (Throwable t) { |
| // Ignore |
| } |
| super.finalize(); |
| } |
| |
| /** |
| * An EGL helper class. |
| */ |
| |
| private static class EglHelper { |
| private static final int EGL_CONTEXT_CLIENT_VERSION = 0x3098; |
| private static final int EGL_OPENGL_ES2_BIT = 4; |
| |
| EGL10 mEgl; |
| EGLDisplay mEglDisplay; |
| EGLSurface mEglSurface; |
| EGLConfig mEglConfig; |
| EGLContext mEglContext; |
| |
| private EGLConfig chooseEglConfig() { |
| int[] configsCount = new int[1]; |
| EGLConfig[] configs = new EGLConfig[1]; |
| int[] configSpec = getConfig(); |
| if (!mEgl.eglChooseConfig(mEglDisplay, configSpec, configs, 1, configsCount)) { |
| throw new IllegalArgumentException("eglChooseConfig failed " + |
| GLUtils.getEGLErrorString(mEgl.eglGetError())); |
| } else if (configsCount[0] > 0) { |
| return configs[0]; |
| } |
| return null; |
| } |
| |
| private static int[] getConfig() { |
| return new int[] { |
| EGL10.EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, |
| EGL10.EGL_RED_SIZE, 8, |
| EGL10.EGL_GREEN_SIZE, 8, |
| EGL10.EGL_BLUE_SIZE, 8, |
| EGL10.EGL_ALPHA_SIZE, 8, |
| EGL10.EGL_DEPTH_SIZE, 0, |
| EGL10.EGL_STENCIL_SIZE, 0, |
| EGL10.EGL_NONE |
| }; |
| } |
| |
| EGLContext createContext(EGL10 egl, EGLDisplay eglDisplay, EGLConfig eglConfig) { |
| int[] attribList = { EGL_CONTEXT_CLIENT_VERSION, 2, EGL10.EGL_NONE }; |
| return egl.eglCreateContext(eglDisplay, eglConfig, EGL10.EGL_NO_CONTEXT, attribList); |
| } |
| |
| /** |
| * Initialize EGL for a given configuration spec. |
| */ |
| public void start() { |
| /* |
| * Get an EGL instance |
| */ |
| mEgl = (EGL10) EGLContext.getEGL(); |
| |
| /* |
| * Get to the default display. |
| */ |
| mEglDisplay = mEgl.eglGetDisplay(EGL10.EGL_DEFAULT_DISPLAY); |
| |
| if (mEglDisplay == EGL10.EGL_NO_DISPLAY) { |
| throw new RuntimeException("eglGetDisplay failed"); |
| } |
| |
| /* |
| * We can now initialize EGL for that display |
| */ |
| int[] version = new int[2]; |
| if (!mEgl.eglInitialize(mEglDisplay, version)) { |
| throw new RuntimeException("eglInitialize failed"); |
| } |
| mEglConfig = chooseEglConfig(); |
| |
| /* |
| * Create an EGL context. We want to do this as rarely as we can, because an |
| * EGL context is a somewhat heavy object. |
| */ |
| mEglContext = createContext(mEgl, mEglDisplay, mEglConfig); |
| |
| if (mEglContext == null || mEglContext == EGL10.EGL_NO_CONTEXT) { |
| mEglContext = null; |
| throwEglException("createContext"); |
| } |
| |
| mEglSurface = null; |
| } |
| |
| /** |
| * Create an egl surface for the current SurfaceTexture surface. If a surface |
| * already exists, destroy it before creating the new surface. |
| * |
| * @return true if the surface was created successfully. |
| */ |
| public boolean createSurface(SurfaceTexture surface) { |
| /* |
| * Check preconditions. |
| */ |
| if (mEgl == null) { |
| throw new RuntimeException("egl not initialized"); |
| } |
| if (mEglDisplay == null) { |
| throw new RuntimeException("eglDisplay not initialized"); |
| } |
| if (mEglConfig == null) { |
| throw new RuntimeException("mEglConfig not initialized"); |
| } |
| |
| /* |
| * The window size has changed, so we need to create a new |
| * surface. |
| */ |
| destroySurfaceImp(); |
| |
| /* |
| * Create an EGL surface we can render into. |
| */ |
| if (surface != null) { |
| mEglSurface = mEgl.eglCreateWindowSurface(mEglDisplay, mEglConfig, surface, null); |
| } else { |
| mEglSurface = null; |
| } |
| |
| if (mEglSurface == null || mEglSurface == EGL10.EGL_NO_SURFACE) { |
| int error = mEgl.eglGetError(); |
| if (error == EGL10.EGL_BAD_NATIVE_WINDOW) { |
| Log.e("EglHelper", "createWindowSurface returned EGL_BAD_NATIVE_WINDOW."); |
| } |
| return false; |
| } |
| |
| /* |
| * Before we can issue GL commands, we need to make sure |
| * the context is current and bound to a surface. |
| */ |
| if (!mEgl.eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface, mEglContext)) { |
| /* |
| * Could not make the context current, probably because the underlying |
| * SurfaceView surface has been destroyed. |
| */ |
| logEglErrorAsWarning("EGLHelper", "eglMakeCurrent", mEgl.eglGetError()); |
| return false; |
| } |
| |
| return true; |
| } |
| |
| /** |
| * Create a GL object for the current EGL context. |
| */ |
| public GL10 createGL() { |
| return (GL10) mEglContext.getGL(); |
| } |
| |
| /** |
| * Display the current render surface. |
| * @return the EGL error code from eglSwapBuffers. |
| */ |
| public int swap() { |
| if (!mEgl.eglSwapBuffers(mEglDisplay, mEglSurface)) { |
| return mEgl.eglGetError(); |
| } |
| return EGL10.EGL_SUCCESS; |
| } |
| |
| public void destroySurface() { |
| destroySurfaceImp(); |
| } |
| |
| private void destroySurfaceImp() { |
| if (mEglSurface != null && mEglSurface != EGL10.EGL_NO_SURFACE) { |
| mEgl.eglMakeCurrent(mEglDisplay, EGL10.EGL_NO_SURFACE, |
| EGL10.EGL_NO_SURFACE, |
| EGL10.EGL_NO_CONTEXT); |
| mEgl.eglDestroySurface(mEglDisplay, mEglSurface); |
| mEglSurface = null; |
| } |
| } |
| |
| public void finish() { |
| if (mEglContext != null) { |
| mEgl.eglDestroyContext(mEglDisplay, mEglContext); |
| mEglContext = null; |
| } |
| if (mEglDisplay != null) { |
| mEgl.eglTerminate(mEglDisplay); |
| mEglDisplay = null; |
| } |
| } |
| |
| private void throwEglException(String function) { |
| throwEglException(function, mEgl.eglGetError()); |
| } |
| |
| public static void throwEglException(String function, int error) { |
| String message = formatEglError(function, error); |
| throw new RuntimeException(message); |
| } |
| |
| public static void logEglErrorAsWarning(String tag, String function, int error) { |
| Log.w(tag, formatEglError(function, error)); |
| } |
| |
| public static String formatEglError(String function, int error) { |
| return function + " failed: " + error; |
| } |
| |
| } |
| |
| private static class RenderThread extends Thread { |
| private static final int INVALID = -1; |
| private static final int RENDER = 1; |
| private static final int CHANGE_SURFACE = 2; |
| private static final int RESIZE_SURFACE = 3; |
| private static final int FINISH = 4; |
| |
| private EglHelper mEglHelper = new EglHelper(); |
| |
| private Object mLock = new Object(); |
| private int mExecMsgId = INVALID; |
| private SurfaceTexture mSurface; |
| private Renderer mRenderer; |
| private int mWidth, mHeight; |
| |
| private boolean mFinished = false; |
| private GL10 mGL; |
| |
| public RenderThread(Renderer renderer) { |
| super("RenderThread"); |
| mRenderer = renderer; |
| start(); |
| } |
| |
| private void checkRenderer() { |
| if (mRenderer == null) { |
| throw new IllegalArgumentException("Renderer is null!"); |
| } |
| } |
| |
| private void checkSurface() { |
| if (mSurface == null) { |
| throw new IllegalArgumentException("surface is null!"); |
| } |
| } |
| |
| public void setSurface(SurfaceTexture surface) { |
| // If the surface is null we're being torn down, don't need a |
| // renderer then |
| if (surface != null) { |
| checkRenderer(); |
| } |
| mSurface = surface; |
| exec(CHANGE_SURFACE); |
| } |
| |
| public void setSize(int width, int height) { |
| checkRenderer(); |
| checkSurface(); |
| mWidth = width; |
| mHeight = height; |
| exec(RESIZE_SURFACE); |
| } |
| |
| public void render() { |
| checkRenderer(); |
| if (mSurface != null) { |
| exec(RENDER); |
| mSurface.updateTexImage(); |
| } |
| } |
| |
| public void finish() { |
| mSurface = null; |
| exec(FINISH); |
| try { |
| join(); |
| } catch (InterruptedException e) { |
| // Ignore |
| } |
| } |
| |
| private void exec(int msgid) { |
| synchronized (mLock) { |
| if (mExecMsgId != INVALID) { |
| throw new IllegalArgumentException( |
| "Message already set - multithreaded access?"); |
| } |
| mExecMsgId = msgid; |
| mLock.notify(); |
| try { |
| mLock.wait(); |
| } catch (InterruptedException e) { |
| // Ignore |
| } |
| } |
| } |
| |
| private void handleMessageLocked(int what) { |
| switch (what) { |
| case CHANGE_SURFACE: |
| if (mEglHelper.createSurface(mSurface)) { |
| mGL = mEglHelper.createGL(); |
| mRenderer.onSurfaceCreated(mGL, mEglHelper.mEglConfig); |
| } |
| break; |
| case RESIZE_SURFACE: |
| mRenderer.onSurfaceChanged(mGL, mWidth, mHeight); |
| break; |
| case RENDER: |
| mRenderer.onDrawFrame(mGL); |
| mEglHelper.swap(); |
| break; |
| case FINISH: |
| mEglHelper.destroySurface(); |
| mEglHelper.finish(); |
| mFinished = true; |
| break; |
| } |
| } |
| |
| @Override |
| public void run() { |
| synchronized (mLock) { |
| mEglHelper.start(); |
| while (!mFinished) { |
| while (mExecMsgId == INVALID) { |
| try { |
| mLock.wait(); |
| } catch (InterruptedException e) { |
| // Ignore |
| } |
| } |
| handleMessageLocked(mExecMsgId); |
| mExecMsgId = INVALID; |
| mLock.notify(); |
| } |
| mExecMsgId = FINISH; |
| } |
| } |
| } |
| } |