OpenGL.Shader:1-重新认识Android上的OpenGL(Cpp)

0、前言

从这篇文章开始新的系列内容(NativeOpenGL),主要收录的是Android上C++开发的OpenGL.ES,OpenGL.Shader,ARCore(NDK),以及新的图像渲染接口Vulkan,往后会继续扩展Unity,Unreal 等游戏引擎的学习。 在开展新系列的内容之前,假设你已经具备了一定的OpenGL基础知识。如果没有,可以到这里学习 OpenGL.ES在Android上的简单实践 。除了这点之外,希望还会一丢丢的Android C++基础  :)

开始内容前,扯下无聊。从标题上说,怎么看就怎么都觉得是个标题党。毕竟Android已经自带有GLSurfaceView的控件了,为什么还需要什么重新认识Android上的OpenGL?额。。。作为一个有qiang追po求zheng的程序员来说,编码不仅仅是为了完成需求就万事大吉。代码的风格,性能的考验,各种小因素都恰恰的影响着在看文章的你。而且,那一丢丢的基础知识就满足了吗?起码我自己是不满足的,so,NativeOpenGL系列文章应运而生。 

1、还是EglCore

在网上传播的Android NativeOpenGL大多都是参考GoogleSamples/android-ndk上的 hello-gl2,gles3jni,两个项目没多大区别,都是借助GLSurfaceView的三大回调接口,然后通过jni实现逻辑。EGL环境生命周期都没能掌控,这还不算是真正的Native。之前的 OpenGL.ES在Android上的简单实践 系列文章中的水印录制,借助参考Google团队的Grafika项目,确实实现了由开发者完全掌控的EGL环境搭建,但却是Java版本。 所以现在要做的第一件事,就是自己实现Cpp版本的 EglCore EglSurfaceBase 和 WindowSurface三个主要类。

#ifndef NATIVECPPAPP_EGLCORE_H#define NATIVECPPAPP_EGLCORE_H#include #include "../common/constructormagic.h"#define FLAG_RECORDABLE 0x01#define FLAG_TRY_GLES2 0x02#define FLAG_TRY_GLES3 0x04// 参考android-26/android/opengl/EGLxt.java中的定义#define EGL_OPENGL_ES3_BIT_KHR 0x0040#define EGL_RECORDABLE_ANDROID 0x3142// 参考android-26/android/opengl/EGLxt.java中的定义// egl.h没有 eglPresentationTimeANDROID 的接口,// 所以只能自己定义函数指针,并通过eglGetProcAddress动态获取其函数地址了// 使用前记得判断是否为空typedef EGLBoolean (* EGL_PRESENTATION_TIME_ANDROID_PROC)(EGLDisplay display, EGLSurface surface, khronos_stime_nanoseconds_t time);class EglCore {public:    EGLDisplay mEglDisplay;    EGLContext mEglContext;    int mEglVersion = -1;    EGLConfig mEglConfig;public:    EglCore();    ~EglCore();    EglCore(EGLContext sharedContext, int flags);    int initEgl(EGLContext sharedContext, int flags);    void release();    //  创建EGLSurface    EGLSurface createWindowSurface(ANativeWindow * surface);    // 创建离屏Surface    EGLSurface createOffscreenSurface(int width, int height);    // 查询当前surface的状态值。    int querySurface(EGLSurface eglSurface, int what);    // 切换到当前上下文    void makeCurrent(EGLSurface eglSurface);    // 切换到某个上下文    void makeCurrent(EGLSurface drawSurface, EGLSurface readSurface);    // Makes no context current.    void makeNothingCurrent();    // 交换缓冲区显示    bool swapBuffers(EGLSurface eglSurface);    //判断当前的EGLContext 和 EGLSurface是否同一个EGL    bool isCurrent(EGLSurface eglSurface);    // Destroys the specified surface.    // Note the EGLSurface won't actually be destroyed if it's still current in a context.    void releaseSurface(EGLSurface eglSurface);    // 设置pts    void setPresentationTime(EGLSurface eglSurface, long nsecs);    static void logCurrentEglState();    // 动态 设置pts方法    EGL_PRESENTATION_TIME_ANDROID_PROC eglPresentationTimeANDROID = NULL;private:    EGLConfig getConfig(int flags, int version);    void checkEglError(const char *msg);private:    DISALLOW_EVIL_CONSTRUCTORS(EglCore);};#endif //NATIVECPPAPP_EGLCORE_H

其中选取EglCore为例讲解,因为这个最核心,也确实有点小问题需要注意,而且还有个C++的代码艺术介绍给看文章的你,我们从头文件开始,首先我们要自己定义一些标志位,EGL_OPENGL_ES3_BIT_KHR 和 EGL_RECORDABLE_ANDROID 这两个标志位我没在已知的系统头文件找到,所以只能参考Android-SDK android-26/android/opengl/EGLxt.java中自行定义。

