2.7 缓冲区对象

来源:互联网 发布:手机实用小五金软件 编辑:程序博客网 时间:2024/06/05 18:24

缓冲区对象简介

什么是缓冲区对象?

缓冲区,通常是指一个内存块。而缓冲区对象呢?则是指位于图形服务器中的内存块。这样的内存块,通常都会用一个无符号的整型数值,对它进行标识。这个无符号整型标识,叫做缓冲区对象的名称。

图形服务器?
OpenGL是按照客户机-服务器模式设计的,在OpenGL需要数据的任何时候,都必须把数据从客户机内存传输到服务器。缓冲区对象,允许应用程序显式地指定把哪些数据存储在图形服务器中。

缓冲区对象分类

  1. 存储顶点数据的缓冲区对象。
  2. 存储像素数据的缓冲区对象。
  3. 存储成块的、用于着色器的统一变量数据 的 缓冲区对象。

2.7.1 创建缓冲区对象

所谓创建缓冲区对象,其实就是生成缓冲区对象的名称,即生成无符号整型数值。这是通过函数glGenBuffers来实现的。

void glGenBuffers(GLsizei n, GLuint *buffers);

这个函数用来生成1个或者多个缓冲区对象的名称。其中参数n表示缓冲区对象名称个数,buffers存放生成的缓冲区对象的名称。在buffers数组中返回的名称不一定是连续的整数。返回的名称被标记为已使用,以便分配给缓冲区对象。但是,当它们被绑定之后,它们只获得一个合法的状态。零是一个被保留的缓冲区对象名称,从来不会被glGenBuffers()作为缓冲区对象返回。

查询缓冲区对象名称是否被占用

调用glIsBuffer()函数,判断一个标识符是否是一个当前被使用的缓冲区对象的标识符。
GLboolean glIsBuffer(GLuint buffer);
如果buffer是一个已经绑定的缓冲区对象的名称,而且还没有删除,这个函数返回GL_TRUE。
如果buffer为0或者它不是一个缓冲区对象的名称,这个函数返回GL_FALSE。

2.7.2 激活缓冲区对象

这个激活缓冲区对象,又叫绑定缓冲区对象。激活缓冲区,指明了将要使用的是哪一个缓冲区,然后要使用这个缓冲区来干什么。使用的函数叫glBindBuffer()。

如果应用程序有多个缓冲区对象,那么每次使用缓冲区对象之前,都需要告诉OpenGL,我要使用的是哪个缓冲区对象,这个也是通过glBindBuffer来实现缓冲区对象的选择。

禁用缓冲区对象,用0作为缓冲区对象的标识符来调用glBindBuffer()函数。这将把OpenGL切换为默认的不适用缓冲区对象的模式。

void glBindBuffer(GLenum target, GLuint buffer);
指定了当前的活动缓冲区对象。target必须设置为GL_ARRAY_BUFFER、GL_ELEMENT_ARRAY_BUFFER、GL_PIXEL_PACK_BUFFER、GL_PIXEL_UNPACK_BUFFER、GL_COPY_READ_BUFFER、GL_COPY_WRITE_BUFFER、GL_TRANSFORM_FEEDBACK_BUFFER或者GL_UNIFORM_BUFFER。buffer指定了将要绑定的缓冲区对象。

glBindBuffer完成3个任务之一:
1. 当buffer是一个首次使用的非符号整数时,它就创建一个新的缓冲区对象,并把buffer分配给这个缓冲区对象,作为它的名称。
2. 当绑定到一个以前创建的缓冲区对象时,这个缓冲区对象便成为活动的缓冲区对象。
3. 当绑定到一个值为零的buffer时,OpenGL就会停止使用缓冲区对象。

2.7.3 用数据分配和初始化缓冲区对象

前面2小节,讲了产生缓冲区对象的名称,以及选中缓冲区对象,下一步,就是要给这个缓冲区对象赋予数据了。这是通过调用glBufferData()函数实现的。

