自定义GLSurfaceView

慕容高卓
2023-12-01

 第一步:创建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进行缓冲区交换
 * <p>
 * 1.所有的方法方法调用都需要在同一个线程里面实现
 * 2.需要注意的问题:由于surface可能改变,所以在surface改变的时候需要重新调用
 * createWindowSurface方法和eglMakeCurrent方法进行EGLSurface的重新创建,否则可能
 * 不能正常显示
 * <p>
 * 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环境的创建和渲染相关的回调
 * <p>
 * 使用步骤:
 * 1.调用setSurface方法设置相应的surface(如果surface或者其大小发生了改变,则需要再次调用此方法,其他
 * 方法不必重新调用)
 * 2.如果要共享其他EGLContext,则调用setEGLContext方法进行共享EGLContext的设置(非必须)
 * 3.调用setOnEGLThreadListener方法设置监听器(非必须)
 * 4.调用setRenderMode方法设置渲染模式(非必须)
 * 5.调用start方法开启EGL线程
 * 6.调用stop方法停止EGL线程
 * <p>
 * 补充:
 * 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<EGLThread> softReference;
        private EGLUtil eglUtil;// 创建EGL环境的工具类
        private boolean isCreate;// 用来表示是否是第一次调用onCreate方法
        private boolean isFirstDraw;// 是否是第一次绘制

        // 由于第一次绘制的时候总是显示不出来(原因未知),所以这里加一个变量来控制,
        // 如果是第一次绘制,则调用两次onDrawFrame方法,绘制完成后置为false
        private boolean isNeedDoubleDraw = true;

        public WorkThread(SoftReference<EGLThread> 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就能构造出自己想要的效果

 类似资料: