/* * 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; } } } }