OpenGL.Shader:1-AndroidでOpenGL(純Cpp)を再認識

15258 ワード

OpenGL.Shader:1-AndroidのOpenGL(Cpp)を再認識
0、はじめに
この記事から新しいシリーズコンテンツ(NativeOpenGL)が始まり、主にAndroidでC++開発されたOpenGL.ES、OpenGL.Shader、ARCore(NDK)、および新しい画像レンダリングインターフェースVulkanが収録されており、今後はUnity、Unrealなどのゲームエンジンを拡張していく.新しいシリーズの内容を展開する前に、OpenGLの基礎知識を持っていると仮定します.もしなかったら、ここでOpenGLを勉強することができます.AndroidでのESの簡単な実践.それ以外にも、なくしてほしいAndroid C++ベース:)
内容を始める前に、つまらない話をします.タイトルから言えば、どう見てもタイトル党だと思います.AndroidにはGLSurfaceViewのコントロールが付いているのに、なぜAndroidのOpenGLを再認識する必要があるのだろうか.ええ...qiangがpoを追いかけてzhengを求めるプログラマーとして、コードは需要を達成するためだけでなく、万事順調です.コードの風格、性能の試練、各種の小さい要素はすべてちょうど文章を見ているあなたに影響しています.そして、その失われた基礎知識は満足していますか?少なくとも私自身は満足していません.so、NativeOpenGLシリーズの文章が誕生しました. 
1、やはりEglCore
ネット上で伝播しているAndroid NativeOpenGLの多くはGoogleSamples/android-ndk上のhello-gl 2、gles 3 jniを参考にしており、2つのプロジェクトには大きな違いはなく、GLSurfaceViewの3つのコールバックインタフェースを借りてjniを通じて論理を実現している.EGL環境のライフサイクルをコントロールできなかったのは、本当のNativeではありません.前のOpenGL.ESのAndroidでの簡単な実践シリーズの文章の透かし録画は、GoogleチームのGrafikaプロジェクトを参考に、開発者が完全にコントロールするEGL環境構築を実現したが、Javaバージョンだった.だから今しなければならない最初のことは、CppバージョンのEglCore EglSurfaceBaseとWindowSurfaceの3つの主要なクラスを自分で実現することです.
#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という2つのフラグビットは既知のシステムヘッダファイルで見つけられなかったので、Android-SDK android-26/android/opengl/EGLxtしか参照できません.JAvaでは自分で定義します.
2つ目の同じ問題はandroid-26/android/opengl/EGLxtです.JAvaのeglPresentationTimeANDROIDメソッドも、ヘッダファイル定義が見つからないのですが、eglGetProcAddressというメソッドを見つけたので、これでいいでしょう.eglGetProcAddressによって所望の関数アドレスを動的に検索することができ、C++の関数ポインタによってその方法内容を実現することもできるが、eglGetProcAddressは必ずしもeglPresentationTimeANDROIDを検索できるとは限らないので、使用前に空かどうかを判断する必要がある.
3つ目は私が長年使ってきたDISALLOWですEVIL_CONTRUCTORS、これは実は必要ない内容ですが、この内容を理解してから必ず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の3つの変数は必ずデフォルトの空の値を与えなければなりません.cppのデフォルトはjavaの自動空の値ではありません.デフォルトの空の値を与えないと野ポインタが表示されます.initEglの最初のステップmEglDisplay!=EGL_NO_DISPLAYはそのまま終わりますので、ご注意ください.
 
2、自らGLThread/GLRendererを実現する
まず、javaレイヤコードは非常に簡単で、Surface Viewのライフサイクル状態を取得するだけでよいことを説明します.本当に本当に本当に簡単です.
@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();
        }
    });
}

この3つの方法で、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(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;
}

論理も簡単で、1つのスレッドについて、状態に応じてrenderレンダラーオブジェクトに対応する方法を繰り返し、私たちの前のGLSurfaceviewソースコード分析の実行過程、つまりこのような論理フローを思い出します.
続いてGLRenderのインタフェースクラスと対応する実装クラスNativeGLRenderであり、コードは以下の通りである.
#ifndef NATIVECPPAPP_GLRENDER_H
#define NATIVECPPAPP_GLRENDER_H

#include 
// 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 
#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)