diff options
Diffstat (limited to 'WallpaperPicker/src/com/android/photos/views/BlockingGLTextureView.java')
-rw-r--r-- | WallpaperPicker/src/com/android/photos/views/BlockingGLTextureView.java | 438 |
1 files changed, 438 insertions, 0 deletions
diff --git a/WallpaperPicker/src/com/android/photos/views/BlockingGLTextureView.java b/WallpaperPicker/src/com/android/photos/views/BlockingGLTextureView.java new file mode 100644 index 000000000..8a0505185 --- /dev/null +++ b/WallpaperPicker/src/com/android/photos/views/BlockingGLTextureView.java @@ -0,0 +1,438 @@ +/* + * 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; + } + } + } +} |