QRhiBuffer Class

Vertex, index, or uniform (constant) buffer resource. More...

Header: #include <QRhiBuffer>
CMake: find_package(Qt6 REQUIRED COMPONENTS Gui)
target_link_libraries(mytarget PRIVATE Qt6::Gui)
qmake: QT += gui
Since: Qt 6.6
Inherits: QRhiResource

Public Types

struct NativeBuffer
enum Type { Immutable, Static, Dynamic }
enum UsageFlag { VertexBuffer, IndexBuffer, UniformBuffer, StorageBuffer }
flags UsageFlags

Public Functions

virtual char *beginFullDynamicBufferUpdateForCurrentFrame()
virtual bool create() = 0
virtual void endFullDynamicBufferUpdateForCurrentFrame()
virtual QRhiBuffer::NativeBuffer nativeBuffer()
void setSize(quint32 sz)
void setType(QRhiBuffer::Type t)
void setUsage(QRhiBuffer::UsageFlags u)
quint32 size() const
QRhiBuffer::Type type() const
QRhiBuffer::UsageFlags usage() const

Reimplemented Public Functions

virtual QRhiResource::Type resourceType() const override

Detailed Description

Note: This is a RHI API with limited compatibility guarantees, see QRhi for details.

A QRhiBuffer encapsulates zero, one, or more native buffer objects (such as a VkBuffer or MTLBuffer). With some graphics APIs and backends certain types of buffers may not use a native buffer object at all (e.g. OpenGL if uniform buffer objects are not used), but this is transparent to the user of the QRhiBuffer API. Similarly, the fact that some types of buffers may use two or three native buffers underneath, in order to allow efficient per-frame content update without stalling the GPU pipeline, is mostly invisible to the applications and libraries.

A QRhiBuffer instance is always created by calling the QRhi's newBuffer() function. This creates no native graphics resources. To do that, call create() after setting the appropriate options, such as the type, usage flags, size, although in most cases these are already set based on the arguments passed to newBuffer().

Example usage

To create a uniform buffer for a shader where the GLSL uniform block contains a single mat4 member, and update the contents:

 QRhiBuffer *ubuf = rhi->newBuffer(QRhiBuffer::Dynamic, QRhiBuffer::UniformBuffer, 64);
 if (!ubuf->create()) { error(); }
 QRhiResourceUpdateBatch *batch = rhi->nextResourceUpdateBatch();
 QMatrix4x4 mvp;
 // ... set up the modelview-projection matrix
 batch->updateDynamicBuffer(ubuf, 0, 64, mvp.constData());
 // ...
 commandBuffer->resourceUpdate(batch); // or, alternatively, pass 'batch' to a beginPass() call

An example of creating a buffer with vertex data:

 const float vertices[] = { -1.0f, -1.0f, 1.0f, -1.0f, 0.0f, 1.0f };
 QRhiBuffer *vbuf = rhi->newBuffer(QRhiBuffer::Immutable, QRhiBuffer::VertexBuffer, sizeof(vertices));
 if (!vbuf->create()) { error(); }
 QRhiResourceUpdateBatch *batch = rhi->nextResourceUpdateBatch();
 batch->uploadStaticBuffer(vbuf, vertices);
 // ...
 commandBuffer->resourceUpdate(batch); // or, alternatively, pass 'batch' to a beginPass() call

An index buffer:

 static const quint16 indices[] = { 0, 1, 2 };
 QRhiBuffer *ibuf = rhi->newBuffer(QRhiBuffer::Immutable, QRhiBuffer::IndexBuffer, sizeof(indices));
 if (!ibuf->create()) { error(); }
 QRhiResourceUpdateBatch *batch = rhi->nextResourceUpdateBatch();
 batch->uploadStaticBuffer(ibuf, indices);
 // ...
 commandBuffer->resourceUpdate(batch); // or, alternatively, pass 'batch' to a beginPass() call

Common patterns

