Android Camera开发实践(3)EGL原理及使用

2,178 阅读6分钟

欢迎关注公众号:sumsmile /专注图像处理的移动开发老兵~~

EGL简介

EGL全称:Embedded-System Graphics Library。

显示设备的参数设置有十多种,不同硬件系统之间差异很大,比如有的不支持RGB_565,所以维护OpenGLES的khronos组织专门抽出一层,以适配各平台的差异,这个抽象层即EGL。

OpenGLES定义了平台无关的GL绘图指令,EGL则定义了控制 displays、contexts 以及 surfaces 的统一的平台接口。

java是跨平台的,是因为java虚拟机打平了各平台的差异。同理,EGL打平了各平台底层图形硬件的差异,所以openGL ES才能跨平台。类似的功能库有SDL、GLFW、GLUT等。

下文中GL指的OpenGL ES,不再说明。

为什么要用到EGL

基于GLSurfaceView,就可以很简单的继成GL,Android平台替开发者屏蔽了复杂的EGL,懂一点GL就可以渲染酷炫的效果,在图像、视频处理中有广泛的应用。

弊端是,如果有比较复杂的定制,GLSurfaceView就不够用了。比如应用中有多个GL环境,需要动态切换,或多个GL环境需要共享GLContext(context保存了GL的状态、资源),而GLSurfaceView中EGL的实现是默认写死的。

GLSurfaceView源码中,有一个EglHelper类处理了EGL初始化配置的逻辑,后面还会讲到。

Java层配置EGL,自定义GLSurfaceView,参考

继成SurfaceView,完整实现一个GLSurfaceView,就很清楚的了解GLSurfaceView替我们干了啥。

把最核心的逻辑抠出来,画了张流程图:

GLSurfaceView的工作流程:

  • 设置Renderer
  • addCallback(SurfaceView方法)
  • 实现EGLThread(常说的渲染线程)
    • 初始化EGL
    • while(true)中循环调用onSurfaceCreated()、onSurfaceChanged()、onDrawFrame()

从实现EglHelper.java开始,仿照GLSurfaceView自己实现一遍

实现EglHelper,封装EGL初始化逻辑

EGL像个中介,把Display、Surface、Context绑到一起,让这三者结合到一起,紧密配合,碰撞出爱的火花。初始化及绑定的工作封装在EglHelper中。

上图用代码实现,流程如下:

EGL实例

  1. 得到Egl实例

Display

  1. 得到默认的显示设备(就是窗口)
  2. 初始化默认显示设备
  3. 设置显示设备的属性
  4. 从系统中获取对应属性的配置

Context

  1. 创建EglContext

Surface

  1. 创建渲染的Surface

绑定

  1. 绑定EglContext和Surface到显示设备中

渲染到屏幕

  1. 刷新数据,显示渲染场景

EGL设置略繁琐,但是逻辑并不复杂,详细参考github代码

GLSurfaceView实现

GLSurfaceView最核心的逻辑是实现了GLThread,该线程中处理了EGL初始化,以及drawFrame等重要回调

GLThread run()函数实现比较重要单独拿出来看看,完整代码参考

重点关注:

  1. EGLHelper初始化
  2. 不同的RenderMode,如何处理
  3. onCreate() onChange(width, height) onDraw()回调时机
 @Override
