/* * Copyright (C) 2011 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.camera; import android.annotation.TargetApi; import android.graphics.SurfaceTexture; import android.os.ConditionVariable; import android.os.Handler; import android.os.HandlerThread; import android.os.Looper; import android.os.Message; import android.util.Log; import com.android.gallery3d.common.ApiHelper; 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; @TargetApi(ApiHelper.VERSION_CODES.HONEYCOMB) // uses SurfaceTexture public class MosaicPreviewRenderer { private static final String TAG = "MosaicPreviewRenderer"; private static final int EGL_CONTEXT_CLIENT_VERSION = 0x3098; private static final boolean DEBUG = false; private int mWidth; // width of the view in UI private int mHeight; // height of the view in UI private boolean mIsLandscape = true; private final float[] mTransformMatrix = new float[16]; private ConditionVariable mEglThreadBlockVar = new ConditionVariable(); private HandlerThread mEglThread; private EGLHandler mEglHandler; private EGLConfig mEglConfig; private EGLDisplay mEglDisplay; private EGLContext mEglContext; private EGLSurface mEglSurface; private SurfaceTexture mMosaicOutputSurfaceTexture; private SurfaceTexture mInputSurfaceTexture; private EGL10 mEgl; private GL10 mGl; private class EGLHandler extends Handler { public static final int MSG_INIT_EGL_SYNC = 0; public static final int MSG_SHOW_PREVIEW_FRAME_SYNC = 1; public static final int MSG_SHOW_PREVIEW_FRAME = 2; public static final int MSG_ALIGN_FRAME_SYNC = 3; public static final int MSG_RELEASE = 4; public EGLHandler(Looper looper) { super(looper); } @Override public void handleMessage(Message msg) { switch (msg.what) { case MSG_INIT_EGL_SYNC: doInitGL(); mEglThreadBlockVar.open(); break; case MSG_SHOW_PREVIEW_FRAME_SYNC: doShowPreviewFrame(); mEglThreadBlockVar.open(); break; case MSG_SHOW_PREVIEW_FRAME: doShowPreviewFrame(); break; case MSG_ALIGN_FRAME_SYNC: doAlignFrame(); mEglThreadBlockVar.open(); break; case MSG_RELEASE: doRelease(); mEglThreadBlockVar.open(); break; } } private void doAlignFrame() { mInputSurfaceTexture.updateTexImage(); mInputSurfaceTexture.getTransformMatrix(mTransformMatrix); MosaicRenderer.setWarping(true); // Call preprocess to render it to low-res and high-res RGB textures. MosaicRenderer.preprocess(mTransformMatrix); // Now, transfer the textures from GPU to CPU memory for processing MosaicRenderer.transferGPUtoCPU(); MosaicRenderer.updateMatrix(); draw(); mEgl.eglSwapBuffers(mEglDisplay, mEglSurface); } private void doShowPreviewFrame() { mInputSurfaceTexture.updateTexImage(); mInputSurfaceTexture.getTransformMatrix(mTransformMatrix); MosaicRenderer.setWarping(false); // Call preprocess to render it to low-res and high-res RGB textures. MosaicRenderer.preprocess(mTransformMatrix); MosaicRenderer.updateMatrix(); draw(); mEgl.eglSwapBuffers(mEglDisplay, mEglSurface); } private void doInitGL() { // These are copied from GLSurfaceView mEgl = (EGL10) EGLContext.getEGL(); mEglDisplay = mEgl.eglGetDisplay(EGL10.EGL_DEFAULT_DISPLAY); if (mEglDisplay == EGL10.EGL_NO_DISPLAY) { throw new RuntimeException("eglGetDisplay failed"); } int[] version = new int[2]; if (!mEgl.eglInitialize(mEglDisplay, version)) { throw new RuntimeException("eglInitialize failed"); } else { Log.v(TAG, "EGL version: " + version[0] + '.' + version[1]); } int[] attribList = {EGL_CONTEXT_CLIENT_VERSION, 2, EGL10.EGL_NONE }; mEglConfig = chooseConfig(mEgl, mEglDisplay); mEglContext = mEgl.eglCreateContext(mEglDisplay, mEglConfig, EGL10.EGL_NO_CONTEXT, attribList); if (mEglContext == null || mEglContext == EGL10.EGL_NO_CONTEXT) { throw new RuntimeException("failed to createContext"); } mEglSurface = mEgl.eglCreateWindowSurface( mEglDisplay, mEglConfig, mMosaicOutputSurfaceTexture, null); if (mEglSurface == null || mEglSurface == EGL10.EGL_NO_SURFACE) { throw new RuntimeException("failed to createWindowSurface"); } if (!mEgl.eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface, mEglContext)) { throw new RuntimeException("failed to eglMakeCurrent"); } mGl = (GL10) mEglContext.getGL(); mInputSurfaceTexture = new SurfaceTexture(MosaicRenderer.init()); MosaicRenderer.reset(mWidth, mHeight, mIsLandscape); } private void doRelease() { mEgl.eglDestroySurface(mEglDisplay, mEglSurface); mEgl.eglDestroyContext(mEglDisplay, mEglContext); mEgl.eglMakeCurrent(mEglDisplay, EGL10.EGL_NO_SURFACE, EGL10.EGL_NO_SURFACE, EGL10.EGL_NO_CONTEXT); mEgl.eglTerminate(mEglDisplay); mEglSurface = null; mEglContext = null; mEglDisplay = null; releaseSurfaceTexture(mInputSurfaceTexture); mEglThread.quit(); } @TargetApi(ApiHelper.VERSION_CODES.ICE_CREAM_SANDWICH) private void releaseSurfaceTexture(SurfaceTexture st) { if (ApiHelper.HAS_RELEASE_SURFACE_TEXTURE) { st.release(); } } // Should be called from other thread. public void sendMessageSync(int msg) { mEglThreadBlockVar.close(); sendEmptyMessage(msg); mEglThreadBlockVar.block(); } } public MosaicPreviewRenderer(SurfaceTexture tex, int w, int h, boolean isLandscape) { mMosaicOutputSurfaceTexture = tex; mWidth = w; mHeight = h; mIsLandscape = isLandscape; mEglThread = new HandlerThread("PanoramaRealtimeRenderer"); mEglThread.start(); mEglHandler = new EGLHandler(mEglThread.getLooper()); // We need to sync this because the generation of surface texture for input is // done here and the client will continue with the assumption that the // generation is completed. mEglHandler.sendMessageSync(EGLHandler.MSG_INIT_EGL_SYNC); } public void release() { mEglHandler.sendMessageSync(EGLHandler.MSG_RELEASE); } public void showPreviewFrameSync() { mEglHandler.sendMessageSync(EGLHandler.MSG_SHOW_PREVIEW_FRAME_SYNC); } public void showPreviewFrame() { mEglHandler.sendEmptyMessage(EGLHandler.MSG_SHOW_PREVIEW_FRAME); } public void alignFrameSync() { mEglHandler.sendMessageSync(EGLHandler.MSG_ALIGN_FRAME_SYNC); } public SurfaceTexture getInputSurfaceTexture() { return mInputSurfaceTexture; } private void draw() { MosaicRenderer.step(); } private static void checkEglError(String prompt, EGL10 egl) { int error; while ((error = egl.eglGetError()) != EGL10.EGL_SUCCESS) { Log.e(TAG, String.format("%s: EGL error: 0x%x", prompt, error)); } } private static final int EGL_OPENGL_ES2_BIT = 4; private static final int[] CONFIG_SPEC = 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_NONE }; private static EGLConfig chooseConfig(EGL10 egl, EGLDisplay display) { int[] numConfig = new int[1]; if (!egl.eglChooseConfig(display, CONFIG_SPEC, null, 0, numConfig)) { throw new IllegalArgumentException("eglChooseConfig failed"); } int numConfigs = numConfig[0]; if (numConfigs <= 0) { throw new IllegalArgumentException("No configs match configSpec"); } EGLConfig[] configs = new EGLConfig[numConfigs]; if (!egl.eglChooseConfig( display, CONFIG_SPEC, configs, numConfigs, numConfig)) { throw new IllegalArgumentException("eglChooseConfig#2 failed"); } return configs[0]; } }