opengl superbible 5 edition chapter 8——Buffer Objects: Storage Is Now in Your Hands

Up until this point you have had a chance to learn about the basics of OpenGL, how to specify geometry, what shaders are, how to use textures, and so on. Now it’s time to blow the lid off of your applications and introduce faster and more flexible ways of rendering and moving data around. You also learn about off-screen rendering and how to create and control your own framebuffer.

buffer objects are a powerful concept that allows your applications to quickly and easily move data from one part of the rendering pipeline to another, from one object binding to another, from one object binding to another. your data has finally been freed from strongly typed objects. not only can u move data around as u see fit, but u can so so without the involvement of the cpu.

framebuffer objects give u true control over your pixels. u no longer are relegated to 局限于 the limitations of the os window your context is tied to. in fact, you can now render off-screen to nearly as many buffers as you would like. not only that, but u can use whatever size and format surfaces that best fit your needs. now your fragment shaders have ultimate control over which pixels go where.

buffers
instead of creating a hundred different objects of varying types and making developers keep track of which is which, opengl 3.2 generalizes the use of most objects that hold data. now u can allocate as many buffers as you need and then decide how u want to use them later. buffers have many different uses. they can hold vertex data, pixel data, texture data, inputs for shader execution, or the output of different shader stages.

buffers are stored in gpu memory, which provides very fast and efficient access. before opengl had buffer objects, applications had limited options for storing data on the gpu. additionally, updates to data on the gpu often required reloading the whole object. moving data back and forth between system memory and gpu meomory can be a slow process.

first let us look at the basics of dealing with buffer objects. later, we cover more advanced ways of accessing our data in buffer objects and how to use them for different purposes.

creaing your very own buffers
creating a new buffer is simple. just call glGenBuffers to create names for as many new buffers as u need. the actual buffer obejct will created at first use.

Gluint pixBuffObjs[1];
glGenBuffers(1, pixBuffObjs);

once u have the name of your new buffer, u can bind that name to use the buffer.
there are many different binding points in opengl. each binding point allows u to use a buffer for a different purpose. u can think of each attachment or binding point as a slot where only one object can attached at a time. these binding points are listed in table 8.1. we explore how to use each of these bindings in more detail later on.

to bind a buffer for use, u can all glBindBuffer with a target from table 8.1 and the name of the buffer. next we bind our new buffer to the pixel pack buffer attachment point so that we can use glReadPixels to copy pixel data into the buffer.

glBindBuffer(GL_PIXEL_PACK_BUFFER, pixBuffObjs[0]);

to unbind a buffer from an attachment, call glBindBuffer again with the same target and use “0” for the buffer name. u can also just bind another valid buffer to the same target. when u are finished with a buffer, it needs to be cleaned up, just as all other opengl object should be. delete it by calling glDeleteBuffers. as a general practicle, make sure the buffer is not bound to any of the binding points before deleteing.

glDeleteBuffers(1, pixBuffObjs);

filling buffers
creating and deleteing buffers is one thing. but how do u get valid data into a buffer to use it? there are many ways to fill a buffer with data; some of the more complex ones are covered in following chapters. to simply upload your data straight into a buffer of any type u can use the glBufferData function.

glBindBuffer(GL_PIXEL_PACK_BUFFER, pixBuffObjs[0]);
glBufferData(GL_PIXEL_PACK_BUFFER, pixelDataSize, pixelData, GL_DYNAMIC_COPY);
glBindBuffer(GL_PIXEL_PACK_BUFFER, 0);

the buffer u want to use must be bound before calling glBufferData. use the same target for glBufferData as you can used to bind the buffer for the first parameter. the second parameter is the size of the data you are going to upload in bytes, and the third parameter is the data to be uploaded. note that this pointer can also be NULL if u want to allocate a buffer of a specific size but do not need to fill it right away. the fourth parameter of glBufferData is where u tell opengl how u intend to use the buffer.

picking the right value for usage is a little tirckier. the possible usage options are listed in table 8.2. the value of usage is really just a performance hint to help the opengl driver allocate memory in the correct location. for instance, some memory may be easily accessible by the cpu and would be a good choice if your application needs to read from it frequently. other memory might be inaccessible for direct access by the cpu but can be accessed quickly by the gpu. by telling the opengl driver what your plan is ahead of time, your buffer can be allocated in a spot where it can serve you best.