public void run() {
    super.run();
    try {
        guardedRun();
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
}

private void guardedRun() throws InterruptedException {
    isExit = false;
    isStart = false;
    object = new Object();
    mEglHelper = new EglHelper();
    mEglHelper.initEgl(mEGLSurfaceViewWeakRef.get().mSurface, mEGLSurfaceViewWeakRef.get().mEglContext);

    while (true) {
        if (isExit) {
            // 释放资源
            release();
            break;
        }

        if (isStart) {
            if (mEGLSurfaceViewWeakRef.get().mRenderMode == RENDERMODE_WHEN_DIRTY) {
                synchronized(object) {
          // 自动绘制模式下,等待数据更新通知
                    object.wait();
                }
            } else if (mEGLSurfaceViewWeakRef.get().mRenderMode == RENDERMODE_CONTINUOUSLY) {
          // 近似1秒绘制60次
                Thread.sleep(1000 / 60);
            } else {
                throw new IllegalArgumentException("renderMode");
            }
        }

        // 三个重要的回调,回调里有判断是否需要执行
        onCreate();
        onChange(width, height);
        onDraw();
        isStart = true;
    }
}

Activity中使用自定义的GLSurfaceView

demo很简单,点击GLSurfaceView,触发更改颜色。重要的是把整个流程串起来,弄明白了,可以使用OpenGL定义更有意思的效果。

实现效果:

完成Demo:


public class EglMainActivity extends AppCompatActivity implements EglSurfaceView.Renderer{
    public static final String TAG = "EglMainActivity";
    private EglSurfaceView eglSurfaceView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        eglSurfaceView = new EglSurfaceView(this);
        setContentView(eglSurfaceView);

        eglSurfaceView.setRenderer(this);
        eglSurfaceView.setRendererMode(EglSurfaceView.RENDERMODE_WHEN_DIRTY);
        eglSurfaceView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                eglSurfaceView.requestRender();
            }
        });

    }

    @Override
    public void onSurfaceCreated() {
        Log.e(TAG, "onSurfaceCreated");
    }

    @Override
    public void onSurfaceChanged(int width, int height) {
        Log.e(TAG, "onSurfaceChanged");
        GLES20.glViewport(0, 0, width, height);
    }

    @Override
    public void onDrawFrame() {
        Log.e(TAG, "onDrawFrame");
        GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);

        float r = (float) Math.random();
        float g = (float) Math.random();
        float b = (float) Math.random();
        GLES20.glClearColor(r, g, b, 1.0f);

    }
}

补充:

Native层配置EGL

开发复杂的渲染功能时,opengl egl相关的代码放到C++层更方便,比如搭建一个渲染引擎,大多数库都是c++版本的。另一方面,C++层执行逻辑效率会高一些。

需要对NDK、C++开发有一定的了解。

接下来,我们讲讲Native方式配置EGL,逻辑和Java层的差不多。

完整工程:

github.com/summer-go/A…

重点讲不一样的地方:

Android Studio 新建C++工程

工程目录:

代码逻辑:

CMakeLists.txt配置

# cmake最小版本
cmake_minimum_required(VERSION 3.4.1)

add_library( # Sets the name of the library.
        native-lib
        SHARED
        native-lib.cpp
        egl/EglHelper.cpp
        egl/EglThread.cpp
        )

find_library( # Sets the name of the path variable.
        log-lib

        # Specifies the name of the NDK library that
        # you want CMake to locate.
        log)

target_link_libraries( # Specifies the target library.
        native-lib

        # Links the target library to the log library
        # included in the NDK.
        ${log-lib}
        EGL
        GLESv2
        android
        )

实现EglHelper.cpp EglThread.cpp

逻辑与java层差不多,换成c++实现而已,详细参考工程代码

封装jni

jni逻辑放在native-lib.cpp,是C++工程自动创建的文件,名字我也懒得改,就在里面造了。比较简单:

...
EglThread *eglThread = NULL;
// surfaceCreate回调
void callBackOnCreate(){
    LOGE("callBackOnCreate");
}
// surface size change回调
void callBackOnChange(int width, int height){
    LOGE("callBackOnChange");
}

// 最重要的draw回调
void callBackOnDraw(){
    glClearColor(0.0, 1.0, 1.0f, 1.0f);
    glClear(GL_COLOR_BUFFER_BIT);
    LOGE("callBackOnDraw");
}

// native接口,surfaceCreate
// 设置好三个重要的回调指针,设置渲染模式
// 从SurfaceView中获取ANativeWindow
extern "C"
JNIEXPORT void JNICALL
Java_com_android_samples_nativeegl_opengl_NativeOpenGL_nativeSurfaceCreate(JNIEnv *env,jobject thiz,jobject surface) {
    eglThread = new EglThread();
    eglThread->callBackOnCreate(callBackOnCreate);
    eglThread->callBackOnChange(callBackOnChange);
    eglThread->callBackOnDraw(callBackOnDraw);
    eglThread->setRenderModule(RENDER_MODULE_MANUAL);
    ANativeWindow *nativeWindow = ANativeWindow_fromSurface(env, surface);
    eglThread->onSurfaceCreate(nativeWindow);
}

