Vireo  0.0
Vireo 3D Rendering Hardware Interface
Memory buffers

The Buffer class represent linear arrays of data which are used for various purposes by binding them to a graphics or compute pipeline via descriptor sets or certain commands or by directly specifying them as parameters to certain commands.

Thera are four types of buffers :

Depending on their type theses buffers are located in :

There is three types of TRANSFER buffers to take into account the difference of memory alignment and buffers layouts between various graphics API.

Creating a buffer

Buffers are created using vireo::Vireo::createBuffer by specifying the type, the size in bytes of each element (data block) and the number of elements :

frame.materialUniform = vireo->createBuffer(vireo::BufferType::UNIFORM, sizeof(Material), scene.getMaterials().size());

Each instance of the elements in the buffer can be memory aligned. The alignment depends on the type of buffer and the graphic API (Vulkan and DirectX can uses different values). When creating the buffer the alignment is automatically calculated:

Writing data into buffers

For host-accessible buffers (vireo::BufferType::UNIFORM, vireo::BufferType::BUFFER_UPLOAD, vireo::BufferType::IMAGE_UPLOAD and vireo::BufferType::IMAGE_DOWNLOAD) data can be written directly from the CPU by mapping them with vireo::Buffer::map. To "map memory" means to obtain a CPU pointer to a device (GPU) memory, to be able to read from it or write to it in CPU code.

Once the buffer is mapped, data can be written with vireo::Buffer::write. This method take care of the alignment of the element if you write the whole buffer by using vireo::Buffer::WHOLE_SIZE size. If you use a different size or an offset you are responsible for the alignment of the source data.

Once you have written the data you can unmap the buffer memory with vireo::Buffer::unmap.

frame.materialUniform->map();
frame.materialUniform->write(scene.getMaterials().data());
frame.materialUniform->unmap();
Note
You can leave the memory mapped until the end the execution of the application (for uniform buffers whose content change each frame for example). The Buffer destructor will take care of unmapping the memory.

Uploading data into VRAM

There is two methods to upload data into the GPU memory :

  • Using upload() : the easy way
  • Using copy(): the flexible way

using upload()

For device-memory buffers (vireo::BufferType::VERTEX and vireo::BufferType::INDEX) you need to instruct the GPU to copy data from a temporary, host-accessible (staging) buffer to the destination buffer.

By using the vireo::CommandList::upload methods you can easily upload data into a buffer. The upload method will take care of the staging buffer and temporary copy of the data for you.

The GPU commands recorded with a CommandList are executed asynchronously by the GPU. Once you have uploaded all of the data using oneCommandList you need to call vireo::CommandList::cleanup on this CommandList to clear the temporary (staging) buffers used for the host-to-device transfers. The asynchronous nature of the operation means that we have to wait for the end of the operation to free the host-visible allocated memory, which mean you have to call cleanup only after the submit queue have finished the commands execution by blocking the CPU-side with vireo::SubmitQueue::waitIdle.

Note
Since the call to cleanup is done automatically in the command list destructor, you only have to call it explicitly on non-temporary command lists.
// Example of a vertex buffer upload using upload()
// In a local scope for temporary objects destruction
{
// Allocate the buffer
vertexBuffer = vireo->createBuffer(vireo::BufferType::VERTEX, sizeof(Vertex), triangleVertices.size());
// Create a command list
const auto uploadCommandAllocator = vireo->createCommandAllocator(vireo::CommandType::TRANSFER);
const auto uploadCommandList = uploadCommandAllocator->createCommandList();
// Upload data into a temporary buffer and record the copy command
uploadCommandList->begin();
uploadCommandList->upload(vertexBuffer, &triangleVertices[0]);
uploadCommandList->end();
// Execute the copy command
const auto transferQueue = vireo->createSubmitQueue(vireo::CommandType::TRANSFER);
transferQueue->submit({uploadCommandList});
// Wait for the command to executed by the GPU
transferQueue->waitIdle();
} // cleanup() is automatically called

We have used a vireo::CommandType::TRANSFER command list to upload the vertex data in this example but you can use a vireo::CommandType::GRAPHIC command list for that. The vireo::CommandType::TRANSFER command lists and submit queues can take advantage of DMA transfers. Some GPU have only one queue for both transfer and graphics operation, so the use of a specific transfer queue is not guaranteed. Also, the transfer only queues can't do some operations like pipeline barriers on images.

using copy()

If you need to take care of the staging buffer yourself or if you need to copy the data with different source or destination offset you have to use vireo::CommandList::copy.

To use this method you need to :

  • Create a staging buffer of type vireo::BufferType::BUFFER_UPLOAD
  • Write data into this buffer
  • Create the destination buffer of type vireo::BufferType::VERTEX or vireo::BufferType::INDEX
  • Record the copy command to instruct the GPU to copy data from the staging buffer to the destination buffer
  • Keep the staging buffer alive until the copy command have been executed (you may need to wait on the CPU side for the completion of the commands on the submit queue).
// Example of a vertex buffer upload using copy()
// In a local scope for temporary objects destruction
{
// Allocate and fill the staging buffer
const auto stagingBufferVertex = vireo->createBuffer(vireo::BufferType::BUFFER_UPLOAD, sizeof(Vertex), triangleVertices.size());
stagingBufferVertex->map();
stagingBufferVertex->write(triangleVertices.data());
stagingBufferVertex->unmap();
// Allocate the destination buffer
vertexBuffer = vireo->createBuffer(vireo::BufferType::VERTEX, sizeof(Vertex), triangleVertices.size());
// Create a command list
const auto uploadCommandAllocator = vireo->createCommandAllocator(vireo::CommandType::TRANSFER);
const auto uploadCommandList = uploadCommandAllocator->createCommandList();
// Record the copy command
uploadCommandList->begin();
uploadCommandList->copy(stagingBufferVertex, vertexBuffer);
uploadCommandList->end();
// Execute the copy command
const auto transferQueue = vireo->createSubmitQueue(vireo::CommandType::TRANSFER);
transferQueue->submit({uploadCommandList});
// Wait for the command to executed by the GPU
transferQueue->waitIdle();
} // the staging buffer is automatically destroyed