On this page

QRhiIndirectCommandBuffer Class

A prerecorded batch of indirect draw commands. More...

Header: #include <rhi/qrhi.h>
CMake: find_package(Qt6 REQUIRED COMPONENTS GuiPrivate)
target_link_libraries(mytarget PRIVATE Qt6::GuiPrivate)
qmake: QT += gui-private
Since: Qt 6.13
Inherits: QRhiResource

Public Types

enum Type { Draws, IndexedDraws }

Public Functions

void clear()
quint32 commandCount() const
virtual bool create() = 0
void draw(quint32 vertexCount, quint32 instanceCount = 1, quint32 firstVertex = 0, quint32 firstInstance = 0)
void drawIndexed(quint32 indexCount, quint32 instanceCount = 1, quint32 firstIndex = 0, qint32 vertexOffset = 0, quint32 firstInstance = 0)
bool isGpuBuilt() const
quint32 maxCommandCount() const
quint32 recordedCommandCount() const
void setMaxCommandCount(quint32 count)
void setType(QRhiIndirectCommandBuffer::Type t)
QRhiIndirectCommandBuffer::Type type() const

Reimplemented Public Functions

virtual QRhiResource::Type resourceType() const override

Detailed Description

A QRhiIndirectCommandBuffer holds a number of draw commands that are recorded once and can then be replayed any number of times with a single QRhiCommandBuffer::executeIndirect() call. It is an alternative to the buffer-based QRhiCommandBuffer::drawIndirect() family of functions. Under the hood, it may do exactly the same as does (typical with Vulkan, Direct 3D, and OpenGL), or may be implemented differently (Metal).

Create one with QRhi::newIndirectCommandBuffer(), passing in the type and the maximum number of commands, and call create(). The (maximum) command count is mandatory, whichever way the commands are going to be provided: it is the capacity of the object, and create() fails when it is 0.

icb = rhi->newIndirectCommandBuffer(QRhiIndirectCommandBuffer::IndexedDraws, 1024);
if (!icb->create()) { error(); }

Record commands with draw() or drawIndexed(), then hand the object to a resource update batch so that the recorded contents reach the GPU. commitIndirectCommandBuffer() has to be called after recording and before the pass that executes the commands. It is a no-op when nothing changed since the last time, so calling it every frame is cheap.

Executing happens inside a render pass:

icb->clear();
for (const Item &item : items)
    icb->drawIndexed(item.indexCount, 1, item.firstIndex, item.vertexOffset);

QRhiResourceUpdateBatch *u = rhi->nextResourceUpdateBatch();
u->commitIndirectCommandBuffer(icb);

cb->beginPass(rt, Qt::black, { 1.0f, 0 }, u);
cb->setGraphicsPipeline(ps);
cb->setVertexInput(0, 1, &vbufBinding, ibuf, 0, QRhiCommandBuffer::IndexUInt16);
cb->setShaderResources();
cb->executeIndirect(icb);
cb->endPass();

The commands can also be generated on the GPU instead of being recorded on the CPU. In that case fill a buffer with QRhiIndirectDrawCommand or QRhiIndexedIndirectDrawCommand entries from a compute shader, and call QRhiCommandBuffer::buildIndirect() with that buffer. That call must happen outside of any pass. The CPU-side recording functions of QRhiIndirectCommandBuffer are not used in this case.

cb->beginComputePass();
... // a cb->dispatch() to invoke a compute shader that writes to indirectBuf
cb->endComputePass();

QRhiIndirectCommandBufferBuildInfo buildInfo;
buildInfo.topology = ps->topology();
buildInfo.sourceBuffer = indirectBuf;
buildInfo.commandCount = itemCount; // as many as the compute shader wrote
buildInfo.indexBuffer = indexBuffer;
buildInfo.indexFormat = QRhiCommandBuffer::IndexUInt16;
cb->buildIndirect(icb, buildInfo);

cb->beginPass(rt, Qt::black, { 1.0f, 0 });
cb->setGraphicsPipeline(ps);
cb->setVertexInput(0, 1, &vbufBinding, ibuf, 0, QRhiCommandBuffer::IndexUInt16);
cb->setShaderResources();
cb->executeIndirect(icb);
cb->endPass();

When the number of commands is itself decided on the device, set QRhiIndirectCommandBufferBuildInfo::countBuffer instead of working out itemCount on the CPU. See Command counts below.

Command counts

Three counts are involved, and they are not the same thing:

The size of QRhiIndirectCommandBufferBuildInfo::sourceBuffer plays no part in any of this: no count is ever derived from it. The buffer is written by the GPU and is free to be larger than the number of commands actually in it, so leaving QRhiIndirectCommandBufferBuildInfo::commandCount at 0 does not mean "as many as fit", it means maxCommandCount().