在这里插入图片描述
using GL_DYNAMIC_DRAW is a safe value for general buffer usage or situations where u are not sure what the buffer willl be used for. u can always call glBufferData again, refilling the buffer and possibly changing the usage hint. but if u do all glBufferData again, any data originally in the buffer will be deleted. u can use glBufferSubData to update a part of a preexisting buffer without invalidating the contents of the rest of the buffer.

void glBufferSubData(GLenum target, intptr offset, sizeiptr size, const void *data);

most of the parameters for glBufferSubData are the same as those for glBufferData. the new offset parameter allows u to start updating the buffer at a location other than the begining. u also can not change the usage of the buffer because memory has already been allocated.

pixel buffer object
many of newest and most important advances in graphics involve new ways of doing some of the same old operations but in much faster and more efficient ways. pixel buffer objects are similar to texture buffer objects in that they hold pixel/texel data. just like all buffer objects they live in gpu memory. u can access and fill pixel buffer objects, or PBOs, in the same ways u would for any other buffer object type. in fact, the only time a buffer object is really a PBO is when it is attached to a PBO buffer attachment.

the first pbo attachment point is GL_PIXEL_PACK_BUFFER. when a PBO is attached to this target, any opengl operations that read pixels get their data from the PBO. these operations include glReadPixels, glGetTexImage, and glGetCompressedTexImage. normally these operatoins pull data out of a framebuffer or texture and read it back into client memory. when a pbo is attached to the pack buffer, pixel data ends up in the PBO in gpu memory instead of downloaded to the client.

the second pbo attachment point is GL_PIXEL_UNPACK_BUFFER. when a pbo is attached to this target, any opengl operations that draw pixels put their data into an attached pbo. some of these operations glTexImageD, glTexSubImageD, glCompressedTexImageD,and glCompressedTexSubImageD. these operations put data into framebuffers and textures from local cpu memory. but having a pbo bound as the unpack buffer directs the read operations to be the pbo in gpu memory instead of memory on the cpu.

why bother with these pixel buffer objects anyway? after all, u can get pixels to, from, and around the gpu without them. for starters, any calls that read from or write to PBOs or any other buffer object are pipelined. that means the gpu does not have to finish doing everything eles, initiate the data copy, wait for the copy to complete, and then continue. because buffer objects do not have the same ordering issues, they can provide a huge advantage when dealing with apps that have to frequently get to, modify, or update pixel data. some examples are
1/ stream texture updates——in some cases, your application might need to update a texture on every frame. maybe u need to change it based on user input, or maybe you want to stream video. pbos allow your application to make changes to texture data without necessarily having to download and then re-upload the whole surface.

2/ rendering vertex data——because buffer objects are generic data storage, and application can easily use the same buffer for very different purposes. for instance, an application can write vertex data out to a color buffer and then copy that data into a pbo. once complete, the buffer can be attached as a vertex buffer and used to draw new geometry. this just how flexible opengl is; it allows u to “color” new vertex data!

3/ asynchronous calls to glReadPixels——often applications want to grab pixels off the screen, perform some manipulation, and then either save them or use them for drawing again. unfortunately, reading pixel data into cpu memory requires the gpu to wrap up 圆满结束 everything else it’s doing and then perform the copy before any other work can begin or before the actual call can return. what if 倘若 furture draw calls are dependent on the result of the read or of multiple reads? using glReadPixels can throw a real wrench into the works when trying to keep the gpu busy drawing all of your 3D graphics! PBOs come to the rescue. because the read operation is pipelined, the call to glReadPixels can return immediately. u can use even call multiple times with different buffer targets to read different areas.

pixel buffer obejcts are a great container for temporarily storing pixel data locally on the GPU, but remember they need to have storage allocated before they can be used. just like all other buffer objects, calling glBufferData allocates storage for a buffer and fills it with your data. but u do not necessarily have to provide data; passing in NULL for the data pointer simply allocates the memory without filling it. if u do not allocate storage for a buffer before trying to fill it, opengl throws an error.

glBufferData(GL_PIXEL_PACK_BUFFER, pixelDataSize, pixelData, GL_DYNAMIC_COPY);

