转载自:http://hi.baidu.com/bfiyubfpgibjpqq/item/0897eb6a7c7f2d0aa0cf0f22
我们都知道,受到总线带宽的限制,把顶点数据从CPU传到GPU,是比较花费时间的,特别当我们有上百万个顶点要频繁地传送到GPU的时候。
为了解决这个瓶颈问题,于是便有了VBO,VBO的应用就是在显卡中分配一个显存空间,然后一次性把所有的顶点数据发送到这块显存中保存起来,这样,当我们要刷新3D场景的时候,我们就不需要每次都把上百万个顶点从CPU传送到GPU了,只要直接从VBO对像中读取就行了。数据从CPU到GPU大概只有8G的带宽,而由VBO直接读取,速度是50G以上,几十倍的差别。
至于PBO,我没有用过,不想多作评论。在没有FBO之前,就是用来它做离屏渲染的。
VBO,Vertex Buffer Array
为了加快显示速度,显卡增加了一个扩展,即VBO。它本质上是存储几何数据的缓存。它直接把顶点数据放置到显卡中的高速缓存,极大提高了绘制速度。
这个扩展用到ARB_vertex_buffer_object,它可以直接像顶点数组那样使用。唯一不同的地方在于它需要将数据载入显卡的高效缓存,因此需要占用渲染时间。
[参考文章1] 给出了一个使用VBO扩展的例子程序。[参考文章2]罗列了与VBO操作相关的函数
[参考文章3]归纳出VBO扩展的用法流程,并总结出与纹理用法流程的相似性:
初始化阶段:
1. glGenBuffersARB(1, &nVBOVertices); //生成一个句柄
2. glBindBufferARB(GL_ARRAY_BUFFER_ARB, nVBOVertices); //声明该句柄为一个vbo句柄,并选择之
3. glBufferDataARB(GL_ARRAY_BUFFER_ARB, sizeof(vertices), vertices,GL_STATIC_DRAW); //将顶点集上传至server端
使用阶段:
1. glEnableClientState(GL_VERTEX_ARRAY); //开始使用vbo
2. glBindBufferARB(GL_ARRAY_BUFFER_ARB, nVBOVertices); //选择当前使用的vbo
3. glVertexPointer(3, GL_FLOAT, 0, BUFFER_OFFSET(0)); //指定vbo顶点格式
4. glDrawArrays( GL_TRIANGLES, 0, g_pMesh->m_nVertexCount ); //画吧
5. glDisableClientState(GL_VERTEX_ARRAY); //停止使用vbo
收尾阶段:
1. glDeleteBuffersARB(1,&nVBOVertices); //删除句柄,同时删除server端顶点缓冲
再来看看纹理缓冲是怎么使用的,其实差不多:
初始化阶段:
1. glGenTextures(1, &texID);//创建句柄
2. glBindTexture(GL_TEXTURE_2D, texID); //设置句柄类型
3. glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, img->GetWidth(), img->GetHeight(), 0, GL_RGBA, GL_UNSIGNED_BYTE, raw_rgba); //上传纹理缓冲
使用阶段:
1. glEnable(GL_TEXTURE_2D); //开始使用纹理缓冲
2. glBindTexture(GL_TEXTURE_2D, texID); //选择当前使用的纹理缓冲
3. 发送顶点和纹理坐标,画吧...省略
4. glDisable(GL_TEXTURE_2D); //停止使用纹理
收尾阶段:
1. glDeleteTextures(1,&texID);//删除句柄,,同时删除server端缓冲
参考文章:
1. NEHE教程第45讲 http://nehe.gamedev.net/data/lessons/lesson.asp?lesson=45
2. http://hi.baidu.com/excerpts/blog/item/dde4e0ceeeee8c0692457e54.html
3. OpenGL VBO顶点缓冲的使用 http://www.cppblog.com/w2001/archive/2008/05/10/49376.aspx
4. Opengl Vertex Buffer Object http://www.songho.ca/opengl/gl_vbo.html
PBO,即Pixel Buffer Object也是用于GPU的扩展(ARB_vertex_buffer_object)。这里的缓存当然就是GPU的缓存。PBO与VBO扩展类似,只不过它存储的是像素数据而不是顶点数据。PBO借用了VBO框架和所有API函数形式,并加了上两个"target"标志。这两个标识是:
- GL_PIXEL_PACK_BUFFER_ARB 将像素数据传给PBO
- GL_PIXEL_UNPACK_BUFFER_ARB 从PBO得到像素数据
这里的“pack”还是“unpack”,可分别理解为“传给”和“得到”。它们也都可以统一理解为“拷贝”,也就是像素数据的“传递”。
比如说,glReadPixel就是数据从帧缓存(framebuffer)到内存(memory),可理解为“pack”;glDrawPixel是从内存到帧缓存,可理解为“unpack”;glGetTexImage是从纹理对象到内存,可理解为“pack”;glTexImage2d从内存(memory)到纹理对象(texture object),可理解为“unpack”。
下图是PBO与Framebuffer和Text对象之间的传递。
![](http://static11.photo.sina.com.cn/bmiddle/4062094e45468a66af93a)
图1 opengl PBO
使用PBO的好处是快速的像素数据传递,它采用了一种叫DMA(Direct Memory Access)的技术,无需CPU介入。另一个PBO的优点是,这种DMA是异步的。我们可以通过下面两张图来比较使用PBO的与传统的纹理传递的过程。
图2是用传统的方法从图像源(如图像文件或视频)载入图像数据到纹理对象的过程。像素数据首先存到系统内存中,接着使用glTexImage2D将数据从系统内存拷贝到纹理对象。包含的两个子过程均需要有CPU执行。而从图3中,我们可以看到像素数据直接载入到PBO中,这个过程仍需要CPU来执行,但是从数据从PBO到纹理对象的过程则由GPU来执行DMA,而不需要CPU参与。而且opengl可安排异步DMA,不必马上进行像素数据的传递。因此,相比而言,图3中的glTexImage2D立即返回而不是马上执行,这样CPU可以执行其它的操作而不需要等待像素数据传递的结束。
![](http://static13.photo.sina.com.cn/bmiddle/4062094e45468b475b17c)
图2 不使用PBO的纹理载入
![](http://static8.photo.sina.com.cn/bmiddle/4062094e45468b4d13527)
图3 使用PBO的纹理载入
GL_PIXEL_PACK_BUFFER_ARB用于将像素数据从opengl传递给应用程序,GL_PIXEL_UNPACK_BUFFER_ARB则是将像素数据从应用程序传递给opengl。
生成PBO
生成PBO分成3步:
1. 用glGenBuffersARB()生成缓存对象;
2. 用glBindBufferARB()绑定缓存对象;
3. 用glBufferDataARB()将像素数据拷贝到缓存对象。
如果在glBufferDataARB函数中将一个NULL的指针给源数组,PBO只会为之分配一个给定大小的内存空间。glBufferDataARB的另一个参数是关于PBO的性能参数(hint),表明缓存对象如何使用。该参数取GL_STREAM_DRAW_ARB表明是载入,GL_STREAM_READ_ARB表明是异步帧缓存读出。
映射PBO
PBO提供了一种将opengl控制的缓存对象与客户端地址空间进行内存映射的机制。所以客户端可通过glMapBufferARB()和glUnmapbufferARB就可以修改缓存对象的部分或整个数据。
void* glMapBufferARB(GLenum target, GLenum access);
GLboolean glUnmapBufferARB(GLenum target);
glMapBufferARB返回缓存对象的指针。参数target取GL_PIXEL_PACK_BUFFER_ARB或GL_PIXEL_UNPACK_BUFFER_ARB,参数access是能对映射缓存的操作,可取GL_READ_ONLY_ARB、GL_WRITE_ONLY_ARB和GL_READ_WRITE_ARB,分别表明可从PBO读、可向PBO写,可从PBO读也可向PBO写。
要注意:如果GPU正在对缓存对象进行操作,glMapBufferARB不会返回缓存对象直到GPU结束了对缓存对象的处理。为了避免等待,在调用glMapBufferARB之前,先调用glBufferDataARB(用NULL指针作为参数),这时OpenGL将丢弃老的缓存对象,为新的缓存对象分配空间。
在客户端使用PBO后,应调用glUnmapBufferARB来取消映射。glUnmapBufferARB返回GL_TRUE表明成功,否则返回GL_FALSE。
____________________________________________________
Demo
例子程序pboUnpack.zip使用了不同方式来比较将纹理传给OpenGL的模式:
通过按空格键,可以不同模式间切换。
在PBO模式下,每帧的纹理源(像素)都是在映射PBO状态下直接写进去的。再通过调用glTexSubImage2D将PBO中的像素传递给纹理对象。通过使用纹理对象可以在PBO和纹理对象间进行异步DMA传递。它能大大提高像素传递的性能。
由于glTexSubImage2D立即返回,因此CPU能够直接进行其它工作,无需等待实际的像素传递。
![](http://static16.photo.sina.com.cn/bmiddle/4062094e45749e9b2d5ff)
图4 两个PBO更新纹理
为了将像素传递的性能最大化,可以使用多个PBO对象。图4中表明同时使用了两个PBO。在glTexSubImage2D将像素数据从PBO拷贝出来的同时,另一份像素数据写进了另一个PBO。
在第n帧时,PBO1用于glTexSubImage2D,而PBO2用于生成一个新的纹理对象了。再到n+1帧时,两个PBO则互换了角色。由于异步DMA传递,像素数据的更新和拷贝过程可同时进行,即CPU将纹理源更新到PBO,同时GPU将从另一PBO中拷贝出纹理。
![](http://static15.photo.sina.com.cn/bmiddle/4062094e4574a05d0b11e)
例子程序pboPack.zip从窗口的左边读出(pack)像素数据到PBO,在更改它的亮度后,把它在窗口的右边绘制出来。通过按空格键,可以看glReadPixels的性能。
传统使用glReadPixels将阻塞渲染管道(流水线),直到所有的像素数据传递完成,才会将控制权交还给应用程序。相反,使用PBO的glReadPixels可以调度异步DMA传递,能够立即返回而不用等待。因此,CPU可以在OpenGL(GPU)传递像素数据的时候进行其它处理。
![](http://static16.photo.sina.com.cn/bmiddle/4062094e45749f1f9ad1f)
图5 用两个PBO异步glReadPixels
例子程序也使用了两个PBO,在第n帧时,应用帧缓存读出像素数据到PBO1中,同时在PBO中对像素数据进行处理。读与写的过程可同时进行,是因为,在调用glReadPixels时立即返回了,而CPU立即处理PBO2而不会有延迟。在下一帧时,PBO1和PBO2的角色互换。
![](http://static8.photo.sina.com.cn/bmiddle/4062094e4574a08112017)
参考文章
1. http://www.songho.ca/opengl/gl_pbo.html
2. http://hacksoflife.blogspot.com/2006/10/vbos-pbos-and-fbos.html
3. http://www.paulsprojects.net/opengl/rtotex/rtotex.html
使用VBOs
最近翻找有关于VBO的资料,发现网上很少有这方面的中文资料。现将nvidia官方网站上的一篇文章《Using Vertex Buffer Objects(VBOs)》,特将其翻译出来,以供后人使用J。翻译时间不到一天,匆忙之余,难免有误,敬请指正,欢迎来邮,我是Neil。
联系邮箱:zhuhuazha@yahoo.com.cn
联系blog:Neil`s Blog
Using VBOs是来自于Nvidia的官方网站上的白皮书。
来源网址:http://developer.nvidia.com/object/using_VBOs.html,如有转载请注明版权。
使 用 VBOs
概要:
VBO是一种强大的技术:它允许我们在服务器端的高速缓存上存储一定量的数据。
该要素提供这样一种机制:压缩数据到“缓存对象(buffer objects)”,使得处理这些数据时不必从服务器直接取出来,从而加快数据传输速率。
VBOs可在以下几点上帮助我们:
□ 由客户端或者状态函数指向的任意数据体。最典型的就是我们所讨论的glVertexPointer(),glColorPointer(),glNormalPointer()等等。
□ 画图元集的指示数组(glDraw[Range]Elements())。
该机制的最初想法是提供一些能由标识符获取的内存块(缓存)。就像显示列表和纹理,我们可以通过绑定这样一个缓存来激活它。
该绑定操作将每个客户机/状态函数指针变成一定量的偏移,因为我们将会在内存区域里用到,该区域是相对于当前范围缓存的。换句话说,该扩展将客户机/状态函数变成了服务器/状态函数。
我们都知道客户机/状态函数处理数据的范围仅是对于客户机自身是可访问的。其它的任一个客户机都不可能访问到这些数据。通过在服务器端执行这些函数序列,才可以在不同的客户机上共享使用这些数据。许多客户机能绑定通用的缓存,它们都像纹理和显示列表一样通过标识符来处理。
VAR的问题:
以前解决这类任务的扩展方法是用VAR(Vertex Array Range顶点数组序列)。尽管该扩展仍然可以用,但我们建议您用VBO来代替。
VAR功能上完全够用,但在此不妨列举一下开发人员觉得它不好的地方:
□ 它打破了服务器/客户机的模式,因为客户机部分控制了内存管理(本归于服务器)。
□ 并没有提供一个内部的内存管理,VAR所提供的唯一功能就是在服务器的内存上定位一大块内存区域。
□ 当给VAR定位缓存时,开发者需要描述哪里要用到AGP、系统、或者显卡缓存,而这些工作显得麻烦而棘手。
□ 开发人员仍然需要为VAR在本地创建自己的内存定位以优化它们借来的缓存块。
□ 高效的内存管理必须是像旗语一样的防卫系统。
为了简化问题,我们可以说VBO能像VAR样处理问题,并且能为你管理内存(图1)。
内存管理
OpenGL有着与Direct3D为顶点数组提供的AGP/视频内存(显存)管理似的同一层次功能。OpenGL里的工作方式也十分相似。它有映射到内存缓存的能力,从而定义各缓存的利用,映射和取消映射(在D3D中是Lock/Unlock)。
内部内存管理能为我们选择最佳的内存类型(系统、视频卡或者AGP),这取决于我们所用的缓存。
VBO提供了不同的方式与缓存对象进行交互:
□ 绑定缓存(glBindBuffer):该操作允许客户机/状态函数直接在缓存区域工作,而不必在客户机的绝对(物理)内存上。绑定0号缓存则切断VBO,这样就可以用绝对指针回到原有客户机的状态模式。
□ 利用VBO的API加载数据到这些缓存对象上(glBufferData、glBufferSubData、glGetBufferSubData):这些函数让你在客户机区域和服务器的缓存对象作一个复制)。
□ 利用缓存映射技术(glMapBuffer 和glUnmapBuffer):这个类似于D3D的Lock与UnLock。你可以得到一个临时指针作为缓存开始的入口,意味着缓存是映射到了客户机内存上。OpenGL负责如何映射到客户机上。由于这一点,映射必须作为一个短的操作,并且指针不能存储给以后的应用。
对象
VBO与以下两种对象一起工作:
□ 数组缓存(ARRAY_BUFFER_ARB):这些缓存包含顶点属性,例如顶点坐标、纹理坐标数据、各顶点着色数据和法向信息。你可以插入该数据(利用stride参数),或者在另一数组后再写入一个数组(比如先写1000个顶点,再写1000个法向量,等等)。GlVertexPointer glNormalPOinter(等等)必须有正确的指向偏移量。
□ 图元数组缓存(ELEMENT_ARRAY_BUFFER_ARB):该类型缓存主要在glDraw[Range]Element()中用于图元指针。它可能仅包含图元的指示符。
这两种目标平行同步建立,因为图元数组在glDraw[Range]Element()函数中必须与数组缓存同时获取。
用这两种对象的一个有意思的地方是,当保存同一顶点数组缓存时,交换各种图元缓存的能力。我们可以实施LOD,或者当我们工作于同一顶点数据库时改变图元表所产生的其它任何效果。
关于PBO的几句话
建议给VBO增加更多的对象的另一扩展是ARB_pixel_buffer_object(PBO,象素缓存对象)。
尽管它在我们的50.XX版本驱动里还没有(译者注:此为nividia的图形驱动),该扩展允许我们通过增加两个新的对象在纹理、帧缓存、离屏缓存里工作。
换句话说,它会对顶点、法向、图元等等提供同样的机制,但是对于字节数组却不同。
新的两种对象是:
□ PIXEL_PACK_BUFFER:该对象给各种读操作提供缓存,例如glReadPixels和glGetTexImage。这些命令将会把它们的数据写到当前范围缓存对象中。
□ PIXEL_UNPACK_BUFFER:该对象给各种写操作提供缓存,例如glBitmap,glDrawPixels和glTexImage2D。这些命令将会从缓存对象中读取数据。
在VBOs与PBOs混合时会有一些很有意思的优化措施(图2):
□ 渲染到顶点数组:如果我们打算在第一个通道创建一个特殊的顶点数组(用于贴图、移位等等),我们可以避免作为一个顶点程序的输入而在客户机端复制p缓存(pBuffer),并放回到服务器端。VBO/PBO会保持所有数据流都在服务器端。
□ 流纹理:该操作类似于我们操作PDR(Pixel Data Range象素数据范围);我们用MapBuffer/UnMapBuffer来改变纹理数据,基于视频流,然后调用TexSubImage来更新纹理。
图2 VBO/PBO结合实例
新的步骤、功能函数和符号标志
使用标志:
□ STREAM_DRAW_ARB
□ STREAM_READ_ARB
□ STREAM_COPY_ARB
□ STATIC_DRAW_ARB
□ STATIC_READ_ARB
□ STATIC_COPY_ARB
□ DYNAMIC_DRAW_ARB
□ DYNAMIC_READ_ARB
□ DYNAMIC_COPY_ARB
访问标志:
□ READ_ONLY_ARB
□ WRITE_ONLY_ARB
□ READ_WRITE_ARB
对象:
□ ARRAY_BUFFER_ARB
□ ELEMENT_BUFFER_ARB
void BindBufferARB(enum target, uint buffer):
BindBufferARB函数用于绑定对象ID作为使用的实际缓存。如果ID为0就关闭了缓存使用。
void *MapBufferARB( enum target, enum access);
boolean UnmapBufferARB(enum target);
MapBufferARB函数提供一个指针指向当前缓存对象的映射区域,UnmapBufferARB则解除映射。
void BufferDataARB(enum target, sizeiptrARB size, const void *data, enum usage);
BufferDataARB有以下两种用法:
□ 当数据集置为NULL时简化当前缓存对象的内存装载和使用量。这样,你后来就可以通过映射缓存来装载数据。
□ 定位缓存,设置usage, 复制一些数据;特别用于处理静态内存模型。
void BufferSubDataARB( enum target, intptrARB offset, sizeiptrARB size, const void*data)
该函数用于在缓存对象的特定区域内复制数据。
void GetBufferSubDataARB(enum target, intptrARB offset, sizeiptrARB size, void *data);
该函数用于在当前缓存的特定区域获取数据。
void DeleteBufferARB(sizei n, const uint *buffers);
void GenBufferARB(sizei n, uint *buffers);
boolean IsBufferARB(uint buffer);
这三组函数类似于显示列表/纹理标识符;它们能缓存对象定位、释放或者查询一些标识符。
void GetBufferParameterivARB(enum target, enum pname, int *params);
该函数返回关于当前缓存对象的各种参数,pname可能是:
□ BUFFER_SIZE_ARB:返回缓存对象的大小。
□ BUFFER_USAGE_ARB:返回缓存对象的用法。
□ BUFFER_ACCESS_ARB:返回缓存对象的可访问标志。
□ BUFFER_MAPPED_ARB:告诉你我们是否已经映射了该缓存。
void GetBufferPointervARB(enum target, enum pname, void **params);
该函数返回缓存的实际指针,如果该缓存已经被映射了的话(MapBufferARB)。Pname这次只能是BUFFER_MAP_POINTER_ARB。
Get{Boolean, Integer, Float, Double}v的标签
缓存对象ID0是保留的,当0缓存对象与所给出的对象外时,该绑定缓存所影响的命令才能正常起作用。当非0缓存出界了,指针就表示一个偏移,并且将会超出VBO的管理。
你可以使用以下标签来得知哪个缓存是作为VBO偏移的:
□ ARRAY_BUFFER_BINDING_ARB
□ ELEMENT_ARRAY_BUFFER_BINDING_ARB
□ VERTEX_ ARRAY_BUFFER_BINDING_ARB
□ NORMAL_ ARRAY_BUFFER_BINDING_ARB
□ COLOR_ ARRAY_BUFFER_BINDING_ARB
□ INDEX_ ARRAY_BUFFER_BINDING_ARB
□ TEXTURE_COORD_ ARRAY_BUFFER_BINDING_ARB
□ EDGE_FLAG_ ARRAY_BUFFER_BINDING_ARB
□ SECONDAR_COLOR_ ARRAY_BUFFER_BINDING_ARB
□ FOG_COORDINATE_ ARRAY_BUFFER_BINDING_ARB
□ WEIGTH_ ARRAY_BUFFER_BINDING_ARB
GetVertexAttribivARB的标签:
当使用VBO和顶点程序工作时,一些属性会有一些任意的意思:例如一个法向数组可能不仅仅是用于存储法向信息。替代前一节使用标签的方法是:你可以使用属性的索引。该标签允许你通过偏移系统利用VBO来查询使用了哪个属性码。
□ VERTEX_ATTRIB_ARRAY_BUFFER_BINDING_ARB
各函数的目的
glBufferDataARB()
该函数是应用与内存之间的抽象层。但在每个缓存对象的背后是复杂的内存管理系统。
基本上该函数主要有以下作用:
□ 检查存储数据变化的大小和使用类型
□ 如果大小为0,释放该缓存对象的内存。
□ 如果大小和存储类型没有变化,并且该缓存没有被GPU使用,我们就可以使用它。所有的一切都已经为使用设置好。
□ 另一方面,如果GPU在用它,或者将会用到它,或者存储类型有变化,我们就必须为该缓存申请另一块内存区域以作准备。
□ 如果数据指针不用空,我们就会复制新的数据到内存区域。
从中我们可见到我们在第二次调用该函数前所拥有的内存不一定与调用后的内存是同一块。但是,从应用程序的角度来看它仍然是相同的(同一缓存对象)。但是在驱动层面上,我们正在优化并且允许应用程序不必等候GPU。
实际上,我们已经定位了一大块内存池用于后面的程序定位。当我们调用 函数glBufferDataARB时,我们为当前缓存对象保留了其中一块。然后我们就用数据来填充,并用它来绘制,并标记该缓存为用过的(类似于glFence函数)。
如果我们在GPU动作完成之前再次调用glBufferDataARB,我们可以简单的为该缓存对象从内存池中新开一块区域。这是可能的,因为BufferDataARB会认为我们准备重新描述缓存里面的数据(BufferSubDataARB反对的)。
使用标志
使用参数是帮助VBO内存管理完全优化你的缓存的一个关键值。
标志名称 定义
STATIC_… 假定1到n是更新绘制。表明数据仅描述一次(在初始化中)
DYNAMIC_… N对N的绘制。通常表明数据经常更新,但每次更新都会绘制多次。例如,每少数几帧更新任一动态数据。
STREAM_… 1对1的绘制。每绘制一次都会更新。STREAM有点像DYNAMIC:数据会发生变化。但是,数据会一直变化,因此它可能会在完成的时候变得不稳定(像显存),当它消失时(比如模式变换)会立即被替换掉。
…_READ_… 指我们必须有一个读取数据的简单访问:AGP或者系统内存会很适合。
…_COPY_… 意味我们即将执行_READ_和_DRAW_操作
…_DRAW_… 指缓存将向GPU发送数据。我们或许想利用视频(STATIC | STREAM_DRAW_ARB),或者AGP(DYNAMIC_DRAW_ARB)内存
表1 标签使用列表
这种内存使用的合并,能帮助内存管理者平衡三种内存:系统、AGP、显示。另一方面,它还得计算出能为其它缓存回收多少内存区域。STATIC,STREAM, 和DYNAMIC这里就是为此目的。
但是STATIC,STREAM, 和DYNAMIC只是一些简单的建议和提示潜在的使用模式。它们并不特别强迫驱动器作任何事情,但是它们帮助我们决策缓存管理配置和映射行为。我们假定定位和利用的数据总是可获取的,直到你特意释放它,通过删除缓存(如果你打算在其上创建另一缓存时所用的重量级方法)或者调用BufferDataARB(…,NULL, ..);第二种方式更可取。
在服务器端,这些并不是严重约束。它们建议帮助我们确定在哪里放置数据并且如何管理它。没有任何东西阻碍你创建一个STATIC数据存储,然后每一帧都更新它。也没有任何理由限制你不能创建 STREAMING数据而却不能修改它。尽管如此,我们仍然极力反对这种行为。
glBufferSubDataARB()
该函数提供一个为已有缓存替换一定范围数据的方法。注意:为了避免冲突,我们必须等待GPU如果有GPU正在此块上工作的话,结果就有可能丧失一些效果。
GlBindBufferARB()
该函数加载内部参数,这样在顶点数组上的下一步操作,或者任一VBO函数,都可以在当前缓存对象上工作。注意:这种绑定操作是很廉价的:是一种预前绑定,等等其它操作来变化VBO管理者的内部状态。
glMapBufferARB()
该函数映射缓存对象到客户机内存中,依据访问标志。在最好的情况下,没有任何的数据传输:驱动器正好“展现”实际的指针到AGP或者系统内存中。
在其它情况下,如果访问标志条件不满足,则有可能会传输一些数据。例如,要求一个指针去读取一个缓存,该缓存存在于显存,这时就要求驱动器降级这个缓存到AGP或者系统缓存。
利用访问标志来尽量有效的准备内存,有赖于我们需要对它所做的事情。比如,我们可以用WRITE_ONLY读取映射缓存。这只是对于驱动器的一个提示符,而不是限制。尽管如此,驱动器会允许我们从一个WRITE_ONLY缓存(写仍然很快)非常慢的读取。
glVertexPointer()
该函数依据当前的缓存对象设置偏移量(初始为一个指针)。VBO内存管理的大部分工作都是在此完成的。
VBO使用建议
以下是一些保持VBO高效工作的建议。
用glMapBufferARB()的glBufferDataARB()
有时我们需要更新缓存对象里的数据,而我们又不想再次访问原缓存里的旧数据。这种情况典型的发生在我们前面所提到的调用glBufferData中。
但是,我们可以使用glMapBuffer来更新整个数据集。不幸的是,这步操作比glBufferData开销更大。我们必须谨记:驱动器并不能猜到我们将对glMapBuffer所返回的内存指针会做什么:我们仅是改变其中少数字节?或者我们要更新所有数据?
由glMapBuffer返回的指针指向的是数据的实际地址。有可能GPU正在使用这些数据,因此申请更新数据将会强制要求驱动器等待GPU完成它的绘制任务。
为了解决该冲突,你只须用一个空指针来调用glBufferDataARB()。然后调用glMapBuffer,就会通知驱动器前面用的数据已经无效了。接下来的结果就是,如果GPU仍然工作于这些数据,它们将不会冲突,因此我们将这些数据置为无效了。GlMapBuffer函数返回一个新的指针,我们可以用该指针而同时GPU正在工作于前面的数据。
注意:Microsoft®的Direct3D®解决该问题的办法是:通过D3DLOCK_DISCARD提供一个参数标志给Lock()方法。
避免每个VBO调用glVertexPointer()一次以上
glVertexPointer()函数在VBO里做了很多装载工作,因此要避免冗余。
最有效的办法是绑定VBO缓存,装载各种数组指针(glNormalPointer etc)后再调用glVertexPointer()。每个VBO应该只调用一次glVertexPointer()。
你可能会认为VBO管理的本质工作是在glBindBufferARB中完成的,但实际上正好相反。VBO系统等待着即将来临的输入函数(像glVertexPointer)。
绑定操作相对于各种指针的装载来说开销是很小的。
该建议适用于任何像glVertexPointer()一样工作的函数。
在glDrawArrays()参数里面用“First”值代替改变glVertexPointer函数
在函数
glDrawArrays(Glenum mode, Glint first, Glsizei count);
通过代替改变glVertexPointer到一个特定的偏移位置并将其first置为NULL的方法,改变glDrawArrays()里的“first”参数值将更为有效。正如我们前面所讲到的,这将会阻止通过VBO管理者执行另一装载步骤的过程。
用glDrawRangeElements()代替glDrawElements()来绘制
用范围图元来画更有效率是基于以下两个原因:
□ 如果所描述的范围匹配于16位的整数,驱动器将会优化通过GPU指示数的格式。它可以将一个32位的整数转为16位的整数。这样,就会得到2X的提升。
□ Range是VBO管理者的精确信息,它可以用来优化它的内部内存配置。