OpenGL 概念整理
OpenGL 概念整理1. OpenGL1.1 术语说明概念描述版本OpenGLOpen Graphics Library Khronos 定义GPU功能实现的SPEC,标准API4.6OpenGL ESEmbedded System 为嵌入式系统定义的相关补充接口3.2EGL用于GPU渲染与机器原生窗口之间通信的API,独立于OpenGL ES各个版本VulkanKhronos组织新定义的接口,
OpenGL 概念整理
1. OpenGL
1.1 术语说明
概念 | 描述 | 版本 |
---|---|---|
OpenGL | Open Graphics Library Khronos 定义GPU功能实现的SPEC,标准API | 4.6 |
OpenGL ES | Embedded System 为嵌入式系统定义的相关补充接口 | 3.2 |
EGL | 用于GPU渲染与机器原生窗口之间通信的API,独立于OpenGL ES各个版本 | |
Vulkan | Khronos组织新定义的接口,相比于OpenGL更加高效 | 1.2 |
1.2 OpenGL 理解
从功能上理解,OpenGL 是开发者操作GPU渲染的工具,需要将所定义的行为给到GPU去执行渲染操作:
- 将绘制源数据提供给到GPU;
- 将执行操作(程序)给到GPU;
- 绘制完成后将显示内容提供给到framebuffer;
针对于上述功能需求,Khronos将OpenGL设计成为一组状态机,规定一系列的变量描述OpenGL此刻应当如何运行,其状态通常被称为上下文(Context),我们可以在程序中配置他们,这些状态会一直生效到下次改变:
- Context:作为状态机的上下文,描述当前状态的值以及改变状态的值;
- Object: OpenGL将一系列具有相同功能的状态抽象出状态的子集称之为对象
- OpenGL Shading Language:GPU的编程语言,使用GLSL来编写shader程序
概念 | 说明 |
---|---|
Vertex | 顶点 GPU渲染绘制是对于点的操作,这里是物体顶点坐标的计算操作 |
Fragment | 片元操作,理解为对于像素点的颜色计算操作 |
Shader | 着色器:GPU中执行程序的名称(ARM 将HW 叫做Shader Core),一般有Vertex、fragment、geometry等 |
Texture | 作为Fragment输入操作的源数据,多种方式提供给到GPU |
1.3 VBO、EBO、VAO的理解
顶点数据如何提供给到GPU:
对象 | 说明 |
---|---|
VBO | Vertex Buffer Object,一块buffer,描述顶点的数据对象 |
EBO | Element Buffer Object,描述元素的数据对象,存储顶点的索引,可以复用顶点构建三角形 |
VAO | Vertex Arrary Object,一块buffer,顶点数组对象,可以描述多个顶点 |
GLfloat vertices[] = {
0.5f, 0.5f, 0.0f, // 右上角
0.5f, -0.5f, 0.0f, // 右下角
-0.5f, -0.5f, 0.0f, // 左下角
-0.5f, 0.5f, 0.0f // 左上角
};
GLuint indices[] = { // 注意索引从0开始!
0, 1, 3, // 第一个三角形
1, 2, 3 // 第二个三角形
};
GLuint VBO, VAO, EBO;//声明变量
glGenVertexArrays(1, &VAO);
glGenBuffers(1, &VBO);
glGenBuffers(1, &EBO); //生成对应buffer
glBindVertexArray(VAO); //绑定VAO
glBindBuffer(GL_ARRAY_BUFFER, VBO);//绑定VBO 将我们定义的顶点数据填充到VBO中
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);绑定EBO 并填充数据
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(GLfloat), (GLvoid*)0);//设置顶点属性指针,这里是描述VAO中各个数据的含义
glEnableVertexAttribArray(0); //解除绑定
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindVertexArray(0);
上述code建立了VAO的数据索引,是初始化过程,后续在实际绘制时绑定VAO即可使用上述数据;
1.4 Shader
-
Shader是运行在GPU上的程序,这些程序是Pipeline中某一个特定的环节执行(所谓vertex、fragment、geometry shader的可编程特性就是这里实现的)
-
Shader只是一种把输入转化为输出的程序。着色器也是一种非常独立的程序,因为它们之间不能相互通信;它们之间唯一的沟通只有通过输入和输出。
-
Shader 程序使用类C语言实现,即上文提到的GLSL;
典型Shader程序基本机构如下:
#version version_number //声明版本号,特性不同
in type in_variable_name;//声明输入变量
in type in_variable_name;
out type out_variable_name;//声明输出变量
uniform type uniform_name;// 声明uniform变量
int main()//主程序
{
// 处理输入并进行一些图形操作
...
// 输出处理过的结果到输出变量
out_variable_name = weird_stuff_we_processed;
}
Uniform是一种从CPU中的应用向GPU中的着色器发送数据的方式,但uniform和顶点属性有些不同。首先,uniform是全局的(Global)。全局意味着uniform变量必须在每个着色器程序对象中都是独一无二的,而且它可以被着色器程序的任意着色器在任意阶段访问。第二,无论你把uniform值设置成什么,uniform会一直保存它们的数据,直到它们被重置或更新。
1.5 标准化设备坐标
OpenGL 中的坐标如下图所示:
X轴和Y轴都是从(-1, 1)范围,对应到实际屏幕上
2. OpenGL ES 介绍
OpenGL ES 是 OpenGL API的子集,针对嵌入式设备设计;
OpenGL ES 是从 OpenGL 裁剪的定制而来的,去除了glBegin/glEnd,四边形(GL_QUADS)、多边形(GL_POLYGONS)等复杂图元等许多非绝对必要的特性;
3. EGL 介绍
EGL 是 渲染API 和原生窗口系统之间的接口;
- 可以理解为GPU渲染完成后需要将out数据丢到framebuffer或者其他buffer中用于后续显示操作,所以这部分与平台特性强相关,所以抽象出来一组标准接口,作为GPU渲染与Display显示的桥梁;
提供如下机制:
- 与设备原生窗口通信
- 查询绘制surface的可用类型和配置
- 创建绘制surface
- 同步渲染
- 管理纹理贴图等渲染资源
主要接口如下:
-
检查错误:EGL API 成功返回EGL_TRUE,失败返回EGL_FALSE,具体原因需要调用如下接口
EGLint eglGetError();
-
创建本地系统和OpenGL ES的连接并进行初始化
//创建display EGLDisplay eglDisplay(EGLNativeDisplayType displayId); //初始化 EGLBoolean eglInitialize(EGLDisplay display, // 创建步骤时返回的对象 EGLint *majorVersion, // 返回 EGL 主版本号 EGLint *minorVersion); // 返回 EGL 次版本号
-
找到可用surface配置项
//获取所有配置 EGLBoolean eglGetConfigs(EGLDisplay display, // 指定显示的连接 EGLConfig *configs, // 指定 GLConfig 列表 EGLint maxReturnConfigs, // 最多返回的 GLConfig 数 EGLint *numConfigs); // 实际返回的 GLConfig 数 //查询EGL Config配置 EGLBoolean eglGetConfigAttrib(EGLDisplay display, // 指定显示的连接 EGLConfig config, // 指定要查询的 GLConfig EGLint attribute, // 返回特定属性 EGLint *value); // 返回值 // 确认配置 EGLBoolean eglChooseChofig(EGLDispay display, // 指定显示的连接 const EGLint *attribList, // 指定 configs 匹配的属性列表,可以为 NULL EGLConfig *config, // 调用成功,返会符合条件的 EGLConfig 列表 EGLint maxReturnConfigs, // 最多返回的符合条件的 GLConfig 数 ELGint *numConfigs ); // 实际返回的符合条件的 EGLConfig 数
-
创建Surface
EGLSurface eglCreateWindowSurface(EGLDisplay display, // 指定显示的连接 EGLConfig config, // 符合条件的 EGLConfig EGLNatvieWindowType window, // 指定原生窗口 const EGLint *attribList); // 指定窗口属性列表,可为 NULL
-
创建并关联上下文
//创建上下文 EGLContext eglCreateContext(EGLDisplay display, // 指定显示的连接 EGLConfig config, // 前面选好的 EGLConfig EGLContext shareContext, // 允许其它 EGLContext 共享数据,使用 EGL_NO_CONTEXT 表示不共享 const EGLint* attribList); // 指定操作的属性列表,只能接受一个属性 EGL_CONTEXT_CLIENT_VERSION //关联上下文 EGLBoolean eglMakeCurrent(EGLDisplay display, // 指定显示的连接 EGLSurface draw, // EGL 绘图表面 EGLSurface read, // EGL 读取表面 EGLContext context); // 指定连接到该表面的上下文
如下为完整流程举例说明:
ret = modeset_open(&fd, "/dev/dri/card0");
//...
ret = modeset_prepare(fd);
//...
gbm = gbm_create_device(fd);
//...
gbm_surface = gbm_surface_create(gbm, pws_current->ws_width, pws_current->ws_height, GBM_FORMAT_ARGB8888, 0);
//...
egl_display = eglGetDisplay(gbm);
//...
ret = eglInitialize(egl_display, NULL, NULL);
//...
ret = eglGetConfigs(egl_display, NULL, 0, &max_config_num);
//...
pconfigs = (EGLConfig*)malloc(sizeof(EGLConfig) * max_config_num);
//...
ret = eglChooseConfig(egl_display, attr, pconfigs, max_config_num, &max_config_num);
//...
int i=0;
for ( i=0; i<max_config_num; i++ )
{
EGLint value;
/*Use this to explicitly check that the EGL config has the expected color depths */
eglGetConfigAttrib( egl_display, pconfigs[i], EGL_RED_SIZE, &value );
if ( 8 != value ) continue;
eglGetConfigAttrib( egl_display, pconfigs[i], EGL_GREEN_SIZE, &value );
if ( 8 != value ) continue;
eglGetConfigAttrib( egl_display, pconfigs[i], EGL_BLUE_SIZE, &value );
if ( 8 != value ) continue;
eglGetConfigAttrib( egl_display, pconfigs[i], EGL_ALPHA_SIZE, &value );
if ( 8 != value ) continue;
eglGetConfigAttrib( egl_display, pconfigs[i], EGL_SAMPLES, &value );
if ( 4 != value ) continue;
printf("-------use config[%d] ------\r\n", i);
ecfg = pconfigs[i];
break;
}
egl_surface = eglCreateWindowSurface(egl_display, ecfg, (NativeWindowType)gbm_surface, NULL);
//...
egl_context = eglCreateContext(egl_display, ecfg, EGL_NO_CONTEXT, ctxattr);
//...
eglMakeCurrent(egl_display, egl_surface, egl_surface, egl_context);
上述为标准的EGL获取display的path,其目的是获取到显示device和申请到surface用于后续buffer调换;
4. buffer流转图示
单buffer:
如上图,只申请一个buffer进行流转的情况,由于只有一个buffer,所以存在Display模块正在显示的时候GPU同时绘制,则出现撕裂;
双buffer:
双buffer缓冲机制即添加同步操作,避免不同的user操作同一个buffer的行为:
- GPU在swapbuffer操作时对当前idle状态buffer进行渲染操作;
- 渲染完成后调用page flip操作将render完成的buffer地址给到Display模块;
- Display模块会在下次vsync信号过来,显示该buffer数据;
双buffer可以有效的避免buffer绘制时显示异常的问题(比如撕裂),但是由于render和display操作耗时不同,会存在等待耗时较长的操作的情况(一般是等待render);
多buffer流转:
多buffer流转可以解决上述等待的情况,可以理解为将render和show操作分离,或者将render时间与show时间的差值划分到各个buffer中;
注意一定做好同步操作,否则会出现撕裂抖帧等问题、
- GPU 从Idle queue中获取到buffer进行渲染操作,完成后将其添加到busy queue中;
- Display模块从Busy queue中获取到buffer进行显示操作,在下次Vsync过来时替换buffer显示,如果此时Busy queue中为空,则仍显示此帧数据,并且不释放这个buffer;
- 如果GPU申请Idle buffer时为空,则等待show完成,一般来说合理的buffer数量为3~5个,也可以动态申请,即在没有buffer时动态申请添加;
更多推荐
所有评论(0)