void glBufferData(GLenum target, GLsizeiptr size, const GLvoid *data, GLenum usage);
分配size个存储单位(通常是字节)的OpenGL服务器内存,用于存储顶点数据或索引。以前所有与当前绑定对象相关联的数据都将被删除。

其实这个函数,就相当于C语言的malloc,只是这个分配的内存,位于服务器上。而且,使用data里面的内容,去初始化这个缓冲区对象。

参数中的target和glBindBuffer中的target含义一样,可以取GL_ARRAY_BUFFER(表示顶点数据)、GL_ELEMENT_ARRAY_BUFFER(表示索引数据)、GL_PIXEL_PACK_BUFFER(表示从OpenGL获取的像素数据)、GL_PIXEL_UNPACK_BUFFER(表示传递给OpenGL的像素数据)、GL_COPY_READ_BUFFER、GL_COPY_WRITE_BUFFER(表示在缓冲区之间复制数据)、GL_TRANSFORM_FEEDBACK_BUFFER(表示执行一个变换反馈着色器的结果)或者GL_UNIFORM_BUFFER(表示统一变量值)。

size是内存的大小,单位通常为字节。字节长度通常为数据元素个数 * 数据元素类型的长度。

data可以是一个指向客户机内存的指针(用于初始化缓冲区对象),也可以是NULL。如果它传递的是一个有效的指针,size个单位的存储空间就从客户机复制到服务器。如果它传递的是NULL,这个函数将会保留size个单位的存储空间供以后使用,但不会对它进行初始化。

usage提供了一个提示,就是数据在分配之后将如何进行读取和写入。它的有效值包括GL_STREAM_DRAW、GL_STREAM_READ、GL_STREAM_COPY、GL_STATIC_DRAW、GL_STATIC_READ、GL_STATIC_COPY、GL_DYNAMIC_DRAW、GL_DYNAMIC_READ、GL_DYNAMIC_COPY。如果usage并不是使用的值之一,这个函数返回GL_INVALID_VALUE。

当然,服务器内存分配,也有出错的时候,当内存不足时,glBufferData()将返回GL_OUT_OF_MEMORY。

glBufferData()的最后一个参数,usage,提示OpenGL,我们想用这个数据干什么。在缓冲区对象数据上,可以进行3种类型的操作:
1. 绘图:客户机指定了用于渲染的数据。
2. 读取:从OpenGL缓冲区读取(例如帧缓冲区)数据值,并在应用程序中用于各种与渲染并不直接相关的计算。
3. 复制:从OpenGL缓冲区读取数据值,作为用于渲染的数据。

另外,根据数据更新的频率,有几种不同的操作提示描述了数据读取频率或在渲染中使用的频率:
- 流模式:缓冲区对象中的数据常常需要更新,但是在绘图或其他操作中使用这些数据的次数较少。
- 静态模式:缓冲区对象中的数据只指定1次,但是这些数据被使用的频率很高。
- 动态模式:缓冲区对象中的数据不仅常常需要进行更新,而且使用频率也非常高。

然后书中还列了一个表格,指明usage的每一个取值的含义。太抽象了,不知道啥玩意,PASS。

2.7.4 更新缓冲区对象的数据值

这个好说,当我们没对缓冲区对象中的数据进行初始化,或者初始化完毕之后,又需要修改缓冲区对象的数据,就可以用这个小节的内容。

更新缓冲区对象中的数据,有好几个函数,分别是glBufferSubData,glMapBuffer, glMapBufferRange,然后还有一个取消缓冲区映射的函数,叫glUnmapBuffer。

glBufferSubData

这个函数,通过指定选择的缓冲区对象,更新的偏移,更新的字节数,对缓冲区对象进行更新。

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

用data指向的数据更新与target想关联的当前绑定缓冲区对象中从offset(以字节为单位)开始的size个字节数据。

