Modern OpenGL ES: ndk编程——画一个三角形之创建EGL窗口

时间:2021-02-24 17:49:59
上一节我们 知道了如何用ndk 来创建一个 Activity这一节,我们来创建 GL 的窗口上一节已经知道, Khronos Group 为我们提供了 EGL API 来帮助我们创建 窗口,同步窗口绘制,管理窗口渲染。
1 与窗口系统通信
EGL在OpenGL ES 和 本地窗口系统之间 提供了一个 "glue"层。 在EGL 决定那种类型的绘制surface可以用之前,它先开启了与窗口系统通信的通道。
   1.1 EGLDisplay   因为,每个窗口系统都有其不同的实现机制,因此EGL提供了一种封装了与本地窗口系统交互的系统库的 类型: EGLDisplay。    使用EGL的应用程序所做的第一步就是创建和初始化与本地EGL display的连接。
       EGLDisplay eglGetDisplay(EGLNativeDisplayType displayId)       参数
          displayId: 定义了 display的连接, 默认为 EGL_DEFAULT_DISPLAY
          它用来匹配本地窗口系统display的类
      返回值: 如果失败, 它会返回EGL_NO_DISPLAY
    1.2 初始化EGL    一旦获取了正确的链接, EGL 需要初始化         EGLBoolean eglInitialize(EGLDisplay display, EGLint *majorVersion, EGLint *minorVersion)
         参数            display:             majorVersion:  定义了实现EGL的最大版本            minorVersion:  定义了实现EGL的最小版本
         功能: 该函数初始化了EGL内部的数据结构,并返回其最大最小版本号。 
         返回值: 如果EGL初始化失败,会返回EGL_FALSE,并返回EGL_BAD_DISPLAY,提示一个无效的EGLDisplay, 和返回    EGL_NOT_INITIALIZED 提示EGL没有初始化
2  确定可用的Surfae配置   一旦初始化了EGL, 我们就可以决定渲染Surface的类型和配置。 有两种方式:    确定每个Surface的配置,并找出最佳选择    自己定义一组配置,然后让EGL推荐最佳配置
   在多数情况下,第二种比较简单实现, 而且基本与第一种效果一样。 不管哪种方法,EGL都会返回一个 EGLConfig。 EGLConfig是 EGL内部数据结构的标识, 该数据结构包含了特定Surface的信息和特性: 颜色分量的量化程度(bit数), 深度buffer。 
   2.1 让EGL 选择配置    EGLBoolean eglChooseConfig(EGLDisplay display, const EGLint *attribList, EGLConfig *configs, EGLint maxReturnConfigs, EGLint *numConfigs)
      参数:        display:       attribList: 定义了与 configs匹配的一组属性       configs: 返回的一组符合要求的配置       maxReturnConfigs: 返回最大的配置数目       numConfigs: 返回返回配置的数目               返回值         EGL_TRUE         EGL_FALSE:          EGL_BAD_ATTRIBUTE: 如果attribList里有不符合要求的。
        功能:一旦eglChooseConfig返回成功, 会返回一组符合要求的配置

    2.2 确定配置        EGLConfig 包含了关于 EGL获得的Surface的所有信息。        可获取的颜色表示范围, depth buffers, stencil buffers, multisample buffers等。           在选择可用的Surface配置后,需要确定配置         EGLBoolean eglGetConfigAttrib(EGLDisplay display, EGLConfig config, EGLint attribute, EGLint *value)
         参数:              display: 定义EGL display连接             config: 定义了确认的配置             attribute: 返回的特定属性              value: 返回的特定的值          返回值                EGL_TRUE                EGL_FALSE                EGL_BAD_ATTRIBUTE: 如果attribute不是有效属性 


  3 创建渲染区域: EGL Window   一旦我们确定了与渲染相合适的 EGLConfig 后,我们就可以创建窗口了。    EGLSurface eglCreateWindowSurface(EGLDisplay display, EGLConfig config, EGLNativeWindowType window, const EGLint *attribList)
    参数      display:        config:      window: 指定一个 本地窗口      attribuList, 指定一组窗口属性, 也许是NULL. 是Surface的属性,以EGL_RENDER_BUFFER开头, 以 EGL_NONE 结尾       中间设置的参数有: EGL_SIGLE_BUFFER, EGL_BACK_BUFFER, 默认是 EGL_BACK_BUFFER
    说明      它的第三个参数 就是上一节中 我们创建的Android窗口      EGL_SIGLE_BUFFER 和 EGL_BACK_BUFFER代表了两种渲染的方式。 前者是直接渲染到显示的buffer里, 后者是 先渲染到BACK_BUFFER,然后前后buffer交换。 
    返回值      EGL_BAD_MATCH: EGLConfig 与 native window不匹配      EGL_BAD_CONFIG:EGLConfig不被系统支持      EGL_BAD_NATIVE_WINDOW: 当创建native window 无效      EGL_BAD_ALLOCAT: eglCreateWindowSurface 不能为EGL window分配资源
