OpenGL4.3如何管理buffer中的数据的

来源:互联网 发布:js window事件绑定 编辑:程序博客网 时间:2024/06/06 09:55

OpenGL4.3如何管理buffer中的数据的

继前面的《OpenGl编程指南》第八版学习后终于来到了第三章的内容,这一章首先介绍了一下绘制图形的原语,这部分与我之前看到的各个版本的OpenGl大同小异,就不赘述了,第三章用很重的笔墨分析了一下新版本的OpenGl是如何管理缓存数据的,就是之前提到过数次的gen,bind,buffer几个命令。既然读到这里就做一下笔记,以免日后忘记。

1. glGenBuffers类命令

通常,新版本的OpenGl管理内存都是从类似于glGenBuffers这个命令开始的,此函数的通用定义为:

void glGenBuffers(GLsizei n, GLuint *buffers);

函数说明:分配n个未使用的缓存对象的名字,存储在*buffers制定的数组中
入口参数:GLsizei n:分配名字的数量
*buffers:存储名字的数组地址
出口参数:无

请注意,这里*buffers表示的是一个数组的首地址,也就是要求分配n个缓存对象并把他们的名称(索引,地址,叫什么都可以)以GLint类型存在数组内。这里注意分配的是缓存对象名字而不是分配了缓存块。
在红宝书内特意强调了,这里仅分配了名字,就连对象本身都没有生成,一定要注意!
那么为什么弄得这么麻烦呢?下一个命令很好的解释了原因。

2. glBindBuffer类命令

因为OpenGL有很多类型的缓存,他们分工不同,特性不同,且互相独立(见下表),因此把指定此前生成的对象用来控制哪个缓存的工作分离开来,用glBindBuffer类的命令来指定,在执行这个命令后,OpenGL会生成一个控制指定缓存的对象的实体,如此OpenGL可以保证这些对象用合适的策略来管理缓存。
函数原形:

void glBindBuffer(GLenum target, GLuint buffer); 

函数说明:绑定缓存名称到对应的缓存,如果此前没有为此对象绑定过,则分配新的缓存对象,如果已有对象,则将其激活为缓存中的当前对象,如果为0,则撤销此对象
入口参数:GLenum target:缓存类型
GLuint buffer:此前分配的缓存“名字”
出口参数:无

下表是“绑定目标”的说明表格:

名称 说明 GL_ARRAY_BUFFER 用于设置顶点数组的绑定点,glVertexAttribPointer()函数会用到这个绑定点,并会经常出现在后面的例程中 GL_COPY_READ_BUFFER 与下面的命令共同提供了一个在缓存间拷贝数据的缓存,在这个缓存拷贝数据不会影响到OpenGL的状态,并且它可以用于其他任何对OpenGL特殊的操作的目的 GL_COPY_WRITE_BUFFER 同上 GL_DRAW_INDIRECT_BUFFER 用于存储间接绘图参数的缓存,在书中后续章节会解释 GL_ELEMENT_ARRAY_BUFFER 绑定到这个缓存的数据可以拥有一个顶点索引数组,此数组可被用来执行利用数组索引绘制图形的命令,例如glDrawElements() GL_PIXEL_PACK_BUFFER 象素包缓存可以被用来执行从图象对象读取内容的命令,图像对象比如材质或者帧缓存,而对应的类似函数为glGetTexImage()以及glReadPixels() GL_PIXEL_UNPACK_BUFFER 与pack缓存相反,此缓存作为其它命令数据的来源而不是从别的命令获取数据,类似的命令为glTexImage2D() GL_TEXTURE_BUFFER 材质缓存用来绑定材质对象,如此shader就可以直接从内部获取材质对象内容。材质缓存能够提供一个管理材质的目标位置,但此前仍需要将他们与材质相结合,shader才能够获取他们 GL_TRANSFORM_FEEDBACK_ BUFFER 传输反馈缓存是OpenGl提供的一项能力,当顶点从处理顶点的管线(可能是顶点shader也可能是几何shader,取决于是否启用)移出到下一层管线前,将顶点的一些属性记录在此缓存内,可用于进一步的操作,书中后面的“传输反馈”章节将会对此进行讲解 GL_UNIFORM_BUFFER 此缓存用来为所有标记为uniform的对象提供缓存

在执行了glBindBuffer后,如果是一个新建立的缓存对象,那么其内部的数据实际上是空的,那么就应该用下面的命令把内容放进去。

3. glBufferData类命令

函数原形:

void glBufferData(GLenum target, GLsizeiptr size, const GLvoid *data, GLenum usage);

函数说明:让绑定到目标缓存的缓存对象,分配由size指定大小字节数目的存储空间,如果数据起始地址不是NULL,则分配的空间将会由data的内容填充,而usage会被OpenGl用来区分数据在缓存中的使用目的
入口参数:
GLenum target:指定的缓存
GLsizeiptr size:想要分配的存储空间的大小
GLvoid *data:想放入缓存的数据地址
GLenum usage:使用目的
出口参数:无

在红宝书里指出,glBufferData()是一个重新分配内存的指令,如果新指定的内容比以前的大,则缓存对象会扩充存储空间,如果新指定的内容比以前小,则缓存对象会使存储空间“缩水”。
因此书中提到,在一开始调用这个命令的时候,想分配的空间大小其实不用计算精确到正合适,如果偷懒是可以的。
在前面提到了OpenGL会根据不同的缓存类型采取分配策略,这里函数的usage同样会影响到分配策略,通常usage由三段组成,例如GL_STATIC_DRAW或者GL_DYNAMIC_COPY,第一段一定是GL_,第二段可能是STATIC, DYNAMIC, STREAM,第三段可能是DRAW, READ, COPY,下表给出了每个字段的意义:

字段 含义 STATIC 数据只会被编辑一次,此后被读取数次 DYNAMIC 数据会被多次改变 STREAM 数据仅会被编辑一次,此后仅会被使用几次 DRAW 数据所含的内容由应用程序编辑,并作为OpenGL的绘图或者图像类命令的数据源 READ 数据存储的内容由OpenGL编辑,当应用程序请求时,数据被返还给应用程序 COPY 数据由OpenGL的内读取的数据编辑,并用来作为OpenGL内部绘图类命令的数据源

STATIC类型等同于静态类型,由于它此后不会改变,所以将会被OpenGL存储到一个快速存取的位置,此过程可能会消耗大量的工作,但是为了后面快速运行,只执行一次大量工作是值得的。
DYNAMIC是指动态类型,告诉OpenGL这些数据可能会被多次改变,并需要持续存在很长一段时间。
STREAM相对于前面两个类型,将会甚至只执行一次,因此OpenGL甚至不会把这类数据读入自己的快速内存,因为只要能在第一次的时候读取到就够了。这类型的应用类似于每帧画面之间应用程序在CPU上进行新的数据集合的物理运算的时候。

而后面三个字段则要弄清楚数据是从谁到谁的关系,以及能够使用的范围。
DRAW由应用程序提供给OpenGL,并作为绘制图像的数据。DRAW必须用在能存储顶点的缓存中。
READ由OpenGL操作数据内容,而应用程序负责读取。READ指定的数据必须用在象素相关的缓存中,以及其他能够从OpenGL中返还数据的缓存中。
COPY通常用于OpenGL自身产生数据并将其用于绘制的场合。例如从反馈缓存获取顶点数据,此后被当作顶点数据用于绘图。

至此绑定的三部曲就结束了。下面是红宝书内部一些进阶的技巧。

4. 将多个数据绑定到一个缓存对象指定的位置

如果有很多种数据,却只想放到一个缓存对象中(因为当前激活的缓存对象只能同时有一个),如何做?例如有一个顶点数组,一个材质数组,一个颜色数组,他们在内存中是不连续的,那么笨办法就是在应用程序重新申请一个大内存块,把他们都塞进去,然后执行绑定,如果几个数组特别大,那么开销几乎是不能接受的,有幸,OpenGL提供了一个方法解决这个问题:
函数原形:

void glBufferSubData(GLenum target, GLintptr offset,GLsizeiptr size, const GLvoid *data);

函数说明:在当前目标缓存的缓存对象,从offset位置开始的size指定大小字节数目的存储空间,用data指定的内容替换
入口参数:
GLenum target:指定的缓存
GLintptr offset:地址偏移
GLsizeiptr size:想要分配的存储空间的大小
GLvoid *data:想放入缓存的数据地址
出口参数:无

红宝书还给出了例程,具体解释看我的注释就可以了:

// 顶点位置数组 positionsstatic 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};// 定点颜色数组 colorsstatic 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;//为缓存对象分配一个名字glGenBuffers(1, &buffer);//将对象绑定到GL_ARRAY_BUFFERglBindBuffer(GL_ARRAY_BUFFER, buffer);//为其分配一个由positions和colors大小加起来大小的空间 glBufferData(GL_ARRAY_BUFFER, // 目标缓存sizeof(positions) + sizeof(colors), // 分配的大小NULL, //不填充数据GL_STATIC_DRAW); //数据使用的目的// 将position放到偏移地址0的地方glBufferSubData(GL_ARRAY_BUFFER, //目标缓存,当前激活的对象就是刚才生成的那个0, //地址偏移sizeof(positions), //positions的大小positions); //所填充的内容//将colors放到positions后面glBufferSubData(GL_ARRAY_BUFFER, //目标缓存sizeof(positions), //偏移地址,注意是positions的后面,所以求positions的大小sizeof(colors), //colors的大小colors); //填充的内容// 现在 "positions" 位于偏移地址0且 "colors"位于同块内存中,紧挨着"positions"

5. 用指定的数据清理缓存

如果仅想用特定的数据清除缓存,则可以用下面两个函数 :

void glClearBufferData(GLenum target, GLenum internalformat,GLenum format, GLenum type,const void * data);void glClearBufferSubData(GLenum target,GLenum internalformat,GLintptr offset, GLintptr size,GLenum format, GLenum type,const void * data);

函数说明:用data的内容清除缓存
入口参数:
GLenum target:指定的缓存
GLenum internalformat:填充的内部类型
GLenum format:数据的格式
GLenum type:数据的类型
GLintptr offset:地址偏移
GLsizeiptr size:想要分配的存储空间的大小
GLvoid *data:想放入缓存的数据地址
出口参数:无

两个函数的区别是第一个函数将整个缓存范围清除,而第二个函数指定了清理范围。
这里的format和type的区别参见这里

6. 复制缓存函数

函数定义:

void glCopyBufferSubData(GLenum readtarget,GLenum writetarget,GLintptr readoffset,GLintprr writeoffset, GLsizeiptr size);

函数说明:复制一个缓存对象的内容到另一个缓存对象中
入口参数:
GLenum readtarget:指定的读取缓存对象
GLenum writetarget:指定的写入缓存对象
GLintptr readoffset:读缓存内的偏移值
GLintprr writeoffset:写缓存内的偏移值
GLsizeiptr size:希望操作的字节数
出口参数:无

其中,GL_COPY_READ_BUFFER和GL_COPY_WRITE_BUFFER两个缓存基本上就是用在这里的,使用这两个缓存可以向任何其他缓存对象写入数据或者读出数据,而OpenGL其他的部分不会受到任何影响。

0 0
原创粉丝点击