文章目录
OpenGL 缓存数据
1. 创建与分配缓存
- 使用glCreateBuffers创建缓存,可在buffers中得到一个缓存对象名称的数组。
- 使用glNamedBufferStorage为每个缓存对象分配存储空间
- 拥有存储空间后,使用glBindBuffer,绑定缓存对象到缓存目标。
-
创建缓存
void glCreateBuffers(GLsizei n,GLuint* buffers);
返回n个当前未使用的缓存对象名称(每个都表示一个新创建的缓存对象),并保存到buffers数组中。
-
绑定缓存
void glBindBuffer(GLenum target,GLuint buffer);
将名称为buffer的缓存对象绑定到target所指定的缓存结合点。
buffer必须是通过glCreateBuffers()分配的名称。如果buffer是第一次被绑定,那么它所对应的缓存对象也将被同时创建。
缓存绑定的目标 目标 用途 GL_ARRAY_BUFFER 这个结合点可以用来保存glVertexAttribPointer()设置的顶点数组数据。在实际工程中这一目标可能是最为常用的。 GL_COPY_READ_BUFFER和GL_COPY_WRITE_BUFFER 这两个目标是一对互相匹配的结合点,用于拷贝缓存之间的数据,并不会引起OpenGL的状态变化,也不会影响任何特殊形式的OpenGL调用。 GL_DRAW_INDIRECT_BUFFER 如果采取间接绘制(indirect drawing)的方法,那么这个缓存目标用于存储绘制命令的参数。 GL_ELEMENT_ARRAY_BUFFER 绑定到这个目标的缓存中可以包含顶点索引数据,以便用于glDrawElements()等索引形式的绘制命令。 GL_PIXEL_PACK_BUFFER 这一缓存目标用于从图像对象中读取数据,例如纹理和帧缓存数据。相关的OpenGL命令包括glGetTextImage()和glReadPixels()等 GL_PIXEL_UNPACK_BUFFER 这一缓存目标与之前的GL_PIXEL_PACK_BUFFER相反,它可以作为glTexSubImage2D()等命令的数据源使用。 GL_TEXTURE_BUFFER 纹理缓存也就是直接绑定到纹理对象的缓存,这样就可以直接在着色器中读取它们的数据信息。GL_TEXTURE_BUFFER可以提供一个操控此类缓存的目标,但是我们还需要将缓存关联到纹理,才能确保它们在着色器中可用。 GL_TRANSFORM_FEEDBACK_BUFFER transform feedback 是OpenGL提供的一种便捷方案,它可以在管线的顶点处理部分结束时(即经过了顶点着色,可能还有几何着色阶段),将经过变换的顶点重新捕获,并且将部分属性写入缓存对象中。这一目标就提供了这样的结合点,可以建立专门的缓存来记录这些属性数据。 GL_UNIFORM_BUFFER 这个目标可以用于创建uniform缓存对象(uniform buffer object)的缓存数据。
2.向缓存输入和输出数据
将数据输入和输出OpenGL缓存的方法有很多种,例如:
- 直接显示地传递数据
- 用新的数据替换缓存对象中已有的部分数据
- 由OpenGL负责生成数据然后将它记录到缓存对象中。
最简单方法是在分配内存时读入数据:
void glNamedBufferStorage(GLuint buffer,GLsizeiptr size,const void *data,GLbitfield flags);
为缓存对象buffer分配size大小(单位为字节)的存储空间。如果参数data不是NULL,那么将使用data所在内存区域的内容来初始化整个空间。
flags用来设置缓存的预期用途信息。这些信息flags标识量在用户程序和OpenGL之间构建了协议,允许OpenGL尽可能极致地优化缓存的存储信息。
缓存用途标识符 | |
---|---|
标识符 | 意义 |
GL_DYNAMIC_STORAGE_BIT | 设置之后,缓存的内容可以随后通过glNamedBufferSubData()直接进行修改。如果没有设置,那么缓存内容的修改只能在GPU端完成,例如通过着色器来写入。 |
GL_MAP_READ_BIT | 设置之后,我们可以映射缓存数据到CPU端进行读取。如果没有设置的话,当前缓存调用glMapNameBufferRange()来获取读取权限的做法都会失败。 |
GL_MAP_WRITE_BIT | 设置之后,我们可以映射缓存数据到CPU端进行写入。如果没有设置的话,当前缓存调用glMapNamedBufferRange()来获取写入权限的做法都会失败。 |
GL_MAP_PERSISTENT_BIT | 设置之后,对缓存数据的映射将是永久性的。也就是说,它们在渲染的过程中始终有效。这个标识必须在映射的时候同时设置才能创建永久的映射表。 |
GL_MAP_COHERENT_BIT | 设置之后,缓存数据在GPU端和CPU端的映射将保持一致。这个标识必须在映射的时候同时进行设置才能创建一致的映射表。 |
GL_CLIENT_STORAGE_BIT | 对于不一致的内存(nonuniform memory)系统架构来说,有些内存可能从宿主机访问更为高效,而有些内存可能从GPU访问效率更高。在确保其他标识量都设置正确之后,我们可以通过这个标识来引导OpenGL优先选择CPU(宿主)端进行访问来提升效率。 |
缓存部分初始化
-
数据设置
void glNameBufferSubData(GLuint buffer,GLintptr offset,GLsizeiptr size,const void* data);
使用新的数据替换缓存对象buffer中的部分数据。缓存中从offset字节处开始需要使用地址为data、大小为size的数据块来进行更新。如果offset和size的总和超出了缓存对象绑定数据的范围,那么将产生一个错误。
缓存buffer中存储的数据必须经过glNamedBufferStorage()初始化,并且标识量应当设置为GL_DYNAMIC_STORAGE_BIT。
将glNamedBufferStorage()和glNamedBufferSubData()结合起来使用,例如:
//顶点位置 static const GLfloat positions[] = { -1.0f,-1.0f,0.0f,1.0f, 1.0f,-1.0f,0.0f,1.0f, 1.0f,1.0f,0.0f,1.0f, -1.0f,1.0f,0.0f,1.0f, }; //顶点颜色 static const GLfloat colors[] = { 1.0f,0.0f,0.0f, 0.0f,1.0f,0.0f, 0.0f,0.0f,1.0f, 1.0f,1.0f,1.0f, }; //缓存对象 GLuint buffer; //创建新的缓存对象 glCreateBuffers(1,&buffer); //分配足够的空间(sizeof(positions)+sizeof(colors)) glNamedBufferStorage(buffer, //目标 sizeof(positions) + sizeof(colors), //总计大小 nullptr, //无数据 GL_DYNAMIC_STORAGE_BIT); //标识量 //将位置信息放置在缓存的偏移地址为0的位置 glNamedBufferSubData(buffer, //目标 0, //偏移地址 sizeof(positions), //大小 positions); //数据 //放置在缓存中的颜色信息的偏移地址为当前填充大小值的位置,也就是sizeof(positons) glNamedBufferSubData(buffer, //目标 sizeof(positions), //偏移地址 sizeof(colors), //大小 colors); //数据
-
数据清除
将缓存对象的数据清除为一个已知的值,也可用如下接口。
void glClearNamedBufferData(GLuint buffer,GLenum internalformat,GLenum format,GLenum type,const void* data); void glClearNamedBufferSubData(GLuint buffer,GLenum internalformat,GLintptr offset,GLsizeiptr size,GLenum format,GLenum type,const void* data);
清除缓存对象中所有或者部分数据。名为buffer的缓存存储空间将使用data中存储的数据进行填充。format和type分别指定了data对应数据的格式和类型。
首先将数据被转换到internalformat所指定的格式,然后填充缓存数据的指定区域范围。
glClearNamedBufferData,进行整个区域的指定的数据填充;
glClearNamedBufferSubData(),填充区域时通过offset和size来指定,分别给出了以字节为单位的起始偏移地址和大小。
-
数据复制
缓存对象中的数据也可以使用glCopyNamedBufferSubData()函数相互进行拷贝。
void glCopyNamedBufferSubData(GLuint readBuffer,GLuint writeBuffer,GLintptr readoffset,GLintptr writeoffset,GLsizeiptr size);
将名为readBuffer的缓存对象的一部分存储数据拷贝到名为wirteBuffer的缓存对象的数据区域上。readBuffer对应的数据从readoffset位置开始复制size个字节,然后拷贝到writeBuffer对应的数据的writeoffset位置。
如果readoffset或者writeoffset与size的和超出了绑定的缓存对象的范围,那么OpenGL会产生一个GL_INVALID_VALUE错误。
读取缓存的内容
void glGetNameBufferSubData(GLenum target,GLintptr offset,GLsizeiptr size,void* data);
返回当前名为buffer的缓存对象中的部分或者全部数据。起始数据的偏移字节位置为offset,回读的数据大小为size个字节,它们将从缓存的数据区拷贝到data所指向的内存区域中。
如果缓存对象当前已经被映射,或者offset和size的和超出了缓存对象数据区域的范围,那么将提示一个错误。
3.访问缓存的内容
glNamedBufferData()、glCopyNameBufferSunData()和glGetNamedBufferSubData()都存在一个问题,它们都会导致OpenGL进行一次数据的拷贝操作。
-
根据硬件的配置,也可以通过获取一个指针的形式,直接在应用程序中对OpenGL管理的内存中进行访问。
void* glMapBuffer(GLenum target,GLenum access);
将当前绑定到target的缓存对象的整个数据区域映射到客户端的地址空间中。之后可以根据给定的access策略,通过返回的指针对数据进行直接读或者写的操作。
如果OpenGL无法将缓存对象的数据映射出来,那么glMapBuffer将产生一个错误并返回NULL。发生