pixel buffers are often used to hold 2D images coming from a render target, texture, or other source. but buffer objects are one-dimensional by nature; they do not have an intrinsic width or height. when allocating storage space for 2D images, u can just multiply the width by the height by the size of a pixel. there is no additional padding necessary for storing the pixel data, but your buffer can be larger than necessary for a given set of data. in fact, if you plan to use the same PBO for multiple data sizes, u are much better off sizing the PBO for the largest data set right away than resizing it frequently.

all calls to glBufferData are pipelined with the rest of your draw calls. that means the opengl implementation will not have to wait for all activities to stop before sending the new data to the gpu. there are some times when this can be particularly important. think about all those times u have to wait a few minutes for your favorite games as a new level loads. part of that is uploading a whole bunch of new texture data. or the small hiccups 打嗝 as u enter a new room and texture data is updated. PBOs can help solve some of these problems by providing the texture data when necessary and in a way that does not stall 熄火 all other work.

reading pixel data out of a buffer
once your drawing has reached the screen, u may need to get those pixels back again before they are gone forever. one reason might be to check on what was actually rendered to help decide what needs to be rendered in future scenes. another is to use pixels from previous frames in effects applied to furture frames. whatever the reason, the glReadPixels function is there to help. this function takes pixels from the specified location of the currently enabled read buffer and copies them into local cpu memory.

void* data = (void*)malloc(pixelDataSize);
glReadBuffer(GL_BACK_LEFT);
glReadPixels(0, 0, GetWidth(), GetHeight(), GL_RGB, GL_UNSIGNED_BYTE, pixelData);

when u execute a read of pixel data into client memory, the entire pipeline often has to be emptied to ensure all drawing that would affect the pixels u are about to read has completed. this can have a major impact on your application’s performance. but the good news is we can use buffer objects to overcome this performance issue. u can bind a buffer object to the GL_PIXEL_PACK_BUFFER before u call glReadPixels and set the data pointer in the glReadPixels call to null. this redirects the pixels into a buffer located on the GPU and aovids the performance issues that copying to client memory can cause.

glReadBuffer(GL_BACK_LEFT);
glBindBuffer(GL_PIXEL_PACK_BUFFER, pixBuffObjs[0]);
glReadPixels(0, 0, GetWidth(), GetHeight(), GL_RGB, GL_UNSIGNED_BYTE, NULL);

We use both of these approaches in our first sample application, pix_bufs.

using pbos
incorporating PBOs into your application can be simple but can have huge positive performance impacts. the first sample program for this chapter does a few things, but most importantly it demonstrates how effective PBOs really are.

motion blur is an effect that helps to signal which objects in a scene are moving and how fast they are going. u have probalbly seen these blurring effects in moives, television, or video. when an object moves past the camera at a rate too fast for the shutter speed of a single frame, the image is smeared 弄脏 across pixels of that frame and neighboring frames in the direction of motion. The same effect occurs when the camera is moving quickly relative to an object or the entire scene. Think about taking a picture sideways out a car window as a passenger while driving on the highway.

there are many complex ways to create such an effect in opengl. an application can render multiple times to a buffer, slightly offsetting the last moving objects and blending the results together.
another option is to sample texel data for an object image multiple times in the direction of movement and then blend the sample results together.
there are even more involved methods that use depth buffer data to apply a more dramatic blur to objects closer to the camera.

for the pix_buffs sample application we use another simple approach that stores the results of previous frames and blend them together with the current frame. to make a visible motion blur, the program stores the last five frames. the program can use both the old-fashioned way of copying data to the cpu and back as well as the faster PBO path.

first, in listing 8.1 we initialize the textures and the PBOs necessary.
LISTING 8.1 Set Up PBO and Textures for pix_buffs Sample Program

	// Create blur textures
	glGenTextures(6, blurTextures);

    // XXX I don't think this is necessary. Should set texture data to NULL
	// Allocate a pixel buffer to initialize textures and PBOs
	pixelDataSize = screenWidth*screenHeight*3*sizeof(unsigned int); // XXX This should be unsigned byte
	void* data = (void*)malloc(pixelDataSize);
	memset(data, 0x00, pixelDataSize);

	// Setup 6 texture units for blur effect
	// Initialize texture data
	for (int i=0; i<6;i++)
	{
		glActiveTexture(GL_TEXTURE1+i);
		glBindTexture(GL_TEXTURE_2D, blurTextures[i]);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
		glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, screenWidth, screenHeight, 0, GL_RGB, GL_UNSIGNED_BYTE, data);
	}

	// Alloc space for copying pixels so we dont call malloc on every draw
	glGenBuffers(1, pixBuffObjs);
	glBindBuffer(GL_PIXEL_PACK_BUFFER, pixBuffObjs[0]);
	glBufferData(GL_PIXEL_PACK_BUFFER, pixelDataSize, pixelData, GL_DYNAMIC_COPY);
	glBindBuffer(GL_PIXEL_PACK_BUFFER, 0);

