1mod allocator;
2mod bind;
3mod bundle;
4mod clear;
5mod compute;
6mod compute_command;
7mod draw;
8mod memory_init;
9mod query;
10mod render;
11mod transfer;
12
13use std::sync::Arc;
14
15pub(crate) use self::clear::clear_texture;
16pub use self::{
17 bundle::*, clear::ClearError, compute::*, compute_command::ComputeCommand, draw::*, query::*,
18 render::*, transfer::*,
19};
20pub(crate) use allocator::CommandAllocator;
21
22use self::memory_init::CommandBufferTextureMemoryActions;
23
24use crate::device::{Device, DeviceError};
25use crate::error::{ErrorFormatter, PrettyError};
26use crate::hub::Hub;
27use crate::id::CommandBufferId;
28use crate::lock::{rank, Mutex};
29use crate::snatch::SnatchGuard;
30
31use crate::init_tracker::BufferInitTrackerAction;
32use crate::resource::{Resource, ResourceInfo, ResourceType};
33use crate::track::{Tracker, UsageScope};
34use crate::{api_log, global::Global, hal_api::HalApi, id, resource_log, Label};
35
36use hal::CommandEncoder as _;
37use thiserror::Error;
38
39#[cfg(feature = "trace")]
40use crate::device::trace::Command as TraceCommand;
41
42const PUSH_CONSTANT_CLEAR_ARRAY: &[u32] = &[0_u32; 64];
43
44#[derive(Debug)]
46pub(crate) enum CommandEncoderStatus {
47 Recording,
56
57 Finished,
65
66 Error,
77}
78
79pub(crate) struct CommandEncoder<A: HalApi> {
100 raw: A::CommandEncoder,
108
109 list: Vec<A::CommandBuffer>,
121
122 is_open: bool,
129
130 label: Option<String>,
131}
132
133impl<A: HalApi> CommandEncoder<A> {
135 fn close_and_swap(&mut self) -> Result<(), DeviceError> {
161 if self.is_open {
162 self.is_open = false;
163 let new = unsafe { self.raw.end_encoding()? };
164 self.list.insert(self.list.len() - 1, new);
165 }
166
167 Ok(())
168 }
169
170 fn close(&mut self) -> Result<(), DeviceError> {
181 if self.is_open {
182 self.is_open = false;
183 let cmd_buf = unsafe { self.raw.end_encoding()? };
184 self.list.push(cmd_buf);
185 }
186
187 Ok(())
188 }
189
190 pub(crate) fn discard(&mut self) {
194 if self.is_open {
195 self.is_open = false;
196 unsafe { self.raw.discard_encoding() };
197 }
198 }
199
200 pub(crate) fn open(&mut self) -> Result<&mut A::CommandEncoder, DeviceError> {
204 if !self.is_open {
205 self.is_open = true;
206 let label = self.label.as_deref();
207 unsafe { self.raw.begin_encoding(label)? };
208 }
209
210 Ok(&mut self.raw)
211 }
212
213 fn open_pass(&mut self, label: Option<&str>) -> Result<(), DeviceError> {
218 self.is_open = true;
219 unsafe { self.raw.begin_encoding(label)? };
220
221 Ok(())
222 }
223}
224
225pub(crate) struct BakedCommands<A: HalApi> {
226 pub(crate) encoder: A::CommandEncoder,
227 pub(crate) list: Vec<A::CommandBuffer>,
228 pub(crate) trackers: Tracker<A>,
229 buffer_memory_init_actions: Vec<BufferInitTrackerAction<A>>,
230 texture_memory_actions: CommandBufferTextureMemoryActions<A>,
231}
232
233pub(crate) struct DestroyedBufferError(pub id::BufferId);
234pub(crate) struct DestroyedTextureError(pub id::TextureId);
235
236pub struct CommandBufferMutable<A: HalApi> {
238 pub(crate) encoder: CommandEncoder<A>,
243
244 status: CommandEncoderStatus,
246
247 pub(crate) trackers: Tracker<A>,
249
250 buffer_memory_init_actions: Vec<BufferInitTrackerAction<A>>,
257 texture_memory_actions: CommandBufferTextureMemoryActions<A>,
258
259 pub(crate) pending_query_resets: QueryResetMap<A>,
260 #[cfg(feature = "trace")]
261 pub(crate) commands: Option<Vec<TraceCommand>>,
262}
263
264impl<A: HalApi> CommandBufferMutable<A> {
265 pub(crate) fn open_encoder_and_tracker(
266 &mut self,
267 ) -> Result<(&mut A::CommandEncoder, &mut Tracker<A>), DeviceError> {
268 let encoder = self.encoder.open()?;
269 let tracker = &mut self.trackers;
270
271 Ok((encoder, tracker))
272 }
273}
274
275pub struct CommandBuffer<A: HalApi> {
294 pub(crate) device: Arc<Device<A>>,
295 limits: wgt::Limits,
296 support_clear_texture: bool,
297 pub(crate) info: ResourceInfo<CommandBuffer<A>>,
298
299 pub(crate) data: Mutex<Option<CommandBufferMutable<A>>>,
306}
307
308impl<A: HalApi> Drop for CommandBuffer<A> {
309 fn drop(&mut self) {
310 if self.data.lock().is_none() {
311 return;
312 }
313 resource_log!("resource::CommandBuffer::drop {:?}", self.info.label());
314 let mut baked = self.extract_baked_commands();
315 unsafe {
316 baked.encoder.reset_all(baked.list.into_iter());
317 }
318 unsafe {
319 use hal::Device;
320 self.device.raw().destroy_command_encoder(baked.encoder);
321 }
322 }
323}
324
325impl<A: HalApi> CommandBuffer<A> {
326 pub(crate) fn new(
327 encoder: A::CommandEncoder,
328 device: &Arc<Device<A>>,
329 #[cfg(feature = "trace")] enable_tracing: bool,
330 label: Option<String>,
331 ) -> Self {
332 CommandBuffer {
333 device: device.clone(),
334 limits: device.limits.clone(),
335 support_clear_texture: device.features.contains(wgt::Features::CLEAR_TEXTURE),
336 info: ResourceInfo::new(
337 label
338 .as_ref()
339 .unwrap_or(&String::from("<CommandBuffer>"))
340 .as_str(),
341 None,
342 ),
343 data: Mutex::new(
344 rank::COMMAND_BUFFER_DATA,
345 Some(CommandBufferMutable {
346 encoder: CommandEncoder {
347 raw: encoder,
348 is_open: false,
349 list: Vec::new(),
350 label,
351 },
352 status: CommandEncoderStatus::Recording,
353 trackers: Tracker::new(),
354 buffer_memory_init_actions: Default::default(),
355 texture_memory_actions: Default::default(),
356 pending_query_resets: QueryResetMap::new(),
357 #[cfg(feature = "trace")]
358 commands: if enable_tracing {
359 Some(Vec::new())
360 } else {
361 None
362 },
363 }),
364 ),
365 }
366 }
367
368 pub(crate) fn insert_barriers_from_tracker(
369 raw: &mut A::CommandEncoder,
370 base: &mut Tracker<A>,
371 head: &Tracker<A>,
372 snatch_guard: &SnatchGuard,
373 ) {
374 profiling::scope!("insert_barriers");
375
376 base.buffers.set_from_tracker(&head.buffers);
377 base.textures.set_from_tracker(&head.textures);
378
379 Self::drain_barriers(raw, base, snatch_guard);
380 }
381
382 pub(crate) fn insert_barriers_from_scope(
383 raw: &mut A::CommandEncoder,
384 base: &mut Tracker<A>,
385 head: &UsageScope<A>,
386 snatch_guard: &SnatchGuard,
387 ) {
388 profiling::scope!("insert_barriers");
389
390 base.buffers.set_from_usage_scope(&head.buffers);
391 base.textures.set_from_usage_scope(&head.textures);
392
393 Self::drain_barriers(raw, base, snatch_guard);
394 }
395
396 pub(crate) fn drain_barriers(
397 raw: &mut A::CommandEncoder,
398 base: &mut Tracker<A>,
399 snatch_guard: &SnatchGuard,
400 ) {
401 profiling::scope!("drain_barriers");
402
403 let buffer_barriers = base.buffers.drain_transitions(snatch_guard);
404 let (transitions, textures) = base.textures.drain_transitions(snatch_guard);
405 let texture_barriers = transitions
406 .into_iter()
407 .enumerate()
408 .map(|(i, p)| p.into_hal(textures[i].unwrap().raw().unwrap()));
409
410 unsafe {
411 raw.transition_buffers(buffer_barriers);
412 raw.transition_textures(texture_barriers);
413 }
414 }
415}
416
417impl<A: HalApi> CommandBuffer<A> {
418 fn get_encoder(
425 hub: &Hub<A>,
426 id: id::CommandEncoderId,
427 ) -> Result<Arc<Self>, CommandEncoderError> {
428 let storage = hub.command_buffers.read();
429 match storage.get(id.into_command_buffer_id()) {
430 Ok(cmd_buf) => match cmd_buf.data.lock().as_ref().unwrap().status {
431 CommandEncoderStatus::Recording => Ok(cmd_buf.clone()),
432 CommandEncoderStatus::Finished => Err(CommandEncoderError::NotRecording),
433 CommandEncoderStatus::Error => Err(CommandEncoderError::Invalid),
434 },
435 Err(_) => Err(CommandEncoderError::Invalid),
436 }
437 }
438
439 pub fn is_finished(&self) -> bool {
440 match self.data.lock().as_ref().unwrap().status {
441 CommandEncoderStatus::Finished => true,
442 _ => false,
443 }
444 }
445
446 pub(crate) fn extract_baked_commands(&mut self) -> BakedCommands<A> {
447 log::trace!(
448 "Extracting BakedCommands from CommandBuffer {:?}",
449 self.info.label()
450 );
451 let data = self.data.lock().take().unwrap();
452 BakedCommands {
453 encoder: data.encoder.raw,
454 list: data.encoder.list,
455 trackers: data.trackers,
456 buffer_memory_init_actions: data.buffer_memory_init_actions,
457 texture_memory_actions: data.texture_memory_actions,
458 }
459 }
460
461 pub(crate) fn from_arc_into_baked(self: Arc<Self>) -> BakedCommands<A> {
462 let mut command_buffer = Arc::into_inner(self)
463 .expect("CommandBuffer cannot be destroyed because is still in use");
464 command_buffer.extract_baked_commands()
465 }
466}
467
468impl<A: HalApi> Resource for CommandBuffer<A> {
469 const TYPE: ResourceType = "CommandBuffer";
470
471 type Marker = crate::id::markers::CommandBuffer;
472
473 fn as_info(&self) -> &ResourceInfo<Self> {
474 &self.info
475 }
476
477 fn as_info_mut(&mut self) -> &mut ResourceInfo<Self> {
478 &mut self.info
479 }
480
481 fn label(&self) -> String {
482 let str = match self.data.lock().as_ref().unwrap().encoder.label.as_ref() {
483 Some(label) => label.clone(),
484 _ => String::new(),
485 };
486 str
487 }
488}
489
490#[derive(Copy, Clone, Debug)]
491pub struct BasePassRef<'a, C> {
492 pub label: Option<&'a str>,
493 pub commands: &'a [C],
494 pub dynamic_offsets: &'a [wgt::DynamicOffset],
495 pub string_data: &'a [u8],
496 pub push_constant_data: &'a [u32],
497}
498
499#[doc(hidden)]
511#[derive(Debug)]
512#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
513pub struct BasePass<C> {
514 pub label: Option<String>,
515
516 pub commands: Vec<C>,
518
519 pub dynamic_offsets: Vec<wgt::DynamicOffset>,
524
525 pub string_data: Vec<u8>,
530
531 pub push_constant_data: Vec<u32>,
536}
537
538impl<C: Clone> BasePass<C> {
539 fn new(label: &Label) -> Self {
540 Self {
541 label: label.as_ref().map(|cow| cow.to_string()),
542 commands: Vec::new(),
543 dynamic_offsets: Vec::new(),
544 string_data: Vec::new(),
545 push_constant_data: Vec::new(),
546 }
547 }
548
549 #[cfg(feature = "trace")]
550 fn from_ref(base: BasePassRef<C>) -> Self {
551 Self {
552 label: base.label.map(str::to_string),
553 commands: base.commands.to_vec(),
554 dynamic_offsets: base.dynamic_offsets.to_vec(),
555 string_data: base.string_data.to_vec(),
556 push_constant_data: base.push_constant_data.to_vec(),
557 }
558 }
559
560 pub fn as_ref(&self) -> BasePassRef<C> {
561 BasePassRef {
562 label: self.label.as_deref(),
563 commands: &self.commands,
564 dynamic_offsets: &self.dynamic_offsets,
565 string_data: &self.string_data,
566 push_constant_data: &self.push_constant_data,
567 }
568 }
569}
570
571#[derive(Clone, Debug, Error)]
572#[non_exhaustive]
573pub enum CommandEncoderError {
574 #[error("Command encoder is invalid")]
575 Invalid,
576 #[error("Command encoder must be active")]
577 NotRecording,
578 #[error(transparent)]
579 Device(#[from] DeviceError),
580}
581
582impl Global {
583 pub fn command_encoder_finish<A: HalApi>(
584 &self,
585 encoder_id: id::CommandEncoderId,
586 _desc: &wgt::CommandBufferDescriptor<Label>,
587 ) -> (CommandBufferId, Option<CommandEncoderError>) {
588 profiling::scope!("CommandEncoder::finish");
589
590 let hub = A::hub(self);
591
592 let error = match hub.command_buffers.get(encoder_id.into_command_buffer_id()) {
593 Ok(cmd_buf) => {
594 let mut cmd_buf_data = cmd_buf.data.lock();
595 let cmd_buf_data = cmd_buf_data.as_mut().unwrap();
596 match cmd_buf_data.status {
597 CommandEncoderStatus::Recording => {
598 if let Err(e) = cmd_buf_data.encoder.close() {
599 Some(e.into())
600 } else {
601 cmd_buf_data.status = CommandEncoderStatus::Finished;
602 log::trace!("Command buffer {:?}", encoder_id);
605 None
606 }
607 }
608 CommandEncoderStatus::Finished => Some(CommandEncoderError::NotRecording),
609 CommandEncoderStatus::Error => {
610 cmd_buf_data.encoder.discard();
611 Some(CommandEncoderError::Invalid)
612 }
613 }
614 }
615 Err(_) => Some(CommandEncoderError::Invalid),
616 };
617
618 (encoder_id.into_command_buffer_id(), error)
619 }
620
621 pub fn command_encoder_push_debug_group<A: HalApi>(
622 &self,
623 encoder_id: id::CommandEncoderId,
624 label: &str,
625 ) -> Result<(), CommandEncoderError> {
626 profiling::scope!("CommandEncoder::push_debug_group");
627 api_log!("CommandEncoder::push_debug_group {label}");
628
629 let hub = A::hub(self);
630
631 let cmd_buf = CommandBuffer::get_encoder(hub, encoder_id)?;
632 let mut cmd_buf_data = cmd_buf.data.lock();
633 let cmd_buf_data = cmd_buf_data.as_mut().unwrap();
634 #[cfg(feature = "trace")]
635 if let Some(ref mut list) = cmd_buf_data.commands {
636 list.push(TraceCommand::PushDebugGroup(label.to_string()));
637 }
638
639 let cmd_buf_raw = cmd_buf_data.encoder.open()?;
640 if !self
641 .instance
642 .flags
643 .contains(wgt::InstanceFlags::DISCARD_HAL_LABELS)
644 {
645 unsafe {
646 cmd_buf_raw.begin_debug_marker(label);
647 }
648 }
649 Ok(())
650 }
651
652 pub fn command_encoder_insert_debug_marker<A: HalApi>(
653 &self,
654 encoder_id: id::CommandEncoderId,
655 label: &str,
656 ) -> Result<(), CommandEncoderError> {
657 profiling::scope!("CommandEncoder::insert_debug_marker");
658 api_log!("CommandEncoder::insert_debug_marker {label}");
659
660 let hub = A::hub(self);
661
662 let cmd_buf = CommandBuffer::get_encoder(hub, encoder_id)?;
663 let mut cmd_buf_data = cmd_buf.data.lock();
664 let cmd_buf_data = cmd_buf_data.as_mut().unwrap();
665
666 #[cfg(feature = "trace")]
667 if let Some(ref mut list) = cmd_buf_data.commands {
668 list.push(TraceCommand::InsertDebugMarker(label.to_string()));
669 }
670
671 if !self
672 .instance
673 .flags
674 .contains(wgt::InstanceFlags::DISCARD_HAL_LABELS)
675 {
676 let cmd_buf_raw = cmd_buf_data.encoder.open()?;
677 unsafe {
678 cmd_buf_raw.insert_debug_marker(label);
679 }
680 }
681 Ok(())
682 }
683
684 pub fn command_encoder_pop_debug_group<A: HalApi>(
685 &self,
686 encoder_id: id::CommandEncoderId,
687 ) -> Result<(), CommandEncoderError> {
688 profiling::scope!("CommandEncoder::pop_debug_marker");
689 api_log!("CommandEncoder::pop_debug_group");
690
691 let hub = A::hub(self);
692
693 let cmd_buf = CommandBuffer::get_encoder(hub, encoder_id)?;
694 let mut cmd_buf_data = cmd_buf.data.lock();
695 let cmd_buf_data = cmd_buf_data.as_mut().unwrap();
696
697 #[cfg(feature = "trace")]
698 if let Some(ref mut list) = cmd_buf_data.commands {
699 list.push(TraceCommand::PopDebugGroup);
700 }
701
702 let cmd_buf_raw = cmd_buf_data.encoder.open()?;
703 if !self
704 .instance
705 .flags
706 .contains(wgt::InstanceFlags::DISCARD_HAL_LABELS)
707 {
708 unsafe {
709 cmd_buf_raw.end_debug_marker();
710 }
711 }
712 Ok(())
713 }
714}
715
716fn push_constant_clear<PushFn>(offset: u32, size_bytes: u32, mut push_fn: PushFn)
717where
718 PushFn: FnMut(u32, &[u32]),
719{
720 let mut count_words = 0_u32;
721 let size_words = size_bytes / wgt::PUSH_CONSTANT_ALIGNMENT;
722 while count_words < size_words {
723 let count_bytes = count_words * wgt::PUSH_CONSTANT_ALIGNMENT;
724 let size_to_write_words =
725 (size_words - count_words).min(PUSH_CONSTANT_CLEAR_ARRAY.len() as u32);
726
727 push_fn(
728 offset + count_bytes,
729 &PUSH_CONSTANT_CLEAR_ARRAY[0..size_to_write_words as usize],
730 );
731
732 count_words += size_to_write_words;
733 }
734}
735
736#[derive(Debug, Copy, Clone)]
737struct StateChange<T> {
738 last_state: Option<T>,
739}
740
741impl<T: Copy + PartialEq> StateChange<T> {
742 fn new() -> Self {
743 Self { last_state: None }
744 }
745 fn set_and_check_redundant(&mut self, new_state: T) -> bool {
746 let already_set = self.last_state == Some(new_state);
747 self.last_state = Some(new_state);
748 already_set
749 }
750 fn reset(&mut self) {
751 self.last_state = None;
752 }
753}
754
755impl<T: Copy + PartialEq> Default for StateChange<T> {
756 fn default() -> Self {
757 Self::new()
758 }
759}
760
761#[derive(Debug)]
762struct BindGroupStateChange {
763 last_states: [StateChange<id::BindGroupId>; hal::MAX_BIND_GROUPS],
764}
765
766impl BindGroupStateChange {
767 fn new() -> Self {
768 Self {
769 last_states: [StateChange::new(); hal::MAX_BIND_GROUPS],
770 }
771 }
772
773 fn set_and_check_redundant(
774 &mut self,
775 bind_group_id: id::BindGroupId,
776 index: u32,
777 dynamic_offsets: &mut Vec<u32>,
778 offsets: &[wgt::DynamicOffset],
779 ) -> bool {
780 if offsets.is_empty() {
782 if let Some(current_bind_group) = self.last_states.get_mut(index as usize) {
785 if current_bind_group.set_and_check_redundant(bind_group_id) {
787 return true;
788 }
789 }
790 } else {
791 if let Some(current_bind_group) = self.last_states.get_mut(index as usize) {
795 current_bind_group.reset();
796 }
797 dynamic_offsets.extend_from_slice(offsets);
798 }
799 false
800 }
801 fn reset(&mut self) {
802 self.last_states = [StateChange::new(); hal::MAX_BIND_GROUPS];
803 }
804}
805
806impl Default for BindGroupStateChange {
807 fn default() -> Self {
808 Self::new()
809 }
810}
811
812trait MapPassErr<T, O> {
813 fn map_pass_err(self, scope: PassErrorScope) -> Result<T, O>;
814}
815
816#[derive(Clone, Copy, Debug, Error)]
817pub enum PassErrorScope {
818 #[error("In a bundle parameter")]
819 Bundle,
820 #[error("In a pass parameter")]
821 Pass(id::CommandEncoderId),
822 #[error("In a set_bind_group command")]
823 SetBindGroup(id::BindGroupId),
824 #[error("In a set_pipeline command")]
825 SetPipelineRender(id::RenderPipelineId),
826 #[error("In a set_pipeline command")]
827 SetPipelineCompute(id::ComputePipelineId),
828 #[error("In a set_push_constant command")]
829 SetPushConstant,
830 #[error("In a set_vertex_buffer command")]
831 SetVertexBuffer(id::BufferId),
832 #[error("In a set_index_buffer command")]
833 SetIndexBuffer(id::BufferId),
834 #[error("In a set_viewport command")]
835 SetViewport,
836 #[error("In a set_scissor_rect command")]
837 SetScissorRect,
838 #[error("In a draw command, indexed:{indexed} indirect:{indirect}")]
839 Draw {
840 indexed: bool,
841 indirect: bool,
842 pipeline: Option<id::RenderPipelineId>,
843 },
844 #[error("While resetting queries after the renderpass was ran")]
845 QueryReset,
846 #[error("In a write_timestamp command")]
847 WriteTimestamp,
848 #[error("In a begin_occlusion_query command")]
849 BeginOcclusionQuery,
850 #[error("In a end_occlusion_query command")]
851 EndOcclusionQuery,
852 #[error("In a begin_pipeline_statistics_query command")]
853 BeginPipelineStatisticsQuery,
854 #[error("In a end_pipeline_statistics_query command")]
855 EndPipelineStatisticsQuery,
856 #[error("In a execute_bundle command")]
857 ExecuteBundle,
858 #[error("In a dispatch command, indirect:{indirect}")]
859 Dispatch {
860 indirect: bool,
861 pipeline: Option<id::ComputePipelineId>,
862 },
863 #[error("In a pop_debug_group command")]
864 PopDebugGroup,
865}
866
867impl PrettyError for PassErrorScope {
868 fn fmt_pretty(&self, fmt: &mut ErrorFormatter) {
869 match *self {
871 Self::Pass(id) => {
872 fmt.command_buffer_label(&id.into_command_buffer_id());
873 }
874 Self::SetBindGroup(id) => {
875 fmt.bind_group_label(&id);
876 }
877 Self::SetPipelineRender(id) => {
878 fmt.render_pipeline_label(&id);
879 }
880 Self::SetPipelineCompute(id) => {
881 fmt.compute_pipeline_label(&id);
882 }
883 Self::SetVertexBuffer(id) => {
884 fmt.buffer_label(&id);
885 }
886 Self::SetIndexBuffer(id) => {
887 fmt.buffer_label(&id);
888 }
889 Self::Draw {
890 pipeline: Some(id), ..
891 } => {
892 fmt.render_pipeline_label(&id);
893 }
894 Self::Dispatch {
895 pipeline: Some(id), ..
896 } => {
897 fmt.compute_pipeline_label(&id);
898 }
899 _ => {}
900 }
901 }
902}