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

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/a360940265a/article/details/88600962

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 <EGL/egl.h>
#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;
}

// 设置显示时间戳 pts
void 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的生命周期状态即可。真的真的很简单。

@Override
protected 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 JNICALL
Java_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 JNICALL
Java_org_zzrblog_nativecpp_NativeEGL_onSurfaceChange(JNIEnv *env, jobject instance,
                                                     jint width, jint height)
{

    glThread->onSurfaceChange(width, height);
}

extern "C"
JNIEXPORT void JNICALL
Java_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<GLThread *>(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 <android/native_window.h>
// C++接口类的注意事项
// https://blog.csdn.net/netyeaxi/article/details/80724557
class 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 <assert.h>
#include <GLES2/gl2.h>
#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<GLfloat>(r_count / 100.0),
                 0.6,
                 static_cast<GLfloat>(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)

猜你喜欢

转载自blog.csdn.net/a360940265a/article/details/88600962
今日推荐