Accessing the Content of Buffers

The issue with all of the functions covered in this section so far (glBufferData(), glBufferSubData(), glCopyBufferSubData(), and glGetBufferSubData()) is that they all cause OpenGL to make a copy of your data. glBufferData() and glBufferSubData() both copy data from your application’s memory into memory owned by OpenGL. Obviously, glCopyBufferSubData() causes a copy of previously buffered data to be made. glGetBufferSubData() copies data from memory owned by OpenGL into memory provided by your application. Depending on the hardware configuration, it’s very possible that the memory owned by OpenGL would be accessible to your application if only you had a pointer to it. Well, you can get that pointer using glMapBuffer().

When you call glMapBuffer(), the function returns a pointer to memory that represents the data store of the buffer object attached to target. Note that this memory represents only this buffer—it is not necessarily the memory that the graphics processor will use. The access parameter specifies how the application intends to use the memory once it is mapped. It must be one of the tokens shown in Table 3.4.

Table 3.4. Access Modes for glMapBuffer()

Image

If glMapBuffer() fails to map the buffer object’s data store, it returns NULL. The access parameter forms a contract between you and OpenGL that specifies how you will access the memory. If you violate that contract, bad things will happen, which may include ignoring writes to the buffer, corrupting your data or even crashing your program.3

3. The unfortunate thing is that so many applications do violate this contract that most OpenGL implementations will assume you don’t know what you’re doing and will treat all calls to glMapBuffer() as if you specified GL_READ_WRITE as the access parameter, just so these other applications will work.


Note

When you map a buffer whose data store is in memory that will not be accessible to your application, OpenGL may need to move the data around so that when you use the pointer it gives you, you get what you expect. Likewise, when you’re done with the data and have modified it, OpenGL may need to move it back to a place where the graphics processor can see it. This can be expensive in terms of performance, so great care should be taken when doing this.


When the buffer is mapped with the GL_READ_ONLY or GL_READ_WRITE access mode, the data that was in the buffer object becomes visible to your application. You can read it back, write it to a file, and even modify it in place (so long as you used GL_READ_WRITE as the access mode). If access is GL_READ_WRITE or GL_WRITE_ONLY, you can write data into memory using the pointer OpenGL gave you. Once you are done using the data or writing data into the buffer object, you must unmap it using glUnmapBuffer(), whose prototype is as follows:

When you unmap the buffer, any data you wrote into the memory given to you by OpenGL becomes visible in the buffer object. This means that you can place data into buffer objects by allocating space for them using glBufferData() and passing NULL as the data parameter, mapping them, writing data into them directly, and then unmapping them again. Example 3.2 contains an example of loading the contents of a file into a buffer object.

Example 3.2. Initializing a Buffer Object with glMapBuffer()

GLuint buffer;
FILE * f;
size_t filesize;

// Open a file and find its size
f = fopen("data.dat", "rb");
fseek(f, 0, SEEK_END);
filesize = ftell(f);
fseek(f, 0, SEEK_SET);

// Create a buffer by generating a name and binding it to a buffer
// binding point - GL_COPY_WRITE_BUFFER here (because the binding means
// nothing in this example).
glGenBuffers(1, &buffer);
glBindBuffer(GL_COPY_WRITE_BUFFER, buffer);

// Allocate the data store for the buffer by passing NULL for the
// data parameter.
glBufferData(GL_COPY_WRITE_BUFFER, (GLsizei)filesize, NULL,
             GL_STATIC_DRAW);
// Map the buffer...
void * data = glMapBuffer(GL_COPY_WRITE_BUFFER, GL_WRITE_ONLY);

// Read the file into the buffer.
fread(data, 1, filesize, f);

// Okay, done, unmap the buffer and close the file.
glUnmapBuffer(GL_COPY_WRITE_BUFFER);
fclose(f);

In Example 3.2, the entire contents of a file are read into a buffer object in a single operation. The buffer object is created and allocated to the same size as the file. Once the buffer is mapped, the file can be read directly into the buffer object’s data store. No copies are made by the application, and, if the data store is visible to both the application and the graphics processor, no copies will be made by OpenGL.