这个已经说的很明确了,有target所指的缓冲区对象,有对象起始偏移offset,有更新数据的大小size,有更新数据本身data。

glMapBuffer

这个函数好方便的,它直接返回缓冲区对象的指针,然后我们就可以通过直接修改返回指针的内容,从而达到修改缓冲区对象的目的。这就和我们修改内存中的内容一样的了。

GLvoid *glMapBuffer(GLenum target, GLenum access);

返回一个指针,指向与target相关联的当前绑定缓冲区对象的数据存储。
如果返回值为NULL,则表示这个缓冲区无法被映射或者它以前已经被映射。

完成了对数据存储的访问之后,可以调用glUnmapBuffer()取消对这个缓冲区的映射。

GLboolean glUnmapBuffer(GLenum target);

glMapBufferRange

如果只需要更新缓冲区中相对较少的值,或者更新一个很大的缓冲区对象中很小的连续范围的值,使用glMapBufferRange()效率更高。

GLvoid *glMapBufferRange(GLenum target, GLintptr offset,        GLsizeiptr length, GLbitfield access);

这个函数,和glMapBuffer相比,多了两个参数,offset和length,这两个参数,限制了缓冲区对象的映射范围。

注意,如果access标志中,指定了GL_MAP_FLUSH_EXPLICIT_BIT时,应该通过调用glFlushMappedBufferRange()向OpenGL表明映射缓冲区中的范围需要修改。

GLvoid glFlushMappedBufferRange(GLenum target, GLintptr offset, GLsizeiptr length);

表示一个缓冲区范围中的值已经修改了,这可能引发OpenGL服务器更新缓冲区对象的缓存版本。

2.7.5 在缓冲区对象之间复制数据

缓冲区对象,它本身表示一块内存。然后在缓冲区对象之间复制数据,相当于把数据从一块内存,移到另一块内存中。

在OpenGL 3.1之前的版本,需要通过内存,作为中间转接人,将数据进行复制。

在OpenGL 3.1之后,有一个函数,可以直接在服务器内存之间进行数据复制。这个函数叫glCopyBufferSubData()。

void glCopyBufferSubData(GLenum readbuffer, GLenum writebuffer, GLintptr readoffset, GLintptr writeoffset,    GLsizeiptr size);

这个函数的参数,已经很明确了,和strcpy的参数差不多,指定了从哪里读,写到哪里去,然后读的偏移,写的偏移,读写多少字节。

2.7.6 清除缓冲区对象

这个和创建缓冲区对象对应。使用完缓冲区对象之后,就要删除缓冲区对象。和内存创建,删除,是一回事。

void glDeleteBuffers(GLsizei n, const GLuint *buffers);

不说了,参数和创建缓冲区对象完全一样。

2.7.7 使用缓冲区对象存储顶点数组数据

这是一个例子,告诉我们如何使用缓冲区对象。最基础的就是用缓冲区对象来存储顶点数组数据。

这个用法,其实和之前的使用顶点数组一样。只是,现在的数据内存,位于服务器上,而不是客户机上了。

下面是具体步骤,使用缓冲区对象存储顶点数组数据:
1. 生成缓冲区对象标识符。
2. 绑定一个缓冲区对象,确定它是用于存储顶点数据还是索引。
3. 请求数据的存储空间,并且对这些数据元素进行初始化。
4. 指定相对于缓冲区起始位置的偏移量,对诸如glVertexPointer()这样的顶点数组函数进行初始化。
5. 绑定适当的缓冲区对象,用于渲染。
6. 使用适当的顶点数组渲染函数进行渲染,例如glDrawArrays()或glDrawElements()。

这6个步骤中,其中1,2,3, 5其实都是缓冲区对象相关的步骤,4和6是顶点数组的步骤。注意,这里的glVertexPointer的最后一个参数,顶点数据,使用的不是内存指针了,而是使用BUFFER_OFFSET(0)这样的东西,来替换内存指针了。

0 0
原创粉丝点击