1mod allocator;
2mod bind;
3mod bundle;
4mod clear;
5mod compute;
6mod compute_command;
7mod draw;
8mod memory_init;
9mod query;
10mod render;
11mod render_command;
12mod timestamp_writes;
13mod transfer;
14
15use std::sync::Arc;
16
17pub(crate) use self::clear::clear_texture;
18pub use self::{
19 bundle::*, clear::ClearError, compute::*, compute_command::ComputeCommand, draw::*, query::*,
20 render::*, render_command::RenderCommand, transfer::*,
21};
22pub(crate) use allocator::CommandAllocator;
23
24pub(crate) use timestamp_writes::ArcPassTimestampWrites;
25pub use timestamp_writes::PassTimestampWrites;
26
27use self::memory_init::CommandBufferTextureMemoryActions;
28
29use crate::device::{Device, DeviceError};
30use crate::lock::{rank, Mutex};
31use crate::snatch::SnatchGuard;
32
33use crate::init_tracker::BufferInitTrackerAction;
34use crate::resource::{InvalidResourceError, Labeled};
35use crate::track::{DeviceTracker, Tracker, UsageScope};
36use crate::LabelHelpers;
37use crate::{api_log, global::Global, id, resource_log, Label};
38
39use thiserror::Error;
40
41#[cfg(feature = "trace")]
42use crate::device::trace::Command as TraceCommand;
43
44const PUSH_CONSTANT_CLEAR_ARRAY: &[u32] = &[0_u32; 64];
45
46#[derive(Debug)]
48pub(crate) enum CommandEncoderStatus {
49 Recording,
61
62 Locked,
71
72 Finished,
80
81 Error,
92}
93
94pub(crate) struct CommandEncoder {
115 raw: Box<dyn hal::DynCommandEncoder>,
123
124 list: Vec<Box<dyn hal::DynCommandBuffer>>,
136
137 is_open: bool,
144
145 hal_label: Option<String>,
146}
147
148impl CommandEncoder {
150 fn close_and_swap(&mut self, device: &Device) -> Result<(), DeviceError> {
176 if self.is_open {
177 self.is_open = false;
178 let new = unsafe { self.raw.end_encoding() }.map_err(|e| device.handle_hal_error(e))?;
179 self.list.insert(self.list.len() - 1, new);
180 }
181
182 Ok(())
183 }
184
185 fn close(&mut self, device: &Device) -> Result<(), DeviceError> {
196 if self.is_open {
197 self.is_open = false;
198 let cmd_buf =
199 unsafe { self.raw.end_encoding() }.map_err(|e| device.handle_hal_error(e))?;
200 self.list.push(cmd_buf);
201 }
202
203 Ok(())
204 }
205
206 pub(crate) fn discard(&mut self) {
210 if self.is_open {
211 self.is_open = false;
212 unsafe { self.raw.discard_encoding() };
213 }
214 }
215
216 pub(crate) fn open(
220 &mut self,
221 device: &Device,
222 ) -> Result<&mut dyn hal::DynCommandEncoder, DeviceError> {
223 if !self.is_open {
224 self.is_open = true;
225 let hal_label = self.hal_label.as_deref();
226 unsafe { self.raw.begin_encoding(hal_label) }
227 .map_err(|e| device.handle_hal_error(e))?;
228 }
229
230 Ok(self.raw.as_mut())
231 }
232
233 fn open_pass(&mut self, hal_label: Option<&str>, device: &Device) -> Result<(), DeviceError> {
238 self.is_open = true;
239 unsafe { self.raw.begin_encoding(hal_label) }.map_err(|e| device.handle_hal_error(e))?;
240
241 Ok(())
242 }
243}
244
245pub(crate) struct BakedCommands {
246 pub(crate) encoder: Box<dyn hal::DynCommandEncoder>,
247 pub(crate) list: Vec<Box<dyn hal::DynCommandBuffer>>,
248 pub(crate) trackers: Tracker,
249 buffer_memory_init_actions: Vec<BufferInitTrackerAction>,
250 texture_memory_actions: CommandBufferTextureMemoryActions,
251}
252
253pub struct CommandBufferMutable {
255 pub(crate) encoder: CommandEncoder,
260
261 status: CommandEncoderStatus,
263
264 pub(crate) trackers: Tracker,
266
267 buffer_memory_init_actions: Vec<BufferInitTrackerAction>,
274 texture_memory_actions: CommandBufferTextureMemoryActions,
275
276 pub(crate) pending_query_resets: QueryResetMap,
277 #[cfg(feature = "trace")]
278 pub(crate) commands: Option<Vec<TraceCommand>>,
279}
280
281impl CommandBufferMutable {
282 pub(crate) fn open_encoder_and_tracker(
283 &mut self,
284 device: &Device,
285 ) -> Result<(&mut dyn hal::DynCommandEncoder, &mut Tracker), DeviceError> {
286 let encoder = self.encoder.open(device)?;
287 let tracker = &mut self.trackers;
288
289 Ok((encoder, tracker))
290 }
291
292 fn lock_encoder_impl(&mut self, lock: bool) -> Result<(), CommandEncoderError> {
293 match self.status {
294 CommandEncoderStatus::Recording => {
295 if lock {
296 self.status = CommandEncoderStatus::Locked;
297 }
298 Ok(())
299 }
300 CommandEncoderStatus::Locked => {
301 self.encoder.discard();
304 self.status = CommandEncoderStatus::Error;
305 Err(CommandEncoderError::Locked)
306 }
307 CommandEncoderStatus::Finished => Err(CommandEncoderError::NotRecording),
308 CommandEncoderStatus::Error => Err(CommandEncoderError::Invalid),
309 }
310 }
311
312 fn check_recording(&mut self) -> Result<(), CommandEncoderError> {
314 self.lock_encoder_impl(false)
315 }
316
317 fn lock_encoder(&mut self) -> Result<(), CommandEncoderError> {
321 self.lock_encoder_impl(true)
322 }
323
324 fn unlock_encoder(&mut self) -> Result<(), CommandEncoderError> {
329 match self.status {
330 CommandEncoderStatus::Recording => Err(CommandEncoderError::Invalid),
331 CommandEncoderStatus::Locked => {
332 self.status = CommandEncoderStatus::Recording;
333 Ok(())
334 }
335 CommandEncoderStatus::Finished => Err(CommandEncoderError::Invalid),
336 CommandEncoderStatus::Error => Err(CommandEncoderError::Invalid),
337 }
338 }
339
340 pub fn check_finished(&self) -> Result<(), CommandEncoderError> {
341 match self.status {
342 CommandEncoderStatus::Finished => Ok(()),
343 _ => Err(CommandEncoderError::Invalid),
344 }
345 }
346
347 pub(crate) fn finish(&mut self, device: &Device) -> Result<(), CommandEncoderError> {
348 match self.status {
349 CommandEncoderStatus::Recording => {
350 if let Err(e) = self.encoder.close(device) {
351 Err(e.into())
352 } else {
353 self.status = CommandEncoderStatus::Finished;
354 Ok(())
357 }
358 }
359 CommandEncoderStatus::Locked => {
360 self.encoder.discard();
361 self.status = CommandEncoderStatus::Error;
362 Err(CommandEncoderError::Locked)
363 }
364 CommandEncoderStatus::Finished => Err(CommandEncoderError::NotRecording),
365 CommandEncoderStatus::Error => {
366 self.encoder.discard();
367 Err(CommandEncoderError::Invalid)
368 }
369 }
370 }
371
372 pub(crate) fn into_baked_commands(self) -> BakedCommands {
373 BakedCommands {
374 encoder: self.encoder.raw,
375 list: self.encoder.list,
376 trackers: self.trackers,
377 buffer_memory_init_actions: self.buffer_memory_init_actions,
378 texture_memory_actions: self.texture_memory_actions,
379 }
380 }
381
382 pub(crate) fn destroy(mut self, device: &Device) {
383 self.encoder.discard();
384 unsafe {
385 self.encoder.raw.reset_all(self.encoder.list);
386 }
387 unsafe {
388 device.raw().destroy_command_encoder(self.encoder.raw);
389 }
390 }
391}
392
393pub struct CommandBuffer {
412 pub(crate) device: Arc<Device>,
413 support_clear_texture: bool,
414 label: String,
416
417 pub(crate) data: Mutex<Option<CommandBufferMutable>>,
424}
425
426impl Drop for CommandBuffer {
427 fn drop(&mut self) {
428 resource_log!("Drop {}", self.error_ident());
429 if let Some(data) = self.data.lock().take() {
430 data.destroy(&self.device);
431 }
432 }
433}
434
435impl CommandBuffer {
436 pub(crate) fn new(
437 encoder: Box<dyn hal::DynCommandEncoder>,
438 device: &Arc<Device>,
439 label: &Label,
440 ) -> Self {
441 CommandBuffer {
442 device: device.clone(),
443 support_clear_texture: device.features.contains(wgt::Features::CLEAR_TEXTURE),
444 label: label.to_string(),
445 data: Mutex::new(
446 rank::COMMAND_BUFFER_DATA,
447 Some(CommandBufferMutable {
448 encoder: CommandEncoder {
449 raw: encoder,
450 is_open: false,
451 list: Vec::new(),
452 hal_label: label.to_hal(device.instance_flags).map(str::to_owned),
453 },
454 status: CommandEncoderStatus::Recording,
455 trackers: Tracker::new(),
456 buffer_memory_init_actions: Default::default(),
457 texture_memory_actions: Default::default(),
458 pending_query_resets: QueryResetMap::new(),
459 #[cfg(feature = "trace")]
460 commands: if device.trace.lock().is_some() {
461 Some(Vec::new())
462 } else {
463 None
464 },
465 }),
466 ),
467 }
468 }
469
470 pub(crate) fn new_invalid(device: &Arc<Device>, label: &Label) -> Self {
471 CommandBuffer {
472 device: device.clone(),
473 support_clear_texture: device.features.contains(wgt::Features::CLEAR_TEXTURE),
474 label: label.to_string(),
475 data: Mutex::new(rank::COMMAND_BUFFER_DATA, None),
476 }
477 }
478
479 pub(crate) fn insert_barriers_from_tracker(
480 raw: &mut dyn hal::DynCommandEncoder,
481 base: &mut Tracker,
482 head: &Tracker,
483 snatch_guard: &SnatchGuard,
484 ) {
485 profiling::scope!("insert_barriers");
486
487 base.buffers.set_from_tracker(&head.buffers);
488 base.textures.set_from_tracker(&head.textures);
489
490 Self::drain_barriers(raw, base, snatch_guard);
491 }
492
493 pub(crate) fn insert_barriers_from_scope(
494 raw: &mut dyn hal::DynCommandEncoder,
495 base: &mut Tracker,
496 head: &UsageScope,
497 snatch_guard: &SnatchGuard,
498 ) {
499 profiling::scope!("insert_barriers");
500
501 base.buffers.set_from_usage_scope(&head.buffers);
502 base.textures.set_from_usage_scope(&head.textures);
503
504 Self::drain_barriers(raw, base, snatch_guard);
505 }
506
507 pub(crate) fn drain_barriers(
508 raw: &mut dyn hal::DynCommandEncoder,
509 base: &mut Tracker,
510 snatch_guard: &SnatchGuard,
511 ) {
512 profiling::scope!("drain_barriers");
513
514 let buffer_barriers = base
515 .buffers
516 .drain_transitions(snatch_guard)
517 .collect::<Vec<_>>();
518 let (transitions, textures) = base.textures.drain_transitions(snatch_guard);
519 let texture_barriers = transitions
520 .into_iter()
521 .enumerate()
522 .map(|(i, p)| p.into_hal(textures[i].unwrap().raw()))
523 .collect::<Vec<_>>();
524
525 unsafe {
526 raw.transition_buffers(&buffer_barriers);
527 raw.transition_textures(&texture_barriers);
528 }
529 }
530
531 pub(crate) fn insert_barriers_from_device_tracker(
532 raw: &mut dyn hal::DynCommandEncoder,
533 base: &mut DeviceTracker,
534 head: &Tracker,
535 snatch_guard: &SnatchGuard,
536 ) {
537 profiling::scope!("insert_barriers_from_device_tracker");
538
539 let buffer_barriers = base
540 .buffers
541 .set_from_tracker_and_drain_transitions(&head.buffers, snatch_guard)
542 .collect::<Vec<_>>();
543
544 let texture_barriers = base
545 .textures
546 .set_from_tracker_and_drain_transitions(&head.textures, snatch_guard)
547 .collect::<Vec<_>>();
548
549 unsafe {
550 raw.transition_buffers(&buffer_barriers);
551 raw.transition_textures(&texture_barriers);
552 }
553 }
554}
555
556impl CommandBuffer {
557 pub fn try_get<'a>(
558 &'a self,
559 ) -> Result<parking_lot::MappedMutexGuard<'a, CommandBufferMutable>, InvalidResourceError> {
560 let g = self.data.lock();
561 crate::lock::MutexGuard::try_map(g, |data| data.as_mut())
562 .map_err(|_| InvalidResourceError(self.error_ident()))
563 }
564
565 pub fn try_take<'a>(&'a self) -> Result<CommandBufferMutable, InvalidResourceError> {
566 self.data
567 .lock()
568 .take()
569 .ok_or_else(|| InvalidResourceError(self.error_ident()))
570 }
571}
572
573crate::impl_resource_type!(CommandBuffer);
574crate::impl_labeled!(CommandBuffer);
575crate::impl_parent_device!(CommandBuffer);
576crate::impl_storage_item!(CommandBuffer);
577
578#[doc(hidden)]
590#[derive(Debug, Clone)]
591#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
592pub struct BasePass<C> {
593 pub label: Option<String>,
594
595 pub commands: Vec<C>,
597
598 pub dynamic_offsets: Vec<wgt::DynamicOffset>,
603
604 pub string_data: Vec<u8>,
609
610 pub push_constant_data: Vec<u32>,
615}
616
617impl<C: Clone> BasePass<C> {
618 fn new(label: &Label) -> Self {
619 Self {
620 label: label.as_ref().map(|cow| cow.to_string()),
621 commands: Vec::new(),
622 dynamic_offsets: Vec::new(),
623 string_data: Vec::new(),
624 push_constant_data: Vec::new(),
625 }
626 }
627}
628
629#[derive(Clone, Debug, Error)]
630#[non_exhaustive]
631pub enum CommandEncoderError {
632 #[error("Command encoder is invalid")]
633 Invalid,
634 #[error("Command encoder must be active")]
635 NotRecording,
636 #[error(transparent)]
637 Device(#[from] DeviceError),
638 #[error("Command encoder is locked by a previously created render/compute pass. Before recording any new commands, the pass must be ended.")]
639 Locked,
640
641 #[error(transparent)]
642 InvalidColorAttachment(#[from] ColorAttachmentError),
643 #[error(transparent)]
644 InvalidResource(#[from] InvalidResourceError),
645}
646
647impl Global {
648 pub fn command_encoder_finish(
649 &self,
650 encoder_id: id::CommandEncoderId,
651 _desc: &wgt::CommandBufferDescriptor<Label>,
652 ) -> (id::CommandBufferId, Option<CommandEncoderError>) {
653 profiling::scope!("CommandEncoder::finish");
654
655 let hub = &self.hub;
656
657 let cmd_buf = hub.command_buffers.get(encoder_id.into_command_buffer_id());
658
659 let error = match cmd_buf
660 .try_get()
661 .map_err(|e| e.into())
662 .and_then(|mut cmd_buf_data| cmd_buf_data.finish(&cmd_buf.device))
663 {
664 Ok(_) => None,
665 Err(e) => Some(e),
666 };
667
668 (encoder_id.into_command_buffer_id(), error)
669 }
670
671 pub fn command_encoder_push_debug_group(
672 &self,
673 encoder_id: id::CommandEncoderId,
674 label: &str,
675 ) -> Result<(), CommandEncoderError> {
676 profiling::scope!("CommandEncoder::push_debug_group");
677 api_log!("CommandEncoder::push_debug_group {label}");
678
679 let hub = &self.hub;
680
681 let cmd_buf = hub.command_buffers.get(encoder_id.into_command_buffer_id());
682 let mut cmd_buf_data = cmd_buf.try_get()?;
683 cmd_buf_data.check_recording()?;
684
685 #[cfg(feature = "trace")]
686 if let Some(ref mut list) = cmd_buf_data.commands {
687 list.push(TraceCommand::PushDebugGroup(label.to_string()));
688 }
689
690 let cmd_buf_raw = cmd_buf_data.encoder.open(&cmd_buf.device)?;
691 if !cmd_buf
692 .device
693 .instance_flags
694 .contains(wgt::InstanceFlags::DISCARD_HAL_LABELS)
695 {
696 unsafe {
697 cmd_buf_raw.begin_debug_marker(label);
698 }
699 }
700 Ok(())
701 }
702
703 pub fn command_encoder_insert_debug_marker(
704 &self,
705 encoder_id: id::CommandEncoderId,
706 label: &str,
707 ) -> Result<(), CommandEncoderError> {
708 profiling::scope!("CommandEncoder::insert_debug_marker");
709 api_log!("CommandEncoder::insert_debug_marker {label}");
710
711 let hub = &self.hub;
712
713 let cmd_buf = hub.command_buffers.get(encoder_id.into_command_buffer_id());
714 let mut cmd_buf_data = cmd_buf.try_get()?;
715 cmd_buf_data.check_recording()?;
716
717 #[cfg(feature = "trace")]
718 if let Some(ref mut list) = cmd_buf_data.commands {
719 list.push(TraceCommand::InsertDebugMarker(label.to_string()));
720 }
721
722 if !cmd_buf
723 .device
724 .instance_flags
725 .contains(wgt::InstanceFlags::DISCARD_HAL_LABELS)
726 {
727 let cmd_buf_raw = cmd_buf_data.encoder.open(&cmd_buf.device)?;
728 unsafe {
729 cmd_buf_raw.insert_debug_marker(label);
730 }
731 }
732 Ok(())
733 }
734
735 pub fn command_encoder_pop_debug_group(
736 &self,
737 encoder_id: id::CommandEncoderId,
738 ) -> Result<(), CommandEncoderError> {
739 profiling::scope!("CommandEncoder::pop_debug_marker");
740 api_log!("CommandEncoder::pop_debug_group");
741
742 let hub = &self.hub;
743
744 let cmd_buf = hub.command_buffers.get(encoder_id.into_command_buffer_id());
745 let mut cmd_buf_data = cmd_buf.try_get()?;
746 cmd_buf_data.check_recording()?;
747
748 #[cfg(feature = "trace")]
749 if let Some(ref mut list) = cmd_buf_data.commands {
750 list.push(TraceCommand::PopDebugGroup);
751 }
752
753 let cmd_buf_raw = cmd_buf_data.encoder.open(&cmd_buf.device)?;
754 if !cmd_buf
755 .device
756 .instance_flags
757 .contains(wgt::InstanceFlags::DISCARD_HAL_LABELS)
758 {
759 unsafe {
760 cmd_buf_raw.end_debug_marker();
761 }
762 }
763 Ok(())
764 }
765}
766
767fn push_constant_clear<PushFn>(offset: u32, size_bytes: u32, mut push_fn: PushFn)
768where
769 PushFn: FnMut(u32, &[u32]),
770{
771 let mut count_words = 0_u32;
772 let size_words = size_bytes / wgt::PUSH_CONSTANT_ALIGNMENT;
773 while count_words < size_words {
774 let count_bytes = count_words * wgt::PUSH_CONSTANT_ALIGNMENT;
775 let size_to_write_words =
776 (size_words - count_words).min(PUSH_CONSTANT_CLEAR_ARRAY.len() as u32);
777
778 push_fn(
779 offset + count_bytes,
780 &PUSH_CONSTANT_CLEAR_ARRAY[0..size_to_write_words as usize],
781 );
782
783 count_words += size_to_write_words;
784 }
785}
786
787#[derive(Debug, Copy, Clone)]
788struct StateChange<T> {
789 last_state: Option<T>,
790}
791
792impl<T: Copy + PartialEq> StateChange<T> {
793 fn new() -> Self {
794 Self { last_state: None }
795 }
796 fn set_and_check_redundant(&mut self, new_state: T) -> bool {
797 let already_set = self.last_state == Some(new_state);
798 self.last_state = Some(new_state);
799 already_set
800 }
801 fn reset(&mut self) {
802 self.last_state = None;
803 }
804}
805
806impl<T: Copy + PartialEq> Default for StateChange<T> {
807 fn default() -> Self {
808 Self::new()
809 }
810}
811
812#[derive(Debug)]
813struct BindGroupStateChange {
814 last_states: [StateChange<Option<id::BindGroupId>>; hal::MAX_BIND_GROUPS],
815}
816
817impl BindGroupStateChange {
818 fn new() -> Self {
819 Self {
820 last_states: [StateChange::new(); hal::MAX_BIND_GROUPS],
821 }
822 }
823
824 fn set_and_check_redundant(
825 &mut self,
826 bind_group_id: Option<id::BindGroupId>,
827 index: u32,
828 dynamic_offsets: &mut Vec<u32>,
829 offsets: &[wgt::DynamicOffset],
830 ) -> bool {
831 if offsets.is_empty() {
833 if let Some(current_bind_group) = self.last_states.get_mut(index as usize) {
836 if current_bind_group.set_and_check_redundant(bind_group_id) {
838 return true;
839 }
840 }
841 } else {
842 if let Some(current_bind_group) = self.last_states.get_mut(index as usize) {
846 current_bind_group.reset();
847 }
848 dynamic_offsets.extend_from_slice(offsets);
849 }
850 false
851 }
852 fn reset(&mut self) {
853 self.last_states = [StateChange::new(); hal::MAX_BIND_GROUPS];
854 }
855}
856
857impl Default for BindGroupStateChange {
858 fn default() -> Self {
859 Self::new()
860 }
861}
862
863trait MapPassErr<T, O> {
864 fn map_pass_err(self, scope: PassErrorScope) -> Result<T, O>;
865}
866
867#[derive(Clone, Copy, Debug)]
868pub enum DrawKind {
869 Draw,
870 DrawIndirect,
871 MultiDrawIndirect,
872 MultiDrawIndirectCount,
873}
874
875#[derive(Clone, Copy, Debug, Error)]
876pub enum PassErrorScope {
877 #[error("In a bundle parameter")]
880 Bundle,
881 #[error("In a pass parameter")]
882 Pass,
883 #[error("In a set_bind_group command")]
884 SetBindGroup,
885 #[error("In a set_pipeline command")]
886 SetPipelineRender,
887 #[error("In a set_pipeline command")]
888 SetPipelineCompute,
889 #[error("In a set_push_constant command")]
890 SetPushConstant,
891 #[error("In a set_vertex_buffer command")]
892 SetVertexBuffer,
893 #[error("In a set_index_buffer command")]
894 SetIndexBuffer,
895 #[error("In a set_blend_constant command")]
896 SetBlendConstant,
897 #[error("In a set_stencil_reference command")]
898 SetStencilReference,
899 #[error("In a set_viewport command")]
900 SetViewport,
901 #[error("In a set_scissor_rect command")]
902 SetScissorRect,
903 #[error("In a draw command, kind: {kind:?}")]
904 Draw { kind: DrawKind, indexed: bool },
905 #[error("While resetting queries after the renderpass was ran")]
906 QueryReset,
907 #[error("In a write_timestamp command")]
908 WriteTimestamp,
909 #[error("In a begin_occlusion_query command")]
910 BeginOcclusionQuery,
911 #[error("In a end_occlusion_query command")]
912 EndOcclusionQuery,
913 #[error("In a begin_pipeline_statistics_query command")]
914 BeginPipelineStatisticsQuery,
915 #[error("In a end_pipeline_statistics_query command")]
916 EndPipelineStatisticsQuery,
917 #[error("In a execute_bundle command")]
918 ExecuteBundle,
919 #[error("In a dispatch command, indirect:{indirect}")]
920 Dispatch { indirect: bool },
921 #[error("In a push_debug_group command")]
922 PushDebugGroup,
923 #[error("In a pop_debug_group command")]
924 PopDebugGroup,
925 #[error("In a insert_debug_marker command")]
926 InsertDebugMarker,
927}