【专业技术】OPENGL与EGL

什么是OpenGL ES

OpenGL ES是一套适用于手持嵌入式设备的3DAPI。比如手机、PDA、汽车、航空等等上面都可以使用到OpenGL ES。OpenGL ES是免授权费的、跨平台的、功能完善的2D和3D图形应用程序接口API,它是桌面OpenGL的子集,是从OpenGL裁剪定制而来的。由于手持设备的相关局限性,OpenGL ES相对于OpenGL不可避免的进行了相关的精简。去除了OpenGL中比如glBegin/glEnd,四边形(GL_QUADS)、多边形(GL_POLYGONS)等复杂图元等许多非绝对必要的特性。但是OpenGL方面的很多知识,OpenGL ES都是可以借鉴的。 OpenGL ES其实是一个状态机(State machine),它保存一种状态直至其改变。每个状态都有本身默认的缺省值,可以通过相关的查询和设置函数进行相关的查询和设置。 大多数的OpenGL ES的应用都采用的是相同的操作顺序,这一系列的处理阶段被称作OpenGL ES的渲染管线(pipeline)。

什么是EGL

OpenGL实现跨平台的功能,在不同的操作系统上需要不同的类似适配层的内容,比如在Windows操作系统上需要WGL。同样的,OpenGL ES是一个平台中立的图形库,在它能够工作前,需要与一个实际的窗口关联起来,但是,与OpenGL不一样的是,OpenGL是每个窗口系统需要一个与之对应的适配层,Windows需要WGL,X-Window需要xgl,Mac OS需要agl,而OpenGL ES的这层,是统一的一个标准,这个标准就是EGL。

下面大致介绍下EGL的使用,在surfaceflinger第1篇:surfaceflinger启动中的初始化部分,曾经简单描述了EGL的使用。结合代码来看基本和下面的使用步骤对应。

1. 获取Display:

Display代表的是显示器,有的系统上有多个显示器,也就会有多个display。获得Display需要调用EGLDisplay eglGetDisplay(EGLNativeDisplayType display_id);,参数一般为EGL_DEFAULT_DISPLAY。该参数的实际意义是平台相关的,比如在windows平台上,一般返回的就是DC。

2. 初始化egl:

获得了Display后,调用EGLBoolean eglInitialize(EGLDisplay dpy, EGLint *major, EGLint *minor);该函数会进行一些相关的内部初始化工作。我们可以通过这个函数获得egl的版本号。

3. 选择Config:

Config实际就是FrameBuffer的参数,在Windows下对应于PixelFormat,在X-Window下对应Visual。可以用函数EGLBoolean eglChooseConfig(EGLDisplay dpy, const EGLint *attrib_list, EGLConfig *configs, EGLint config_size, EGLint *num_config);,其中attrib_list 是以EGL_NONE结束的参数数组,通常以id,value依次存放,对于个别标识性的属性可以只有id,没有value。另一个办法是用EGLBoolean eglGetConfigs(EGLDisplay dpy, EGLConfig *configs, EGLint config_size, EGLint *num_config); 来获得所有config。这两个函数都会返回不多于config_size个Config,结果保存在configs[]中,系统的总Config个数保存在num_config中。可以利用eglGetConfig()中间两个参数为0来查询系统支持的Config总个数。Config有众多的Attribute,这些Attribute决定FrameBuffer的格式和能力,通过eglGetConfigAttrib ()来读取,但不能修改。

4. 构造Surface:

有了Config,就可以开始构造Surface了。Surface实际上就是一个FrameBuffer。通过函数EGLSurface eglCreateWindowSurface(EGLDisplay dpy, EGLConfig config,EGLNativeWindowType win, const EGLint *attrib_list)可以创建一个Surface。系统通常还支持另外两种Surface:PixmapSurface和PBufferSurface,这两种都不是可显示的Surface,PixmapSurface是保存在系统内存中的位图,PBuffer则是保存在显存中的帧。Surface也有一些attribute,基本上都可以故名思意,EGL_HEIGHT EGL_WIDTH EGL_LARGEST_PBUFFER EGL_TEXTURE_FORMAT EGL_TEXTURE_TARGET EGL_MIPMAP_TEXTURE EGL_MIPMAP_LEVEL,通过eglSurfaceAttrib()设置、eglQuerySurface()读取。

