第一步:创建EGLUtil工具类,主要是封装了EGL环境的创建函数
package com.leilu.mycamera;
import android.opengl.EGLExt;
import android.util.Log;
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 static android.opengl.EGL14.EGL_CONTEXT_CLIENT_VERSION;
import static android.opengl.EGL14.EGL_OPENGL_ES2_BIT;
import static javax.microedition.khronos.egl.EGL10.EGL_BLUE_SIZE;
import static javax.microedition.khronos.egl.EGL10.EGL_DEFAULT_DISPLAY;
import static javax.microedition.khronos.egl.EGL10.EGL_GREEN_SIZE;
import static javax.microedition.khronos.egl.EGL10.EGL_NONE;
import static javax.microedition.khronos.egl.EGL10.EGL_NO_CONTEXT;
import static javax.microedition.khronos.egl.EGL10.EGL_NO_DISPLAY;
import static javax.microedition.khronos.egl.EGL10.EGL_NO_SURFACE;
import static javax.microedition.khronos.egl.EGL10.EGL_RED_SIZE;
import static javax.microedition.khronos.egl.EGL10.EGL_RENDERABLE_TYPE;
import static javax.microedition.khronos.egl.EGL10.EGL_SURFACE_TYPE;
import static javax.microedition.khronos.egl.EGL10.EGL_WINDOW_BIT;
/**
* 创建EGL环境的工具类,根据GLSurfaceView源码,创建EGL环境主要分为以下几步
* 1.获取EGL对象
* 2.调用eglGetDisplay方法获取EGLDisplay,然后调用eglInitialize方法初始化
* 3.调用eglChooseConfig进行配置
* 4.调用eglCreateContext方法创建EGLContext
* 5.调用eglCreateWindowSurface方法创建EGLSurface
* 6.调用eglMakeCurrent方法绑定EGLSurface到EGLContext
* 7.绘制完成后调用eglSwapBuffers进行缓冲区交换
*
* 1.所有的方法方法调用都需要在同一个线程里面实现
* 2.需要注意的问题:由于surface可能改变,所以在surface改变的时候需要重新调用
* createWindowSurface方法和eglMakeCurrent方法进行EGLSurface的重新创建,否则可能
* 不能正常显示
*
* Created by ll on 2018/9/10.
*/
public class EGLUtil {
public static final int VERSION_2 = 2;
public static final int VERSION_3 = 3;
private EGLContext mEglContext = EGL_NO_CONTEXT;
private EGLSurface mEglSurface = EGL_NO_SURFACE;
private EGLDisplay mEglDisplay = EGL_NO_DISPLAY;
private EGL10 mEGL;
private EGLConfig mEGLConfig;
private int mVersion = VERSION_2;
public EGLUtil() {
this(VERSION_2);
}
public EGLUtil(int version) {
if (version == VERSION_2 || version == VERSION_3) {
mVersion = version;
}
}
public boolean init(EGLContext shareContext) {
mEGL = (EGL10) EGLContext.getEGL();
mEglDisplay = mEGL.eglGetDisplay(EGL_DEFAULT_DISPLAY);
if (mEglDisplay == EGL_NO_DISPLAY) {
Log.i("==", "eglGetDisplay failed!");
return false;
}
if (!mEGL.eglInitialize(mEglDisplay, null)) {
Log.i("==", "eglInitialize failed!");
return false;
}
if (!chooseConfig()) {
mEglDisplay = EGL_NO_DISPLAY;
return false;
}
if (!createEGLContext(shareContext)) {
mEglDisplay = EGL_NO_DISPLAY;
mEglContext = EGL_NO_CONTEXT;
return false;
}
return true;
}
private boolean chooseConfig() {
int[] num_config = new int[1];
int type = EGL_OPENGL_ES2_BIT;
if (mVersion == VERSION_3) {
type = EGLExt.EGL_OPENGL_ES3_BIT_KHR;
}
int[] attributeList = {
EGL_SURFACE_TYPE, EGL_WINDOW_BIT,
EGL_RED_SIZE, 8,
EGL_GREEN_SIZE, 8,
EGL_BLUE_SIZE, 8,
// EGL_ALPHA_SIZE, 8,
// EGL_DEPTH_SIZE, 16,
// EGL_STENCIL_SIZE, 8,
EGL_RENDERABLE_TYPE, type,
EGL_NONE
};
if (!mEGL.eglChooseConfig(mEglDisplay, attributeList, null, 0,
num_config)) {
Log.i("==", "eglChooseConfig failed!");
return false;
}
int numConfigs = num_config[0];
if (numConfigs <= 0) {
Log.i("==", "eglChooseConfig failed:numConfigs <= 0!");
return false;
}
EGLConfig[] configs = new EGLConfig[numConfigs];
if (!mEGL.eglChooseConfig(mEglDisplay, attributeList, configs, numConfigs,
num_config)) {
Log.i("==", "eglChooseConfig#2 failed");
}
mEGLConfig = configs[0];
return true;
}
public boolean createEGLContext(EGLContext shareContext) {
if (mEglDisplay == EGL_NO_DISPLAY || mEGLConfig == null) {
return false;
}
int attrib_list[] = {
EGL_CONTEXT_CLIENT_VERSION, mVersion,
EGL_NONE
};
if (shareContext != null) {
mEglContext = mEGL.eglCreateContext(mEglDisplay, mEGLConfig, shareContext, attrib_list);
} else {
mEglContext = mEGL.eglCreateContext(mEglDisplay, mEGLConfig, EGL_NO_CONTEXT, attrib_list);
}
if (mEglContext == EGL_NO_CONTEXT) {
mEglDisplay = EGL_NO_DISPLAY;
Log.i("==", "eglCreateContext failed");
return false;
}
return true;
}
public boolean eglMakeCurrent() {
if (mEglDisplay == EGL_NO_DISPLAY || mEglContext == EGL_NO_CONTEXT) {
return false;
}
if (!mEGL.eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface, mEglContext)) {
Log.i("==", "eglMakeCurrent failed");
return false;
}
return true;
}
public boolean createWindowSurface(Object surface) {
releaseWindowSurface();
if (mEglDisplay == EGL_NO_DISPLAY) {
return false;
}
mEglSurface = mEGL.eglCreateWindowSurface(mEglDisplay, mEGLConfig, surface, null);
if (mEglSurface == EGL_NO_SURFACE) {
Log.i("==", "eglCreateWindowSurface failed");
return false;
}
return true;
}
public boolean eglSwapBuffers() {
if (mEglDisplay != EGL_NO_DISPLAY && mEglSurface != EGL_NO_SURFACE) {
return mEGL.eglSwapBuffers(mEglDisplay, mEglSurface);
}
return false;
}
public boolean releaseWindowSurface() {
if (mEglDisplay != EGL_NO_DISPLAY && mEglSurface != EGL_NO_SURFACE) {
if (mEGL.eglMakeCurrent(mEglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT)) {
return mEGL.eglDestroySurface(mEglDisplay, mEglSurface);
}
}
return false;
}
public void release() {
if (mEglDisplay != EGL_NO_DISPLAY) {
mEGL.eglMakeCurrent(mEglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
mEGL.eglDestroyContext(mEglDisplay, mEglContext);
mEGL.eglDestroySurface(mEglDisplay, mEglSurface);
mEGL.eglTerminate(mEglDisplay);
}
mEglDisplay = EGL_NO_DISPLAY;
mEglContext = EGL_NO_CONTEXT;
mEglSurface = EGL_NO_SURFACE;
mEGLConfig = null;
}
public EGLContext getEGLContext() {
return mEglContext;
}
}
第二步:创建EGLThread,因为opengl是基于状态的,所有的操作都需要在同一个线程中完成,所以需要创建一个专门给opengl
运行的线程
package com.leilu.mycamera;
import java.lang.ref.SoftReference;
import javax.microedition.khronos.egl.EGLContext;
/**
* EGL线程,负责EGL环境的创建和渲染相关的回调
*
* 使用步骤:
* 1.调用setSurface方法设置相应的surface(如果surface或者其大小发生了改变,则需要再次调用此方法,其他
* 方法不必重新调用)
* 2.如果要共享其他EGLContext,则调用setEGLContext方法进行共享EGLContext的设置(非必须)
* 3.调用setOnEGLThreadListener方法设置监听器(非必须)
* 4.调用setRenderMode方法设置渲染模式(非必须)
* 5.调用start方法开启EGL线程
* 6.调用stop方法停止EGL线程
*
* 补充:
* 1.如果需要获取EGLContext,则调用getEGLContext方法获取,可能返回null
* 2.调用isRunning方法可以判断EGL线程是否正在运行
* 3.如果是RENDERMODE_WHEN_DIRTY模式,则每次绘制完毕需要手动调用requestRender方法进行刷新
* Created by ll on 2018/9/11.
*/
public class EGLThread {
public static final int RENDERMODE_WHEN_DIRTY = 0;// 手动刷新模式
public static final int RENDERMODE_CONTINUOUSLY = 1;// 不断刷新模式
private WorkThread mWorkThread;// EGL环境的线程
private boolean mIsRunning;// EGL线程是一个不断循环的线程,用来判断是否应该退出循环
private OnEGLThreadListener mOnEGLThreadListener;// 渲染监听
private int mRenderMode = RENDERMODE_CONTINUOUSLY;// 默认是不断刷新模式
private Object mSurface;
private boolean mIsSurfaceChanged;// 当surface或者其大小发生改变的时候此属性为true
private int mWidth, mHeight;// surface的宽高
private EGLContext mSharedEGLContext;// 共享的EGL上下文
/**
* 设置surface,如果surface发生改变,则需要再次调用以绑定新的surface
*
* @param surface Surface或者SurfaceTexture
* @param width
* @param height
*/
public void setSurface(Object surface, int width, int height) {
if (surface != null) {
mSurface = surface;
mWidth = width;
mHeight = height;
mIsSurfaceChanged = true;
requestRender();
}
}
/**
* 设置渲染模式
* RENDERMODE_WHEN_DIRTY或者RENDERMODE_CONTINUOUSLY
*
* @param renderMode
*/
public void setRenderMode(int renderMode) {
if (renderMode == RENDERMODE_CONTINUOUSLY || renderMode == RENDERMODE_WHEN_DIRTY) {
mRenderMode = renderMode;
}
}
/**
* 设置共享的EGLContext,此方法必须在start方法前调用
*
* @param eglContext
*/
public synchronized void setEGLContext(EGLContext eglContext) {
if (mWorkThread != null) {
throw new IllegalStateException("The GLThread is running!");
}
mSharedEGLContext = eglContext;
}
/**
* EGL线程是否正在运行
*
* @return
*/
public boolean isRunning() {
return mIsRunning;
}
/**
* 开启EGL线程
*/
public synchronized void start() {
if (mIsRunning) {
return;
}
if (mSurface == null) {
throw new IllegalStateException("The surface is null!");
}
if (mWorkThread != null) {
throw new IllegalStateException("The GLThread is running!");
}
mIsRunning = true;
mWorkThread = new WorkThread(new SoftReference<>(this));
mWorkThread.start();
}
/**
* 停止EGL线程
*/
public synchronized void stop() {
mIsRunning = false;
requestRender();
if (mWorkThread != null) {
mWorkThread.interrupt();
}
}
/**
* 设置渲染监听
*
* @param onEGLThreadListener
*/
public void setOnEGLThreadListener(OnEGLThreadListener onEGLThreadListener) {
mOnEGLThreadListener = onEGLThreadListener;
}
/**
* 如果是手动刷新模式,则需要调用此方法进行刷新
*/
public void requestRender() {
if (mWorkThread != null && mRenderMode == RENDERMODE_WHEN_DIRTY) {
mWorkThread.requestRender();
}
}
/**
* 获取EGLContext
*
* @return
*/
public EGLContext getEGLContext() {
if (mWorkThread != null) {
return mWorkThread.getEGLContext();
}
return null;
}
private static class WorkThread extends Thread {
// 同步锁,如果是RENDERMODE_WHEN_DIRTY模式,则会wait,当调用
// requestRender的时候就会调用notify
private final Object renderModeLock = new Object();
private SoftReference softReference;
private EGLUtil eglUtil;// 创建EGL环境的工具类
private boolean isCreate;// 用来表示是否是第一次调用onCreate方法
private boolean isFirstDraw;// 是否是第一次绘制
// 由于第一次绘制的时候总是显示不出来(原因未知),所以这里加一个变量来控制,
// 如果是第一次绘制,则调用两次onDrawFrame方法,绘制完成后置为false
private boolean isNeedDoubleDraw = true;
public WorkThread(SoftReference softReference) {
this.softReference = softReference;
this.eglUtil = new EGLUtil();
}
// 如果是RENDERMODE_WHEN_DIRTY模式,则会wait
// 调用此方法取消wait
public void requestRender() {
synchronized (renderModeLock) {
renderModeLock.notifyAll();
}
}
@Override
public void run() {
isCreate = true;
isFirstDraw = true;
if (eglUtil.init(softReference.get().mSharedEGLContext)) {// 如果EGL初始化成功
if (createWindowSurface(softReference.get())) {
while (softReference.get() != null && softReference.get().mIsRunning) {
EGLThread eglThread = softReference.get();
// 如果是手动刷新模式,则判断是否是第一次绘制,如果不是则diaoyngwait方法阻塞,
// 等待用户手动调用requestRender的时候取消阻塞
if (eglThread.mRenderMode == RENDERMODE_WHEN_DIRTY) {
if (!isFirstDraw) {
synchronized (renderModeLock) {
try {
renderModeLock.wait();
} catch (InterruptedException e) {
break;
}
}
}
isFirstDraw = false;
}
// onCreate
if (isCreate) {
if (eglThread.mOnEGLThreadListener != null) {
eglThread.mOnEGLThreadListener.onCreate();
}
isCreate = false;
}
// onSurfaceChanged
if (eglThread.mIsSurfaceChanged) {
if (!createWindowSurface(eglThread)) {
break;
}
if (eglThread.mOnEGLThreadListener != null) {
eglThread.mOnEGLThreadListener.onSurfaceChanged(eglThread.mSurface, eglThread.mWidth, eglThread.mHeight);
}
eglThread.mIsSurfaceChanged = false;
}
// onDrawFrame
if (!draw(softReference.get())) {
break;
}
}
}
// release
eglUtil.release();
EGLThread eglThread = softReference.get();
if (eglThread != null) {
eglThread.release();
}
}
}
private boolean createWindowSurface(EGLThread eglThread) {
// 创建EGLSurface
if (!eglUtil.createWindowSurface(eglThread.mSurface)) {
return false;
}
// 绑定EGLSurface
if (!eglUtil.eglMakeCurrent()) {
return false;
}
return true;
}
public EGLContext getEGLContext() {
return eglUtil.getEGLContext();
}
private boolean draw(EGLThread eglThread) {
if (eglThread != null && eglThread.mIsRunning) {
if (eglThread.mOnEGLThreadListener != null) {
eglThread.mOnEGLThreadListener.onDrawFrame();
// 由于第一次绘制的时候总是显示不出来(原因未知),所以这里加一个变量来控制,
// 如果是第一次绘制,则调用两次onDrawFrame方法,绘制完成后置为false
if (isNeedDoubleDraw) {
eglThread.mOnEGLThreadListener.onDrawFrame();
isNeedDoubleDraw = false;
}
}
eglUtil.eglSwapBuffers();
if (eglThread.mRenderMode == EGLThread.RENDERMODE_CONTINUOUSLY) {
try {
Thread.sleep(16);
} catch (InterruptedException e) {
return false;
}
}
return true;
}
return false;
}
}
private void release() {
mIsSurfaceChanged = false;
mSurface = null;
mIsRunning = false;
mWorkThread = null;
mSharedEGLContext = null;
mWidth = 0;
mHeight = 0;
mOnEGLThreadListener = null;
mRenderMode = RENDERMODE_CONTINUOUSLY;
}
public interface OnEGLThreadListener {
void onCreate();
void onSurfaceChanged(Object surface, int width, int height);
void onDrawFrame();
}
}
第三步:构造一个MyGLSurfaceView
package com.leilu.mycamera;
import android.content.Context;
import android.util.AttributeSet;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import javax.microedition.khronos.egl.EGLContext;
/**
* 1.实现EGL线程
* 2.实现不断刷新模式和手动刷新模式
* 3.可以共享EGLContext
* Created by ll on 2018/9/11.
*/
public class MyGLSurfaceView extends SurfaceView implements SurfaceHolder.Callback {
private EGLThread mEGLThread;
public MyGLSurfaceView(Context context) {
this(context, null);
}
public MyGLSurfaceView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public MyGLSurfaceView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
getHolder().addCallback(this);
mEGLThread = new EGLThread();
}
@Override
public void surfaceCreated(SurfaceHolder holder) {
}
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
mEGLThread.setSurface(holder.getSurface(), width, height);
if (!mEGLThread.isRunning()) {
mEGLThread.start();
}
}
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
}
/**
* 手动刷新
*/
public void requestRender() {
if (mEGLThread != null) {
mEGLThread.requestRender();
}
}
// 设置刷新模式
public void setRenderMode(int renderMode) {
if (mEGLThread != null) {
mEGLThread.setRenderMode(renderMode);
}
}
/**
* 设置render监听器
*
* @param listener
*/
public void setRenderListener(EGLThread.OnEGLThreadListener listener) {
if (mEGLThread != null) {
mEGLThread.setOnEGLThreadListener(listener);
}
}
/**
* 设置共享的EGLContext
*
* @param shareContext
*/
public void setEGLContexnt(EGLContext shareContext) {
if (mEGLThread != null && mEGLThread.isRunning()) {
if (mEGLThread.isRunning()) {
throw new IllegalStateException("The EGLThread is Running!");
}
mEGLThread.setEGLContext(shareContext);
}
}
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
if (mEGLThread != null) {
mEGLThread.stop();
}
}
}
这样就可以在布局或者代码中使用MyGLSurafeView控件了
为什么需要自定义GLSurfaceView呢,系统不是默认有一个GLSurfaceView吗?
那是因为系统的GLSurfaceView满足不了某些需求:
1、共享EGLContext,假如需要共享纹理,GLSurfaceView没有提供此方法
2、通过EGLThread类,可以构造出不同种类的GLSurfaceView,比如照相机界面,上面是主渲染,下面有一排滤镜的画面,这就需要共享纹理来实现,使用EGLThread就能构造出自己想要的效果