when all resources are set up, the scene is rendered into the back buffer as if nothing special was going on.
instead of just calling swap, the result is copied into a texture to be used for the blur effect. for the traditional path, this happens by calling glReadPixels to get the pixel data and then glTexImage2D to move the pixel data into a texture object.
哦我明白了,我们首先将数据写入到PBO,然后再将PBO的数据移动一张贴图上去。the texture target for the data rotates between each of the six blur textures. if texture 3 was used last time, texture 4 will be used next. that means texture 4 will contain data from this frame, texture 3 from the last, texture 2 from two frames ago, and so on. the target for the current frame wraps around again after the last texture has been used. the pixel data for the last six frames is always ordered and available in this “texture ring buffer”.

the PBO path is slightly different. instead of copying the data back to the cpu, the PBO is bound to the GL_PIXEL_PACK_BUFFER, and when we call glReadPixels, the pixels are redirected to the PBO instead of back to the cpu. then that same buffer is unbound from the GL_PIXEL_PACK_BUFFER attachment and bound to the GL_PIXEL_UNPACK_BUFFER. when glTexImage2D is called next, the pixel data in the buffer is loaded into the texture, all without ever leaving the gpu and remaining pipelined with other opengl commands. u can see this process in listing 8.2. finally, the ring buffer is updated to point to the next blur texture. u can press the P button while running the program to switch between the two paths.

to do the actual blur, the fragment shader samples from all six textures and averages the results. the fragment shader only needs one set of texture coordinates given that all textures are the same size and need to align with the other textures.
listing 8.3 shows the shader code for performing all six texture samples. this shader is used to shade the screen aligned quad setup by building an orthographic modelview projection matrix based on the window width and height. the orthographic matrix creates a transform that maps coordinates directly to screen space. The orthographic matrix creates a transform that maps coordinates directly to screen space. For every unit you increase the x coordinate of geometry, you move one more pixel to the right on the screen. Going up one unit in the y direction equates to one pixel higher on the screen. The result is 2D rendering where the coordinates of the geometry are also the pixel locations on the screen.

#version 150 
// blur.fs
// outputs weighted, blended result of four textures
// 

in vec2 vTexCoord;
uniform sampler2D textureUnit0;
uniform sampler2D textureUnit1;
uniform sampler2D textureUnit2;
uniform sampler2D textureUnit3;
uniform sampler2D textureUnit4;
uniform sampler2D textureUnit5;
void main(void) 
{ 
	// 0 is the newest image and 5 is the oldest
	vec4 blur0 = texture(textureUnit0, vTexCoord); 
	vec4 blur1 = texture(textureUnit1, vTexCoord); 
	vec4 blur2 = texture(textureUnit2, vTexCoord); 
	vec4 blur3 = texture(textureUnit3, vTexCoord); 
	vec4 blur4 = texture(textureUnit4, vTexCoord); 
	vec4 blur5 = texture(textureUnit5, vTexCoord); 
	
	vec4 summedBlur = blur0	+ blur1 + blur2 +blur3 + blur4 + blur5;
	gl_FragColor = summedBlur/6;
}

when u first start pix_buffs, the program will be using the client-side memory path to load the blur textures. as the object moves from side-to-side, notic how the blur occurs only on the axis of movement. u can see the effect in figure 8.1. pressing P switches the PBO path on and off. the + and - keys on the number pad speed up and slow down the movement of the object. as the speed changes, notice how the amount of motion blur also changes.

the speed of the program is printed in the title bar. notice the difference in performance between the client copy and PBO copy speeds——it’s huge! on slower systems the PBO path is almost six times faster than the client memory path. how would u like your programs to run six times faster? 你的程序怎么会快6倍呢?paying attention to how u move your data around can help do exactly that. 仔细观察数据是怎么移动的,也许能够帮助你解决疑虑。these huge performance gains are one reason why buffer objects are now an important part of opengl programs.

