/// <https://gpuweb.github.io/gpuweb/#dom-gpurendercommandsmixin-draw> fn validate_draw(
vertex: &[Option<VertexState>],
step: &[VertexStep],
first_vertex: u32,
vertex_count: u32,
first_instance: u32,
instance_count: u32,
) -> Result<(), DrawError> { let vertices_end = first_vertex as u64 + vertex_count as u64; let instances_end = first_instance as u64 + instance_count as u64;
for (idx, (vbs, step)) in vertex.iter().zip(step).enumerate() { let Some(vbs) = vbs else { continue;
};
let stride_count = match step.mode {
wgt::VertexStepMode::Vertex => vertices_end,
wgt::VertexStepMode::Instance => instances_end,
};
if stride_count == 0 { continue;
}
let offset = (stride_count - 1) * step.stride + step.last_stride; let limit = vbs.range.end - vbs.range.start; if offset > limit { return Err(DrawError::VertexOutOfBounds {
step_mode: step.mode,
offset,
limit,
slot: idx as u32,
});
}
}
Ok(())
}
// See https://gpuweb.github.io/gpuweb/#dom-gpurendercommandsmixin-drawindexed fn validate_indexed_draw(
vertex: &[Option<VertexState>],
step: &[VertexStep],
index_state: &IndexState,
first_index: u32,
index_count: u32,
first_instance: u32,
instance_count: u32,
) -> Result<(), DrawError> { let last_index = first_index as u64 + index_count as u64; let index_limit = index_state.limit(); if last_index > index_limit { return Err(DrawError::IndexBeyondLimit {
last_index,
index_limit,
});
}
let stride_count = first_instance as u64 + instance_count as u64; for (idx, (vbs, step)) in vertex.iter().zip(step).enumerate() { let Some(vbs) = vbs else { continue;
};
let offset = (stride_count - 1) * step.stride + step.last_stride; let limit = vbs.range.end - vbs.range.start; if offset > limit { return Err(DrawError::VertexOutOfBounds {
step_mode: step.mode,
offset,
limit,
slot: idx as u32,
});
}
}
Ok(())
}
/// 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>,
}
/// 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)?;
let bind_group_guard = hub.bind_groups.read(); let pipeline_guard = hub.render_pipelines.read(); let buffer_guard = hub.buffers.read();
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);
let bind_group = bind_group_guard.get(bind_group_id).get()?;
bind_group.same_device(&state.device)?;
let max_bind_groups = state.device.limits.max_bind_groups; if index >= max_bind_groups { return Err(RenderCommandError::BindGroupIndexOutOfRange {
index,
max: max_bind_groups,
}
.into());
}
// 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()];
state
.buffer_memory_init_actions
.extend_from_slice(&bind_group.used_buffer_ranges);
state
.texture_memory_init_actions
.extend_from_slice(&bind_group.used_texture_ranges);
state.set_bind_group(index, &bind_group, offsets_range); unsafe { state.trackers.merge_bind_group(&bind_group.used)? };
state.trackers.bind_groups.insert_single(bind_group); // Note: stateless trackers are not merged: the lifetime reference // is held to the bind group itself.
Ok(())
}
if pipeline.flags.contains(PipelineFlags::WRITES_DEPTH) && is_depth_read_only { return Err(RenderCommandError::IncompatibleDepthAccess(pipeline.error_ident()).into());
} if pipeline.flags.contains(PipelineFlags::WRITES_STENCIL) && is_stencil_read_only { return Err(RenderCommandError::IncompatibleStencilAccess(pipeline.error_ident()).into());
}
let pipeline_state = PipelineState::new(&pipeline);
state
.commands
.push(ArcRenderCommand::SetPipeline(pipeline.clone()));
// If this pipeline uses push constants, zero out their values. iflet Some(iter) = pipeline_state.zero_push_constants() {
state.commands.extend(iter)
}
state
.buffer_memory_init_actions
.extend(buffer.initialization_status.read().create_action(
&buffer,
offset..(offset + size_of::<wgt::DrawIndirectArgs>() as u64),
MemoryInitKind::NeedsInitializedMemory,
));
if indexed { let index = match state.index {
Some(refmut index) => index,
None => return Err(DrawError::MissingIndexBuffer.into()),
};
state.commands.extend(index.flush());
}
/// 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("Invalid number of samples {0}")]
InvalidSampleCount(u32),
}
/// Error type returned from `RenderBundleEncoder::new` if the sample count is invalid. #[derive(Clone, Debug, Error)] #[non_exhaustive] pubenum ExecutionError { #[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. #[derive(Debug)] pubstruct RenderBundle { // Normalized command stream. It can be executed verbatim, // without re-binding anything on the pipeline change.
base: BasePass<ArcRenderCommand>, 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 {}
impl 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,
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,
} => { letmut bg = None; if bind_group.is_some() { let bind_group = bind_group.as_ref().unwrap(); let raw_bg = bind_group.try_raw(snatch_guard)?;
bg = Some(raw_bg);
} unsafe {
raw.set_bind_group(
pipeline_layout.as_ref().unwrap().raw(),
*index,
bg,
&offsets[..*num_dynamic_offsets],
)
};
offsets = &offsets[*num_dynamic_offsets..];
}
Cmd::SetPipeline(pipeline) => { unsafe { raw.set_render_pipeline(pipeline.raw()) };
/// 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. #[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 = matchself.format {
wgt::IndexFormat::Uint16 => 2,
wgt::IndexFormat::Uint32 => 4,
};
/// Generate a `SetIndexBuffer` command to prepare for an indexed draw /// command, if needed. fn flush(&mutself) -> Option<ArcRenderCommand> { ifself.is_dirty { self.is_dirty = false;
Some(ArcRenderCommand::SetIndexBuffer {
buffer: self.buffer.clone(),
index_format: self.format,
offset: self.range.start,
size: wgt::BufferSize::new(self.range.end - self.range.start),
})
} else {
None
}
}
}
/// 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. /// /// [`flush`]: IndexState::flush #[derive(Debug)] struct VertexState {
buffer: Arc<Buffer>,
range: Range<wgt::BufferAddress>,
is_dirty: bool,
}
/// Generate a `SetVertexBuffer` command for this slot, if necessary. /// /// `slot` is the index of the vertex buffer slot that `self` tracks. fn flush(&mutself, slot: u32) -> Option<ArcRenderCommand> { ifself.is_dirty { self.is_dirty = false;
Some(ArcRenderCommand::SetVertexBuffer {
slot,
buffer: self.buffer.clone(),
offset: self.range.start,
size: wgt::BufferSize::new(self.range.end - self.range.start),
})
} else {
None
}
}
}
/// A bind group that has been set at a particular index during render bundle encoding. #[derive(Debug)] struct BindState { /// The id of the bind group set at this index.
bind_group: Arc<BindGroup>,
/// The range of dynamic offsets for this bind group, in the original /// command stream's `BassPass::dynamic_offsets` array.
dynamic_offsets: Range<usize>,
/// True if this index's contents have been changed since the last time we /// generated a `SetBindGroup` command.
is_dirty: bool,
}
/// The bundle's current pipeline, and some cached information needed for validation. struct PipelineState { /// The pipeline
pipeline: Arc<RenderPipeline>,
/// How this pipeline's vertex shader traverses each vertex buffer, indexed /// by vertex buffer slot number.
steps: Vec<VertexStep>,
/// Ranges of push constants this pipeline uses, copied from the pipeline /// layout.
push_constant_ranges: ArrayVec<wgt::PushConstantRange, { SHADER_STAGE_COUNT }>,
/// The number of bind groups this pipeline uses.
used_bind_groups: usize,
}
/// Return a sequence of commands to zero the push constant ranges this /// pipeline uses. If no initialization is necessary, return `None`. fn zero_push_constants(&self) -> Option<impl Iterator<Item = ArcRenderCommand>> { if !self.push_constant_ranges.is_empty() { let nonoverlapping_ranges = super::bind::compute_nonoverlapping_ranges(&self.push_constant_ranges);
/// 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<PipelineState>,
/// The bind group set at each index, if any.
bind: ArrayVec<Option<BindState>, { hal::MAX_BIND_GROUPS }>,
/// The state of each vertex buffer slot.
vertex: ArrayVec<Option<VertexState>, { hal::MAX_VERTEX_BUFFERS }>,
/// 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>,
impl State { /// Return the current pipeline state. Return an error if none is set. fn pipeline(&self) -> Result<&PipelineState, RenderBundleErrorInner> { self.pipeline
.as_ref()
.ok_or(DrawError::MissingPipeline.into())
}
/// Mark all non-empty bind group table entries from `index` onwards as dirty. fn invalidate_bind_group_from(&mutself, index: usize) { for contents inself.bind[index..].iter_mut().flatten() {
contents.is_dirty = true;
}
}
fn set_bind_group(
&mutself,
slot: u32,
bind_group: &Arc<BindGroup>,
dynamic_offsets: Range<usize>,
) { // If this call wouldn't actually change this index's state, we can // return early. (If there are dynamic offsets, the range will always // be different.) if dynamic_offsets.is_empty() { iflet Some(ref contents) = self.bind[slot as usize] { if contents.bind_group.is_equal(bind_group) { return;
}
}
}
// Record the index's new state. self.bind[slot as usize] = Some(BindState {
bind_group: bind_group.clone(),
dynamic_offsets,
is_dirty: true,
});
// Once we've changed the bind group at a particular index, all // subsequent indices need to be rewritten. self.invalidate_bind_group_from(slot as usize + 1);
}
/// Determine which bind group slots need to be re-set after a pipeline change. /// /// Given that we are switching from the current pipeline state to `new`, /// whose layout is `layout`, mark all the bind group slots that we need to /// emit new `SetBindGroup` commands for as dirty. /// /// According to `wgpu_hal`'s rules: /// /// - If the layout of any bind group slot changes, then that slot and /// all following slots must have their bind groups re-established. /// /// - Changing the push constant ranges at all requires re-establishing /// all bind groups. fn invalidate_bind_groups(&mutself, new: &PipelineState, layout: &PipelineLayout) { matchself.pipeline {
None => { // Establishing entirely new pipeline state. self.invalidate_bind_group_from(0);
}
Some(ref old) => { if old.pipeline.is_equal(&new.pipeline) { // Everything is derived from the pipeline, so if the id has // not changed, there's no need to consider anything else. return;
}
// Any push constant change invalidates all groups. if old.push_constant_ranges != new.push_constant_ranges { self.invalidate_bind_group_from(0);
} else { let first_changed = self.bind.iter().zip(&layout.bind_group_layouts).position(
|(entry, layout)| match *entry {
Some(ref contents) => !contents.bind_group.layout.is_equal(layout),
None => false,
},
); iflet Some(slot) = first_changed { self.invalidate_bind_group_from(slot);
}
}
}
}
}
/// 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);
}
fn flush_vertices(&mutself) { let commands = self
.vertex
.iter_mut()
.enumerate()
.flat_map(|(i, vs)| vs.as_mut().and_then(|vs| vs.flush(i as u32))); self.commands.extend(commands);
}
/// Generate `SetBindGroup` commands for any bind groups that need to be updated. fn flush_binds(&mutself, used_bind_groups: usize, dynamic_offsets: &[wgt::DynamicOffset]) { // Append each dirty bind group's dynamic offsets to `flat_dynamic_offsets`. for contents inself.bind[..used_bind_groups].iter().flatten() { if contents.is_dirty { self.flat_dynamic_offsets
.extend_from_slice(&dynamic_offsets[contents.dynamic_offsets.clone()]);
}
}
// Then, generate `SetBindGroup` commands to update the dirty bind // groups. After this, all bind groups are clean. let commands = self.bind[..used_bind_groups]
.iter_mut()
.enumerate()
.flat_map(|(i, entry)| { iflet Some(refmut contents) = *entry { if contents.is_dirty {
contents.is_dirty = false; let offsets = &contents.dynamic_offsets; return Some(ArcRenderCommand::SetBindGroup {
index: i.try_into().unwrap(),
bind_group: Some(contents.bind_group.clone()),
num_dynamic_offsets: offsets.end - offsets.start,
});
}
}
None
});
pubmod bundle_ffi { usesuper::{RenderBundleEncoder, RenderCommand}; usecrate::{id, RawString}; use std::{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_push_constants(
pass: &mut RenderBundleEncoder,
stages: wgt::ShaderStages,
offset: u32,
size_bytes: u32,
data: *const u8,
) {
assert_eq!(
offset & (wgt::PUSH_CONSTANT_ALIGNMENT - 1), 0, "Push constant offset must be aligned to 4 bytes."
);
assert_eq!(
size_bytes & (wgt::PUSH_CONSTANT_ALIGNMENT - 1), 0, "Push constant 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.push_constant_data.len().try_into().expect( "Ran out of push constant space. Don't set 4gb of push constants 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.26 Sekunden
(vorverarbeitet am 2026-06-20)
¤
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.