5. 创建Context:

OpenGL ES的pipeline从程序的角度看就是一个状态机,有当前的颜色、纹理坐标、变换矩阵、渲染模式等一大堆状态,这些状态作用于程序提交的顶点坐标等图元从而形成帧缓冲内的像素。在OpenGL ES的编程接口中,Context就代表这个状态机,程序的主要工作就是向Context提供图元、设置状态,偶尔也从Context里获取一些信息。用EGLContext eglCreateContext(EGLDisplay dpy, EGLSurface write, EGLSurface read, EGLContext * share_list)来创建一个Context。

6. 绘制:

应用程序通过OpenGL API进行绘制,一帧完成之后,调用eglSwapBuffers(EGLDisplay dpy, EGLContext ctx)来显示。

这里贴出一个OpenGL ES的hello world程序方便理解:

// 头文件包含
#include "TG3.h"
#include
#include
#include "OGLES2Hello********.h"
  
// 宏定义
#define VERTEX_ARRAY 0
  
// 定义Display、config、surface、context
EGLDisplay eglDisplay = 0;
EGLConfig eglConfig = 0;
EGLSurface eglSurface = 0;
EGLContext eglContext = 0;
EGLNativeWindowType eglWindow = 0;
  
// 沃Phone窗口指针
extern TWindow *g_pThis;
  
bool TestEGLError()
{
    //eglGetError返回上一个egl中的错误,用户在每个egl函数调用结束都需要调用这个函数。
    EGLint iErr = eglGetError();
    if (iErr != EGL_SUCCESS)
    {
        return false;
    }
  
    return true;
}
  
bool CreateEGLContext()
{
    // 第一步:获得或者创建一个可以用于OpenGL ES输出的EGLNativeWindowType
    eglWindow = (EGLNativeWindowType)g_pThis;
  
    //第二步:获得默认的Display。通常我们只有一块屏幕,参数传EGL_DEFAULT_DISPLAY就可以了。
    eglDisplay = eglGetDisplay((EGLNativeDisplayType) EGL_DEFAULT_DISPLAY);
  
    //第三步:初始化EGL,如果我们不想要版本号,后两个参数也可以传NULL进去。
    EGLint iMajorVersion, iMinorVersion;
    if (!eglInitialize(eglDisplay, &iMajorVersion, &iMinorVersion))
    {
        return false;
    }
  
    //第四步:指定需要的配置属性,一个EGL的配置描述了Surfaces上像素的格式信息。当前我们要的是Windows的surface,在屏幕上是可见的,以EGL_NONE结尾。
    const EGLint pi32ConfigAttribs[] =
    {
        EGL_LEVEL, 0,
        EGL_SURFACE_TYPE, EGL_WINDOW_BIT,
        EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
        EGL_NATIVE_RENDERABLE, EGL_FALSE,
        EGL_DEPTH_SIZE, EGL_DONT_CARE,
        EGL_NONE
    };
  
    //第五步:寻找一个符合所有要求的配置,我们需要的只是其中一个,所以可以限制config的个数为1。
    int iConfigs;
    if (!eglChooseConfig(eglDisplay, pi32ConfigAttribs, &eglConfig, 1, &iConfigs) || (iConfigs != 1))
    {
        return false;
    }
  
    //第六步:创建一个可画的surface。这里创建时可见的windows surface。Pixmaps和pbuffers都是不可见的。
    eglSurface = eglCreateWindowSurface(eglDisplay, eglConfig, eglWindow, NULL);
  
    if(eglSurface == EGL_NO_SURFACE)
    {
        eglGetError(); // Clear error
        eglSurface = eglCreateWindowSurface(eglDisplay, eglConfig, NULL, NULL);
    }
  
    if (!TestEGLError())
    {
        return false;
    }
  
    //第七步:创建Context。我们OpenGL ES的资源,比如纹理只有在这个context里是可见的。
    // 绑定API (可以是OpenGLES或者 OpenVG)
    eglBindAPI(EGL_OPENGL_ES_API);
    EGLint ai32ContextAttribs[] = { EGL_CONTEXT_CLIENT_VERSION, 2, EGL_NONE };
    eglContext = eglCreateContext(eglDisplay, eglConfig, NULL, ai32ContextAttribs);
  
    if (!TestEGLError())
    {
        return false;
    }
  
    //第八步:将创建的context绑定到当前的线程,使用我们创建的surface进行读和写。Context绑定到线程上,你就可以不用担心其他的进程影响你的OpenGL ES应用程序。
    eglMakeCurrent(eglDisplay, eglSurface, eglSurface, eglContext);
    if (!TestEGLError())
    {
        return false;
    }
    return true;
}
  
