/// Describes a [`RenderBundleEncoder`]. #[derive(Clone, Debug, Default, PartialEq, Eq, Hash)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pubstruct RenderBundleEncoderDescriptor<'a> { /// Debug label of the render bundle encoder. /// /// This will show up in graphics debuggers for easy identification. pub label: Label<'a>, /// The formats of the color attachments that this render bundle is capable /// to rendering to. /// /// This must match the formats of the color attachments in the /// renderpass this render bundle is executed in. pub color_formats: Cow<'a, [Option<wgt::TextureFormat>]>, /// Information about the depth attachment that this render bundle is /// capable to rendering to. /// /// The format must match the format of the depth attachments in the /// renderpass this render bundle is executed in. pub depth_stencil: Option<wgt::RenderBundleDepthStencil>, /// Sample count this render bundle is capable of rendering to. /// /// This must match the pipelines and the renderpasses it is used in. pub sample_count: u32, /// If this render bundle will rendering to multiple array layers in the /// attachments at the same time. pub multiview: Option<NonZeroU32>,
}
/// Validate a render bundle descriptor. /// /// The underlying `device` is required to fully validate the descriptor. /// If omitted, some validation will be skipped. /// /// Returns a tuple (is_depth_read_only, is_stencil_read_only). fn validate_render_bundle_encoder_descriptor(
desc: &RenderBundleEncoderDescriptor,
device: Option<&Arc<Device>>,
) -> Result<(bool, bool), CreateRenderBundleError> { letmut have_attachment = false;
let max_color_attachments = device.map_or(hal::MAX_COLOR_ATTACHMENTS as u32, |device| {
assert!(device.limits.max_color_attachments <= hal::MAX_COLOR_ATTACHMENTS as u32);
device.limits.max_color_attachments
});
check_color_attachment_count(desc.color_formats.len(), max_color_attachments)?;
for &format in desc.color_formats.iter().flatten() {
have_attachment = true; if !format.has_color_aspect() { return Err(CreateRenderBundleError::FormatNotColor(format));
} iflet Some(device) = device { let format_features = device.describe_format_features(format)?; if !format_features
.allowed_usages
.contains(wgt::TextureUsages::RENDER_ATTACHMENT)
{ return Err(CreateRenderBundleError::FormatNotRenderable(format));
}
}
}
let (is_depth_read_only, is_stencil_read_only) = match desc.depth_stencil {
Some(ds) => {
have_attachment = true; let has_depth = ds.format.has_depth_aspect(); let has_stencil = ds.format.has_stencil_aspect(); if !has_depth && !has_stencil { return Err(CreateRenderBundleError::FormatNotDepthOrStencil(ds.format));
} else {
(
!has_depth || ds.depth_read_only,
!has_stencil || ds.stencil_read_only,
)
}
} // There's no depth/stencil attachment, so these values just don't // matter. Choose the most accommodating value, to simplify // validation.
None => (true, true),
};
if !have_attachment { return Err(CreateRenderBundleError::NoAttachment);
}
Ok((is_depth_read_only, is_stencil_read_only))
}
impl RenderBundleEncoder { /// Create a new `RenderBundleEncoder`. /// /// The underlying `device` is required to fully validate the descriptor. /// If the device is not available, some validation will be deferred /// until `finish()`. pubfn new(
desc: &RenderBundleEncoderDescriptor,
device: Option<&Arc<Device>>,
parent_id: id::DeviceId,
) -> Result<Self, CreateRenderBundleError> { let (is_depth_read_only, is_stencil_read_only) =
validate_render_bundle_encoder_descriptor(desc, device)?;
/// Convert this encoder's commands into a [`RenderBundle`]. /// /// We want executing a [`RenderBundle`] to be quick, so we take /// this opportunity to clean up the [`RenderBundleEncoder`]'s /// command stream and gather metadata about it that will help /// keep [`ExecuteBundle`] simple and fast. We remove redundant /// commands (along with their side data), note resource usage, /// and accumulate buffer and texture initialization actions. /// /// [`ExecuteBundle`]: RenderCommand::ExecuteBundle pub(crate) fn finish( self,
desc: &RenderBundleDescriptor,
device: &Arc<Device>,
hub: &Hub,
) -> Result<Arc<RenderBundle>, RenderBundleError> { let scope = PassErrorScope::Bundle;
device.check_is_valid().map_pass_err(scope)?;
{ // Reconstruct and revalidate the encoder descriptor, because // `RenderBundleEncoder` is serializable and could have been tampered. let encoder_desc = RenderBundleEncoderDescriptor {
label: self.base.label.as_ref().map(Cow::from),
color_formats: Cow::Borrowed(&self.context.attachments.colors),
depth_stencil: self.context.attachments.depth_stencil.map(|format| {
wgt::RenderBundleDepthStencil {
format,
depth_read_only: self.is_depth_read_only,
stencil_read_only: self.is_stencil_read_only,
}
}),
sample_count: self.context.sample_count,
multiview: self.context.multiview_mask,
};
let indices = &state.device.tracker_indices;
state.trackers.buffers.set_size(indices.buffers.size());
state.trackers.textures.set_size(indices.textures.size());
let State {
trackers,
flat_dynamic_offsets,
device,
commands,
buffer_memory_init_actions,
texture_memory_init_actions,
..
} = state;
let tracker_indices = device.tracker_indices.bundles.clone(); let discard_hal_labels = device
.instance_flags
.contains(wgt::InstanceFlags::DISCARD_HAL_LABELS);
// Identify the next `num_dynamic_offsets` entries from `dynamic_offsets`. let offsets_range = state.next_dynamic_offset..state.next_dynamic_offset + num_dynamic_offsets;
state.next_dynamic_offset = offsets_range.end; let offsets = &dynamic_offsets[offsets_range.clone()];
let bind_group = bind_group_id.map(|id| bind_group_guard.get(id));
state
.vertex
.limits
.validate_vertex_limit(first_vertex, vertex_count)?;
state
.vertex
.limits
.validate_instance_limit(first_instance, instance_count)?;
let stride = super::get_src_stride_of_indirect_args(family); // TODO(https://github.com/gfx-rs/wgpu/issues/8051): It would be better to report this // as a validation error, but it's pathological, so let's do the simpler thing for now // and do the better thing as part of eliminating pass/bundle duplication.
assert!(offset <= wgt::BufferAddress::MAX - stride);
state
.buffer_memory_init_actions
.extend(buffer.initialization_status.read().create_action(
&buffer,
offset..(offset + stride),
MemoryInitKind::NeedsInitializedMemory,
));
let vertex_or_index_limit = if family == DrawCommandFamily::DrawIndexed { let index = state.index.as_mut().unwrap();
state.commands.extend(index.flush());
index.limit()
} else {
state.vertex.limits.vertex_limit
}; let instance_limit = state.vertex.limits.instance_limit;
let buffer_uses = if state.device.indirect_validation.is_some()
&& family != DrawCommandFamily::DrawMeshTasks
{
wgt::BufferUses::STORAGE_READ_ONLY
} else {
wgt::BufferUses::INDIRECT
};
/// Error type returned from `RenderBundleEncoder::new` if the sample count is invalid. #[derive(Clone, Debug, Error)] #[non_exhaustive] pubenum CreateRenderBundleError { #[error(transparent)]
ColorAttachment(#[from] ColorAttachmentError), #[error("Format {0:?} does not have a color aspect")]
FormatNotColor(wgt::TextureFormat), #[error("Color attachment format {0:?} is not renderable")]
FormatNotRenderable(wgt::TextureFormat), #[error("Format {0:?} is not a depth/stencil format")]
FormatNotDepthOrStencil(wgt::TextureFormat), #[error("Render bundle must have at least one attachment (color or depth/stencil)")]
NoAttachment, #[error("Invalid number of samples {0}")]
InvalidSampleCount(u32), #[error(transparent)]
MissingFeatures(#[from] MissingFeatures),
}
/// Error type returned from `RenderBundleEncoder::new` if the sample count is invalid. #[derive(Clone, Debug, Error)] #[non_exhaustive] pubenum ExecutionError { #[error(transparent)]
Device(#[from] DeviceError), #[error(transparent)]
DestroyedResource(#[from] DestroyedResourceError), #[error("Using {0} in a render bundle is not implemented")]
Unimplemented(&'static str),
}
//Note: here, `RenderBundle` is just wrapping a raw stream of render commands. // The plan is to back it by an actual Vulkan secondary buffer, D3D12 Bundle, // or Metal indirect command buffer. /// cbindgen:ignore #[derive(Debug)] pubstruct RenderBundle { // Normalized command stream. It can be executed verbatim, // without re-binding anything on the pipeline change.
base: BasePass<ArcRenderCommand, Infallible>, pub(super) is_depth_read_only: bool, pub(super) is_stencil_read_only: bool, pub(crate) device: Arc<Device>, pub(crate) used: RenderBundleScope, pub(super) buffer_memory_init_actions: Vec<BufferInitTrackerAction>, pub(super) texture_memory_init_actions: Vec<TextureInitTrackerAction>, pub(super) context: RenderPassContext, /// The `label` from the descriptor used to create the resource.
label: String, pub(crate) tracking_data: TrackingData,
discard_hal_labels: bool,
}
impl Drop for RenderBundle { fn drop(&mutself) {
resource_log!("Drop {}", self.error_ident());
}
}
#[cfg(send_sync)] unsafeimpl Send for RenderBundle {} #[cfg(send_sync)] unsafeimpl Sync for RenderBundle {}
/// Actually encode the contents into a native command buffer. /// /// This is partially duplicating the logic of `render_pass_end`. /// However the point of this function is to be lighter, since we already had /// a chance to go through the commands in `render_bundle_encoder_finish`. /// /// Note that the function isn't expected to fail, generally. /// All the validation has already been done by this point. /// The only failure condition is if some of the used buffers are destroyed. pub(super) unsafefn execute(
&self,
raw: &mutdyn hal::DynCommandEncoder,
indirect_draw_validation_resources: &mutcrate::indirect_validation::DrawResources,
indirect_draw_validation_batcher: &mutcrate::indirect_validation::DrawBatcher,
snatch_guard: &SnatchGuard,
) -> Result<(), ExecutionError> { letmut offsets = self.base.dynamic_offsets.as_slice(); letmut pipeline_layout = None::<Arc<PipelineLayout>>; if !self.discard_hal_labels { iflet Some(ref label) = self.base.label { unsafe { raw.begin_debug_marker(label) };
}
}
use ArcRenderCommand as Cmd; for command inself.base.commands.iter() { match command {
Cmd::SetBindGroup {
index,
num_dynamic_offsets,
bind_group,
} => { let raw_bg = bind_group.as_ref().unwrap().try_raw(snatch_guard)?; unsafe {
raw.set_bind_group(
pipeline_layout.as_ref().unwrap().raw(),
*index,
raw_bg,
&offsets[..*num_dynamic_offsets],
)
};
offsets = &offsets[*num_dynamic_offsets..];
}
Cmd::SetPipeline(pipeline) => { unsafe { raw.set_render_pipeline(pipeline.raw()) };
pipeline_layout = Some(pipeline.layout.clone());
}
Cmd::SetIndexBuffer {
buffer,
index_format,
offset,
size,
} => { let buffer = buffer.try_raw(snatch_guard)?; // SAFETY: The binding size was checked against the buffer size // in `set_index_buffer` and again in `IndexState::flush`. let bb = hal::BufferBinding::new_unchecked(buffer, *offset, *size); unsafe { raw.set_index_buffer(bb, *index_format) };
}
Cmd::SetVertexBuffer {
slot,
buffer,
offset,
size,
} => { let buffer = buffer.as_ref().unwrap().try_raw(snatch_guard)?; // SAFETY: The binding size was checked against the buffer size // in `set_vertex_buffer` and again in `VertexState::flush`. let bb = hal::BufferBinding::new_unchecked(buffer, *offset, *size); unsafe { raw.set_vertex_buffer(*slot, bb) };
}
Cmd::SetImmediate {
offset,
size_bytes,
values_offset,
} => { let pipeline_layout = pipeline_layout.as_ref().unwrap();
iflet Some(values_offset) = *values_offset { let values_end_offset =
(values_offset + size_bytes / wgt::IMMEDIATE_DATA_ALIGNMENT) as usize; let data_slice =
&self.base.immediates_data[(values_offset as usize)..values_end_offset];
/// A render bundle's current index buffer state. /// /// [`RenderBundleEncoder::finish`] records the currently set index buffer here, /// and calls [`State::flush_index`] before any indexed draw command to produce /// a `SetIndexBuffer` command if one is necessary. /// /// Binding ranges must be validated against the size of the buffer before /// being stored in `IndexState`. #[derive(Debug)] struct IndexState {
buffer: Arc<Buffer>,
format: wgt::IndexFormat,
range: Range<wgt::BufferAddress>,
is_dirty: bool,
}
impl IndexState { /// Return the number of entries in the current index buffer. /// /// Panic if no index buffer has been set. fn limit(&self) -> u64 { let bytes_per_index = self.format.byte_size() as u64;
/// Generate a `SetIndexBuffer` command to prepare for an indexed draw /// command, if needed. fn flush(&mutself) -> Option<ArcRenderCommand> { // This was all checked before, but let's check again just in case. let binding_size = self
.range
.end
.checked_sub(self.range.start)
.filter(|_| self.range.end <= self.buffer.size)
.expect("index range must be contained in buffer");
/// The state of a single vertex buffer slot during render bundle encoding. /// /// [`RenderBundleEncoder::finish`] uses this to drop redundant /// `SetVertexBuffer` commands from the final [`RenderBundle`]. It /// records one vertex buffer slot's state changes here, and then /// calls this type's [`flush`] method just before any draw command to /// produce a `SetVertexBuffer` commands if one is necessary. /// /// Binding ranges must be validated against the size of the buffer before /// being stored in `VertexState`. /// /// [`flush`]: IndexState::flush #[derive(Debug)] /// State for analyzing and cleaning up bundle command streams. /// /// To minimize state updates, [`RenderBundleEncoder::finish`] /// actually just applies commands like [`SetBindGroup`] and /// [`SetIndexBuffer`] to the simulated state stored here, and then /// calls the `flush_foo` methods before draw calls to produce the /// update commands we actually need. /// /// [`SetBindGroup`]: RenderCommand::SetBindGroup /// [`SetIndexBuffer`]: RenderCommand::SetIndexBuffer struct State { /// Resources used by this bundle. This will become [`RenderBundle::used`].
trackers: RenderBundleScope,
/// The currently set pipeline, if any.
pipeline: Option<Arc<RenderPipeline>>,
/// The state of each vertex buffer slot.
vertex: super::VertexState,
/// The current index buffer, if one has been set. We flush this state /// before indexed draw commands.
index: Option<IndexState>,
/// Dynamic offset values used by the cleaned-up command sequence. /// /// This becomes the final [`RenderBundle`]'s [`BasePass`]'s /// [`dynamic_offsets`] list. /// /// [`dynamic_offsets`]: BasePass::dynamic_offsets
flat_dynamic_offsets: Vec<wgt::DynamicOffset>,
device: Arc<Device>,
commands: Vec<ArcRenderCommand>,
buffer_memory_init_actions: Vec<BufferInitTrackerAction>,
texture_memory_init_actions: Vec<TextureInitTrackerAction>,
next_dynamic_offset: usize,
binder: Binder, /// A bitmask, tracking which 4-byte slots have been written via `set_immediates`. /// Checked against the pipeline's required slots before each draw call.
immediate_slots_set: naga::valid::ImmediateSlots,
}
impl State { /// Set the bundle's current index buffer and its associated parameters. fn set_index_buffer(
&mutself,
buffer: Arc<Buffer>,
format: wgt::IndexFormat,
range: Range<wgt::BufferAddress>,
) { matchself.index {
Some(ref current) if current.buffer.is_equal(&buffer)
&& current.format == format
&& current.range == range =>
{ return
}
_ => (),
}
/// Generate a `SetIndexBuffer` command to prepare for an indexed draw /// command, if needed. fn flush_index(&mutself) { let commands = self.index.as_mut().and_then(|index| index.flush()); self.commands.extend(commands);
}
/// Validation for a draw command. /// /// This should be further deduplicated with similar validation on render/compute passes. fn is_ready(&mutself, family: DrawCommandFamily) -> Result<(), DrawError> { iflet Some(pipeline) = self.pipeline.as_ref() { self.binder.check_compatibility(pipeline.as_ref())?; self.binder.check_late_buffer_bindings()?;
if family == DrawCommandFamily::DrawIndexed { let index_format = match &self.index {
Some(index) => index.format,
None => return Err(DrawError::MissingIndexBuffer),
};
/// Generate `SetBindGroup` commands for any bind groups that need to be updated. /// /// This should be further deduplicated with similar code on render/compute passes. fn flush_bindings(&mutself) { let start = self.binder.take_rebind_start_index(); let entries = self.binder.list_valid_with_start(start);
impl<E> MapPassErr<RenderBundleError> for E where
E: Into<RenderBundleErrorInner>,
{ fn map_pass_err(self, scope: PassErrorScope) -> RenderBundleError {
RenderBundleError {
scope,
inner: self.into(),
}
}
}
pubmod bundle_ffi { usesuper::{RenderBundleEncoder, RenderCommand}; usecrate::{command::DrawCommandFamily, id, RawString}; use core::{convert::TryInto, slice}; use wgt::{BufferAddress, BufferSize, DynamicOffset, IndexFormat};
/// # Safety /// /// This function is unsafe as there is no guarantee that the given pointer is /// valid for `offset_length` elements. pubunsafefn wgpu_render_bundle_set_bind_group(
bundle: &mut RenderBundleEncoder,
index: u32,
bind_group_id: Option<id::BindGroupId>,
offsets: *const DynamicOffset,
offset_length: usize,
) { let offsets = unsafe { slice::from_raw_parts(offsets, offset_length) };
let redundant = bundle.current_bind_groups.set_and_check_redundant(
bind_group_id,
index,
&mut bundle.base.dynamic_offsets,
offsets,
);
/// # Safety /// /// This function is unsafe as there is no guarantee that the given pointer is /// valid for `data` elements. pubunsafefn wgpu_render_bundle_set_immediates(
pass: &mut RenderBundleEncoder,
offset: u32,
size_bytes: u32,
data: *const u8,
) {
assert_eq!(
offset & (wgt::IMMEDIATE_DATA_ALIGNMENT - 1), 0, "Immediate data offset must be aligned to 4 bytes."
);
assert_eq!(
size_bytes & (wgt::IMMEDIATE_DATA_ALIGNMENT - 1), 0, "Immediate data size must be aligned to 4 bytes."
); let data_slice = unsafe { slice::from_raw_parts(data, size_bytes as usize) }; let value_offset = pass.base.immediates_data.len().try_into().expect( "Ran out of immediate data space. Don't set 4gb of immediates per RenderBundle.",
);
/// # Safety /// /// This function is unsafe as there is no guarantee that the given `label` /// is a valid null-terminated string. pubunsafefn wgpu_render_bundle_push_debug_group(
_bundle: &mut RenderBundleEncoder,
_label: RawString,
) { //TODO
}
/// # Safety /// /// This function is unsafe as there is no guarantee that the given `label` /// is a valid null-terminated string. pubunsafefn wgpu_render_bundle_insert_debug_marker(
_bundle: &mut RenderBundleEncoder,
_label: RawString,
) { //TODO
}
}
Messung V0.5 in Prozent
¤ Dauer der Verarbeitung: 0.44 Sekunden
(vorverarbeitet am 2026-08-27)
¤
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.