简述
缓存对象(buffer object)在OpenGL中十分重要,几乎所有的功能都需要用到它。常见操作包括它的创建,数据的输入, 数据的输出,销毁。
创建
在OPenGL中可以使用函数glGenBuffers()来创建缓存对象,调用之后只是得到了一个缓存对象名称的数组,这些名称只是个标记,并不是真正的缓存对象,需要绑定到OPenGL系统中才会创建出来,可以调用函数glBindVertexArray()来绑定。
void glGenBuffers(GLsizei n, GLuint *buffers) 返回值:void 参 数:n 需要返回缓存对象的个数 buffers 缓存对象名 |
void glBindBuffer(GLenum target, GLuint buffer) 返回值:void 参 数:target 绑定的目标,缓存目标可以根据实际情况设置,以达到最优的内存管理 buffer 缓存对象名 |
缓存绑定的目标
目标 | 用途 |
GL_ARRAY_BUFFER | 可以用来保存glVertexAttribPointer()设置的顶点数组数据 |
GL_COPY_READ_BUFFER GL_COPY_WRITYE_BUFFER | 这两个目标是一对互相匹配的结合点,用于拷贝缓存之间的数据,并且不会引起OpenGL的状态变化,也不会产生任何特殊形式的OPenGL调用 |
GL_DRAW_INDIRECT_BUFFER | 如果采取间接绘制,那么这个目标用于存储绘制命令的参数 |
GL_ELEMENT_ARRAY_BUFFER | 绑定这个目标的缓存可以包含顶点索引数据,用于glDrawElements()等索引形式的绘制命令 |
GL_PLXEL_PACK_BUFFER | 这一缓存目标用于从图象对象中读取数据,例如纹理和帧缓存数据,相关OPenGL命令包括glGetTexImage()和glReadPixels()等 |
GL_PLXEL_UNPACK_BUFFER | 这一缓存目标与GL_PLXEL_PACK_BUFFER正好相反,它可以作为glTexImage2D()等命令的数据源使用 |
GL_TEXTURE_BUFFER | 纹理缓存也就是直接绑定到纹理对象的缓存,这样就可以直接在着色器中读取他们的信息,GL_TEXTURE_BUFFER可以提供一个操控此类缓存的目标,但还需要将缓存关联到纹理,才能确保能够使用 |
GL_TRANSFORM_FEEDBACK_BUFFER | transform feedback是OpenGL提供的一种便捷方案,它可以在管线的顶点处理部分结束时,将经过变换的顶点重新捕获,并将部分属性写入到缓存对象中,通常在几何着色器中使用较多 |
GL_UNFORM_BUFFER | 这个目标可以用于创建uniform缓存对象的缓存数据 |
输入数据
缓存对象经过glBindBuffer()绑定后,只是创建了一块没有任何有效数据的缓存区域,还得向其中输入一些数据才能使用。OpenGL输入数据的方式有多种,比如直接显式的传递数据,或者用新的数据替换缓存对象中已有的部分数据等。
(1)在分配内存的时候读入数据,可以通过glBUfferData()函数来完成,
void glBUfferData(GLenum target, GLsizeiptr size, const void *data, GLenum usage) 返回值:void 参 数:target 缓存绑定的目标 size 数据大小 data 首地址 usage 缓存用途标识符 |
glBUfferData()函数式真正的为缓存对象分配存储空间的,如果新的数据大小比缓存对象当前分配的还要大,那么缓存对象的大小将从新设置更大的空间,反之,如果会设置更小的空间。
OpenGL对于缓存对象存储数据中的最优分配方案的管理,除了依赖初始化绑定时的参数target目标,还有一个参数就是glBUfferData()中的usage。usage必须是内置的标识符,标识符具体如下
分解的标识符 | 意义 |
_STATIC_ | 数据存储内容只写入一次,然后多次使用 |
_DYNAMIC_ | 数据存储内容会被反复写入和反复使用 |
_STREAM_ | 数据存储内容只写入一次,不会频繁使用 |
_DRAW_ | 数据存储内容由应用程序负责写入,作为OPenGL绘制和图像命令的数据源 |
_READ_ | 数据存储内容通过OPenGL反馈的数据写入,然后在应用程序进行查询是返回这些数据 |
_COPY_ | 数据存储内容通过OPenGL反馈的数据写入,作为OPenGL绘制和图像命令的数据源 |
具体实现
//顶点位置
float points[] = {
0.0f, 0.0f, 1.0f, 0.0f, 0.0f,
0.4f, 0.0f, 0.0f, 1.0f, 0.0f,
0.346f, 0.2f, 0.0f, 0.0f, 1.0f,
0.2f, 0.346f, 0.0f, 0.0f, 1.0f,
0.0f, 0.4f, 1.0f, 0.0f, 1.0f,
-0.2f, 0.346f, 1.0f, 1.0f, 1.0f,
};
//缓存对象
unsigned int buffer;
//创建缓存对象
glGenBuffers(1, &buffer);
//绑定
glBindBuffer(GL_ARRAY_BUFFER, buffer);
//分配空间,并传入数据
glBufferData(GL_ARRAY_BUFFER, sizeof(points), &points, GL_STATIC_DRAW);
(2)缓存的部分初始化
如果有两个数组,一个包含顶点数据,另一个包含颜色信息,需要将这些数据进行紧凑的打包,并且存入一个缓存对象让OpenGL使用,在数组之间的数据,内存可能不是连续的,一次无法使用glBufferData()一次输入所有数据。这时候需要引入另外一个函数glBufferSubData()。这两个函数结合使用,就可以对一个缓存对象初始化了。
void glBufferSubData(GLenum target, GLintptr offset, GLsizeiptr size, const void *data) 返回值:void 参 数:target 缓存绑定的目标 offset 地址偏移量 size 数据大小 data 数据 |
具体实现
//顶点位置
float points[] =
{
0.0f, 0.0f, 1.0f, 0.0f, 0.0f,
0.4f, 0.0f, 0.0f, 1.0f, 0.0f,
0.346f, 0.2f, 0.0f, 0.0f, 1.0f,
0.2f, 0.346f, 0.0f, 0.0f, 1.0f,
0.0f, 0.4f, 1.0f, 0.0f, 1.0f,
-0.2f, 0.346f, 1.0f, 1.0f, 1.0f,
};
//顶点颜色
float color[] =
{
1.0f, 0.0f, 0.0f,
0.0f, 1.0f, 0.0f,
0.0f, 0.0f, 1.0f,
0.0f, 0.0f, 1.0f,
1.0f, 0.0f, 1.0f,
1.0f, 1.0f, 1.0f,
}
//缓存对象
unsigned int buffer;
//创建缓存对象
glGenBuffers(1, &buffer);
//绑定
glBindBuffer(GL_ARRAY_BUFFER, buffer);
//分配空间,第三个参数为NULL,只分配空间没有传入数据
glBufferData(GL_ARRAY_BUFFER, sizeof(points) + sizeof(color) , NULL, GL_STATIC_DRAW);
//传入顶点位置信息
glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(points), points);
//传入顶点颜色信息
glBufferSubData(GL_ARRAY_BUFFER, sizeof(points),, sizeof(color), color);
(3)通过映射缓存对象输入数据
目前为止的输入数据函数glBufferSubData()和glBindBuffer()都会进行数据拷贝操作。如果需要反复修改数据时,那么效率就太低了。其实还可以通过获取缓存的指针,直接在应用程序中对OPenGL管理的内存进行操作,对应的函数就是glMapBuffer()和glUmMapBuffer()。glMapBuffer()用于映射缓存,glUmMapBuffer()用于解除映射。
void* glMapBuffer(GLenum target, GLenum access) 返回值:返回一个指向绑定到target的缓存对象的内存,如果失败则返回空 参 数:target 缓存绑定的目标 access 访问模式 |
GLboolean glUmMapBuffer(GLenum target) 返回值:如果解除映射成功则返回GL_TRUE 参 数:target 缓存绑定的目标 |
访问模式
宏标识符 | 意义 |
GL_READ_ONLY | 对映射的内存进行只读操作 |
GL_WRITE_ONLY | 对映射的内存进行只写操作 |
GL_READ_WRITE | 对映射的内存进行读或写操作 |
具体实现
//顶点位置
float points[] = {
0.0f, 0.0f, 1.0f, 0.0f, 0.0f,
0.4f, 0.0f, 0.0f, 1.0f, 0.0f,
0.346f, 0.2f, 0.0f, 0.0f, 1.0f,
0.2f, 0.346f, 0.0f, 0.0f, 1.0f,
0.0f, 0.4f, 1.0f, 0.0f, 1.0f,
-0.2f, 0.346f, 1.0f, 1.0f, 1.0f,
};
//缓存对象
unsigned int buffer;
//创建缓存对象
glGenBuffers(1, &buffer);
//绑定
glBindBuffer(GL_ARRAY_BUFFER, buffer);
//分配空间,但不传入数据
glBufferData(GL_ARRAY_BUFFER, sizeof(points), NULL, GL_STATIC_DRAW);
//内存映射
void* data = glMapBuffer(GL_ARRAY_BUFFER, GL_WRITE_ONLY);
//把数据写入指针
memcpy(data , points, sizeof(points));
//解除映射
glUmMapBuffer()
输出数据
既然可以把数据写入缓存当然也能从缓存读取数据,可以使用函数glGetBufferSubData()从绑定的缓存中直接读取数据。也可以使用内存映射的方式读取,通过glBufferData()函数返回一个指向缓存的指针,再从中读取数据。
void glGetBufferSubData(GLenum target, GLintptr offset, GLsizeiptr size, GLvoid* data) 返回值:返回一个指向绑定到target的缓存对象的内存,如果失败则返回空 参 数:target 缓存绑定的目标 offset 首地址偏移量 size 回读数据的大小 data 传出参数,返回读到的数据 |
销毁
在完成对缓存数据的处理后,可以丢弃缓存数据了。要丢弃缓存对象的部分和全部数据,可以使用gllnvalidateBufferData()和gllnvalidateBufferSubData()。
void gllnvalidateBufferData(GLuint buffer) void gllnvalidateBufferSubData(GLuint buffer, GLintptr offset, GLsizeiptr length) 返回值:void 参 数:buffer缓存对象 offset首地址偏移量 length丢弃数据的大小 |
参见:《OpenGL编程指南》第八版第3章