A device-side count, via QRhiIndirectCommandBufferBuildInfo::countBuffer, narrows the count further when the commands are executed, and can only reduce it: the value in the count buffer is clamped to what commandCount() returns.

Either way, the indirect command buffer is fully prepared before the render pass begins. That is what separates it from the buffer-based QRhiCommandBuffer::drawIndirect() family, where some backends (Metal above a certain draw count, and always for the count variants) have to interrupt and restart the render pass in order to encode the commands. Such an interruption costs a QRhiRenderBuffer depth-stencil buffer its contents unless QRhiRenderBuffer::NoTransientBacking was set, and may also be degrading performance due to having to reload the color buffer values. With executeIndirect() the question does not arise.

For Metal, the buffer-based API implies (ahove a certain draw count, or whhen using the count variants) having to run a compute kernel to create an MTLIndirectCommandBuffer from the Vulkan/Direct 3D/OpenGL style indirect buffer. With QRhiIndirectCommandBuffer this is not always necessary, because now, at least when the commands are generated on the CPU side, an MTLIndirectCommandBuffer can be created and set up normally, by calling MTLIndirectCommandBuffer's Objective-C API, instead of having to inject a compute pass. When generating the commands on the GPU, the extra compute pass is still necessary, but at least it will not interrupt the render pass, by design, unlike with the direct buffer-based API. Note however that using MTLIndirectCommandBuffer natively from C++/Objective-C to perform repeated CPU-side draw call generation can prove to be quite expensive (when frequently re-recording a larger set of draw commands), compared to the GPU-side draw command generation, even though that involves an extra encoding compute pass to "convert" the Vulkan/Direct 3D/OpenGL style indirect buffer to what Metal prefers. See the next section.

Recording on the CPU or building on the GPU

The two ways of populating an indirect command buffer are not equivalent in cost, and the difference grows with the number of commands.

Recording with draw() or drawIndexed() is work proportional to the number of commands, and it is repeated whenever the contents change. Where that work lands depends on the backend: those that keep the commands in a buffer upload it from QRhiResourceUpdateBatch::commitIndirectCommandBuffer(), whereas Metal encodes each command individually into a native MTLIndirectCommandBuffer, one native call per command, and does so from QRhiCommandBuffer::executeIndirect().

Either way the result is cached and keyed on the recorded contents, so an indirect command buffer that is recorded once and then executed unchanged frame after frame costs nothing beyond the first few frames. That is the case CPU recording is meant for: a command set that is stable, or changes rarely, relative to how often it is executed.

The opposite case - many commands, cleared and re-recorded every frame - is the one to avoid. Regenerating tens of thousands of commands per frame that way can be an order of magnitude more expensive on Apple platforms than filling a QRhiBuffer and calling QRhiCommandBuffer::drawIndexedIndirect() on it, because of the per-command native encoding. When the command set is both large and regenerated every frame, generate it on the GPU and use QRhiCommandBuffer::buildIndirect(); the alternative is to stay with the buffer-based drawIndirect() family and accept the render pass interruption it may cause (which is still not recommended, even if it would perform better).

A given QRhiIndirectCommandBuffer holds one kind of command: either non-indexed draws (Draws) or indexed draws (IndexedDraws), never a mix of the two. This mirrors what the underlying APIs can express: a single indirect draw call always has one command type and one stride.

The requirements are the same as for the indirect draws this stands in for: QRhi::DrawIndirect has to be supported, the graphics pipeline should be created with UsesIndirectDraws, and a count buffer additionally needs QRhi::DrawIndirectCount to be supported.

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

See also QRhiCommandBuffer::executeIndirect() and QRhiCommandBuffer::drawIndirect().

Member Type Documentation

enum QRhiIndirectCommandBuffer::Type

Specifies the kind of commands an indirect command buffer holds.

ConstantValueDescription
QRhiIndirectCommandBuffer::Draws0Non-indexed draw commands, recorded with draw().
QRhiIndirectCommandBuffer::IndexedDraws1Indexed draw commands, recorded with drawIndexed().

Member Function Documentation

void QRhiIndirectCommandBuffer::clear()

Discards all commands recorded so far.

Can be called at any time, also before create(). The recorded contents only become visible to the GPU once the object is passed to QRhiResourceUpdateBatch::commitIndirectCommandBuffer().

This resets recordedCommandCount() to 0. It does not undo a QRhiCommandBuffer::buildIndirect(): an indirect command buffer that gets its commands from the GPU keeps doing so, and commandCount() is unchanged.

Note: Clearing and re-recording invalidates whatever the backend cached for the previous contents, so the per-command cost of recording is paid again. Call this only when the commands actually have to change. See Recording on the CPU or building on the GPU for why that matters at high command counts.

