diff options
Diffstat (limited to 'third_party/rust/wgpu-core/src/command/mod.rs')
-rw-r--r-- | third_party/rust/wgpu-core/src/command/mod.rs | 732 |
1 files changed, 732 insertions, 0 deletions
diff --git a/third_party/rust/wgpu-core/src/command/mod.rs b/third_party/rust/wgpu-core/src/command/mod.rs new file mode 100644 index 0000000000..2d5fca200a --- /dev/null +++ b/third_party/rust/wgpu-core/src/command/mod.rs @@ -0,0 +1,732 @@ +mod bind; +mod bundle; +mod clear; +mod compute; +mod draw; +mod memory_init; +mod query; +mod render; +mod transfer; + +use std::slice; +use std::sync::Arc; + +pub(crate) use self::clear::clear_texture; +pub use self::{ + bundle::*, clear::ClearError, compute::*, draw::*, query::*, render::*, transfer::*, +}; + +use self::memory_init::CommandBufferTextureMemoryActions; + +use crate::device::{Device, DeviceError}; +use crate::error::{ErrorFormatter, PrettyError}; +use crate::hub::Hub; +use crate::id::CommandBufferId; +use crate::snatch::SnatchGuard; + +use crate::init_tracker::BufferInitTrackerAction; +use crate::resource::{Resource, ResourceInfo, ResourceType}; +use crate::track::{Tracker, UsageScope}; +use crate::{api_log, global::Global, hal_api::HalApi, id, resource_log, Label}; + +use hal::CommandEncoder as _; +use parking_lot::Mutex; +use thiserror::Error; + +#[cfg(feature = "trace")] +use crate::device::trace::Command as TraceCommand; + +const PUSH_CONSTANT_CLEAR_ARRAY: &[u32] = &[0_u32; 64]; + +#[derive(Debug)] +pub(crate) enum CommandEncoderStatus { + Recording, + Finished, + Error, +} + +pub(crate) struct CommandEncoder<A: HalApi> { + raw: A::CommandEncoder, + list: Vec<A::CommandBuffer>, + is_open: bool, + label: Option<String>, +} + +//TODO: handle errors better +impl<A: HalApi> CommandEncoder<A> { + /// Closes the live encoder + fn close_and_swap(&mut self) -> Result<(), DeviceError> { + if self.is_open { + self.is_open = false; + let new = unsafe { self.raw.end_encoding()? }; + self.list.insert(self.list.len() - 1, new); + } + + Ok(()) + } + + fn close(&mut self) -> Result<(), DeviceError> { + if self.is_open { + self.is_open = false; + let cmd_buf = unsafe { self.raw.end_encoding()? }; + self.list.push(cmd_buf); + } + + Ok(()) + } + + fn discard(&mut self) { + if self.is_open { + self.is_open = false; + unsafe { self.raw.discard_encoding() }; + } + } + + fn open(&mut self) -> Result<&mut A::CommandEncoder, DeviceError> { + if !self.is_open { + self.is_open = true; + let label = self.label.as_deref(); + unsafe { self.raw.begin_encoding(label)? }; + } + + Ok(&mut self.raw) + } + + fn open_pass(&mut self, label: Option<&str>) -> Result<(), DeviceError> { + self.is_open = true; + unsafe { self.raw.begin_encoding(label)? }; + + Ok(()) + } +} + +pub struct BakedCommands<A: HalApi> { + pub(crate) encoder: A::CommandEncoder, + pub(crate) list: Vec<A::CommandBuffer>, + pub(crate) trackers: Tracker<A>, + buffer_memory_init_actions: Vec<BufferInitTrackerAction<A>>, + texture_memory_actions: CommandBufferTextureMemoryActions<A>, +} + +pub(crate) struct DestroyedBufferError(pub id::BufferId); +pub(crate) struct DestroyedTextureError(pub id::TextureId); + +pub struct CommandBufferMutable<A: HalApi> { + encoder: CommandEncoder<A>, + status: CommandEncoderStatus, + pub(crate) trackers: Tracker<A>, + buffer_memory_init_actions: Vec<BufferInitTrackerAction<A>>, + texture_memory_actions: CommandBufferTextureMemoryActions<A>, + pub(crate) pending_query_resets: QueryResetMap<A>, + #[cfg(feature = "trace")] + pub(crate) commands: Option<Vec<TraceCommand>>, +} + +impl<A: HalApi> CommandBufferMutable<A> { + pub(crate) fn open_encoder_and_tracker( + &mut self, + ) -> Result<(&mut A::CommandEncoder, &mut Tracker<A>), DeviceError> { + let encoder = self.encoder.open()?; + let tracker = &mut self.trackers; + + Ok((encoder, tracker)) + } +} + +pub struct CommandBuffer<A: HalApi> { + pub(crate) device: Arc<Device<A>>, + limits: wgt::Limits, + support_clear_texture: bool, + pub(crate) info: ResourceInfo<CommandBuffer<A>>, + pub(crate) data: Mutex<Option<CommandBufferMutable<A>>>, +} + +impl<A: HalApi> Drop for CommandBuffer<A> { + fn drop(&mut self) { + if self.data.lock().is_none() { + return; + } + resource_log!("resource::CommandBuffer::drop {:?}", self.info.label()); + let mut baked = self.extract_baked_commands(); + unsafe { + baked.encoder.reset_all(baked.list.into_iter()); + } + unsafe { + use hal::Device; + self.device.raw().destroy_command_encoder(baked.encoder); + } + } +} + +impl<A: HalApi> CommandBuffer<A> { + pub(crate) fn new( + encoder: A::CommandEncoder, + device: &Arc<Device<A>>, + #[cfg(feature = "trace")] enable_tracing: bool, + label: Option<String>, + ) -> Self { + CommandBuffer { + device: device.clone(), + limits: device.limits.clone(), + support_clear_texture: device.features.contains(wgt::Features::CLEAR_TEXTURE), + info: ResourceInfo::new( + label + .as_ref() + .unwrap_or(&String::from("<CommandBuffer>")) + .as_str(), + ), + data: Mutex::new(Some(CommandBufferMutable { + encoder: CommandEncoder { + raw: encoder, + is_open: false, + list: Vec::new(), + label, + }, + status: CommandEncoderStatus::Recording, + trackers: Tracker::new(), + buffer_memory_init_actions: Default::default(), + texture_memory_actions: Default::default(), + pending_query_resets: QueryResetMap::new(), + #[cfg(feature = "trace")] + commands: if enable_tracing { + Some(Vec::new()) + } else { + None + }, + })), + } + } + + pub(crate) fn insert_barriers_from_tracker( + raw: &mut A::CommandEncoder, + base: &mut Tracker<A>, + head: &Tracker<A>, + snatch_guard: &SnatchGuard, + ) { + profiling::scope!("insert_barriers"); + + base.buffers.set_from_tracker(&head.buffers); + base.textures.set_from_tracker(&head.textures); + + Self::drain_barriers(raw, base, snatch_guard); + } + + pub(crate) fn insert_barriers_from_scope( + raw: &mut A::CommandEncoder, + base: &mut Tracker<A>, + head: &UsageScope<A>, + snatch_guard: &SnatchGuard, + ) { + profiling::scope!("insert_barriers"); + + base.buffers.set_from_usage_scope(&head.buffers); + base.textures.set_from_usage_scope(&head.textures); + + Self::drain_barriers(raw, base, snatch_guard); + } + + pub(crate) fn drain_barriers( + raw: &mut A::CommandEncoder, + base: &mut Tracker<A>, + snatch_guard: &SnatchGuard, + ) { + profiling::scope!("drain_barriers"); + + let buffer_barriers = base.buffers.drain_transitions(snatch_guard); + let (transitions, textures) = base.textures.drain_transitions(snatch_guard); + let texture_barriers = transitions + .into_iter() + .enumerate() + .map(|(i, p)| p.into_hal(textures[i].unwrap().raw().unwrap())); + + unsafe { + raw.transition_buffers(buffer_barriers); + raw.transition_textures(texture_barriers); + } + } +} + +impl<A: HalApi> CommandBuffer<A> { + fn get_encoder( + hub: &Hub<A>, + id: id::CommandEncoderId, + ) -> Result<Arc<Self>, CommandEncoderError> { + let storage = hub.command_buffers.read(); + match storage.get(id.transmute()) { + Ok(cmd_buf) => match cmd_buf.data.lock().as_ref().unwrap().status { + CommandEncoderStatus::Recording => Ok(cmd_buf.clone()), + CommandEncoderStatus::Finished => Err(CommandEncoderError::NotRecording), + CommandEncoderStatus::Error => Err(CommandEncoderError::Invalid), + }, + Err(_) => Err(CommandEncoderError::Invalid), + } + } + + pub fn is_finished(&self) -> bool { + match self.data.lock().as_ref().unwrap().status { + CommandEncoderStatus::Finished => true, + _ => false, + } + } + + pub(crate) fn extract_baked_commands(&mut self) -> BakedCommands<A> { + log::trace!( + "Extracting BakedCommands from CommandBuffer {:?}", + self.info.label() + ); + let data = self.data.lock().take().unwrap(); + BakedCommands { + encoder: data.encoder.raw, + list: data.encoder.list, + trackers: data.trackers, + buffer_memory_init_actions: data.buffer_memory_init_actions, + texture_memory_actions: data.texture_memory_actions, + } + } + + pub(crate) fn from_arc_into_baked(self: Arc<Self>) -> BakedCommands<A> { + if let Some(mut command_buffer) = Arc::into_inner(self) { + command_buffer.extract_baked_commands() + } else { + panic!("CommandBuffer cannot be destroyed because is still in use"); + } + } +} + +impl<A: HalApi> Resource for CommandBuffer<A> { + const TYPE: ResourceType = "CommandBuffer"; + + type Marker = crate::id::markers::CommandBuffer; + + fn as_info(&self) -> &ResourceInfo<Self> { + &self.info + } + + fn as_info_mut(&mut self) -> &mut ResourceInfo<Self> { + &mut self.info + } + + fn label(&self) -> String { + let str = match self.data.lock().as_ref().unwrap().encoder.label.as_ref() { + Some(label) => label.clone(), + _ => String::new(), + }; + str + } +} + +#[derive(Copy, Clone, Debug)] +pub struct BasePassRef<'a, C> { + pub label: Option<&'a str>, + pub commands: &'a [C], + pub dynamic_offsets: &'a [wgt::DynamicOffset], + pub string_data: &'a [u8], + pub push_constant_data: &'a [u32], +} + +/// A stream of commands for a render pass or compute pass. +/// +/// This also contains side tables referred to by certain commands, +/// like dynamic offsets for [`SetBindGroup`] or string data for +/// [`InsertDebugMarker`]. +/// +/// Render passes use `BasePass<RenderCommand>`, whereas compute +/// passes use `BasePass<ComputeCommand>`. +/// +/// [`SetBindGroup`]: RenderCommand::SetBindGroup +/// [`InsertDebugMarker`]: RenderCommand::InsertDebugMarker +#[doc(hidden)] +#[derive(Debug)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct BasePass<C> { + pub label: Option<String>, + + /// The stream of commands. + pub commands: Vec<C>, + + /// Dynamic offsets consumed by [`SetBindGroup`] commands in `commands`. + /// + /// Each successive `SetBindGroup` consumes the next + /// [`num_dynamic_offsets`] values from this list. + pub dynamic_offsets: Vec<wgt::DynamicOffset>, + + /// Strings used by debug instructions. + /// + /// Each successive [`PushDebugGroup`] or [`InsertDebugMarker`] + /// instruction consumes the next `len` bytes from this vector. + pub string_data: Vec<u8>, + + /// Data used by `SetPushConstant` instructions. + /// + /// See the documentation for [`RenderCommand::SetPushConstant`] + /// and [`ComputeCommand::SetPushConstant`] for details. + pub push_constant_data: Vec<u32>, +} + +impl<C: Clone> BasePass<C> { + fn new(label: &Label) -> Self { + Self { + label: label.as_ref().map(|cow| cow.to_string()), + commands: Vec::new(), + dynamic_offsets: Vec::new(), + string_data: Vec::new(), + push_constant_data: Vec::new(), + } + } + + #[cfg(feature = "trace")] + fn from_ref(base: BasePassRef<C>) -> Self { + Self { + label: base.label.map(str::to_string), + commands: base.commands.to_vec(), + dynamic_offsets: base.dynamic_offsets.to_vec(), + string_data: base.string_data.to_vec(), + push_constant_data: base.push_constant_data.to_vec(), + } + } + + pub fn as_ref(&self) -> BasePassRef<C> { + BasePassRef { + label: self.label.as_deref(), + commands: &self.commands, + dynamic_offsets: &self.dynamic_offsets, + string_data: &self.string_data, + push_constant_data: &self.push_constant_data, + } + } +} + +#[derive(Clone, Debug, Error)] +#[non_exhaustive] +pub enum CommandEncoderError { + #[error("Command encoder is invalid")] + Invalid, + #[error("Command encoder must be active")] + NotRecording, + #[error(transparent)] + Device(#[from] DeviceError), +} + +impl Global { + pub fn command_encoder_finish<A: HalApi>( + &self, + encoder_id: id::CommandEncoderId, + _desc: &wgt::CommandBufferDescriptor<Label>, + ) -> (CommandBufferId, Option<CommandEncoderError>) { + profiling::scope!("CommandEncoder::finish"); + + let hub = A::hub(self); + + let error = match hub.command_buffers.get(encoder_id.transmute()) { + Ok(cmd_buf) => { + let mut cmd_buf_data = cmd_buf.data.lock(); + let cmd_buf_data = cmd_buf_data.as_mut().unwrap(); + match cmd_buf_data.status { + CommandEncoderStatus::Recording => { + if let Err(e) = cmd_buf_data.encoder.close() { + Some(e.into()) + } else { + cmd_buf_data.status = CommandEncoderStatus::Finished; + //Note: if we want to stop tracking the swapchain texture view, + // this is the place to do it. + log::trace!("Command buffer {:?}", encoder_id); + None + } + } + CommandEncoderStatus::Finished => Some(CommandEncoderError::NotRecording), + CommandEncoderStatus::Error => { + cmd_buf_data.encoder.discard(); + Some(CommandEncoderError::Invalid) + } + } + } + Err(_) => Some(CommandEncoderError::Invalid), + }; + + (encoder_id.transmute(), error) + } + + pub fn command_encoder_push_debug_group<A: HalApi>( + &self, + encoder_id: id::CommandEncoderId, + label: &str, + ) -> Result<(), CommandEncoderError> { + profiling::scope!("CommandEncoder::push_debug_group"); + api_log!("CommandEncoder::push_debug_group {label}"); + + let hub = A::hub(self); + + let cmd_buf = CommandBuffer::get_encoder(hub, encoder_id)?; + let mut cmd_buf_data = cmd_buf.data.lock(); + let cmd_buf_data = cmd_buf_data.as_mut().unwrap(); + #[cfg(feature = "trace")] + if let Some(ref mut list) = cmd_buf_data.commands { + list.push(TraceCommand::PushDebugGroup(label.to_string())); + } + + let cmd_buf_raw = cmd_buf_data.encoder.open()?; + if !self + .instance + .flags + .contains(wgt::InstanceFlags::DISCARD_HAL_LABELS) + { + unsafe { + cmd_buf_raw.begin_debug_marker(label); + } + } + Ok(()) + } + + pub fn command_encoder_insert_debug_marker<A: HalApi>( + &self, + encoder_id: id::CommandEncoderId, + label: &str, + ) -> Result<(), CommandEncoderError> { + profiling::scope!("CommandEncoder::insert_debug_marker"); + api_log!("CommandEncoder::insert_debug_marker {label}"); + + let hub = A::hub(self); + + let cmd_buf = CommandBuffer::get_encoder(hub, encoder_id)?; + let mut cmd_buf_data = cmd_buf.data.lock(); + let cmd_buf_data = cmd_buf_data.as_mut().unwrap(); + + #[cfg(feature = "trace")] + if let Some(ref mut list) = cmd_buf_data.commands { + list.push(TraceCommand::InsertDebugMarker(label.to_string())); + } + + if !self + .instance + .flags + .contains(wgt::InstanceFlags::DISCARD_HAL_LABELS) + { + let cmd_buf_raw = cmd_buf_data.encoder.open()?; + unsafe { + cmd_buf_raw.insert_debug_marker(label); + } + } + Ok(()) + } + + pub fn command_encoder_pop_debug_group<A: HalApi>( + &self, + encoder_id: id::CommandEncoderId, + ) -> Result<(), CommandEncoderError> { + profiling::scope!("CommandEncoder::pop_debug_marker"); + api_log!("CommandEncoder::pop_debug_group"); + + let hub = A::hub(self); + + let cmd_buf = CommandBuffer::get_encoder(hub, encoder_id)?; + let mut cmd_buf_data = cmd_buf.data.lock(); + let cmd_buf_data = cmd_buf_data.as_mut().unwrap(); + + #[cfg(feature = "trace")] + if let Some(ref mut list) = cmd_buf_data.commands { + list.push(TraceCommand::PopDebugGroup); + } + + let cmd_buf_raw = cmd_buf_data.encoder.open()?; + if !self + .instance + .flags + .contains(wgt::InstanceFlags::DISCARD_HAL_LABELS) + { + unsafe { + cmd_buf_raw.end_debug_marker(); + } + } + Ok(()) + } +} + +fn push_constant_clear<PushFn>(offset: u32, size_bytes: u32, mut push_fn: PushFn) +where + PushFn: FnMut(u32, &[u32]), +{ + let mut count_words = 0_u32; + let size_words = size_bytes / wgt::PUSH_CONSTANT_ALIGNMENT; + while count_words < size_words { + let count_bytes = count_words * wgt::PUSH_CONSTANT_ALIGNMENT; + let size_to_write_words = + (size_words - count_words).min(PUSH_CONSTANT_CLEAR_ARRAY.len() as u32); + + push_fn( + offset + count_bytes, + &PUSH_CONSTANT_CLEAR_ARRAY[0..size_to_write_words as usize], + ); + + count_words += size_to_write_words; + } +} + +#[derive(Debug, Copy, Clone)] +struct StateChange<T> { + last_state: Option<T>, +} + +impl<T: Copy + PartialEq> StateChange<T> { + fn new() -> Self { + Self { last_state: None } + } + fn set_and_check_redundant(&mut self, new_state: T) -> bool { + let already_set = self.last_state == Some(new_state); + self.last_state = Some(new_state); + already_set + } + fn reset(&mut self) { + self.last_state = None; + } +} + +impl<T: Copy + PartialEq> Default for StateChange<T> { + fn default() -> Self { + Self::new() + } +} + +#[derive(Debug)] +struct BindGroupStateChange { + last_states: [StateChange<id::BindGroupId>; hal::MAX_BIND_GROUPS], +} + +impl BindGroupStateChange { + fn new() -> Self { + Self { + last_states: [StateChange::new(); hal::MAX_BIND_GROUPS], + } + } + + unsafe fn set_and_check_redundant( + &mut self, + bind_group_id: id::BindGroupId, + index: u32, + dynamic_offsets: &mut Vec<u32>, + offsets: *const wgt::DynamicOffset, + offset_length: usize, + ) -> bool { + // For now never deduplicate bind groups with dynamic offsets. + if offset_length == 0 { + // If this get returns None, that means we're well over the limit, + // so let the call through to get a proper error + if let Some(current_bind_group) = self.last_states.get_mut(index as usize) { + // Bail out if we're binding the same bind group. + if current_bind_group.set_and_check_redundant(bind_group_id) { + return true; + } + } + } else { + // We intentionally remove the memory of this bind group if we have dynamic offsets, + // such that if you try to bind this bind group later with _no_ dynamic offsets it + // tries to bind it again and gives a proper validation error. + if let Some(current_bind_group) = self.last_states.get_mut(index as usize) { + current_bind_group.reset(); + } + dynamic_offsets + .extend_from_slice(unsafe { slice::from_raw_parts(offsets, offset_length) }); + } + false + } + fn reset(&mut self) { + self.last_states = [StateChange::new(); hal::MAX_BIND_GROUPS]; + } +} + +impl Default for BindGroupStateChange { + fn default() -> Self { + Self::new() + } +} + +trait MapPassErr<T, O> { + fn map_pass_err(self, scope: PassErrorScope) -> Result<T, O>; +} + +#[derive(Clone, Copy, Debug, Error)] +pub enum PassErrorScope { + #[error("In a bundle parameter")] + Bundle, + #[error("In a pass parameter")] + Pass(id::CommandEncoderId), + #[error("In a set_bind_group command")] + SetBindGroup(id::BindGroupId), + #[error("In a set_pipeline command")] + SetPipelineRender(id::RenderPipelineId), + #[error("In a set_pipeline command")] + SetPipelineCompute(id::ComputePipelineId), + #[error("In a set_push_constant command")] + SetPushConstant, + #[error("In a set_vertex_buffer command")] + SetVertexBuffer(id::BufferId), + #[error("In a set_index_buffer command")] + SetIndexBuffer(id::BufferId), + #[error("In a set_viewport command")] + SetViewport, + #[error("In a set_scissor_rect command")] + SetScissorRect, + #[error("In a draw command, indexed:{indexed} indirect:{indirect}")] + Draw { + indexed: bool, + indirect: bool, + pipeline: Option<id::RenderPipelineId>, + }, + #[error("While resetting queries after the renderpass was ran")] + QueryReset, + #[error("In a write_timestamp command")] + WriteTimestamp, + #[error("In a begin_occlusion_query command")] + BeginOcclusionQuery, + #[error("In a end_occlusion_query command")] + EndOcclusionQuery, + #[error("In a begin_pipeline_statistics_query command")] + BeginPipelineStatisticsQuery, + #[error("In a end_pipeline_statistics_query command")] + EndPipelineStatisticsQuery, + #[error("In a execute_bundle command")] + ExecuteBundle, + #[error("In a dispatch command, indirect:{indirect}")] + Dispatch { + indirect: bool, + pipeline: Option<id::ComputePipelineId>, + }, + #[error("In a pop_debug_group command")] + PopDebugGroup, +} + +impl PrettyError for PassErrorScope { + fn fmt_pretty(&self, fmt: &mut ErrorFormatter) { + // This error is not in the error chain, only notes are needed + match *self { + Self::Pass(id) => { + fmt.command_buffer_label(&id.transmute()); + } + Self::SetBindGroup(id) => { + fmt.bind_group_label(&id); + } + Self::SetPipelineRender(id) => { + fmt.render_pipeline_label(&id); + } + Self::SetPipelineCompute(id) => { + fmt.compute_pipeline_label(&id); + } + Self::SetVertexBuffer(id) => { + fmt.buffer_label(&id); + } + Self::SetIndexBuffer(id) => { + fmt.buffer_label(&id); + } + Self::Draw { + pipeline: Some(id), .. + } => { + fmt.render_pipeline_label(&id); + } + Self::Dispatch { + pipeline: Some(id), .. + } => { + fmt.compute_pipeline_label(&id); + } + _ => {} + } + } +} |