A call to create() destroys any existing native resources if create() was successfully called before. If those native resources are still in use by an in-flight frame (i.e., there's a chance they are still read by the GPU), the destroying of those resources is deferred automatically. Thus a very common and convenient pattern to safely increase the size of an already initialized buffer is the following. In practice this drops and creates a whole new set of native resources underneath, so it is not necessarily a cheap operation, but is more convenient and still faster than the alternatives, because by not destroying the buf object itself, all references to it stay valid in other data structures (e.g., in any QRhiShaderResourceBinding the QRhiBuffer is referenced from).

 if (buf->size() < newSize) {
     buf->setSize(newSize);
     if (!buf->create()) { error(); }
 }
 // continue using buf, fill it with new data

When working with uniform buffers, it will sometimes be necessary to combine data for multiple draw calls into a single buffer for efficiency reasons. Be aware of the aligment requirements: with some graphics APIs offsets for a uniform buffer must be aligned to 256 bytes. This applies both to QRhiShaderResourceBinding and to the dynamic offsets passed to setShaderResources(). Use the ubufAlignment() and ubufAligned() functions to create portable code. As an example, the following is an outline for issuing multiple (N) draw calls with the same pipeline and geometry, but with a different data in the uniform buffers exposed at binding point 0. This assumes the buffer is exposed via uniformBufferWithDynamicOffset() which allows passing a QRhiCommandBuffer::DynamicOffset list to setShaderResources().

 const int N = 2;
 const int UB_SIZE = 64 + 4; // assuming a uniform block with { mat4 matrix; float opacity; }
 const int ONE_UBUF_SIZE = rhi->ubufAligned(UB_SIZE);
 const int TOTAL_UBUF_SIZE = N * ONE_UBUF_SIZE;
 QRhiBuffer *ubuf = rhi->newBuffer(QRhiBuffer::Dynamic, QRhiBuffer::UniformBuffer, TOTAL_UBUF_SIZE);
 if (!ubuf->create()) { error(); }
 QRhiResourceUpdateBatch *batch = rhi->nextResourceUpdateBatch();
 for (int i = 0; i < N; ++i) {
     batch->updateDynamicBuffer(ubuf, i * ONE_UBUF_SIZE, 64, matrix.constData());
     updates->updateDynamicBuffer(ubuf, i * ONE_UBUF_SIZE + 64, 4, &opacity);
 }
 // ...
 // beginPass(), set pipeline, etc., and then:
 for (int i = 0; i < N; ++i) {
     QRhiCommandBuffer::DynamicOffset dynOfs[] = { { 0, i * ONE_UBUF_SIZE } };
     cb->setShaderResources(srb, 1, dynOfs);
     cb->draw(36);
 }

See also QRhiResourceUpdateBatch, QRhi, and QRhiCommandBuffer.

Member Type Documentation

enum QRhiBuffer::Type

Specifies storage type of buffer resource.

ConstantValueDescription
QRhiBuffer::Immutable0Indicates that the data is not expected to change ever after the initial upload. Under the hood such buffer resources are typically placed in device local (GPU) memory (on systems where applicable). Uploading new data is possible, but may be expensive. The upload typically happens by copying to a separate, host visible staging buffer from which a GPU buffer-to-buffer copy is issued into the actual GPU-only buffer.
QRhiBuffer::Static1Indicates that the data is expected to change only infrequently. Typically placed in device local (GPU) memory, where applicable. On backends where host visible staging buffers are used for uploading, the staging buffers are kept around for this type, unlike with Immutable, so subsequent uploads do not suffer in performance. Frequent updates, especially updates in consecutive frames, should be avoided.
QRhiBuffer::Dynamic2Indicates that the data is expected to change frequently. Not recommended for large buffers. Typically backed by host visible memory in 2 copies in order to allow for changing without stalling the graphics pipeline. The double buffering is managed transparently to the applications and is not exposed in the API here in any form. This is the recommended, and, with some backends, the only possible, type for buffers with UniformBuffer usage.

enum QRhiBuffer::UsageFlag
flags QRhiBuffer::UsageFlags

Flag values to specify how the buffer is going to be used.

ConstantValueDescription
QRhiBuffer::VertexBuffer1 << 0Vertex buffer. This allows the QRhiBuffer to be used in setVertexInput().
QRhiBuffer::IndexBuffer1 << 1Index buffer. This allows the QRhiBuffer to be used in setVertexInput().
QRhiBuffer::UniformBuffer1 << 2Uniform buffer (also called constant buffer). This allows the QRhiBuffer to be used in combination with UniformBuffer. When NonDynamicUniformBuffers is reported as not supported, this usage can only be combined with the type Dynamic.
QRhiBuffer::StorageBuffer1 << 3Storage buffer. This allows the QRhiBuffer to be used in combination with BufferLoad, BufferStore, or BufferLoadStore. This usage can only be combined with the types Immutable or Static, and is only available when the Compute feature is reported as supported.

The UsageFlags type is a typedef for QFlags<UsageFlag>. It stores an OR combination of UsageFlag values.

Member Function Documentation

[virtual] char *QRhiBuffer::beginFullDynamicBufferUpdateForCurrentFrame()

Returns a pointer to a memory block with the host visible buffer data.

This is a shortcut for medium-to-large dynamic uniform buffers that have their entire contents (or at least all regions that are read by the shaders in the current frame) changed in every frame and the QRhiResourceUpdateBatch-based update mechanism is seen too heavy due to the amount of data copying involved.

The call to this function must be eventually followed by a call to endFullDynamicUniformBufferUpdateForCurrentFrame(), before recording any render or compute pass that relies on this buffer.

Warning: Updating data via this method is not compatible with QRhiResourceUpdateBatch-based updates and readbacks. Unexpected behavior may occur when attempting to combine the two update models for the same buffer. Similarly, the data updated this direct way may not be visible to readBackBuffer operations, depending on the backend.

Warning: When updating buffer data via this method, the update must be done in every frame, otherwise backends that perform double or triple buffering of resources may end up in unexpected behavior.

Warning: Partial updates are not possible with this approach since some backends may choose a strategy where the previous contents of the buffer is lost upon calling this function. Data must be written to all regions that are read by shaders in the frame currently being prepared.

Warning: This function can only be called when recording a frame, so between QRhi::beginFrame() and QRhi::endFrame().

Warning: This function can only be called on Dynamic buffers.

[pure virtual] bool QRhiBuffer::create()

Creates the corresponding native graphics resources. If there are already resources present due to an earlier create() with no corresponding destroy(), then destroy() is called implicitly first.

Returns true when successful, false when a graphics operation failed. Regardless of the return value, calling destroy() is always safe.

[virtual] void QRhiBuffer::endFullDynamicBufferUpdateForCurrentFrame()

To be called when the entire contents of the buffer data has been updated in the memory block returned from beginFullDynamicBufferUpdateForCurrentFrame().

[virtual] QRhiBuffer::NativeBuffer QRhiBuffer::nativeBuffer()

Returns the underlying native resources for this buffer. The returned value will be empty if exposing the underlying native resources is not supported by the backend.

A QRhiBuffer may be backed by multiple native buffer objects, depending on the type() and the QRhi backend in use. When this is the case, all of them are returned in the objects array in the returned struct, with slotCount specifying the number of native buffer objects. While recording a frame, QRhi::currentFrameSlot() can be used to determine which of the native buffers QRhi is using for operations that read or write from this QRhiBuffer within the frame being recorded.

In some cases a QRhiBuffer will not be backed by a native buffer object at all. In this case slotCount will be set to 0 and no valid native objects are returned. This is not an error, and is perfectly valid when a given backend does not use native buffers for QRhiBuffers with certain types or usages.

Note: Be aware that QRhi backends may employ various buffer update strategies. Unlike textures, where uploading image data always means recording a buffer-to-image (or similar) copy command on the command buffer, buffers, in particular Dynamic and UniformBuffer ones, can operate in many different ways. For example, a QRhiBuffer with usage type UniformBuffer may not even be backed by a native buffer object at all if uniform buffers are not used or supported by a given backend and graphics API. There are also differences to how data is written to the buffer and the type of backing memory used. For buffers backed by host visible memory, calling this function guarantees that pending host writes are executed for all the returned native buffers.

See also QRhi::currentFrameSlot() and QRhi::FramesInFlight.

[override virtual] QRhiResource::Type QRhiBuffer::resourceType() const

Reimplements: QRhiResource::resourceType() const.

Returns the resource type.

void QRhiBuffer::setSize(quint32 sz)

Sets the size of the buffer in bytes. The size is normally specified in QRhi::newBuffer() so this function is only used when the size has to be changed. As with other setters, the size only takes effect when calling create(), and for already created buffers this involves releasing the previous native resource and creating new ones under the hood.

Backends may choose to allocate buffers bigger than sz in order to fulfill alignment requirements. This is hidden from the applications and size() will always report the size requested in sz.

See also size().

void QRhiBuffer::setType(QRhiBuffer::Type t)

Sets the buffer's type to t.

See also type().

void QRhiBuffer::setUsage(QRhiBuffer::UsageFlags u)

Sets the buffer's usage flags to u.

See also usage().

quint32 QRhiBuffer::size() const

Returns the buffer's size in bytes.

This is always the value that was passed to setSize() or QRhi::newBuffer(). Internally, the native buffers may be bigger if that is required by the underlying graphics API.

See also setSize().

QRhiBuffer::Type QRhiBuffer::type() const

Returns the buffer type.

See also setType().

QRhiBuffer::UsageFlags QRhiBuffer::usage() const

Returns the buffer's usage flags.

See also setUsage().