There may be significant performance advantages to initializing buffer objects in this manner. The logic is this; when you call glBufferData() or glBufferSubData(), once those functions return, you are free to do whatever you want with the memory you gave them—free it, use it for something else—it doesn’t matter. This means that those functions must be done with that memory by the time they return, and so they need to make a copy of your data. However, when you call glMapBuffer(), the pointer you get points at memory owned by OpenGL. When you call glUnmapBuffer(), OpenGL still owns that memory—it’s the application that has to be done with it. This means that if the data needs to be moved or copied, OpenGL can start that process when you call glUnmapBuffer() and return immediately, content in the knowledge that it can finish the operation at its leisure without your application interfering in any way. Thus the copy that OpenGL needs to perform can overlap whatever your application does next (making more buffers, reading more files, and so on). If it doesn’t need to make a copy, then great! The unmap operation essentially becomes free in that case.

Asynchronous and Explicit Mapping

To address many of the issues involved with mapping buffers using glMapBuffer() (such as applications incorrectly specifying the access parameter or always using GL_READ_WRITE), glMapBufferRange() uses flags to specify access more precisely. The prototype for glMapBufferRange() is as follows:

For glMapBufferRange(), access is a bitfield that must contain one or both of the GL_MAP_READ_BIT and the GL_MAP_WRITE_BIT indicating whether the application plans to read from the mapped data store, write to it, or do both. In addition, access may contain one or more of the flags shown in Table 3.5.

Table 3.5. Flags for Use with glMapBufferRange()

Image

As you can see from the flags listed in Table 3.5, the command provides a significant level of control over how OpenGL uses the data in the buffer and how it synchronizes operations that may access that data.

When you specify that you want to invalidate the data in the buffer object by specifying either the GL_MAP_INVALIDATE_RANGE_BIT or GL_MAP_INVALIDATE_BUFFER_BIT, this indicates to OpenGL that it is free to dispose of any previously stored data in the buffer object. Either of the flags can be set only if you also specify that you’re going to write to the buffer by also setting the GL_MAP_WRITE_BIT flag. If you specify GL_MAP_INVALIDATE_RANGE_BIT, it indicates that you will update the entire range (or at least all the parts of it that you care about). If you set the GL_MAP_INVALIDATE_BUFFER_BIT, it means that you don’t care what ends up in the parts of the buffer that you didn’t map. Either way, setting the flags indicates that you’re planning to update the rest of the buffer with subsequent maps.4 When OpenGL is allowed to throw away the rest of the buffer’s data, it doesn’t have to make any effort to merge your modified data back into the rest of the original buffer. It’s probably a good idea to use GL_MAP_INVALIDATE_BUFFER_BIT for the first section of the buffer that you map, and then GL_MAP_INVALIDATE_RANGE_BIT for the rest of the buffer.

4. Don’t specify the GL_MAP_INVALIDATE_BUFFER_BIT for every section, otherwise only the last section you mapped will have valid data in it!

The GL_MAP_UNSYNCHRONIZED_BIT flag is used to disengage OpenGL’s automatic synchronization between data transfer and use. Without this bit, OpenGL will finish up any in-flight commands that might be using the buffer object. This can stall the OpenGL pipeline, causing a bubble and a loss of performance. If you can guarantee that all pending commands will be complete before you actually modify the contents of the buffer (but not necessarily before you call glMapBufferRange()) through a method such as calling glFinish() or using a sync object (which are described in “Atomic Operations and Synchronization” on Page 578 in Chapter 11), then OpenGL doesn’t need to do this synchronization for you.

Finally, the GL_MAP_FLUSH_EXPLICIT_BIT flag indicates that the application will take on the responsibility of letting OpenGL know which parts of the buffer it has modified before calling glUnmapBuffer(). It does this through a call to glFlushMappedBufferRange(), whose prototype is as follows:

It is possible to call glFlushMappedBufferRange() multiple times on separate or even overlapping ranges of a mapped buffer object. The range of the buffer object specified by offset and length must lie within the range of buffer object that has been mapped, and that range must have been mapped by a call to glMapBufferRange() with access including the GL_MAP_FLUSH_EXPLICIT_BIT flag set. When this call is made, OpenGL assumes that you’re done modifying the specified range of the mapped buffer object, and can begin any operations it needs to perform in order to make that data usable such as copying it to graphics processor visible memory, or flushing, or invalidating data caches. It can do these things even though some or all of the buffer is still mapped. This is a useful way to parallelize OpenGL with other operations that your application might perform. For example, if you need to load a very large piece of data from a file into a buffer, map a range of the buffer large enough to hold the whole file, then read chunks of the file, and after each chunk call glFlushMappedBufferRange(). OpenGL will then operate in parallel to your application, reading more data from the file for the next chunk.

By combining these flags in various ways, it is possible to optimize data transfer between the application and OpenGL or to use advanced techniques such as multithreading or asynchronous file operations.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
44.200.194.255