// 调用surfaceChange
extern "C"
JNIEXPORT void JNICALL
Java_com_android_samples_nativeegl_opengl_NativeOpenGL_nativeSurfaceChanged(JNIEnv *env, jobject thiz,jint width,jint height) {
    if(eglThread){
        eglThread->onSurfaceChange(width, height);
    }
}

// 调用SurfaceDestroyed
extern "C"
JNIEXPORT void JNICALL
Java_com_android_samples_nativeegl_opengl_NativeOpenGL_nativeSurfaceDestroyed(JNIEnv *env,jobject thiz) {
    if(eglThread){
        eglThread->isExit = true;
        //等待线程结束
        pthread_join(eglThread->mEglThread, NULL);
        delete eglThread;
        eglThread = NULL;
    }
}

NativeGLSurfaceView实现

NativeGLSurfaceView继承SurfaceView,几乎是个壳子,关键方法里调到Native方法

class NativeGLSurfaceView extends SurfaceView implements SurfaceHolder.Callback {
    private NativeOpenGL mNativeOpenGL;

    public NativeGLSurfaceView(Context context) {
        this(context, null);
    }

    public NativeGLSurfaceView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public NativeGLSurfaceView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        mNativeOpenGL = new NativeOpenGL();
        getHolder().addCallback(this);
    }

    @Override
    public void surfaceCreated(SurfaceHolder holder) {
        mNativeOpenGL.nativeSurfaceCreate(holder.getSurface());
    }

    @Override
    public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
        mNativeOpenGL.nativeSurfaceChanged(width, height);
    }

    @Override
    public void surfaceDestroyed(SurfaceHolder holder) {
        mNativeOpenGL.nativeSurfaceDestroyed();
    }
}

NativeOpenGL封装Native方法 java层接口 so easy

class NativeOpenGL {
    static {
        System.loadLibrary("native-lib");
    }

    public native void nativeSurfaceCreate(Surface surface);

    public native void nativeSurfaceChanged(int width, int height);

    public native void nativeSurfaceDestroyed();
}

最后在Activity中调用NativeGLSurfaceView

protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        }

SurfaceView写在布局中

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        xmlns:tools="http://schemas.android.com/tools"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".MainActivity">
    <com.android.samples.nativeegl.opengl.NativeGLSurfaceView
            android:id="@+id/sample_text"
            android:layout_width="match_parent"
            android:layout_height="match_parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

实现效果很简单,随便画个颜色

你也可以改成自己喜欢的颜色 修改native-lib.cpp callBackOnDraw方法

void callBackOnDraw(){
    glClearColor(0.0, 1.0, 1.0f, 1.0f);
    glClear(GL_COLOR_BUFFER_BIT);
    LOGE("callBackOnDraw");
}

开发一个完整的demo,涉及到不少细节,无法在一篇文章中详细阐述,读者朋友们有时间,可以读读文末附录的参考

欢迎关注公众号:sumsmile /专注图像处理的移动开发老兵~~

参考

[1] Android配置EGL环境: www.jianshu.com/p/ce3496ab9…

[2] Android自定义GLSurfaceView: www.jianshu.com/p/08f9338ae…

[3] EglHelper实现: github.com/summer-go/A…

[4] EglSurfaceView实现: github.com/summer-go/A…

[5] 剖析EGL及GL线程源码: cloud.tencent.com/developer/a…

[6] EGL OpenGLES版本选择: www.cnblogs.com/kiffa/archi…

[7] eglmakeCurrent api: www.khronos.org/registry/EG…;

[8] makeCurrent、eglSwapBuffers: www.zybuluo.com/SR1s/note/6…

[9] 非常棒的EGL opengl示意图: www.icode9.com/content-4-6…

[10] c++ lock使用: blog.csdn.net/u012109245/…

[11] android c++ EGL环境实现: www.jianshu.com/p/cb34f965a…