use thiserror::Error; use wgt::{BufferAddress, DynamicOffset};
usesuper::{bind::BinderError, memory_init::CommandBufferTextureMemoryActions}; usecrate::ray_tracing::TlasAction; use std::{fmt, mem::size_of, str, sync::Arc};
pubstruct ComputePass { /// All pass data & records is stored here. /// /// If this is `None`, the pass is in the 'ended' state and can no longer be used. /// Any attempt to record more commands will result in a validation error.
base: Option<BasePass<ArcComputeCommand>>,
/// Parent command buffer that this pass records commands into. /// /// If it is none, this pass is invalid and any operation on it will return an error.
parent: Option<Arc<CommandBuffer>>,
impl ComputePass { /// If the parent command buffer is invalid, the returned pass will be invalid. fn new(parent: Option<Arc<CommandBuffer>>, desc: ArcComputePassDescriptor) -> Self { let ArcComputePassDescriptor {
label,
timestamp_writes,
} = desc;
#[derive(Clone, Debug, Default)] pubstruct ComputePassDescriptor<'a> { pub label: Label<'a>, /// Defines where and when timestamp values will be written for this pass. pub timestamp_writes: Option<&'a PassTimestampWrites>,
}
struct ArcComputePassDescriptor<'a> { pub label: &'a Label<'a>, /// Defines where and when timestamp values will be written for this pass. pub timestamp_writes: Option<ArcPassTimestampWrites>,
}
#[derive(Clone, Debug, Error)] #[non_exhaustive] pubenum DispatchError { #[error("Compute pipeline must be set")]
MissingPipeline, #[error(transparent)]
IncompatibleBindGroup(#[from] Box<BinderError>), #[error( "Each current dispatch group size dimension ({current:?}) must be less or equal to {limit}"
)]
InvalidGroupSize { current: [u32; 3], limit: u32 }, #[error(transparent)]
BindingSizeTooSmall(#[from] LateMinBufferBindingSizeMismatch),
}
/// Error encountered when performing a compute pass. #[derive(Clone, Debug, Error)] pubenum ComputePassErrorInner { #[error(transparent)]
Device(#[from] DeviceError), #[error(transparent)]
Encoder(#[from] CommandEncoderError), #[error("Parent encoder is invalid")]
InvalidParentEncoder, #[error("Bind group index {index} is greater than the device's requested `max_bind_group` limit {max}")]
BindGroupIndexOutOfRange { index: u32, max: u32 }, #[error(transparent)]
DestroyedResource(#[from] DestroyedResourceError), #[error("Indirect buffer offset {0:?} is not a multiple of 4")]
UnalignedIndirectBufferOffset(BufferAddress), #[error("Indirect buffer uses bytes {offset}..{end_offset} which overruns indirect buffer of size {buffer_size}")]
IndirectBufferOverrun {
offset: u64,
end_offset: u64,
buffer_size: u64,
}, #[error(transparent)]
ResourceUsageCompatibility(#[from] ResourceUsageCompatibilityError), #[error(transparent)]
MissingBufferUsage(#[from] MissingBufferUsageError), #[error("Cannot pop debug group, because number of pushed debug groups is zero")]
InvalidPopDebugGroup, #[error(transparent)]
Dispatch(#[from] DispatchError), #[error(transparent)]
Bind(#[from] BindError), #[error(transparent)]
PushConstants(#[from] PushConstantUploadError), #[error("Push constant offset must be aligned to 4 bytes")]
PushConstantOffsetAlignment, #[error("Push constant size must be aligned to 4 bytes")]
PushConstantSizeAlignment, #[error("Ran out of push constant space. Don't set 4gb of push constants per ComputePass.")]
PushConstantOutOfMemory, #[error(transparent)]
QueryUse(#[from] QueryUseError), #[error(transparent)]
MissingFeatures(#[from] MissingFeatures), #[error(transparent)]
MissingDownlevelFlags(#[from] MissingDownlevelFlags), #[error("The compute pass has already been ended and no further commands can be recorded")]
PassEnded, #[error(transparent)]
InvalidResource(#[from] InvalidResourceError),
}
/// Immediate texture inits required because of prior discards. Need to /// be inserted before texture reads.
pending_discard_init_fixups: SurfacesInDiscardState,
}
// `extra_buffer` is there to represent the indirect buffer that is also // part of the usage scope. fn flush_states(
&mutself,
indirect_buffer: Option<TrackerIndex>,
) -> Result<(), ResourceUsageCompatibilityError> { for bind_group inself.binder.list_active() { unsafe { self.scope.merge_bind_group(&bind_group.used)? }; // Note: stateless trackers are not merged: the lifetime reference // is held to the bind group itself.
}
// Add the state of the indirect buffer if it hasn't been hit before. unsafe { self.intermediate_trackers
.buffers
.set_and_remove_from_usage_scope_sparse(&mutself.scope.buffers, indirect_buffer);
}
impl Global { /// Creates a compute pass. /// /// If creation fails, an invalid pass is returned. /// Any operation on an invalid pass will return an error. /// /// If successful, puts the encoder into the [`Locked`] state. /// /// [`Locked`]: crate::command::CommandEncoderStatus::Locked pubfn command_encoder_create_compute_pass(
&self,
encoder_id: id::CommandEncoderId,
desc: &ComputePassDescriptor<'_>,
) -> (ComputePass, Option<CommandEncoderError>) { let hub = &self.hub;
letmut arc_desc = ArcComputePassDescriptor {
label: &desc.label,
timestamp_writes: None, // Handle only once we resolved the encoder.
};
let make_err = |e, arc_desc| (ComputePass::new(None, arc_desc), Some(e));
let cmd_buf = hub.command_buffers.get(encoder_id.into_command_buffer_id());
/// Note that this differs from [`Self::compute_pass_end`], it will /// create a new pass, replay the commands and end the pass. #[doc(hidden)] #[cfg(any(feature = "serde", feature = "replay"))] pubfn compute_pass_end_with_unresolved_commands(
&self,
encoder_id: id::CommandEncoderId,
base: BasePass<super::ComputeCommand>,
timestamp_writes: Option<&PassTimestampWrites>,
) -> Result<(), ComputePassError> { let pass_scope = PassErrorScope::Pass;
#[cfg(feature = "trace")]
{ let cmd_buf = self
.hub
.command_buffers
.get(encoder_id.into_command_buffer_id()); letmut cmd_buf_data = cmd_buf.data.lock(); let cmd_buf_data = cmd_buf_data.get_inner().map_pass_err(pass_scope)?;
// We automatically keep extending command buffers over time, and because // we want to insert a command buffer _before_ what we're about to record, // we need to make sure to close the previous one.
encoder.close_if_open().map_pass_err(pass_scope)?; let raw_encoder = encoder
.open_pass(base.label.as_deref())
.map_pass_err(pass_scope)?;
letmut state = State {
binder: Binder::new(),
pipeline: None,
scope: device.new_usage_scope(),
debug_scope_depth: 0,
let indices = &state.device.tracker_indices;
state.tracker.buffers.set_size(indices.buffers.size());
state.tracker.textures.set_size(indices.textures.size());
let query_set = state.tracker.query_sets.insert_single(tw.query_set);
// Unlike in render passes we can't delay resetting the query sets since // there is no auxiliary pass. let range = iflet (Some(index_a), Some(index_b)) =
(tw.beginning_of_pass_write_index, tw.end_of_pass_write_index)
{
Some(index_a.min(index_b)..index_a.max(index_b) + 1)
} else {
tw.beginning_of_pass_write_index
.or(tw.end_of_pass_write_index)
.map(|i| i..i + 1)
}; // Range should always be Some, both values being None should lead to a validation error. // But no point in erroring over that nuance here! iflet Some(range) = range { unsafe {
state.raw_encoder.reset_queries(query_set.raw(), range);
}
}
let State {
snatch_guard,
tracker,
intermediate_trackers,
pending_discard_init_fixups,
..
} = state;
// Stop the current command buffer.
encoder.close().map_pass_err(pass_scope)?;
// Create a new command buffer, which we will insert _before_ the body of the compute pass. // // Use that buffer to insert barriers and clear discarded images. let transit = encoder
.open_pass(Some("(wgpu internal) Pre Pass"))
.map_pass_err(pass_scope)?;
fixup_discarded_surfaces(
pending_discard_init_fixups.into_iter(),
transit,
&mut tracker.textures,
device,
&snatch_guard,
);
CommandBuffer::insert_barriers_from_tracker(
transit,
tracker,
&intermediate_trackers,
&snatch_guard,
); // Close the command buffer, and swap it with the previous.
encoder.close_and_swap().map_pass_err(pass_scope)?;
cmd_buf_data_guard.mark_successful();
state
.buffer_memory_init_actions
.extend(bind_group.used_buffer_ranges.iter().filter_map(|action| {
action
.buffer
.initialization_status
.read()
.check_action(action)
}));
for action in bind_group.used_texture_ranges.iter() {
state
.pending_discard_init_fixups
.extend(state.texture_memory_actions.register_init_action(action));
}
let pipeline_layout = state.binder.pipeline_layout.clone(); let entries = state
.binder
.assign_group(index as usize, bind_group, &state.temp_offsets); if !entries.is_empty() && pipeline_layout.is_some() { let pipeline_layout = pipeline_layout.as_ref().unwrap().raw(); for (i, e) in entries.iter().enumerate() { iflet Some(group) = e.group.as_ref() { let raw_bg = group.try_raw(&state.snatch_guard)?; unsafe {
state.raw_encoder.set_bind_group(
pipeline_layout,
index + i as u32,
Some(raw_bg),
&e.dynamic_offsets,
);
}
}
}
}
Ok(())
}
// Rebind resources if state.binder.pipeline_layout.is_none()
|| !state
.binder
.pipeline_layout
.as_ref()
.unwrap()
.is_equal(&pipeline.layout)
{ let (start_index, entries) = state
.binder
.change_pipeline_layout(&pipeline.layout, &pipeline.late_sized_buffer_groups); if !entries.is_empty() { for (i, e) in entries.iter().enumerate() { iflet Some(group) = e.group.as_ref() { let raw_bg = group.try_raw(&state.snatch_guard)?; unsafe {
state.raw_encoder.set_bind_group(
pipeline.layout.raw(),
start_index as u32 + i as u32,
Some(raw_bg),
&e.dynamic_offsets,
);
}
}
}
}
// TODO: integrate this in the code below once we simplify push constants
state.push_constants.clear(); // Note that can only be one range for each stage. See the `MoreThanOnePushConstantRangePerStage` error. iflet Some(push_constant_range) =
pipeline.layout.push_constant_ranges.iter().find_map(|pcr| {
pcr.stages
.contains(wgt::ShaderStages::COMPUTE)
.then_some(pcr.range.clone())
})
{ // Note that non-0 range start doesn't work anyway https://github.com/gfx-rs/wgpu/issues/4502 let len = push_constant_range.len() / wgt::PUSH_CONSTANT_ALIGNMENT as usize;
state.push_constants.extend(core::iter::repeat(0).take(len));
}
// Clear push constant ranges let non_overlapping = super::bind::compute_nonoverlapping_ranges(&pipeline.layout.push_constant_ranges); for range in non_overlapping { let offset = range.range.start; let size_bytes = range.range.end - offset; super::push_constant_clear(offset, size_bytes, |clear_offset, clear_data| unsafe {
state.raw_encoder.set_push_constants(
pipeline.layout.raw(),
wgt::ShaderStages::COMPUTE,
clear_offset,
clear_data,
);
});
}
}
Ok(())
}
fn set_push_constant(
state: &mut State,
push_constant_data: &[u32],
offset: u32,
size_bytes: u32,
values_offset: u32,
) -> Result<(), ComputePassErrorInner> { let end_offset_bytes = offset + size_bytes; let values_end_offset = (values_offset + size_bytes / wgt::PUSH_CONSTANT_ALIGNMENT) as usize; let data_slice = &push_constant_data[(values_offset as usize)..values_end_offset];
let pipeline_layout = state
.binder
.pipeline_layout
.as_ref() // TODO: don't error here, lazily update the push constants using `state.push_constants`
.ok_or(ComputePassErrorInner::Dispatch(
DispatchError::MissingPipeline,
))?;
let offset_in_elements = (offset / wgt::PUSH_CONSTANT_ALIGNMENT) as usize; let size_in_elements = (size_bytes / wgt::PUSH_CONSTANT_ALIGNMENT) as usize;
state.push_constants[offset_in_elements..][..size_in_elements].copy_from_slice(data_slice);
for (i, e) in state.binder.list_valid() { let group = e.group.as_ref().unwrap(); let raw_bg = group.try_raw(&state.snatch_guard)?; unsafe {
state.raw_encoder.set_bind_group(
pipeline.layout.raw(),
i as u32,
Some(raw_bg),
&e.dynamic_offsets,
);
}
}
}
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.