第二个同样的问题,android-26/android/opengl/EGLxt.java中的eglPresentationTimeANDROID方法,也是找不到头文件定义,但发现了eglGetProcAddress这一个方法,这下子就妙啦。借助eglGetProcAddress可以动态查找想要的函数地址,通过C++的函数指针,我们也能实现其方法内容,不过eglGetProcAddress也不一定能确定查找得到eglPresentationTimeANDROID,所以使用前需要判断是否为空。

第三个就是我使用多年的 DISALLOW_EVIL_CONSTRUCTORS,这个其实是没必要的内容,但是理解这个内容后必定对C++又有更高程度的认识。  这宏定义做了些什么能让你得道飞升?请看这里的代码艺术

EglCore::EglCore() {    mEglDisplay = EGL_NO_DISPLAY;    mEglContext = EGL_NO_CONTEXT;    mEglConfig = NULL;    initEgl(NULL, 0);}EglCore::~EglCore() {    if (mEglDisplay != EGL_NO_DISPLAY) {        // 类析构函数 检测回收资源        LOGW(TAG, "WARNING: EglCore was not explicitly released -- state may be leaked");        release();    }}EglCore::EglCore(EGLContext sharedContext, int flags) {    mEglDisplay = EGL_NO_DISPLAY;    mEglContext = EGL_NO_CONTEXT;    mEglConfig = NULL;    initEgl(sharedContext, flags);}int EglCore::initEgl(EGLContext sharedContext, int flags){    if (mEglDisplay != EGL_NO_DISPLAY) {        LOGE("EGL already set up");        return -1;    }    if (sharedContext == NULL) {        sharedContext = EGL_NO_CONTEXT;    }    mEglDisplay = eglGetDisplay(EGL_DEFAULT_DISPLAY);    if(mEglDisplay == EGL_NO_DISPLAY) {        LOGE("eglGetDisplay wrong");        return -1;    }    EGLint *version = new EGLint[2];    if (!eglInitialize(mEglDisplay, &version[0], &version[1]) ) {        mEglDisplay = NULL;        LOGE("unable to initialize");        return -1;    }    if ((flags & FLAG_TRY_GLES3) != 0) {        EGLConfig config = getConfig(flags, 3);        if (config != NULL) {            const EGLint attrib3_list[] = {                    EGL_CONTEXT_CLIENT_VERSION, 3,                    EGL_NONE            };            EGLContext context = eglCreateContext(mEglDisplay, config, EGL_NO_CONTEXT, attrib3_list);            if (eglGetError() == EGL_SUCCESS) {                LOGD("Got GLES 3 config");                mEglConfig = config;                mEglContext = context;                mEglVersion = 3;            }        }    }    //如果只要求GLES版本2  又或者GLES3失败了。    if (mEglContext == EGL_NO_CONTEXT) {        EGLConfig config = getConfig(flags, 2);        if (config != NULL) {            int attrib2_list[] = {                    EGL_CONTEXT_CLIENT_VERSION, 2,                    EGL_NONE            };            EGLContext context = eglCreateContext(mEglDisplay, config, sharedContext, attrib2_list);            if (eglGetError() == EGL_SUCCESS) {                LOGD("Got GLES 2 config");                mEglConfig = config;                mEglContext = context;                mEglVersion = 2;            }        }    }    // 动态获取eglPresentationTimeANDROID方法的地址    eglPresentationTimeANDROID = (EGL_PRESENTATION_TIME_ANDROID_PROC)            eglGetProcAddress("eglPresentationTimeANDROID");    if (!eglPresentationTimeANDROID) {        LOGE("eglPresentationTimeANDROID is not available!");    }    return 0;}// 设置显示时间戳 ptsvoid EglCore::setPresentationTime(EGLSurface eglSurface, long nsecs) {    if(eglPresentationTimeANDROID)        eglPresentationTimeANDROID(mEglDisplay, eglSurface, nsecs);    else        LOGE("eglPresentationTimeANDROID is not available!");}

 以上是部分的实现代码,我就不全部贴上来了,和之前Java版本的没两样,需要的同学到 https://github.com/MrZhaozhirong/NativeCppApp 这里follow。 需要注意的是,我们利用eglGetProcAddress获取eglPresentationTimeANDROID的函数地址,指向自定义的函数指针,再强调一遍,eglPresentationTimeANDROID不一定能找到!所以使用前记得判空。其次就是构造函数的mEglDisplay mEglContext mEglConfig三个变量一定要给一个默认空值,因为cpp默认不像java自动给空值,如果不给默认空值导致出现了野指针,initEgl的第一步mEglDisplay != EGL_NO_DISPLAY就直接结束了,请大家注意。

 

2、自己实现GLThread / GLRenderer

首先说明一下,java层代码非常简单,只需要获取SurfaceView的生命周期状态即可。真的真的很简单。

@Overrideprotected void onCreate(@Nullable Bundle savedInstanceState) {    super.onCreate(savedInstanceState);    setContentView(R.layout.activity_native_gl);    SurfaceView surfaceview = findViewById(R.id.easy_surface_view);    surfaceview.getHolder().setFormat(PixelFormat.RGBA_8888);    final NativeEGL nativeEGL = new NativeEGL();    surfaceview.getHolder().addCallback(new SurfaceHolder.Callback() {        @Override        public void surfaceCreated(SurfaceHolder holder) {            nativeEGL.onSurfaceCreate(holder.getSurface());        }        @Override        public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {            nativeEGL.onSurfaceChange(width, height);        }        @Override        public void surfaceDestroyed(SurfaceHolder holder) {            nativeEGL.onSurfaceDestroy();        }    });}

通过这三个方法,我们就能掌握到Android系统的渲染窗体Surface的生命周期。我们把获取surface并通过生命周期的方法传入到NativeEGL当中。NativeEGL代码也是贼简单。(因为优秀的我已经完美的封装实现好了)

GLThread *glThread = NULL;NativeGLRender* testRender = NULL;extern "C"JNIEXPORT void JNICALLJava_org_zzrblog_nativecpp_NativeEGL_onSurfaceCreate(JNIEnv *env, jobject instance, jobject surface){    ANativeWindow* nativeWindow = ANativeWindow_fromSurface(env, surface);    glThread = new GLThread();    testRender = new NativeGLRender();    glThread->setGLRender(testRender);    glThread->onSurfaceCreate(nativeWindow);}extern "C"JNIEXPORT void JNICALLJava_org_zzrblog_nativecpp_NativeEGL_onSurfaceChange(JNIEnv *env, jobject instance,                                                     jint width, jint height){    glThread->onSurfaceChange(width, height);}extern "C"JNIEXPORT void JNICALLJava_org_zzrblog_nativecpp_NativeEGL_onSurfaceDestroy(JNIEnv *env, jobject instance){    glThread->setGLRender(NULL);    glThread->onSurfaceDestroy();    glThread->release();    delete testRender;    delete glThread;}

显然GLThread是关键类,它跟随着Surface生命回调作相应的处理,现在就来看看GLThread的实现。

GLThread::GLThread() {    isExit = false;    isCreate = false;    isChange = false;    isStart = false;}GLThread::~GLThread() { }void *glThreadImpl(void *context){    GLThread *glThread = static_cast(context);    glThread->isExit = false;    while(true)    {        if (glThread->isExit)        {            LOGI("GLThread onDestroy.");            if(glThread->mRender!=NULL)            {                glThread->mRender->surfaceDestroyed();            }            break;        }        usleep(16000);  // 1s/0.016s = 62.5 fps        //onCreate        if (glThread->isCreate)        {            glThread->isCreate = false;            LOGI("GLThread onCreate.");            if(glThread->mRender!=NULL)            {                glThread->mRender->surfaceCreated(glThread->window);            }        }        //onChange        if(glThread->isChange)        {            glThread->isChange = false;            glThread->isStart = true;            LOGI("GLThread onChange.");            if(glThread->mRender!=NULL)            {                glThread->mRender->surfaceChanged(glThread->width,glThread->height);            }        }        //onDraw        if(glThread->isStart)        {            //LOGI("GLThread onDraw.");            glThread->mRender->renderOnDraw();        }    }    return 0;}void GLThread::onSurfaceCreate(ANativeWindow* window){    this->window = window;    this->isCreate = true;    pthread_create(&mThreadImpl, NULL, glThreadImpl, this);}void GLThread::onSurfaceChange(int w, int h){    this->width = w;    this->height = h;    this->isChange = true;}void GLThread::onSurfaceDestroy() {    this->isExit = true;}void GLThread::setGLRender(GLRender * render) {    this->mRender = render;}

逻辑也是很简单,就一个线程,不断的根据状态回调render渲染器对象对应的方法,回想我们之前 GLSurfaceview源码分析的执行过程,也就是这样一个逻辑流程而已。

紧接着就是GLRender的接口类和对应的实现类NativeGLRender,代码如下:

#ifndef NATIVECPPAPP_GLRENDER_H#define NATIVECPPAPP_GLRENDER_H#include // C++接口类的注意事项// https://blog.csdn.net/netyeaxi/article/details/80724557class GLRender {public:    virtual ~GLRender() {};    virtual void surfaceCreated(ANativeWindow *window)=0;    virtual void surfaceChanged(int width, int height)=0;    virtual void renderOnDraw()=0;    virtual void surfaceDestroyed(void)=0;};#endif //NATIVECPPAPP_GLRENDER_H

其中Cpp定义接口类的语法,是Cpp面试多肽的基础考点,析构函数需要带virtual关键字,其中的知识点在注释的连接有所提及,我就不重复说明了。只有记住如果使用Cpp多肽这种特性的时候,纯虚基类必须是定义virtual的析构函数,并最好带上默认的实现。

#ifndef NATIVECPPAPP_NATIVEGLRENDER_H#define NATIVECPPAPP_NATIVEGLRENDER_H#include "../egl/GLRender.hpp"#include "../common/constructormagic.h"#include "../egl/EglCore.h"#include "../egl/WindowSurface.h"#include "../objects/CubeIndex.h"class NativeGLRender : public GLRender{public:    NativeGLRender();    ~NativeGLRender();    void surfaceCreated(ANativeWindow *window) override;    void surfaceChanged(int width, int height) override;    void renderOnDraw() override;    void surfaceDestroyed(void) override;private:    int r_count;    EglCore * mEglCore;    WindowSurface * mWindowSurface;    DISALLOW_EVIL_CONSTRUCTORS(NativeGLRender);};#endif //NATIVECPPAPP_NATIVEGLRENDER_H
#include #include #include "NativeGLRender.h"#include "../common/zzr_common.h"NativeGLRender::NativeGLRender() {    mEglCore = NULL;    mWindowSurface = NULL;}NativeGLRender::~NativeGLRender() {    if (mWindowSurface) {        mWindowSurface->release();        delete mWindowSurface;        mWindowSurface = NULL;    }    if (mEglCore) {        mEglCore->release();        delete mEglCore;        mEglCore = NULL;    }}void NativeGLRender::surfaceCreated(ANativeWindow *window){    if (mEglCore == NULL) {        mEglCore = new EglCore(NULL, FLAG_RECORDABLE);    }    mWindowSurface = new WindowSurface(mEglCore, window, true);    assert(mWindowSurface != NULL && mEglCore != NULL);    LOGD("render surface create ... ");}void NativeGLRender::surfaceChanged(int width, int height){    mWindowSurface->makeCurrent();    LOGD("render surface change ... update MVP!");    mWindowSurface->swapBuffers();}void NativeGLRender::renderOnDraw(){    if (mEglCore == NULL) {        LOGW("Skipping drawFrame after shutdown");        return;    }    mWindowSurface->makeCurrent();    glClear(GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT);    r_count++;    if(r_count > 255) {        r_count = 0;    }    glClearColor(static_cast(r_count / 100.0),                 0.6,                 static_cast(1.0 - r_count / 100.0),                 1.0);    mWindowSurface->swapBuffers();}void NativeGLRender::surfaceDestroyed(void){    // 清空自定义模型,纹理,各种BufferObject}

最后就是我们根据业务逻辑作出调整的GLRender实现类,使用上和GLSurfaceView.java基本上是一模一样的。到此我们就完全的在NDK内部,创建和实现了由开发者完全可控的EGL+渲染窗体+draw回调,类似于一个游戏引擎开发器一样,接管了上层的渲染窗体,就安安心心的实现业务逻辑了。而且性能上,我敢保证,是大大的优于Android.opengl.GLSurfaceView.java!

 

本项目代码 https://github.com/MrZhaozhirong/NativeCppApp(看清楚,之前的内容是BlogApp)

更多相关文章

  1. C语言函数的递归(上)
  2. Android(Java):对应用进行单元测试
  3. Android中对MIME类型的理解
  4. android Aspectj实践问题
  5. android 一个很漂亮的控件ObservableScrollView(含片段代码和源码
  6. android:layout_margin真实含义 及 自定义复合控件 layout()执行
  7. Android的回调机制
  8. Android(安卓)利用addView 动态给Activity添加View组件
  9. 第一行代码读书笔记 Kotlin Android

随机推荐

  1. onConfigurationChanged会重新 OnCreate
  2. div+css命名规范 嫁接android xml命名
  3. 我的Android进阶之旅------>Android之选
  4. android-电话拨号器&点击事件四种写法
  5. android 问题汇总系列之四
  6. Android 响应式编程框架 - RxJava2(二)基础
  7. Android开机自启动程序设置及控制方法思
  8. Android Studio中genymotion安装方法
  9. Android中实现java与PHP服务器(基于新浪云
  10. Android_Touch事件的分发和消费机制