bool Render()
{
    //第九步:使用OpenGL ES API画一些东西。到这里,所有的东西都已准备就绪,我们做好了往屏幕上画东西的准备。
    bool bRet = false;
  
    //单元矩阵,用于投影和模型变换
    float pfIdentity[] =
    {
        1.0f,0.0f,0.0f,0.0f,
        0.0f,1.0f,0.0f,0.0f,
        0.0f,0.0f,1.0f,0.0f,
        0.0f,0.0f,0.0f,1.0f
    };
  
    // Vertex和Fragment的shader
    // gl_FragColor指定了最终的像素颜色。
    // gl_position则指定了最终的点在人眼坐标中的位置。
    char szFragShaderSrc[] = {"\
                              void main (void)\
                              {\
                              gl_FragColor = vec4(1.0, 1.0, 0.66 ,1.0);\
                              }"};
    char szVertShaderSrc[] = {"\
                              attribute highp vec4 myVertex;\
                              uniform mediump mat4 myPMVMatrix;\
                              void main(void)\
                              {\
                              gl_Position = myPMVMatrix * myVertex;\
                              }"};
  
    char * pszFragShader = (char *)szFragShaderSrc;
    char * pszVertShader = (char *)szVertShaderSrc;
  
    GLuint uiFragShader = 0;
    GLuint uiVertShader = 0; /* 用来存放Vertex和Fragment shader的句柄 */ 
    GLuint uiProgramObject = 0; /* 用来存放创建的program的句柄*/ 
  
    GLint bShaderCompiled;
    GLint bLinked;
  
    // 我们要画一个三角形,所以,我们先创建一个顶点缓冲区
    GLuint ui32Vbo = 0; // 顶点缓冲区对象句柄
  
    //顶点数据 这9个数据分别为3个点的X、Y、Z坐标
    GLfloat afVertices[] = { -0.4f,-0.4f,0.0f, // Position
        0.4f ,-0.4f,0.0f,
        0.0f ,0.4f ,0.0f};
  
    int i32InfoLogLength, i32CharsWritten;
    char* pszInfoLog = NULL;
    int i32Location = 0;
    unsigned int uiSize = 0;
  
    //创建Fragment着色器对象
    uiFragShader = glCreateShader(GL_FRAGMENT_SHADER);
  
    // 将代码加载进来
    glShaderSource(uiFragShader, 1, (const char**)&pszFragShader, NULL);
  
    //编译代码
    glCompileShader(uiFragShader);
  
    //看编译是否成功进行
    glGetShaderiv(uiFragShader, GL_COMPILE_STATUS, &bShaderCompiled);
  
    if (!bShaderCompiled)
    {
        // 错误发生,首先获取错误的长度
        glGetShaderiv(uiFragShader, GL_INFO_LOG_LENGTH, &i32InfoLogLength);
        //开辟足够的空间来存储错误信息
        pszInfoLog = new char[i32InfoLogLength];
        glGetShaderInfoLog(uiFragShader, i32InfoLogLength, &i32CharsWritten, pszInfoLog);
        delete[] pszInfoLog;
        goto cleanup;
    }
  
    // 使用同样的方法加载Vertex Shader
    uiVertShader = glCreateShader(GL_VERTEX_SHADER);
    glShaderSource(uiVertShader, 1, (const char**)&pszVertShader, NULL);
    glCompileShader(uiVertShader);
    glGetShaderiv(uiVertShader, GL_COMPILE_STATUS, &bShaderCompiled);
    if (!bShaderCompiled)
    {
        glGetShaderiv(uiVertShader, GL_INFO_LOG_LENGTH, &i32InfoLogLength);
        pszInfoLog = new char[i32InfoLogLength];
        glGetShaderInfoLog(uiVertShader, i32InfoLogLength, &i32CharsWritten, pszInfoLog);
        delete[] pszInfoLog;
        goto cleanup;
    }
  
    // 创建着色器程序
    uiProgramObject = glCreateProgram();
  
    // 将Vertex和Fragment Shader绑定进去。
    glAttachShader(uiProgramObject, uiFragShader);
    glAttachShader(uiProgramObject, uiVertShader);
  
    //将用户自定义的顶点属性myVertex绑定到VERTEX_ARRAY。
    glBindAttribLocation(uiProgramObject, VERTEX_ARRAY, "myVertex");
  
    // 链接着色器程序
    glLinkProgram(uiProgramObject);
  
    // 判断链接是否成功的操作
    glGetProgramiv(uiProgramObject, GL_LINK_STATUS, &bLinked);
    if (!bLinked)
    {
        glGetProgramiv(uiProgramObject, GL_INFO_LOG_LENGTH, &i32InfoLogLength);
        pszInfoLog = new char[i32InfoLogLength];
        glGetProgramInfoLog(uiProgramObject, i32InfoLogLength, &i32CharsWritten, pszInfoLog);
        delete[] pszInfoLog;
        goto cleanup;
    }
  
    // 使用着色器程序
    glUseProgram(uiProgramObject);
  
    // 设置清除颜色,以RGBA的模式,每个分量的值从0.0到1.0
    glClearColor(0.6f, 0.8f, 1.0f, 1.0f);
  
    //生成一个顶点缓冲区对象
    glGenBuffers(1, &ui32Vbo);
  
    //绑定生成的缓冲区对象到GL_ARRAY_BUFFER
    glBindBuffer(GL_ARRAY_BUFFER, ui32Vbo);
  
    // 加载顶点数据
    uiSize = 3 * (sizeof(GLfloat) * 3); // Calc afVertices size (3 vertices * stride (3 GLfloats per vertex))
    glBufferData(GL_ARRAY_BUFFER, uiSize, afVertices, GL_STATIC_DRAW);
  
    // 画三角形
    {
        //清除颜色缓冲区。glClear同样也能清除深度缓冲区(GL_DEPTH_BUFFER)和模板缓冲区(GL_STENCIL_BUFFER_BIT)
        glClear(GL_COLOR_BUFFER_BIT);
  
  
        //获取myPMVMatrix在shader中的位置
        i32Location = glGetUniformLocation(uiProgramObject, "myPMVMatrix");
  
        //传值给获取到的位置,也就是将pfIdentity传给myPMVMatrix
        glUniformMatrix4fv( i32Location, 1, GL_FALSE, pfIdentity);
  
        //将VERTEX_ARRAY置为有效。
        glEnableVertexAttribArray(VERTEX_ARRAY);
  
        // 将定点数据传到VERTEX_ARRAY
        glVertexAttribPointer(VERTEX_ARRAY, 3, GL_FLOAT, GL_FALSE, 0, 0);
  
        //画一个三角形。
        glDrawArrays(GL_TRIANGLES, 0, 3);
  
        //SwapBuffers。就可以将三角形显示出来
        eglSwapBuffers(eglDisplay, eglSurface);
    }
    bRet = true;
  
cleanup:
    // 释放资源
    if (uiProgramObject)
        glDeleteProgram(uiProgramObject);
    if (uiFragShader)
        glDeleteShader(uiFragShader);
    if (uiVertShader)
        glDeleteShader(uiVertShader);
  
    // Delete the VBO as it is no longer needed
    if (ui32Vbo)
        glDeleteBuffers(1, &ui32Vbo);
  
    return bRet;
}
  
