blob: e12fe432ece6ae8bd0dec9fed6c55bca259c6eb6 [file] [log] [blame]
/*
* 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();
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.sendEmptyMessage(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];
}
}