when u swith to the PBO path, the amount of blur is reduced. that happens because the sample program uses the last five frames to create the blended output no matter how fast the program is running. when using PBOs, the last five frames are visually much closer together (because the faster rendering permits a higher frame rate), creating less blur. take a look at figure 8.2. u can try to change the program to create more blur for the PBO path or change the program so the blur is the same regardless of the path chosen. another good exercise is to try different methods of applying motion blur or use weightings when combining the frame textures. 把每个图赋予不同的权重。

texture buffer object
u have seen how some buffer binding targets such as GL_PIXEL_PACK_BUFFER and GL_COPY_READ_BUFFER are used for upadting and fetching data from a buffer while it is on the gpu. other buffer bindings like GL_TEXTURE_BUFFER, GL_TRANSFORM_FEEDBACK_BUFFER, and GL_UNIFORM_BUFFER allow buffers to be used directly in the rendering pipeline. some of these binding points are explored in the following chapters, but now it is time to see how buffer objects can be used directly with textures.

a texture consists of two main components: texture sampling state and a data buffer containing the texture values. now u can attach a buffer object to the GL_TEXTURE_BUFFER buffer binding point of a texture as well. u may ask, “why bother with another texture binding?” that is a fair question! texture buffers, also known as texBOs or TBOs, allow u to do several things that traditional textures do not. first, texture buffers can be filled directly with data from other rendering results such as transform feedback, pixel read operations, or vertex data. this saves quite a bit of time since your application can turn right around and fetch pixel data from a previous render call directly in a shader.

another feature of texBOs is relaxed size restrictions. texture buffers are similar to a traditional one-dimensional texture but can be much larger. the maximum size presented by the opengl specification for texture buffers is 64 times larger than 1D textures, but on some implementations the size of texture buffers can be tens of thousands of times larger!

So what can you do with these texBOs? Well, for starters all sorts of shader math that was previously difficult if not impossible. TexBOs provide shaders with access to large amounts of data in many different formats and types, allowing shaders to operate on data in ways usually reserved for CPUs. Texture buffers can be used to provide access to vertex arrays in both fragment and vertex shaders. This can be useful when shaders need information about neighboring geometry to make runtime decisions and calculations. But to do this, you often need to also pass the size of your texBO into the shader as a uniform. Texture buffers are born as normal buffers and become true texture buffers when bound to a texture or to the GL_TEXTURE_BUFFER binding point.

glBindBuffer(GL_TEXTURE_BUFFER, texBO[0]);
glBufferData(GL_TEXTURE_BUFFER, sizeof(float)*count, fileData, GL_STATIC_DRAW);

but texBOs must be bound to a texture unit before they can be truly useful. to bind a texBo to a texture, call glTexBuffer but first make sure the texture u want to use is bound:

glActiveTexture(GL_TEXTURE1); //1号纹理单元
glBindTexture(GL_TEXTURE_BUFFER, texBOTexture); //texBoTexture 是纹理,外部加载进来的
glTexBuffer(GL_TEXTURE_BUFFER, GL_R32F, texBO[0]); //texBO[0]是纹理缓冲对象

Although texture buffer objects look and operate much like normal textures, there are some important differences. Texture buffers cannot be accessed by normal samplers in shaders—i.e., sampler1D and sampler2D. Instead, you must use a new sampler called samplerBuffer. Because the sampler type is different, the sample function used to get a value from the texture buffer is also different. You can use texelFetch to read from a texture buffer.

uniform samplerBuffer lumCurveSampler;
void main(void) 
{
	. . .
	int offset = int(vColor.r * (1024-1));
	lumFactor.r = texelFetch(lumCurveSampler, offset ).r;
}

When your shader looks up values in a texture buffer, it must use a nonnormalized integer-based index. Traditional sample functions like texture accept coordinates from 0.0 to 1.0. But the texBO lookup function, texelFetch, takes an integer index from 0 to the size of the buffer. If your texture lookup coordinates are already normalized, you can convert to an index by multiplying by the size of the texBO minus one and casting the result to an integer.

frambuffer objects, going beyond the window

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值