4 min read
Being as they have an integrated resource system, the 1.x versions of Ogre have had a somewhat opaque buffer system. Anyone who has worked closely with the system, doing dynamic uploads for example, will have discovered that uploads are done by creating the object and then later uploading the actual data into it.
For its’ era, this was a perfectly adequate design. When it was conceived, this method largely reflected the way that graphics APIs worked – or at the very least permitted you to work. In OpenGL, for example, you create the buffer object then upload data into it using the glBufferData command.
Everything changed with Direct3D 10
The old ways – A look at Direct3D 9
Direct3D9 divided buffer types using two properties – the “usage” which described how the application intended to use the buffer, and the “pool” which described how the memory was to be allocated. For our purposes, only one usage flag matters:
“D3DUSAGE_DYNAMIC – Set to indicate that the vertex buffer requires dynamic memory use. This is useful for drivers because it enables them to decide where to place the buffer. In general, static vertex buffers are placed in video memory and dynamic vertex buffers are placed in AGP memory. Note that there is no separate static use. If you do not specify D3DUSAGE_DYNAMIC, the vertex buffer is made static. D3DUSAGE_DYNAMIC is strictly enforced through the D3DLOCK_DISCARD and D3DLOCK_NOOVERWRITE locking flags. As a result, D3DLOCK_DISCARD and D3DLOCK_NOOVERWRITE are valid only on vertex buffers created with D3DUSAGE_DYNAMIC” - MSDN
D3DUSAGE_DYNAMIC provides the driver with a hint that the application will be modifying the buffer frequently, and omitting it informs it that the application will be updating it infrequently, if at all.
This is a useful abstraction, but we can do better: if the buffer is entirely off limits to the GPU, then the driver can make further optimizations with the aim of increasing performance.
The changes of Direct3D11
Direct3D11 takes the existing “dynamic/static” disjunction, renaming “static” to “default”, and adds two new modes, bringing us to a total of four:
- D3D11_USAGE_IMMUTABLE – Immutable buffers must be initialised with data when they are uploaded, and from then on are read-only for both the CPU and GPU
- D3D11_USAGE_DEFAULT – Essentially Direct3D 9′s default mode. This is optimized for infrequent updates
- D3D11_USAGE_DYNAMIC – Essentially Direct3D 9′s D3DUSAGE_DYNAMIC. Optimized for frequent updates and for use as a GPU render target
- D3D11_USAGE_STAGING – A specialized buffer usage that cannot be directly read or written by the GPU. D3D11_USAGE_STAGING is used to create DMA capable buffers for quickly copying data from the GPU.
The most important of these is D3D11_USAGE_IMMUTABLE. While Ogre does sometimes copy back data from the GPU, staging buffers are a much smaller performance win than immutable buffers, and have a higher logistical complexity for the engine.
The changes in Ogre 2.0
The most obvious change is the addition of the enumerants HBU_IMMUTABLE and TU_IMMUTABLE for hardware buffers and textures respectively. These signal to Ogre, and therefore to the driver (where possible) that the contents of the buffer will never change.
Deeper changes have been required to the Texture and HardwareBuffer objects.
The Texture API has changed significantly in Ogre 2.0 as the resource system has been changed. In older versions, the Texture object tracked the “source”, “desired” and “internal” versions of various properties – from width and height to pixel format. As Ogre 2.0 is separating the “resident” representation of the resources from their sources, all but the internal versions of the properties have been removed.
Instead, the loader sets the properties to those it desires for the texture object before calling the new _determineInternalFormat function. This function will then use its’ knowledge of the render system’s capabilities in order to determine the actual format to be used.
Once the internal format has been determined, the loader may use the _uploadBuffers function in order to actually fill the texture with data. For all the existing texture types, this is optional; the loader may later get the buffer by using the getBuffer method and then use the blit method of the returned buffer object to do the upload, as was the standard method in previous versions of Ogre. However, when using an immutable texture, the _uploadBuffers method must be used, in order to accommodate the contract associated with immutable textures.
Other buffer types are managed separately through the HardwareBufferManager. For each of these types, an additional pointer is being added to their constructor function in order to enable immediate upload.