/// A pass's [encoder state](https://www.w3.org/TR/webgpu/#encoder-state) and /// its validity are two distinct conditions, i.e., the full matrix of /// (open, ended) x (valid, invalid) is possible. /// /// The presence or absence of the `parent` `Option` indicates the pass's state. /// The presence or absence of an error in `base.error` indicates the pass's /// validity. pubstruct ComputePass { /// All pass data & records is stored here.
base: ComputeBasePass,
/// Parent command encoder that this pass records commands into. /// /// If this is `Some`, then the pass is in WebGPU's "open" state. If it is /// `None`, then the pass is in the "ended" state. /// See <https://www.w3.org/TR/webgpu/#encoder-state>
parent: Option<Arc<CommandEncoder>>,
impl ComputePass { /// If the parent command encoder is invalid, the returned pass will be invalid. fn new(parent: Arc<CommandEncoder>, desc: ArcComputePassDescriptor) -> Self { let ArcComputePassDescriptor {
label,
timestamp_writes,
} = desc;
#[derive(Clone, Debug, Default)] pubstruct ComputePassDescriptor<'a, PTW = PassTimestampWrites> { pub label: Label<'a>, /// Defines where and when timestamp values will be written for this pass. pub timestamp_writes: Option<PTW>,
}
/// cbindgen:ignore type ArcComputePassDescriptor<'a> = ComputePassDescriptor<'a, ArcPassTimestampWrites>;
#[derive(Clone, Debug, Error)] #[non_exhaustive] pubenum DispatchError { #[error("Compute pipeline must be set")]
MissingPipeline(pass::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("Not all immediate data required by the pipeline has been set via set_immediates (missing byte ranges: {missing})")]
MissingImmediateData {
missing: naga::valid::ImmediateSlots,
},
}
/// Error encountered when performing a compute pass. #[derive(Clone, Debug, Error)] pubenum ComputePassErrorInner { #[error(transparent)]
Device(#[from] DeviceError), #[error(transparent)]
EncoderState(#[from] EncoderStateError), #[error("Parent encoder is invalid")]
InvalidParentEncoder, #[error(transparent)]
DebugGroupError(#[from] DebugGroupError), #[error(transparent)]
BindGroupIndexOutOfRange(#[from] pass::BindGroupIndexOutOfRange), #[error(transparent)]
DestroyedResource(#[from] DestroyedResourceError), #[error("Indirect buffer offset {0:?} is not a multiple of 4")]
UnalignedIndirectBufferOffset(BufferAddress), #[error("Indirect buffer of {args_size} bytes starting at offset {offset} would overrun buffer of size {buffer_size}")]
IndirectBufferOverrun {
args_size: u64,
offset: u64,
buffer_size: u64,
}, #[error(transparent)]
ResourceUsageCompatibility(#[from] ResourceUsageCompatibilityError), #[error(transparent)]
MissingBufferUsage(#[from] MissingBufferUsageError), #[error(transparent)]
Dispatch(#[from] DispatchError), #[error(transparent)]
Bind(#[from] BindError), #[error(transparent)]
ImmediateData(#[from] ImmediateUploadError), #[error("Immediate data offset must be aligned to 4 bytes")]
ImmediateOffsetAlignment, #[error("Immediate data size must be aligned to 4 bytes")]
ImmediateDataizeAlignment, #[error("Ran out of immediate data space. Don't set 4gb of immediates per ComputePass.")]
ImmediateOutOfMemory, #[error(transparent)]
QueryUse(#[from] QueryUseError), #[error(transparent)]
TransitionResources(#[from] TransitionResourcesError), #[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), #[error(transparent)]
TimestampWrites(#[from] TimestampWritesError), // This one is unreachable, but required for generic pass support #[error(transparent)]
InvalidValuesOffset(#[from] pass::InvalidValuesOffset),
}
/// Error encountered when performing a compute pass, stored for later reporting /// when encoding ends. #[derive(Clone, Debug, Error)] #[error("{scope}")] pubstruct ComputePassError { pub scope: PassErrorScope, #[source] pub(super) inner: ComputePassErrorInner,
}
/// A bitmask, tracking which 4-byte slots have been written via `set_immediates`. /// Checked against the pipeline's required slots before each dispatch.
immediate_slots_set: naga::valid::ImmediateSlots,
/// Flush binding state in preparation for a dispatch. /// /// # Differences between render and compute passes /// /// There are differences between the `flush_bindings` implementations for /// render and compute passes, because render passes have a single usage /// scope for the entire pass, and compute passes have a separate usage /// scope for each dispatch. /// /// For compute passes, bind groups are merged into a fresh usage scope /// here, not into the pass usage scope within calls to `set_bind_group`. As /// specified by WebGPU, for compute passes, we merge only the bind groups /// that are actually used by the pipeline, unlike render passes, which /// merge every bind group that is ever set, even if it is not ultimately /// used by the pipeline. /// /// For compute passes, we call `drain_barriers` here, because barriers may /// be needed before each dispatch if a previous dispatch had a conflicting /// usage. For render passes, barriers are emitted once at the start of the /// render pass. /// /// # Indirect buffer handling /// /// The `indirect_buffer` argument should be passed for any indirect /// dispatch (with or without validation). It will be checked for /// conflicting usages according to WebGPU rules. For the purpose of /// these rules, the fact that we have actually processed the buffer in /// the validation pass is an implementation detail. /// /// The `track_indirect_buffer` argument should be set when doing indirect /// dispatch *without* validation. In this case, the indirect buffer will /// be added to the tracker in order to generate any necessary transitions /// for that usage. /// /// When doing indirect dispatch *with* validation, the indirect buffer is /// processed by the validation pass and is not used by the actual dispatch. /// The indirect validation code handles transitions for the validation /// pass. fn flush_bindings(
&mutself,
indirect_buffer: Option<&Arc<Buffer>>,
track_indirect_buffer: bool,
) -> Result<(), ComputePassErrorInner> { for bind_group inself.pass.binder.list_active() { unsafe { self.pass.scope.merge_bind_group(&bind_group.used)? };
}
// Add the indirect buffer. Because usage scopes are per-dispatch, this // is the only place where INDIRECT usage could be added, and it is safe // for us to remove it below. iflet Some(buffer) = indirect_buffer { self.pass
.scope
.buffers
.merge_single(buffer, wgt::BufferUses::INDIRECT)?;
}
// For compute, usage scopes are associated with each dispatch and not // with the pass as a whole. However, because the cost of creating and // dropping `UsageScope`s is significant (even with the pool), we // add and then remove usage from a single usage scope.
for bind_group inself.pass.binder.list_active() { self.intermediate_trackers
.set_and_remove_from_usage_scope_sparse(&mutself.pass.scope, &bind_group.used);
}
/// Compute pass version of [`command::transition_resources`](crate::command::transition_resources). /// See also `State::flush_bindings` for details on the implementation. fn transition_resources(
state: &mut State,
buffer_transitions: Vec<wgt::BufferTransition<Arc<Buffer>>>,
texture_transitions: Vec<wgt::TextureTransition<Arc<TextureView>>>,
) -> Result<(), TransitionResourcesError> { let indices = &state.pass.base.device.tracker_indices;
state.pass.scope.buffers.set_size(indices.buffers.size());
state.pass.scope.textures.set_size(indices.textures.size());
state
.intermediate_trackers
.textures
.set_and_remove_from_usage_scope_sparse(&mut state.pass.scope.textures, &textures);
// Record any needed barriers based on tracker data
CommandEncoder::drain_barriers(
state.pass.base.raw_encoder,
&mut state.intermediate_trackers,
state.pass.base.snatch_guard,
);
Ok(())
}
// Running the compute pass.
impl Global { /// Creates a compute pass. /// /// If creation fails, an invalid pass is returned. Attempting to record /// commands into an invalid pass is permitted, but a validation error will /// ultimately be generated when the parent encoder is finished, and it is /// not possible to run any commands from the invalid pass. /// /// If successful, puts the encoder into the [`Locked`] state. /// /// [`Locked`]: crate::command::CommandEncoderStatus::Locked pubfn command_encoder_begin_compute_pass(
&self,
encoder_id: id::CommandEncoderId,
desc: &ComputePassDescriptor<'_>,
) -> (ComputePass, Option<CommandEncoderError>) { use EncoderStateError as SErr;
let scope = PassErrorScope::Pass; let hub = &self.hub;
let label = desc.label.as_deref().map(Cow::Borrowed);
let cmd_enc = hub.command_encoders.get(encoder_id); letmut cmd_buf_data = cmd_enc.data.lock();
match desc
.timestamp_writes
.as_ref()
.map(|tw| { Self::validate_pass_timestamp_writes::<ComputePassErrorInner>(
&cmd_enc.device,
&hub.query_sets.read(),
tw,
)
})
.transpose()
{
Ok(timestamp_writes) => { let arc_desc = ArcComputePassDescriptor {
label,
timestamp_writes,
};
(ComputePass::new(cmd_enc, arc_desc), None)
}
Err(err) => (
ComputePass::new_invalid(cmd_enc, &label, err.map_pass_err(scope)),
None,
),
}
}
Err(err @ SErr::Locked) => { // Attempting to open a new pass while the encoder is locked // invalidates the encoder, but does not generate a validation // error.
cmd_buf_data.invalidate(err.clone());
drop(cmd_buf_data);
(
ComputePass::new_invalid(cmd_enc, &label, err.map_pass_err(scope)),
None,
)
}
Err(err @ (SErr::Ended | SErr::Submitted)) => { // Attempting to open a new pass after the encode has ended // generates an immediate validation error.
drop(cmd_buf_data);
(
ComputePass::new_invalid(cmd_enc, &label, err.clone().map_pass_err(scope)),
Some(err.into()),
)
}
Err(err @ SErr::Invalid) => { // Passes can be opened even on an invalid encoder. Such passes // are even valid, but since there's no visible side-effect of // the pass being valid and there's no point in storing recorded // commands that will ultimately be discarded, we open an // invalid pass to save that work.
drop(cmd_buf_data);
(
ComputePass::new_invalid(cmd_enc, &label, err.map_pass_err(scope)),
None,
)
}
Err(SErr::Unlocked) => {
unreachable!("lock_encoder cannot fail due to the encoder being unlocked")
}
}
}
let cmd_enc = pass.parent.take().ok_or(EncoderStateError::Ended)?; letmut cmd_buf_data = cmd_enc.data.lock();
cmd_buf_data.unlock_encoder()?;
let base = pass.base.take();
iflet Err(ComputePassError {
inner:
ComputePassErrorInner::EncoderState(
err @ (EncoderStateError::Locked | EncoderStateError::Ended),
),
scope: _,
}) = base
{ // Most encoding errors are detected and raised within `finish()`. // // However, we raise a validation error here if the pass was opened // within another pass, or on a finished encoder. The latter is // particularly important, because in that case reporting errors via // `CommandEncoder::finish` is not possible. return Err(err.clone());
}
// 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.
parent_state
.raw_encoder
.close_if_open()
.map_pass_err(pass_scope)?; let raw_encoder = parent_state
.raw_encoder
.open_pass(base.label.as_deref())
.map_pass_err(pass_scope)?;
let indices = &device.tracker_indices;
state
.pass
.base
.tracker
.buffers
.set_size(indices.buffers.size());
state
.pass
.base
.tracker
.textures
.set_size(indices.textures.size());
let query_set = state
.pass
.base
.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
.pass
.base
.raw_encoder
.reset_queries(query_set.raw(), range);
}
}
let State {
pass: pass::PassState {
pending_discard_init_fixups,
..
},
intermediate_trackers,
..
} = state;
// Stop the current command encoder.
parent_state.raw_encoder.close().map_pass_err(pass_scope)?;
// Create a new command encoder, which we will insert _before_ the body of the compute pass. // // Use that buffer to insert barriers and clear discarded images. let transit = parent_state
.raw_encoder
.open_pass(hal_label(
Some("(wgpu internal) Pre Pass"),
device.instance_flags,
))
.map_pass_err(pass_scope)?;
fixup_discarded_surfaces(
pending_discard_init_fixups.into_iter(),
transit,
&mut parent_state.tracker.textures,
device,
parent_state.snatch_guard,
);
CommandEncoder::insert_barriers_from_tracker(
transit,
parent_state.tracker,
&intermediate_trackers,
parent_state.snatch_guard,
); // Close the command encoder, and swap it with the previous.
parent_state
.raw_encoder
.close_and_swap()
.map_pass_err(pass_scope)?;
let pipeline = state
.pass
.base
.tracker
.compute_pipelines
.insert_single(pipeline)
.clone();
unsafe {
state
.pass
.base
.raw_encoder
.set_compute_pipeline(pipeline.raw());
}
// Rebind resources
pass::change_pipeline_layout::<ComputePassErrorInner, _>(
&mut state.pass,
&pipeline.layout,
&pipeline.late_sized_buffer_groups,
|| { // This only needs to be here for compute pipelines because they use immediates for // validating indirect draws.
state.immediates.clear(); // Note that can only be one range for each stage. See the `MoreThanOneImmediateRangePerStage` error. if pipeline.layout.immediate_size != 0 { // Note that non-0 range start doesn't work anyway https://github.com/gfx-rs/wgpu/issues/4502 let len = pipeline.layout.immediate_size as usize
/ wgt::IMMEDIATE_DATA_ALIGNMENT as usize;
state.immediates.extend(core::iter::repeat_n(0, len));
}
},
)
}
for (i, group, dynamic_offsets) in state.pass.binder.list_valid() { let raw_bg = group.try_raw(state.pass.base.snatch_guard)?; unsafe {
state.pass.base.raw_encoder.set_bind_group(
pipeline.layout.raw(),
i as u32,
raw_bg,
dynamic_offsets,
);
}
}
}
unsafe {
state
.pass
.base
.raw_encoder
.transition_buffers(&[hal::BufferBarrier {
buffer: params.dst_buffer,
usage: hal::StateTransition {
from: wgt::BufferUses::STORAGE_READ_WRITE,
to: wgt::BufferUses::INDIRECT,
},
}]);
}
let buf_raw = buffer.try_raw(state.pass.base.snatch_guard)?; unsafe {
state
.pass
.base
.raw_encoder
.dispatch_workgroups_indirect(buf_raw, offset);
}
}
Ok(())
}
// Recording a compute pass. // // The only error that should be returned from these methods is // `EncoderStateError::Ended`, when the pass has already ended and an immediate // validation error is raised. // // All other errors should be stored in the pass for later reporting when // `CommandEncoder.finish()` is called. // // The `pass_try!` macro should be used to handle errors appropriately. Note // that the `pass_try!` and `pass_base!` macros may return early from the // function that invokes them, like the `?` operator. impl Global { pubfn compute_pass_set_bind_group(
&self,
pass: &mut ComputePass,
index: u32,
bind_group_id: Option<id::BindGroupId>,
offsets: &[DynamicOffset],
) -> Result<(), PassStateError> { let scope = PassErrorScope::SetBindGroup;
// This statement will return an error if the pass is ended. It's // important the error check comes before the early-out for // `set_and_check_redundant`. let base = pass_base!(pass, scope);
// This statement will return an error if the pass is ended. // Its important the error check comes before the early-out for `redundant`. let base = pass_base!(pass, scope);
if redundant { return Ok(());
}
let hub = &self.hub; let pipeline = pass_try!(base, scope, hub.compute_pipelines.get(pipeline_id).get());
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.