4 创建渲染的 Context     一个渲染的Context是 OpenGL ES3.0的一个数据结构,包含了所有要操作的 状态信息。      如, 它包含了 vertex/fragment shader以及顶点数据的所有引用。       EGLContext eglCreateContext(EGLDisplay display, EGLConfig config, EGLContext shareContext, const EGLint *attribList)
     参数        display:        config,         sharedContext: 运行多个 EGL Context共享某种类型的数据,如shader program和 texture map。 如果没有共享的:EGL_NO_CONTEXT       attribList:  指定EGL Context的版本
5 使EGLContext 称为当前Context   这很重要。 我们可能会创建多个 Context, 因此需要当前渲染的Surface绑定一个 Context。       EGLBoolean eglMakeCurrent(EGLDisplay display , EGLSurface draw, EGLSurface read, EGLContext context)    参数      display:      draw: 指定现实的Surface        read: 指定渲染到的Surface      context: 指定了Surface 依附的Context   
______________________________________________________________________________实现EGL 创建窗口   按照上一帖,
   void  android_main(strut android_app *app)不仅是 我们程序的入口,而且app 还为我们提供了 本地的window。
   struct android_app中 有一个 用于 处理app命令的回掉函数:      static void onAppCmd(struct android_app *app, int32_t cmd)          其中,cmd有几种类型: APP_CMD_SAVE_STATE, APP_CMD_INIT_WINDOW, APP_CMD_TERM_WINDOW, APP_CMD_GAINED_FOCUS, APP_CMD_LOST_FOCUS。         这几种类型 代表了 窗口的生命周期。  当窗口启动时 会执行 APP_CAM_INIT_WINDOW, APP_CMD_GAINED_FOCUS; 当窗口销毁时会有  APP_CMD_LOST_FOCUS, APP_CMD_TERM_WINDOW

在本地窗口处于 APP_CMD_INIT_WINDOW的时候,我们可以创建EGL窗口
     由于,android_app 有一个userData,可以用来接受 用户自己定义的对象,如 
     
void android_main(struct android_app *app)
{
....
winContext wContext; // 自己定义的窗口上下文结构体,里面包含了创建窗口的所有信息
app->userData = &wContext;

....
}
这样 ,我们在 生命周期方法里,就能够对 我们定义的窗口上下文进行初始化,并创建窗口,如
void onAppCmd(struct android_app *app, int_32 md)
{
winContext *wContext = (winContext) app->userData;

switch(cmd)
{
...
case APP_CMD_INIT_WINDOW:
wContext->eglNativeDisplay = EGL_DEFAULT_DISPLAY;
wContext->eglNativeWindow = app->window;
....
//创建窗口
}
}



     在上面1-5 的分析中,可以看出,创建一个 EGL窗口,         首先需要 EGLDisplay去获取本地系统的一个连接         其次,要选择Surface的配置,并确定该配置         然后,根据 android_app 提供的本地window,以及EGLDisplay 去创建一个Surface,并制定Surface的渲染模式。         最后, 指定Surface的 Context。
           在上面1-5 的分析中,可以看出,创建一个 EGL窗口,         首先需要 EGLDisplay去获取本地系统的一个连接         其次,要选择Surface的配置,并确定该配置         然后,根据 android_app 提供的本地window,以及EGLDisplay 去创建一个Surface,并制定Surface的渲染模式。         最后, 指定Surface的 Context。