wgpu_core/command/
mod.rs

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/// The current state of a [`CommandBuffer`].
45#[derive(Debug)]
46pub(crate) enum CommandEncoderStatus {
47    /// Ready to record commands. An encoder's initial state.
48    ///
49    /// Command building methods like [`command_encoder_clear_buffer`] and
50    /// [`command_encoder_run_compute_pass`] require the encoder to be in this
51    /// state.
52    ///
53    /// [`command_encoder_clear_buffer`]: Global::command_encoder_clear_buffer
54    /// [`command_encoder_run_compute_pass`]: Global::command_encoder_run_compute_pass
55    Recording,
56
57    /// Command recording is complete, and the buffer is ready for submission.
58    ///
59    /// [`Global::command_encoder_finish`] transitions a
60    /// `CommandBuffer` from the `Recording` state into this state.
61    ///
62    /// [`Global::queue_submit`] drops command buffers unless they are
63    /// in this state.
64    Finished,
65
66    /// An error occurred while recording a compute or render pass.
67    ///
68    /// When a `CommandEncoder` is left in this state, we have also
69    /// returned an error result from the function that encountered
70    /// the problem. Future attempts to use the encoder (that is,
71    /// calls to [`CommandBuffer::get_encoder`]) will also return
72    /// errors.
73    ///
74    /// Calling [`Global::command_encoder_finish`] in this state
75    /// discards the command buffer under construction.
76    Error,
77}
78
79/// A raw [`CommandEncoder`][rce], and the raw [`CommandBuffer`][rcb]s built from it.
80///
81/// Each wgpu-core [`CommandBuffer`] owns an instance of this type, which is
82/// where the commands are actually stored.
83///
84/// This holds a `Vec` of raw [`CommandBuffer`][rcb]s, not just one. We are not
85/// always able to record commands in the order in which they must ultimately be
86/// submitted to the queue, but raw command buffers don't permit inserting new
87/// commands into the middle of a recorded stream. However, hal queue submission
88/// accepts a series of command buffers at once, so we can simply break the
89/// stream up into multiple buffers, and then reorder the buffers. See
90/// [`CommandEncoder::close_and_swap`] for a specific example of this.
91///
92/// Note that a [`CommandEncoderId`] actually refers to a [`CommandBuffer`].
93/// Methods that take a command encoder id actually look up the command buffer,
94/// and then use its encoder.
95///
96/// [rce]: hal::Api::CommandEncoder
97/// [rcb]: hal::Api::CommandBuffer
98/// [`CommandEncoderId`]: crate::id::CommandEncoderId
99pub(crate) struct CommandEncoder<A: HalApi> {
100    /// The underlying `wgpu_hal` [`CommandEncoder`].
101    ///
102    /// Successfully executed command buffers' encoders are saved in a
103    /// [`CommandAllocator`] for recycling.
104    ///
105    /// [`CommandEncoder`]: hal::Api::CommandEncoder
106    /// [`CommandAllocator`]: crate::command::CommandAllocator
107    raw: A::CommandEncoder,
108
109    /// All the raw command buffers for our owning [`CommandBuffer`], in
110    /// submission order.
111    ///
112    /// These command buffers were all constructed with `raw`. The
113    /// [`wgpu_hal::CommandEncoder`] trait forbids these from outliving `raw`,
114    /// and requires that we provide all of these when we call
115    /// [`raw.reset_all()`][CE::ra], so the encoder and its buffers travel
116    /// together.
117    ///
118    /// [CE::ra]: hal::CommandEncoder::reset_all
119    /// [`wgpu_hal::CommandEncoder`]: hal::CommandEncoder
120    list: Vec<A::CommandBuffer>,
121
122    /// True if `raw` is in the "recording" state.
123    ///
124    /// See the documentation for [`wgpu_hal::CommandEncoder`] for
125    /// details on the states `raw` can be in.
126    ///
127    /// [`wgpu_hal::CommandEncoder`]: hal::CommandEncoder
128    is_open: bool,
129
130    label: Option<String>,
131}
132
133//TODO: handle errors better
134impl<A: HalApi> CommandEncoder<A> {
135    /// Finish the current command buffer, if any, and place it
136    /// at the second-to-last position in our list.
137    ///
138    /// If we have opened this command encoder, finish its current
139    /// command buffer, and insert it just before the last element in
140    /// [`self.list`][l]. If this command buffer is closed, do nothing.
141    ///
142    /// On return, the underlying hal encoder is closed.
143    ///
144    /// What is this for?
145    ///
146    /// The `wgpu_hal` contract requires that each render or compute pass's
147    /// commands be preceded by calls to [`transition_buffers`] and
148    /// [`transition_textures`], to put the resources the pass operates on in
149    /// the appropriate state. Unfortunately, we don't know which transitions
150    /// are needed until we're done recording the pass itself. Rather than
151    /// iterating over the pass twice, we note the necessary transitions as we
152    /// record its commands, finish the raw command buffer for the actual pass,
153    /// record a new raw command buffer for the transitions, and jam that buffer
154    /// in just before the pass's. This is the function that jams in the
155    /// transitions' command buffer.
156    ///
157    /// [l]: CommandEncoder::list
158    /// [`transition_buffers`]: hal::CommandEncoder::transition_buffers
159    /// [`transition_textures`]: hal::CommandEncoder::transition_textures
160    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    /// Finish the current command buffer, if any, and add it to the
171    /// end of [`self.list`][l].
172    ///
173    /// If we have opened this command encoder, finish its current
174    /// command buffer, and push it onto the end of [`self.list`][l].
175    /// If this command buffer is closed, do nothing.
176    ///
177    /// On return, the underlying hal encoder is closed.
178    ///
179    /// [l]: CommandEncoder::list
180    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    /// Discard the command buffer under construction, if any.
191    ///
192    /// The underlying hal encoder is closed, if it was recording.
193    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    /// Begin recording a new command buffer, if we haven't already.
201    ///
202    /// The underlying hal encoder is put in the "recording" state.
203    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    /// Begin recording a new command buffer for a render pass, with
214    /// its own label.
215    ///
216    /// The underlying hal encoder is put in the "recording" state.
217    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
236/// The mutable state of a [`CommandBuffer`].
237pub struct CommandBufferMutable<A: HalApi> {
238    /// The [`wgpu_hal::Api::CommandBuffer`]s we've built so far, and the encoder
239    /// they belong to.
240    ///
241    /// [`wgpu_hal::Api::CommandBuffer`]: hal::Api::CommandBuffer
242    pub(crate) encoder: CommandEncoder<A>,
243
244    /// The current state of this command buffer's encoder.
245    status: CommandEncoderStatus,
246
247    /// All the resources that the commands recorded so far have referred to.
248    pub(crate) trackers: Tracker<A>,
249
250    /// The regions of buffers and textures these commands will read and write.
251    ///
252    /// This is used to determine which portions of which
253    /// buffers/textures we actually need to initialize. If we're
254    /// definitely going to write to something before we read from it,
255    /// we don't need to clear its contents.
256    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
275/// A buffer of commands to be submitted to the GPU for execution.
276///
277/// Whereas the WebGPU API uses two separate types for command buffers and
278/// encoders, this type is a fusion of the two:
279///
280/// - During command recording, this holds a [`CommandEncoder`] accepting this
281///   buffer's commands. In this state, the [`CommandBuffer`] type behaves like
282///   a WebGPU `GPUCommandEncoder`.
283///
284/// - Once command recording is finished by calling
285///   [`Global::command_encoder_finish`], no further recording is allowed. The
286///   internal [`CommandEncoder`] is retained solely as a storage pool for the
287///   raw command buffers. In this state, the value behaves like a WebGPU
288///   `GPUCommandBuffer`.
289///
290/// - Once a command buffer is submitted to the queue, it is removed from the id
291///   registry, and its contents are taken to construct a [`BakedCommands`],
292///   whose contents eventually become the property of the submission queue.
293pub 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    /// The mutable state of this command buffer.
300    ///
301    /// This `Option` is populated when the command buffer is first created.
302    /// When this is submitted, dropped, or destroyed, its contents are
303    /// extracted into a [`BakedCommands`] by
304    /// [`CommandBuffer::extract_baked_commands`].
305    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    /// Return the [`CommandBuffer`] for `id`, for recording new commands.
419    ///
420    /// In `wgpu_core`, the [`CommandBuffer`] type serves both as encoder and
421    /// buffer, which is why this function takes an [`id::CommandEncoderId`] but
422    /// returns a [`CommandBuffer`]. The returned command buffer must be in the
423    /// "recording" state. Otherwise, an error is returned.
424    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/// A stream of commands for a render pass or compute pass.
500///
501/// This also contains side tables referred to by certain commands,
502/// like dynamic offsets for [`SetBindGroup`] or string data for
503/// [`InsertDebugMarker`].
504///
505/// Render passes use `BasePass<RenderCommand>`, whereas compute
506/// passes use `BasePass<ComputeCommand>`.
507///
508/// [`SetBindGroup`]: RenderCommand::SetBindGroup
509/// [`InsertDebugMarker`]: RenderCommand::InsertDebugMarker
510#[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    /// The stream of commands.
517    pub commands: Vec<C>,
518
519    /// Dynamic offsets consumed by [`SetBindGroup`] commands in `commands`.
520    ///
521    /// Each successive `SetBindGroup` consumes the next
522    /// [`num_dynamic_offsets`] values from this list.
523    pub dynamic_offsets: Vec<wgt::DynamicOffset>,
524
525    /// Strings used by debug instructions.
526    ///
527    /// Each successive [`PushDebugGroup`] or [`InsertDebugMarker`]
528    /// instruction consumes the next `len` bytes from this vector.
529    pub string_data: Vec<u8>,
530
531    /// Data used by `SetPushConstant` instructions.
532    ///
533    /// See the documentation for [`RenderCommand::SetPushConstant`]
534    /// and [`ComputeCommand::SetPushConstant`] for details.
535    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                            //Note: if we want to stop tracking the swapchain texture view,
603                            // this is the place to do it.
604                            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        // For now never deduplicate bind groups with dynamic offsets.
781        if offsets.is_empty() {
782            // If this get returns None, that means we're well over the limit,
783            // so let the call through to get a proper error
784            if let Some(current_bind_group) = self.last_states.get_mut(index as usize) {
785                // Bail out if we're binding the same bind group.
786                if current_bind_group.set_and_check_redundant(bind_group_id) {
787                    return true;
788                }
789            }
790        } else {
791            // We intentionally remove the memory of this bind group if we have dynamic offsets,
792            // such that if you try to bind this bind group later with _no_ dynamic offsets it
793            // tries to bind it again and gives a proper validation error.
794            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        // This error is not in the error chain, only notes are needed
870        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}