quint32 QRhiIndirectCommandBuffer::commandCount() const

Returns the number of commands QRhiCommandBuffer::executeIndirect() issues by default.

This is recordedCommandCount() for a CPU-recorded indirect command buffer, and the count resolved from QRhiIndirectCommandBufferBuildInfo::commandCount once QRhiCommandBuffer::buildIndirect() has been called.

A count buffer, if there is one, can reduce the number of draws further at execution time. This function does not, and cannot, account for that.

[pure virtual] bool QRhiIndirectCommandBuffer::create()

Creates the corresponding native objects.

Fails when maxCommandCount() is 0.

A given QRhiIndirectCommandBuffer takes its commands either from draw() and drawIndexed() followed by QRhiResourceUpdateBatch::commitIndirectCommandBuffer(), or from QRhiCommandBuffer::buildIndirect(), but not from both. Moving to the latter is one-way: clear() does not undo it, and neither does a subsequent commitIndirectCommandBuffer(); isGpuBuilt() stays true and the commands keep coming from the buffer. Call create() again to get an indirect command buffer that is populated from the CPU once more.

Note: Like with every other QRhi resource, destroy() gives up the contents, and so create() starts from an empty indirect command buffer: recordedCommandCount() is 0 afterwards. Setting a different type() or maxCommandCount() and calling create() again therefore needs the commands to be recorded again as well.

Returns true when successful, false when a graphics operation failed.

void QRhiIndirectCommandBuffer::draw(quint32 vertexCount, quint32 instanceCount = 1, quint32 firstVertex = 0, quint32 firstInstance = 0)

Records a non-indexed draw command with vertexCount, instanceCount, firstVertex, and firstInstance.

The semantics are the same as QRhiCommandBuffer::draw().

Note: Only valid on an indirect command buffer of type Draws.

void QRhiIndirectCommandBuffer::drawIndexed(quint32 indexCount, quint32 instanceCount = 1, quint32 firstIndex = 0, qint32 vertexOffset = 0, quint32 firstInstance = 0)

Records an indexed draw command with indexCount, instanceCount, firstIndex, vertexOffset, and firstInstance.

The semantics are the same as QRhiCommandBuffer::drawIndexed().

Note: Only valid on an indirect command buffer of type IndexedDraws.

bool QRhiIndirectCommandBuffer::isGpuBuilt() const

Returns true when QRhiCommandBuffer::buildIndirect() has been called on this indirect command buffer, meaning its contents come from a QRhiBuffer instead of from draw() and drawIndexed().

Once true, this stays true until the next create().

quint32 QRhiIndirectCommandBuffer::maxCommandCount() const

Returns the capacity, i.e. the maximum number of commands.

See also setMaxCommandCount().

quint32 QRhiIndirectCommandBuffer::recordedCommandCount() const

Returns the number of commands recorded with draw() or drawIndexed() since the last clear().

This is unaffected by QRhiCommandBuffer::buildIndirect(): once the commands come from the GPU, whatever was recorded on the CPU is ignored. Use commandCount() to get the number of commands that will actually be executed.

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

Reimplements: QRhiResource::resourceType() const.

Returns the resource type.

void QRhiIndirectCommandBuffer::setMaxCommandCount(quint32 count)

Sets the capacity, the maximum number of commands, to count. The capacity is normally specified in QRhi::newIndirectCommandBuffer(), so this function is only used when it has to be changed. As with other setters, it only takes effect when calling create(), which fails when count is 0.

The capacity applies regardless of how the commands are going to be provided: recording them with draw() and drawIndexed() and building them with QRhiCommandBuffer::buildIndirect() are both bounded by it.

draw() and drawIndexed() ignore, with a warning, any command past the first count ones. QRhiCommandBuffer::buildIndirect() clamps, also with a warning, when QRhiIndirectCommandBufferBuildInfo::commandCount is larger.

See also maxCommandCount(), commandCount(), and recordedCommandCount().

void QRhiIndirectCommandBuffer::setType(QRhiIndirectCommandBuffer::Type t)

Sets the command type t. The type is normally specified in QRhi::newIndirectCommandBuffer(), so this function is only used when it has to be changed. As with other setters, it only takes effect when calling create().

See also type().

QRhiIndirectCommandBuffer::Type QRhiIndirectCommandBuffer::type() const

Returns the type of commands this indirect command buffer holds.

See also setType().

© 2026 The Qt Company Ltd. Documentation contributions included herein are the copyrights of their respective owners. The documentation provided herein is licensed under the terms of the GNU Free Documentation License version 1.3 as published by the Free Software Foundation. Qt and respective logos are trademarks of The Qt Company Ltd. in Finland and/or other countries worldwide. All other trademarks are property of their respective owners.