bool DestroyEGLContext()
{
    //第十步:结束OpenGL ES并删除创建的Windows(如果存在的话).eglminate已经负责清除context和surface。所以调用了eglTerminate后就无需再调用eglDestroySurface和eglDestroyContext了。
    if (eglDisplay != EGL_NO_DISPLAY)
    {
        eglMakeCurrent(eglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
        eglDestroyContext(eglDisplay, eglContext );
        eglDestroySurface(eglDisplay, eglSurface );
        eglTerminate(eglDisplay);
        eglDisplay = EGL_NO_DISPLAY;
    }
  
    //第十一步:删除已经创建的eglWindow。这个是跟具体平台有关的。
    eglWindow = NULL;
    return true;
}
  
//这样,有关OpenGL ES画一个三角形的步骤就走完了,剩下的,只是在沃Phone的平台上进行相关额外的操作,比如窗口的创建,消息循环的处理等。
//根据工程向导新建一个新工程,工程新建完,在**MainForm.cpp中
//新增定义:
TWindow *g_pThis = NULL;
  
//在TMainForm::TMainForm(TApplication * pApp):TWindow(pApp)中加入
    m_bGLESInit = FALSE;
    g_pThis = this;
  
//在case EVENT_WinInit:中加入:
            if (CreateEGLContext())
            {
                m_bGLESInit = TRUE;
                Sys_PostMessage(MESSAGE_PRIOR_LOWEST, 0, &PID_SELF, EVENT_FirstUser, GetWindowHwndId(), 0, NULL, 0);
            }
            bHandled = TRUE;
  
//新增:
    case EVENT_FirstUser:
        {
            if(m_bGLESInit)
            {
                Render();
                Sys_PostMessage(MESSAGE_PRIOR_LOWEST, 0, &PID_SELF, EVENT_FirstUser, GetWindowHwndId(), 0, NULL, 0);
            }
            bHandled = TRUE;
        }
        break;
    case EVENT_GlesUpdateNotify:
        {
            if(m_bGLESInit)
            {
                TRectangle rt;
                GetClientBounds(&rt);
                MarkUpdateRectangle(&rt);
            }
            bHandled = TRUE;
        }
        break;
  
//在case EVENT_WinClose:中加入:
            if (m_bGLESInit)
            {
                DestroyEGLContext();
                m_bGLESInit = FALSE;
            }
            // Stop the application since the main form has been closed
            pApp->SendStopEvent();

OpenGL ES和EGL在Android中

SurfaceFlinger是android系统GUI的核心,但相对于OpenGL ES来讲,它其实只是一个“应用”。在android的GUI系统中,有EGL/OpenGLES,又有SurfaceFlinger、GraphicPlane、DisplayHardware、Gralloc、FramebufferNativeWindow等一系列模块,下面的图可以很好的说明各个模块之间的关系:

根据上面的图,可以有以下分析及结论:

  1. Linux内核提供了统一的framebuffer显示驱动,设备节点/dev/graphics/fb*或者/dev/fb*,以fb0表示第一个Monitor,当前实现中只用到了一个显示屏;
  2. Android的HAL层提供了Gralloc,分为fb和gralloc两个设备。前者负责打开内核中的framebuffer、初始化配置,以及提供post、setSwapInterval等操作,后者则管理帧缓冲区的分配和释放。上层只能通过Gralloc访问帧缓冲区,这样一来就实现了有序的封装保护;
  3. 由于OpenGL ES是一个通用的函数库,在不同的平台系统上需要被“本地化”——即把它与具体平台上的窗口系统建立起关联,这样才能保证它正常工作。从FramebufferNativeWindow这个名称就能判断出来,它就是将OpenGL ES在Android平台上本地化的中介之一。后面我们还会看到应用程序端所使用的另一个“本地窗口”。为OpengGL ES配置本地窗口的是EGL;
  4. OpenGL或者OpenGL ES 更多的只是一个接口协议,实现上既可以采用软件,也能依托于硬件。这一方面给产品开发带来了灵活性,我们可以根据成本与市场定位来决定具体的硬件设计,从而达到很好的定制需求;另一方面,既然有多种实现的可能,那么OpenGL ES在运行时是如何取舍的呢?这也是EGL的作用之一。它会去读取egl.cfg这个配置文件,然后根据用户的设定来动态加载libagl(软件实现)或者libhgl(硬件实现),然后上层才可以正常使用各种glXXX接口;
  5. SurfaceFlinger中持有一个GraphicPlane成员变量mGraphicPlanes来描述“显示屏”;GraphicPlane类中又包含了一个DisplayHardware对象实例(mHw)。具体是在SurfaceFlinger::readyToRun中,完成对它们的创建与初始化。并且DisplayHardware在初始化时还将调用eglInitialize、eglCreateWindowSurface等接口,利用EGL来完成对OpenGLES环境的搭建。 其中: surface =eglCreateWindowSurface(display, config, mNativeWindow.get(), NULL); mNativeWindow 就是一个FramebufferNativeWindow对象。DisplayHardware为OpenGL ES设置了“本地化”所需的窗口;
  6. 很多模块都可以调用OpenGLES提供的API(这些接口以“gl”为前缀,比如glViewport、glClear、glMatrixMode、glLoadIdentity等等),包括SurfaceFlinger、DisplayHardware等;
  7. 与OpenGL ES相关的模块,可以分为如下几类: 配置类:即帮助OpenGL ES完成配置的,包括EGL、DisplayHardware都可以认为是这一类; 依赖类:也就是OpenGL ES要正常运行起来所依赖的“本地化”的东西,上图中是指FramebufferNativeWindow; 使用类:使用者也可能是配置者,比如DisplayHardware既扮演了“帮助”OpenGL的角色,同时它也是其使用方。另外只要处在与OpenGL ES同一个环境(Context)中的模块,都可以使用它来完成操作,比如SurfaceFlinger如果是对EGL的作用、工作方式以及它所提供的重要接口等有不明白的,强烈建议大家先阅读官方文档以及本书应用篇中的章节,否则会大大影响后面的学习和理解。

参考文档:

http://www.bianceng.cn/OS/extra/201306/36755.htm

http://blog.csdn.net/mkhgg/article/details/6738912(原帖没有找到)

原文发布于微信公众号 - 程序员互动联盟(coder_online)

原文发表时间:2015-08-07

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏ASP.NET MVC5 后台权限管理系统

ASP.NET MVC5+EF6+EasyUI 后台管理系统(20)-权限管理系统-根据权限获取菜单

不知不觉到20讲,真是漫长的日子,可惜最近工作挺忙,要不可以有更多的时间来更新,多谢大家的一路支持.如果你觉得好,记得帮我点击推荐^-^ 我们在之前已经插入一些...

1838
来自专栏Kubernetes

深度剖析Kubernetes动态准入控制之Admission Webhooks

Author: xidianwangtao@gmail.com Admission Controll的最佳配置 这部分内容,请参考我的上一篇博文深度剖析K...

3127
来自专栏IT技术精选文摘

跟着实例学习ZooKeeper的用法: Leader选举

Leader Election Barriers Queues Locks Two-phased Commit 其它应用如Name Service, Confi...

2159
来自专栏KK的小酒馆

垃圾回收及内存调试工具的介绍Android应用性能优化

Android的Generational Heap Memory模型和几个内存调试工具:Memory Monitor、Allocation Tracker、He...

991
来自专栏用户2442861的专栏

Redis 缓存 + Spring 的集成示例 (不错的bolg)

http://blog.csdn.net/defonds/article/details/48716161  

392
来自专栏菩提树下的杨过

ExtJs学习笔记(2)_Basic GridPanel[基本网格]

这一节,将学习如何使用网络上最常见的UI控件_Grid 1.静态示例: 静态示例其实官方下载包里,就有sample,这里只贴出代码,后面的如何跟WCF结合,做出...

1929
来自专栏社区的朋友们

SRF & SPP 源码走读

关于 SPP 的解读已经很多,本文尝试从另外的角度解读SRF&SPP;的源码。本文所涉及SRF代码皆以3.1.8版本,SPP代码皆以3.0.1版本为准。

7380
来自专栏lulianqi

一个基于.NET平台的自动化/压力测试系统设计简述

AutoTest是一个基于.NET平台实现的自动化/压力测试的系统,可独立运行于windows平台下,支持分布式部署,不需要其他配置或编译器的支持。(本质是一个...

631
来自专栏深度学习之tensorflow实战篇

r与rjava

前言 Java语言在工业界长期处于霸主地位,Java语法、JVM、JDK、Java开源库,在近10年得到了爆发式的发展,几乎覆盖了应用开发的所有领域。伴随着Ja...

34610
来自专栏木木玲

Netty 那些事儿 ——— Netty实现“流量整形”原理分析及实战

2594

扫描关注云+社区