files/en-us/web/api/gpurenderbundleencoder/index.md
{{APIRef("WebGPU API")}}{{SecureContext_Header}}{{AvailableInWorkers}}
The GPURenderBundleEncoder interface of the {{domxref("WebGPU API", "WebGPU API", "", "nocode")}} is used to pre-record bundles of commands.
The command bundles are encoded by calling the methods of GPURenderBundleEncoder; once the desired commands have been encoded, they are recorded into a {{domxref("GPURenderBundle")}} object instance using the {{domxref("GPURenderBundleEncoder.finish()")}} method. These render bundles can then be reused across multiple render passes by passing the GPURenderBundle objects into {{domxref("GPURenderPassEncoder.executeBundles()")}} calls.
In effect, this is like a partial render pass — GPURenderBundleEncoders have all the same functionality available as {{domxref("GPURenderPassEncoder")}}s, except that they can't begin and end occlusion queries, and can't set the scissor rect, viewport, blend constant, and stencil reference. The GPURenderBundle will inherit all these values from the {{domxref("GPURenderPassEncoder")}} that executes it.
[!NOTE] Currently set vertex buffers, index buffers, bind groups, and pipeline are all cleared prior to executing a render bundle, and once the render bundle has finished executing.
Reusing pre-recoded commands can significantly improve app performance in situations where JavaScript draw call overhead is a bottleneck. Render bundles are most effective in situations where a batch of objects will be drawn the same way across multiple views or frames, with the only differences being the buffer content being used (such as updated matrix uniforms). A good example is VR rendering. Recording the rendering as a render bundle and then tweaking the view matrix and replaying it for each eye is a more efficient way to issue draw calls for both renderings of the scene.
A GPURenderBundleEncoder object instance is created via the {{domxref("GPUDevice.createRenderBundleEncoder()")}} property.
[!NOTE] The methods of
GPURenderBundleEncoderare functionally identical to their equivalents available on {{domxref("GPURenderPassEncoder")}}, except for {{domxref("GPURenderBundleEncoder.finish()")}}, which is similar in purpose to {{domxref("GPUCommandEncoder.finish()")}}.
{{InheritanceDiagram}}
{{domxref("GPURenderBundleEncoder.draw", "draw()")}}
{{domxref("GPURenderBundleEncoder.drawIndexed", "drawIndexed()")}}
{{domxref("GPURenderBundleEncoder.drawIndirect", "drawIndirect()")}}
{{domxref("GPURenderBundleEncoder.drawIndexedIndirect", "drawIndexedIndirect()")}}
{{domxref("GPURenderBundleEncoder.finish", "finish()")}}
{{domxref("GPURenderBundleEncoder.insertDebugMarker", "insertDebugMarker()")}}
{{domxref("GPURenderBundleEncoder.popDebugGroup", "popDebugGroup()")}}
{{domxref("GPURenderBundleEncoder.pushDebugGroup", "pushDebugGroup()")}}
{{domxref("GPURenderBundleEncoder.setBindGroup", "setBindGroup()")}}
{{domxref("GPURenderBundleEncoder.setIndexBuffer", "setIndexBuffer()")}}
{{domxref("GPURenderBundleEncoder.setPipeline", "setPipeline()")}}
{{domxref("GPURenderBundleEncoder.setVertexBuffer", "setVertexBuffer()")}}
In the WebGPU Samples Animometer example, a lot of like operations are done on many different objects simultaneously. A bundle of commands is encoded using the following function:
function recordRenderPass(
passEncoder: GPURenderBundleEncoder | GPURenderPassEncoder
) {
if (settings.dynamicOffsets) {
passEncoder.setPipeline(dynamicPipeline);
} else {
passEncoder.setPipeline(pipeline);
}
passEncoder.setVertexBuffer(0, vertexBuffer);
passEncoder.setBindGroup(0, timeBindGroup);
const dynamicOffsets = [0];
for (let i = 0; i < numTriangles; ++i) {
if (settings.dynamicOffsets) {
dynamicOffsets[0] = i * alignedUniformBytes;
passEncoder.setBindGroup(1, dynamicBindGroup, dynamicOffsets);
} else {
passEncoder.setBindGroup(1, bindGroups[i]);
}
passEncoder.draw(3, 1, 0, 0);
}
}
Later on, a GPURenderBundleEncoder is created, the function is invoked, and the command bundle is recorded into a {{domxref("GPURenderBundle")}} using {{domxref("GPURenderBundleEncoder.finish()")}}:
const renderBundleEncoder = device.createRenderBundleEncoder({
colorFormats: [presentationFormat],
});
recordRenderPass(renderBundleEncoder);
const renderBundle = renderBundleEncoder.finish();
{{domxref("GPURenderPassEncoder.executeBundles()")}} is then used to reuse the work across multiple render passes to improve performance. Study the example code listing for the full context.
// …
return function doDraw(timestamp) {
if (startTime === undefined) {
startTime = timestamp;
}
uniformTime[0] = (timestamp - startTime) / 1000;
device.queue.writeBuffer(uniformBuffer, timeOffset, uniformTime.buffer);
renderPassDescriptor.colorAttachments[0].view = context
.getCurrentTexture()
.createView();
const commandEncoder = device.createCommandEncoder();
const passEncoder = commandEncoder.beginRenderPass(renderPassDescriptor);
if (settings.renderBundles) {
passEncoder.executeBundles([renderBundle]);
} else {
recordRenderPass(passEncoder);
}
passEncoder.end();
device.queue.submit([commandEncoder.finish()]);
};
// …
{{Specifications}}
{{Compat}}