wgpu_core/command/
mod.rs

1mod allocator;
2mod bind;
3mod bundle;
4mod clear;
5mod compute;
6mod compute_command;
7mod draw;
8mod memory_init;
9mod pass;
10mod query;
11mod ray_tracing;
12mod render;
13mod render_command;
14mod timestamp_writes;
15mod transfer;
16mod transition_resources;
17
18use alloc::{borrow::ToOwned as _, boxed::Box, string::String, sync::Arc, vec::Vec};
19use core::mem::{self, ManuallyDrop};
20use core::ops;
21
22pub(crate) use self::clear::clear_texture;
23pub use self::{
24    bundle::*, clear::ClearError, compute::*, compute_command::ComputeCommand, draw::*, query::*,
25    render::*, render_command::RenderCommand, transfer::*,
26};
27pub(crate) use allocator::CommandAllocator;
28
29pub(crate) use timestamp_writes::ArcPassTimestampWrites;
30pub use timestamp_writes::PassTimestampWrites;
31
32use self::memory_init::CommandBufferTextureMemoryActions;
33
34use crate::command::transition_resources::TransitionResourcesError;
35use crate::device::queue::TempResource;
36use crate::device::{Device, DeviceError, MissingFeatures};
37use crate::lock::{rank, Mutex};
38use crate::snatch::SnatchGuard;
39
40use crate::init_tracker::BufferInitTrackerAction;
41use crate::ray_tracing::{AsAction, BuildAccelerationStructureError};
42use crate::resource::{
43    DestroyedResourceError, Fallible, InvalidResourceError, Labeled, ParentDevice as _, QuerySet,
44};
45use crate::storage::Storage;
46use crate::track::{DeviceTracker, ResourceUsageCompatibilityError, Tracker, UsageScope};
47use crate::{api_log, global::Global, id, resource_log, Label};
48use crate::{hal_label, LabelHelpers};
49
50use wgt::error::{ErrorType, WebGpuError};
51
52use thiserror::Error;
53
54#[cfg(feature = "trace")]
55use crate::device::trace::Command as TraceCommand;
56
57const PUSH_CONSTANT_CLEAR_ARRAY: &[u32] = &[0_u32; 64];
58
59/// The current state of a command or pass encoder.
60///
61/// In the WebGPU spec, the state of an encoder (open, locked, or ended) is
62/// orthogonal to the validity of the encoder. However, this enum does not
63/// represent the state of an invalid encoder.
64pub(crate) enum CommandEncoderStatus {
65    /// Ready to record commands. An encoder's initial state.
66    ///
67    /// Command building methods like [`command_encoder_clear_buffer`] and
68    /// [`compute_pass_end`] require the encoder to be in this
69    /// state.
70    ///
71    /// This corresponds to WebGPU's "open" state.
72    /// See <https://www.w3.org/TR/webgpu/#encoder-state-open>
73    ///
74    /// [`command_encoder_clear_buffer`]: Global::command_encoder_clear_buffer
75    /// [`compute_pass_end`]: Global::compute_pass_end
76    Recording(CommandBufferMutable),
77
78    /// Locked by a render or compute pass.
79    ///
80    /// This state is entered when a render/compute pass is created,
81    /// and exited when the pass is ended.
82    ///
83    /// As long as the command encoder is locked, any command building operation
84    /// on it will fail and put the encoder into the [`Self::Error`] state. See
85    /// <https://www.w3.org/TR/webgpu/#encoder-state-locked>
86    Locked(CommandBufferMutable),
87
88    /// Command recording is complete, and the buffer is ready for submission.
89    ///
90    /// [`Global::command_encoder_finish`] transitions a
91    /// `CommandBuffer` from the `Recording` state into this state.
92    ///
93    /// [`Global::queue_submit`] requires that command buffers are
94    /// in this state.
95    ///
96    /// This corresponds to WebGPU's "ended" state.
97    /// See <https://www.w3.org/TR/webgpu/#encoder-state-ended>
98    Finished(CommandBufferMutable),
99
100    /// The command encoder is invalid.
101    ///
102    /// The error that caused the invalidation is stored here, and will
103    /// be raised by `CommandEncoder.finish()`.
104    Error(CommandEncoderError),
105
106    /// Temporary state used internally by methods on `CommandEncoderStatus`.
107    /// Encoder should never be left in this state.
108    Transitioning,
109}
110
111impl CommandEncoderStatus {
112    /// Record commands using the supplied closure.
113    ///
114    /// If the encoder is in the [`Self::Recording`] state, calls the closure to
115    /// record commands. If the closure returns an error, stores that error in
116    /// the encoder for later reporting when `finish()` is called. Returns
117    /// `Ok(())` even if the closure returned an error.
118    ///
119    /// If the encoder is not in the [`Self::Recording`] state, the closure will
120    /// not be called and nothing will be recorded. The encoder will be
121    /// invalidated (if it is not already). If the error is a [validation error
122    /// that should be raised immediately][ves], returns it in `Err`, otherwise,
123    /// returns `Ok(())`.
124    ///
125    /// [ves]: https://www.w3.org/TR/webgpu/#abstract-opdef-validate-the-encoder-state
126    fn record_with<
127        F: FnOnce(&mut CommandBufferMutable) -> Result<(), E>,
128        E: Clone + Into<CommandEncoderError>,
129    >(
130        &mut self,
131        f: F,
132    ) -> Result<(), EncoderStateError> {
133        match self {
134            Self::Recording(_) => {
135                RecordingGuard { inner: self }.record(f);
136                Ok(())
137            }
138            Self::Locked(_) => {
139                // Invalidate the encoder and do not record anything, but do not
140                // return an immediate validation error.
141                self.invalidate(EncoderStateError::Locked);
142                Ok(())
143            }
144            // Encoder is ended. Invalidate the encoder, do not record anything,
145            // and return an immediate validation error.
146            Self::Finished(_) => Err(self.invalidate(EncoderStateError::Ended)),
147            // Encoder is already invalid. Do not record anything, but do not
148            // return an immediate validation error.
149            Self::Error(_) => Ok(()),
150            Self::Transitioning => unreachable!(),
151        }
152    }
153
154    /// Special version of record used by `command_encoder_as_hal_mut`. This
155    /// differs from the regular version in two ways:
156    ///
157    /// 1. The recording closure is infallible.
158    /// 2. The recording closure takes `Option<&mut CommandBufferMutable>`, and
159    ///    in the case that the encoder is not in a valid state for recording, the
160    ///    closure is still called, with `None` as its argument.
161    pub(crate) fn record_as_hal_mut<T, F: FnOnce(Option<&mut CommandBufferMutable>) -> T>(
162        &mut self,
163        f: F,
164    ) -> T {
165        match self {
166            Self::Recording(_) => RecordingGuard { inner: self }.record_as_hal_mut(f),
167            Self::Locked(_) => {
168                self.invalidate(EncoderStateError::Locked);
169                f(None)
170            }
171            Self::Finished(_) => {
172                self.invalidate(EncoderStateError::Ended);
173                f(None)
174            }
175            Self::Error(_) => f(None),
176            Self::Transitioning => unreachable!(),
177        }
178    }
179
180    #[cfg(feature = "trace")]
181    fn get_inner(&mut self) -> &mut CommandBufferMutable {
182        match self {
183            Self::Locked(inner) | Self::Finished(inner) | Self::Recording(inner) => inner,
184            // This is unreachable because this function is only used when
185            // playing back a recorded trace. If only to avoid having to
186            // implement serialization for all the error types, we don't support
187            // storing the errors in a trace.
188            Self::Error(_) => unreachable!("passes in a trace do not store errors"),
189            Self::Transitioning => unreachable!(),
190        }
191    }
192
193    /// Locks the encoder by putting it in the [`Self::Locked`] state.
194    ///
195    /// Render or compute passes call this on start. At the end of the pass,
196    /// they call [`Self::unlock_and_record`] to put the [`CommandBuffer`] back
197    /// into the [`Self::Recording`] state.
198    fn lock_encoder(&mut self) -> Result<(), EncoderStateError> {
199        match mem::replace(self, Self::Transitioning) {
200            Self::Recording(inner) => {
201                *self = Self::Locked(inner);
202                Ok(())
203            }
204            st @ Self::Finished(_) => {
205                // Attempting to open a pass on a finished encoder raises a
206                // validation error but does not invalidate the encoder. This is
207                // related to https://github.com/gpuweb/gpuweb/issues/5207.
208                *self = st;
209                Err(EncoderStateError::Ended)
210            }
211            Self::Locked(_) => Err(self.invalidate(EncoderStateError::Locked)),
212            st @ Self::Error(_) => {
213                *self = st;
214                Err(EncoderStateError::Invalid)
215            }
216            Self::Transitioning => unreachable!(),
217        }
218    }
219
220    /// Unlocks the [`CommandBuffer`] and puts it back into the
221    /// [`Self::Recording`] state, then records commands using the supplied
222    /// closure.
223    ///
224    /// This function is the unlocking counterpart to [`Self::lock_encoder`]. It
225    /// is only valid to call this function if the encoder is in the
226    /// [`Self::Locked`] state.
227    ///
228    /// If the closure returns an error, stores that error in the encoder for
229    /// later reporting when `finish()` is called. Returns `Ok(())` even if the
230    /// closure returned an error.
231    ///
232    /// If the encoder is not in the [`Self::Locked`] state, the closure will
233    /// not be called and nothing will be recorded. If a validation error should
234    /// be raised immediately, returns it in `Err`, otherwise, returns `Ok(())`.
235    fn unlock_and_record<
236        F: FnOnce(&mut CommandBufferMutable) -> Result<(), E>,
237        E: Clone + Into<CommandEncoderError>,
238    >(
239        &mut self,
240        f: F,
241    ) -> Result<(), EncoderStateError> {
242        match mem::replace(self, Self::Transitioning) {
243            Self::Locked(inner) => {
244                *self = Self::Recording(inner);
245                RecordingGuard { inner: self }.record(f);
246                Ok(())
247            }
248            st @ Self::Finished(_) => {
249                *self = st;
250                Err(EncoderStateError::Ended)
251            }
252            Self::Recording(_) => {
253                *self = Self::Error(EncoderStateError::Unlocked.into());
254                Err(EncoderStateError::Unlocked)
255            }
256            st @ Self::Error(_) => {
257                // Encoder is invalid. Do not record anything, but do not
258                // return an immediate validation error.
259                *self = st;
260                Ok(())
261            }
262            Self::Transitioning => unreachable!(),
263        }
264    }
265
266    fn finish(&mut self) -> Result<(), CommandEncoderError> {
267        match mem::replace(self, Self::Transitioning) {
268            Self::Recording(mut inner) => {
269                if let Err(e) = inner.encoder.close_if_open() {
270                    Err(self.invalidate(e.into()))
271                } else {
272                    *self = Self::Finished(inner);
273                    // Note: if we want to stop tracking the swapchain texture view,
274                    // this is the place to do it.
275                    Ok(())
276                }
277            }
278            Self::Finished(_) => Err(self.invalidate(EncoderStateError::Ended.into())),
279            Self::Locked(_) => Err(self.invalidate(EncoderStateError::Locked.into())),
280            Self::Error(err) => Err(self.invalidate(err)),
281            Self::Transitioning => unreachable!(),
282        }
283    }
284
285    // Invalidate the command encoder and store the error `err` causing the
286    // invalidation for diagnostic purposes.
287    //
288    // Since we do not track the state of an invalid encoder, it is not
289    // necessary to unlock an encoder that has been invalidated.
290    fn invalidate<E: Clone + Into<CommandEncoderError>>(&mut self, err: E) -> E {
291        *self = Self::Error(err.clone().into());
292        err
293    }
294}
295
296/// A guard to enforce error reporting, for a [`CommandBuffer`] in the [`Recording`] state.
297///
298/// An [`RecordingGuard`] holds a mutable reference to a [`CommandEncoderStatus`] that
299/// has been verified to be in the [`Recording`] state. The [`RecordingGuard`] dereferences
300/// mutably to the [`CommandBufferMutable`] that the status holds.
301///
302/// Dropping an [`RecordingGuard`] sets the [`CommandBuffer`]'s state to
303/// [`CommandEncoderStatus::Error`]. If your use of the guard was
304/// successful, call its [`mark_successful`] method to dispose of it.
305///
306/// [`Recording`]: CommandEncoderStatus::Recording
307/// [`mark_successful`]: Self::mark_successful
308pub(crate) struct RecordingGuard<'a> {
309    inner: &'a mut CommandEncoderStatus,
310}
311
312impl<'a> RecordingGuard<'a> {
313    pub(crate) fn mark_successful(self) {
314        mem::forget(self)
315    }
316
317    fn record<
318        F: FnOnce(&mut CommandBufferMutable) -> Result<(), E>,
319        E: Clone + Into<CommandEncoderError>,
320    >(
321        mut self,
322        f: F,
323    ) {
324        match f(&mut self) {
325            Ok(()) => self.mark_successful(),
326            Err(err) => {
327                self.inner.invalidate(err);
328            }
329        }
330    }
331
332    /// Special version of record used by `command_encoder_as_hal_mut`. This
333    /// version takes an infallible recording closure.
334    pub(crate) fn record_as_hal_mut<T, F: FnOnce(Option<&mut CommandBufferMutable>) -> T>(
335        mut self,
336        f: F,
337    ) -> T {
338        let res = f(Some(&mut self));
339        self.mark_successful();
340        res
341    }
342}
343
344impl<'a> Drop for RecordingGuard<'a> {
345    fn drop(&mut self) {
346        if matches!(*self.inner, CommandEncoderStatus::Error(_)) {
347            // Don't overwrite an error that is already present.
348            return;
349        }
350        self.inner.invalidate(EncoderStateError::Invalid);
351    }
352}
353
354impl<'a> ops::Deref for RecordingGuard<'a> {
355    type Target = CommandBufferMutable;
356
357    fn deref(&self) -> &Self::Target {
358        match &*self.inner {
359            CommandEncoderStatus::Recording(command_buffer_mutable) => command_buffer_mutable,
360            _ => unreachable!(),
361        }
362    }
363}
364
365impl<'a> ops::DerefMut for RecordingGuard<'a> {
366    fn deref_mut(&mut self) -> &mut Self::Target {
367        match self.inner {
368            CommandEncoderStatus::Recording(command_buffer_mutable) => command_buffer_mutable,
369            _ => unreachable!(),
370        }
371    }
372}
373
374/// A raw [`CommandEncoder`][rce], and the raw [`CommandBuffer`][rcb]s built from it.
375///
376/// Each wgpu-core [`CommandBuffer`] owns an instance of this type, which is
377/// where the commands are actually stored.
378///
379/// This holds a `Vec` of raw [`CommandBuffer`][rcb]s, not just one. We are not
380/// always able to record commands in the order in which they must ultimately be
381/// submitted to the queue, but raw command buffers don't permit inserting new
382/// commands into the middle of a recorded stream. However, hal queue submission
383/// accepts a series of command buffers at once, so we can simply break the
384/// stream up into multiple buffers, and then reorder the buffers. See
385/// [`CommandEncoder::close_and_swap`] for a specific example of this.
386///
387/// Note that a [`CommandEncoderId`] actually refers to a [`CommandBuffer`].
388/// Methods that take a command encoder id actually look up the command buffer,
389/// and then use its encoder.
390///
391/// [rce]: hal::Api::CommandEncoder
392/// [rcb]: hal::Api::CommandBuffer
393/// [`CommandEncoderId`]: crate::id::CommandEncoderId
394pub(crate) struct CommandEncoder {
395    /// The underlying `wgpu_hal` [`CommandEncoder`].
396    ///
397    /// Successfully executed command buffers' encoders are saved in a
398    /// [`CommandAllocator`] for recycling.
399    ///
400    /// [`CommandEncoder`]: hal::Api::CommandEncoder
401    /// [`CommandAllocator`]: crate::command::CommandAllocator
402    pub(crate) raw: ManuallyDrop<Box<dyn hal::DynCommandEncoder>>,
403
404    /// All the raw command buffers for our owning [`CommandBuffer`], in
405    /// submission order.
406    ///
407    /// These command buffers were all constructed with `raw`. The
408    /// [`wgpu_hal::CommandEncoder`] trait forbids these from outliving `raw`,
409    /// and requires that we provide all of these when we call
410    /// [`raw.reset_all()`][CE::ra], so the encoder and its buffers travel
411    /// together.
412    ///
413    /// [CE::ra]: hal::CommandEncoder::reset_all
414    /// [`wgpu_hal::CommandEncoder`]: hal::CommandEncoder
415    pub(crate) list: Vec<Box<dyn hal::DynCommandBuffer>>,
416
417    pub(crate) device: Arc<Device>,
418
419    /// True if `raw` is in the "recording" state.
420    ///
421    /// See the documentation for [`wgpu_hal::CommandEncoder`] for
422    /// details on the states `raw` can be in.
423    ///
424    /// [`wgpu_hal::CommandEncoder`]: hal::CommandEncoder
425    pub(crate) is_open: bool,
426
427    pub(crate) hal_label: Option<String>,
428}
429
430impl CommandEncoder {
431    /// Finish the current command buffer and insert it just before
432    /// the last element in [`self.list`][l].
433    ///
434    /// On return, the underlying hal encoder is closed.
435    ///
436    /// What is this for?
437    ///
438    /// The `wgpu_hal` contract requires that each render or compute pass's
439    /// commands be preceded by calls to [`transition_buffers`] and
440    /// [`transition_textures`], to put the resources the pass operates on in
441    /// the appropriate state. Unfortunately, we don't know which transitions
442    /// are needed until we're done recording the pass itself. Rather than
443    /// iterating over the pass twice, we note the necessary transitions as we
444    /// record its commands, finish the raw command buffer for the actual pass,
445    /// record a new raw command buffer for the transitions, and jam that buffer
446    /// in just before the pass's. This is the function that jams in the
447    /// transitions' command buffer.
448    ///
449    /// # Panics
450    ///
451    /// - If the encoder is not open.
452    ///
453    /// [l]: CommandEncoder::list
454    /// [`transition_buffers`]: hal::CommandEncoder::transition_buffers
455    /// [`transition_textures`]: hal::CommandEncoder::transition_textures
456    fn close_and_swap(&mut self) -> Result<(), DeviceError> {
457        assert!(self.is_open);
458        self.is_open = false;
459
460        let new =
461            unsafe { self.raw.end_encoding() }.map_err(|e| self.device.handle_hal_error(e))?;
462        self.list.insert(self.list.len() - 1, new);
463
464        Ok(())
465    }
466
467    /// Finish the current command buffer and insert it at the beginning
468    /// of [`self.list`][l].
469    ///
470    /// On return, the underlying hal encoder is closed.
471    ///
472    /// # Panics
473    ///
474    /// - If the encoder is not open.
475    ///
476    /// [l]: CommandEncoder::list
477    pub(crate) fn close_and_push_front(&mut self) -> Result<(), DeviceError> {
478        assert!(self.is_open);
479        self.is_open = false;
480
481        let new =
482            unsafe { self.raw.end_encoding() }.map_err(|e| self.device.handle_hal_error(e))?;
483        self.list.insert(0, new);
484
485        Ok(())
486    }
487
488    /// Finish the current command buffer, and push it onto
489    /// the end of [`self.list`][l].
490    ///
491    /// On return, the underlying hal encoder is closed.
492    ///
493    /// # Panics
494    ///
495    /// - If the encoder is not open.
496    ///
497    /// [l]: CommandEncoder::list
498    pub(crate) fn close(&mut self) -> Result<(), DeviceError> {
499        assert!(self.is_open);
500        self.is_open = false;
501
502        let cmd_buf =
503            unsafe { self.raw.end_encoding() }.map_err(|e| self.device.handle_hal_error(e))?;
504        self.list.push(cmd_buf);
505
506        Ok(())
507    }
508
509    /// Finish the current command buffer, if any, and add it to the
510    /// end of [`self.list`][l].
511    ///
512    /// If we have opened this command encoder, finish its current
513    /// command buffer, and push it onto the end of [`self.list`][l].
514    /// If this command buffer is closed, do nothing.
515    ///
516    /// On return, the underlying hal encoder is closed.
517    ///
518    /// [l]: CommandEncoder::list
519    fn close_if_open(&mut self) -> Result<(), DeviceError> {
520        if self.is_open {
521            self.is_open = false;
522            let cmd_buf =
523                unsafe { self.raw.end_encoding() }.map_err(|e| self.device.handle_hal_error(e))?;
524            self.list.push(cmd_buf);
525        }
526
527        Ok(())
528    }
529
530    /// Begin recording a new command buffer, if we haven't already.
531    ///
532    /// The underlying hal encoder is put in the "recording" state.
533    pub(crate) fn open(&mut self) -> Result<&mut dyn hal::DynCommandEncoder, DeviceError> {
534        if !self.is_open {
535            self.is_open = true;
536            let hal_label = self.hal_label.as_deref();
537            unsafe { self.raw.begin_encoding(hal_label) }
538                .map_err(|e| self.device.handle_hal_error(e))?;
539        }
540
541        Ok(self.raw.as_mut())
542    }
543
544    /// Begin recording a new command buffer for a render pass, with
545    /// its own label.
546    ///
547    /// The underlying hal encoder is put in the "recording" state.
548    ///
549    /// # Panics
550    ///
551    /// - If the encoder is already open.
552    pub(crate) fn open_pass(
553        &mut self,
554        label: Option<&str>,
555    ) -> Result<&mut dyn hal::DynCommandEncoder, DeviceError> {
556        assert!(!self.is_open);
557        self.is_open = true;
558
559        let hal_label = hal_label(label, self.device.instance_flags);
560        unsafe { self.raw.begin_encoding(hal_label) }
561            .map_err(|e| self.device.handle_hal_error(e))?;
562
563        Ok(self.raw.as_mut())
564    }
565}
566
567impl Drop for CommandEncoder {
568    fn drop(&mut self) {
569        if self.is_open {
570            unsafe { self.raw.discard_encoding() };
571        }
572        unsafe {
573            self.raw.reset_all(mem::take(&mut self.list));
574        }
575        // SAFETY: We are in the Drop impl and we don't use self.raw anymore after this point.
576        let raw = unsafe { ManuallyDrop::take(&mut self.raw) };
577        self.device.command_allocator.release_encoder(raw);
578    }
579}
580
581/// Look at the documentation for [`CommandBufferMutable`] for an explanation of
582/// the fields in this struct. This is the "built" counterpart to that type.
583pub(crate) struct BakedCommands {
584    pub(crate) encoder: CommandEncoder,
585    pub(crate) trackers: Tracker,
586    pub(crate) temp_resources: Vec<TempResource>,
587    pub(crate) indirect_draw_validation_resources: crate::indirect_validation::DrawResources,
588    buffer_memory_init_actions: Vec<BufferInitTrackerAction>,
589    texture_memory_actions: CommandBufferTextureMemoryActions,
590}
591
592/// The mutable state of a [`CommandBuffer`].
593pub struct CommandBufferMutable {
594    /// The [`wgpu_hal::Api::CommandBuffer`]s we've built so far, and the encoder
595    /// they belong to.
596    ///
597    /// [`wgpu_hal::Api::CommandBuffer`]: hal::Api::CommandBuffer
598    pub(crate) encoder: CommandEncoder,
599
600    /// All the resources that the commands recorded so far have referred to.
601    pub(crate) trackers: Tracker,
602
603    /// The regions of buffers and textures these commands will read and write.
604    ///
605    /// This is used to determine which portions of which
606    /// buffers/textures we actually need to initialize. If we're
607    /// definitely going to write to something before we read from it,
608    /// we don't need to clear its contents.
609    buffer_memory_init_actions: Vec<BufferInitTrackerAction>,
610    texture_memory_actions: CommandBufferTextureMemoryActions,
611
612    pub(crate) pending_query_resets: QueryResetMap,
613
614    as_actions: Vec<AsAction>,
615    temp_resources: Vec<TempResource>,
616
617    indirect_draw_validation_resources: crate::indirect_validation::DrawResources,
618
619    #[cfg(feature = "trace")]
620    pub(crate) commands: Option<Vec<TraceCommand>>,
621}
622
623impl CommandBufferMutable {
624    pub(crate) fn open_encoder_and_tracker(
625        &mut self,
626    ) -> Result<(&mut dyn hal::DynCommandEncoder, &mut Tracker), DeviceError> {
627        let encoder = self.encoder.open()?;
628        let tracker = &mut self.trackers;
629
630        Ok((encoder, tracker))
631    }
632
633    pub(crate) fn into_baked_commands(self) -> BakedCommands {
634        BakedCommands {
635            encoder: self.encoder,
636            trackers: self.trackers,
637            temp_resources: self.temp_resources,
638            indirect_draw_validation_resources: self.indirect_draw_validation_resources,
639            buffer_memory_init_actions: self.buffer_memory_init_actions,
640            texture_memory_actions: self.texture_memory_actions,
641        }
642    }
643}
644
645/// A buffer of commands to be submitted to the GPU for execution.
646///
647/// Whereas the WebGPU API uses two separate types for command buffers and
648/// encoders, this type is a fusion of the two:
649///
650/// - During command recording, this holds a [`CommandEncoder`] accepting this
651///   buffer's commands. In this state, the [`CommandBuffer`] type behaves like
652///   a WebGPU `GPUCommandEncoder`.
653///
654/// - Once command recording is finished by calling
655///   [`Global::command_encoder_finish`], no further recording is allowed. The
656///   internal [`CommandEncoder`] is retained solely as a storage pool for the
657///   raw command buffers. In this state, the value behaves like a WebGPU
658///   `GPUCommandBuffer`.
659///
660/// - Once a command buffer is submitted to the queue, it is removed from the id
661///   registry, and its contents are taken to construct a [`BakedCommands`],
662///   whose contents eventually become the property of the submission queue.
663pub struct CommandBuffer {
664    pub(crate) device: Arc<Device>,
665    support_clear_texture: bool,
666    /// The `label` from the descriptor used to create the resource.
667    label: String,
668
669    /// The mutable state of this command buffer.
670    pub(crate) data: Mutex<CommandEncoderStatus>,
671}
672
673impl Drop for CommandBuffer {
674    fn drop(&mut self) {
675        resource_log!("Drop {}", self.error_ident());
676    }
677}
678
679impl CommandBuffer {
680    pub(crate) fn new(
681        encoder: Box<dyn hal::DynCommandEncoder>,
682        device: &Arc<Device>,
683        label: &Label,
684    ) -> Self {
685        CommandBuffer {
686            device: device.clone(),
687            support_clear_texture: device.features.contains(wgt::Features::CLEAR_TEXTURE),
688            label: label.to_string(),
689            data: Mutex::new(
690                rank::COMMAND_BUFFER_DATA,
691                CommandEncoderStatus::Recording(CommandBufferMutable {
692                    encoder: CommandEncoder {
693                        raw: ManuallyDrop::new(encoder),
694                        list: Vec::new(),
695                        device: device.clone(),
696                        is_open: false,
697                        hal_label: label.to_hal(device.instance_flags).map(str::to_owned),
698                    },
699                    trackers: Tracker::new(),
700                    buffer_memory_init_actions: Default::default(),
701                    texture_memory_actions: Default::default(),
702                    pending_query_resets: QueryResetMap::new(),
703                    as_actions: Default::default(),
704                    temp_resources: Default::default(),
705                    indirect_draw_validation_resources:
706                        crate::indirect_validation::DrawResources::new(device.clone()),
707                    #[cfg(feature = "trace")]
708                    commands: if device.trace.lock().is_some() {
709                        Some(Vec::new())
710                    } else {
711                        None
712                    },
713                }),
714            ),
715        }
716    }
717
718    pub(crate) fn new_invalid(
719        device: &Arc<Device>,
720        label: &Label,
721        err: CommandEncoderError,
722    ) -> Self {
723        CommandBuffer {
724            device: device.clone(),
725            support_clear_texture: device.features.contains(wgt::Features::CLEAR_TEXTURE),
726            label: label.to_string(),
727            data: Mutex::new(rank::COMMAND_BUFFER_DATA, CommandEncoderStatus::Error(err)),
728        }
729    }
730
731    pub(crate) fn insert_barriers_from_tracker(
732        raw: &mut dyn hal::DynCommandEncoder,
733        base: &mut Tracker,
734        head: &Tracker,
735        snatch_guard: &SnatchGuard,
736    ) {
737        profiling::scope!("insert_barriers");
738
739        base.buffers.set_from_tracker(&head.buffers);
740        base.textures.set_from_tracker(&head.textures);
741
742        Self::drain_barriers(raw, base, snatch_guard);
743    }
744
745    pub(crate) fn insert_barriers_from_scope(
746        raw: &mut dyn hal::DynCommandEncoder,
747        base: &mut Tracker,
748        head: &UsageScope,
749        snatch_guard: &SnatchGuard,
750    ) {
751        profiling::scope!("insert_barriers");
752
753        base.buffers.set_from_usage_scope(&head.buffers);
754        base.textures.set_from_usage_scope(&head.textures);
755
756        Self::drain_barriers(raw, base, snatch_guard);
757    }
758
759    pub(crate) fn drain_barriers(
760        raw: &mut dyn hal::DynCommandEncoder,
761        base: &mut Tracker,
762        snatch_guard: &SnatchGuard,
763    ) {
764        profiling::scope!("drain_barriers");
765
766        let buffer_barriers = base
767            .buffers
768            .drain_transitions(snatch_guard)
769            .collect::<Vec<_>>();
770        let (transitions, textures) = base.textures.drain_transitions(snatch_guard);
771        let texture_barriers = transitions
772            .into_iter()
773            .enumerate()
774            .map(|(i, p)| p.into_hal(textures[i].unwrap().raw()))
775            .collect::<Vec<_>>();
776
777        unsafe {
778            raw.transition_buffers(&buffer_barriers);
779            raw.transition_textures(&texture_barriers);
780        }
781    }
782
783    pub(crate) fn insert_barriers_from_device_tracker(
784        raw: &mut dyn hal::DynCommandEncoder,
785        base: &mut DeviceTracker,
786        head: &Tracker,
787        snatch_guard: &SnatchGuard,
788    ) {
789        profiling::scope!("insert_barriers_from_device_tracker");
790
791        let buffer_barriers = base
792            .buffers
793            .set_from_tracker_and_drain_transitions(&head.buffers, snatch_guard)
794            .collect::<Vec<_>>();
795
796        let texture_barriers = base
797            .textures
798            .set_from_tracker_and_drain_transitions(&head.textures, snatch_guard)
799            .collect::<Vec<_>>();
800
801        unsafe {
802            raw.transition_buffers(&buffer_barriers);
803            raw.transition_textures(&texture_barriers);
804        }
805    }
806}
807
808impl CommandBuffer {
809    pub fn take_finished(&self) -> Result<CommandBufferMutable, CommandEncoderError> {
810        use CommandEncoderStatus as St;
811        match mem::replace(
812            &mut *self.data.lock(),
813            CommandEncoderStatus::Error(EncoderStateError::Submitted.into()),
814        ) {
815            St::Finished(command_buffer_mutable) => Ok(command_buffer_mutable),
816            St::Error(err) => Err(err),
817            St::Recording(_) | St::Locked(_) => {
818                Err(InvalidResourceError(self.error_ident()).into())
819            }
820            St::Transitioning => unreachable!(),
821        }
822    }
823}
824
825crate::impl_resource_type!(CommandBuffer);
826crate::impl_labeled!(CommandBuffer);
827crate::impl_parent_device!(CommandBuffer);
828crate::impl_storage_item!(CommandBuffer);
829
830/// A stream of commands for a render pass or compute pass.
831///
832/// This also contains side tables referred to by certain commands,
833/// like dynamic offsets for [`SetBindGroup`] or string data for
834/// [`InsertDebugMarker`].
835///
836/// Render passes use `BasePass<RenderCommand>`, whereas compute
837/// passes use `BasePass<ComputeCommand>`.
838///
839/// [`SetBindGroup`]: RenderCommand::SetBindGroup
840/// [`InsertDebugMarker`]: RenderCommand::InsertDebugMarker
841#[doc(hidden)]
842#[derive(Debug, Clone)]
843#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
844pub struct BasePass<C, E> {
845    pub label: Option<String>,
846
847    /// If the pass is invalid, contains the error that caused the invalidation.
848    ///
849    /// If the pass is valid, this is `None`.
850    ///
851    /// Passes are serialized into traces. but we don't support doing so for
852    /// passes containing errors. These serde attributes allow `E` to be
853    /// `Infallible`.
854    #[cfg_attr(feature = "serde", serde(skip, default = "Option::default"))]
855    pub error: Option<E>,
856
857    /// The stream of commands.
858    pub commands: Vec<C>,
859
860    /// Dynamic offsets consumed by [`SetBindGroup`] commands in `commands`.
861    ///
862    /// Each successive `SetBindGroup` consumes the next
863    /// [`num_dynamic_offsets`] values from this list.
864    pub dynamic_offsets: Vec<wgt::DynamicOffset>,
865
866    /// Strings used by debug instructions.
867    ///
868    /// Each successive [`PushDebugGroup`] or [`InsertDebugMarker`]
869    /// instruction consumes the next `len` bytes from this vector.
870    pub string_data: Vec<u8>,
871
872    /// Data used by `SetPushConstant` instructions.
873    ///
874    /// See the documentation for [`RenderCommand::SetPushConstant`]
875    /// and [`ComputeCommand::SetPushConstant`] for details.
876    pub push_constant_data: Vec<u32>,
877}
878
879impl<C: Clone, E: Clone> BasePass<C, E> {
880    fn new(label: &Label) -> Self {
881        Self {
882            label: label.as_deref().map(str::to_owned),
883            error: None,
884            commands: Vec::new(),
885            dynamic_offsets: Vec::new(),
886            string_data: Vec::new(),
887            push_constant_data: Vec::new(),
888        }
889    }
890
891    fn new_invalid(label: &Label, err: E) -> Self {
892        Self {
893            label: label.as_deref().map(str::to_owned),
894            error: Some(err),
895            commands: Vec::new(),
896            dynamic_offsets: Vec::new(),
897            string_data: Vec::new(),
898            push_constant_data: Vec::new(),
899        }
900    }
901}
902
903/// Checks the state of a [`compute::ComputePass`] or [`render::RenderPass`] and
904/// evaluates to a mutable reference to the [`BasePass`], if the pass is open and
905/// valid.
906///
907/// If the pass is ended or not valid, **returns from the invoking function**,
908/// like the `?` operator.
909///
910/// If the pass is ended (i.e. the application is attempting to record a command
911/// on a finished pass), returns `Err(EncoderStateError::Ended)` from the
912/// invoking function, for immediate propagation as a validation error.
913///
914/// If the pass is open but invalid (i.e. a previous command encountered an
915/// error), returns `Ok(())` from the invoking function. The pass should already
916/// have stored the previous error, which will be transferred to the parent
917/// encoder when the pass is ended, and then raised as a validation error when
918/// `finish()` is called for the parent).
919///
920/// Although in many cases the functionality of `pass_base!` could be achieved
921/// by combining a helper method on the passes with the `pass_try!` macro,
922/// taking the mutable reference to the base pass in a macro avoids borrowing
923/// conflicts when a reference to some other member of the pass struct is
924/// needed simultaneously with the base pass reference.
925macro_rules! pass_base {
926    ($pass:expr, $scope:expr $(,)?) => {
927        match (&$pass.parent, &$pass.base.error) {
928            // Pass is ended
929            (&None, _) => return Err(EncoderStateError::Ended).map_pass_err($scope),
930            // Pass is invalid
931            (&Some(_), &Some(_)) => return Ok(()),
932            // Pass is open and valid
933            (&Some(_), &None) => &mut $pass.base,
934        }
935    };
936}
937pub(crate) use pass_base;
938
939/// Handles the error case in an expression of type `Result<T, E>`.
940///
941/// This macro operates like the `?` operator (or, in early Rust versions, the
942/// `try!` macro, hence the name `pass_try`). **When there is an error, the
943/// macro returns from the invoking function.** However, `Ok(())`, and not the
944/// error itself, is returned. The error is stored in the pass and will later be
945/// transferred to the parent encoder when the pass ends, and then raised as a
946/// validation error when `finish()` is called for the parent.
947///
948/// `pass_try!` also calls [`MapPassErr::map_pass_err`] to annotate the error
949/// with the command being encoded at the time it occurred.
950macro_rules! pass_try {
951    ($base:expr, $scope:expr, $res:expr $(,)?) => {
952        match $res.map_pass_err($scope) {
953            Ok(val) => val,
954            Err(err) => {
955                $base.error.get_or_insert(err);
956                return Ok(());
957            }
958        }
959    };
960}
961pub(crate) use pass_try;
962
963/// Errors related to the state of a command or pass encoder.
964///
965/// The exact behavior of these errors may change based on the resolution of
966/// <https://github.com/gpuweb/gpuweb/issues/5207>.
967#[derive(Clone, Debug, Error)]
968#[non_exhaustive]
969pub enum EncoderStateError {
970    /// Used internally by wgpu functions to indicate the encoder already
971    /// contained an error. This variant should usually not be seen by users of
972    /// the API, since an effort should be made to provide the caller with a
973    /// more specific reason for the encoder being invalid.
974    #[error("Encoder is invalid")]
975    Invalid,
976
977    /// Returned immediately when an attempt is made to encode a command using
978    /// an encoder that has already finished.
979    #[error("Encoding must not have ended")]
980    Ended,
981
982    /// Returned by a subsequent call to `encoder.finish()`, if there was an
983    /// attempt to open a second pass on the encoder while it was locked for
984    /// a first pass (i.e. the first pass was still open).
985    ///
986    /// Note: only command encoders can be locked (not pass encoders).
987    #[error("Encoder is locked by a previously created render/compute pass. Before recording any new commands, the pass must be ended.")]
988    Locked,
989
990    /// Returned when attempting to end a pass if the parent encoder is not
991    /// locked. This can only happen if pass begin/end calls are mismatched.
992    #[error(
993        "Encoder is not currently locked. A pass can only be ended while the encoder is locked."
994    )]
995    Unlocked,
996
997    /// The command buffer has already been submitted.
998    ///
999    /// Although command encoders and command buffers are distinct WebGPU
1000    /// objects, we use `CommandEncoderStatus` for both.
1001    #[error("This command buffer has already been submitted.")]
1002    Submitted,
1003}
1004
1005impl WebGpuError for EncoderStateError {
1006    fn webgpu_error_type(&self) -> ErrorType {
1007        match self {
1008            EncoderStateError::Invalid
1009            | EncoderStateError::Ended
1010            | EncoderStateError::Locked
1011            | EncoderStateError::Unlocked
1012            | EncoderStateError::Submitted => ErrorType::Validation,
1013        }
1014    }
1015}
1016
1017#[derive(Clone, Debug, Error)]
1018#[non_exhaustive]
1019pub enum CommandEncoderError {
1020    #[error(transparent)]
1021    State(#[from] EncoderStateError),
1022    #[error(transparent)]
1023    Device(#[from] DeviceError),
1024    #[error(transparent)]
1025    InvalidResource(#[from] InvalidResourceError),
1026    #[error(transparent)]
1027    DestroyedResource(#[from] DestroyedResourceError),
1028    #[error(transparent)]
1029    ResourceUsage(#[from] ResourceUsageCompatibilityError),
1030    #[error(transparent)]
1031    MissingFeatures(#[from] MissingFeatures),
1032    #[error(transparent)]
1033    Transfer(#[from] TransferError),
1034    #[error(transparent)]
1035    Clear(#[from] ClearError),
1036    #[error(transparent)]
1037    Query(#[from] QueryError),
1038    #[error(transparent)]
1039    BuildAccelerationStructure(#[from] BuildAccelerationStructureError),
1040    #[error(transparent)]
1041    TransitionResources(#[from] TransitionResourcesError),
1042    #[error(transparent)]
1043    ComputePass(#[from] ComputePassError),
1044    #[error(transparent)]
1045    RenderPass(#[from] RenderPassError),
1046}
1047
1048impl CommandEncoderError {
1049    fn is_destroyed_error(&self) -> bool {
1050        matches!(
1051            self,
1052            Self::DestroyedResource(_)
1053                | Self::Clear(ClearError::DestroyedResource(_))
1054                | Self::Query(QueryError::DestroyedResource(_))
1055                | Self::ComputePass(ComputePassError {
1056                    inner: ComputePassErrorInner::DestroyedResource(_),
1057                    ..
1058                })
1059                | Self::RenderPass(RenderPassError {
1060                    inner: RenderPassErrorInner::DestroyedResource(_),
1061                    ..
1062                })
1063        )
1064    }
1065}
1066
1067impl WebGpuError for CommandEncoderError {
1068    fn webgpu_error_type(&self) -> ErrorType {
1069        let e: &dyn WebGpuError = match self {
1070            Self::Device(e) => e,
1071            Self::InvalidResource(e) => e,
1072            Self::MissingFeatures(e) => e,
1073            Self::State(e) => e,
1074            Self::DestroyedResource(e) => e,
1075            Self::Transfer(e) => e,
1076            Self::Clear(e) => e,
1077            Self::Query(e) => e,
1078            Self::BuildAccelerationStructure(e) => e,
1079            Self::TransitionResources(e) => e,
1080            Self::ResourceUsage(e) => e,
1081            Self::ComputePass(e) => e,
1082            Self::RenderPass(e) => e,
1083        };
1084        e.webgpu_error_type()
1085    }
1086}
1087
1088#[derive(Clone, Debug, Error)]
1089#[non_exhaustive]
1090pub enum TimestampWritesError {
1091    #[error(
1092        "begin and end indices of pass timestamp writes are both set to {idx}, which is not allowed"
1093    )]
1094    IndicesEqual { idx: u32 },
1095    #[error("no begin or end indices were specified for pass timestamp writes, expected at least one to be set")]
1096    IndicesMissing,
1097}
1098
1099impl WebGpuError for TimestampWritesError {
1100    fn webgpu_error_type(&self) -> ErrorType {
1101        match self {
1102            Self::IndicesEqual { .. } | Self::IndicesMissing => ErrorType::Validation,
1103        }
1104    }
1105}
1106
1107impl Global {
1108    pub fn command_encoder_finish(
1109        &self,
1110        encoder_id: id::CommandEncoderId,
1111        _desc: &wgt::CommandBufferDescriptor<Label>,
1112    ) -> (id::CommandBufferId, Option<CommandEncoderError>) {
1113        profiling::scope!("CommandEncoder::finish");
1114
1115        let hub = &self.hub;
1116
1117        let cmd_buf = hub.command_buffers.get(encoder_id.into_command_buffer_id());
1118
1119        // Errors related to destroyed resources are not reported until the
1120        // command buffer is submitted.
1121        let error = match cmd_buf.data.lock().finish() {
1122            Err(e) if !e.is_destroyed_error() => Some(e),
1123            _ => None,
1124        };
1125
1126        (encoder_id.into_command_buffer_id(), error)
1127    }
1128
1129    pub fn command_encoder_push_debug_group(
1130        &self,
1131        encoder_id: id::CommandEncoderId,
1132        label: &str,
1133    ) -> Result<(), EncoderStateError> {
1134        profiling::scope!("CommandEncoder::push_debug_group");
1135        api_log!("CommandEncoder::push_debug_group {label}");
1136
1137        let hub = &self.hub;
1138
1139        let cmd_buf = hub.command_buffers.get(encoder_id.into_command_buffer_id());
1140        let mut cmd_buf_data = cmd_buf.data.lock();
1141        cmd_buf_data.record_with(|cmd_buf_data| -> Result<(), CommandEncoderError> {
1142            #[cfg(feature = "trace")]
1143            if let Some(ref mut list) = cmd_buf_data.commands {
1144                list.push(TraceCommand::PushDebugGroup(label.to_owned()));
1145            }
1146
1147            cmd_buf.device.check_is_valid()?;
1148
1149            let cmd_buf_raw = cmd_buf_data.encoder.open()?;
1150            if !cmd_buf
1151                .device
1152                .instance_flags
1153                .contains(wgt::InstanceFlags::DISCARD_HAL_LABELS)
1154            {
1155                unsafe {
1156                    cmd_buf_raw.begin_debug_marker(label);
1157                }
1158            }
1159
1160            Ok(())
1161        })
1162    }
1163
1164    pub fn command_encoder_insert_debug_marker(
1165        &self,
1166        encoder_id: id::CommandEncoderId,
1167        label: &str,
1168    ) -> Result<(), EncoderStateError> {
1169        profiling::scope!("CommandEncoder::insert_debug_marker");
1170        api_log!("CommandEncoder::insert_debug_marker {label}");
1171
1172        let hub = &self.hub;
1173
1174        let cmd_buf = hub.command_buffers.get(encoder_id.into_command_buffer_id());
1175        let mut cmd_buf_data = cmd_buf.data.lock();
1176        cmd_buf_data.record_with(|cmd_buf_data| -> Result<(), CommandEncoderError> {
1177            #[cfg(feature = "trace")]
1178            if let Some(ref mut list) = cmd_buf_data.commands {
1179                list.push(TraceCommand::InsertDebugMarker(label.to_owned()));
1180            }
1181
1182            cmd_buf.device.check_is_valid()?;
1183
1184            if !cmd_buf
1185                .device
1186                .instance_flags
1187                .contains(wgt::InstanceFlags::DISCARD_HAL_LABELS)
1188            {
1189                let cmd_buf_raw = cmd_buf_data.encoder.open()?;
1190                unsafe {
1191                    cmd_buf_raw.insert_debug_marker(label);
1192                }
1193            }
1194
1195            Ok(())
1196        })
1197    }
1198
1199    pub fn command_encoder_pop_debug_group(
1200        &self,
1201        encoder_id: id::CommandEncoderId,
1202    ) -> Result<(), EncoderStateError> {
1203        profiling::scope!("CommandEncoder::pop_debug_marker");
1204        api_log!("CommandEncoder::pop_debug_group");
1205
1206        let hub = &self.hub;
1207
1208        let cmd_buf = hub.command_buffers.get(encoder_id.into_command_buffer_id());
1209        let mut cmd_buf_data = cmd_buf.data.lock();
1210        cmd_buf_data.record_with(|cmd_buf_data| -> Result<(), CommandEncoderError> {
1211            #[cfg(feature = "trace")]
1212            if let Some(ref mut list) = cmd_buf_data.commands {
1213                list.push(TraceCommand::PopDebugGroup);
1214            }
1215
1216            cmd_buf.device.check_is_valid()?;
1217
1218            let cmd_buf_raw = cmd_buf_data.encoder.open()?;
1219            if !cmd_buf
1220                .device
1221                .instance_flags
1222                .contains(wgt::InstanceFlags::DISCARD_HAL_LABELS)
1223            {
1224                unsafe {
1225                    cmd_buf_raw.end_debug_marker();
1226                }
1227            }
1228
1229            Ok(())
1230        })
1231    }
1232
1233    fn validate_pass_timestamp_writes<E>(
1234        device: &Device,
1235        query_sets: &Storage<Fallible<QuerySet>>,
1236        timestamp_writes: &PassTimestampWrites,
1237    ) -> Result<ArcPassTimestampWrites, E>
1238    where
1239        E: From<TimestampWritesError>
1240            + From<QueryUseError>
1241            + From<DeviceError>
1242            + From<MissingFeatures>
1243            + From<InvalidResourceError>,
1244    {
1245        let &PassTimestampWrites {
1246            query_set,
1247            beginning_of_pass_write_index,
1248            end_of_pass_write_index,
1249        } = timestamp_writes;
1250
1251        device.require_features(wgt::Features::TIMESTAMP_QUERY)?;
1252
1253        let query_set = query_sets.get(query_set).get()?;
1254
1255        query_set.same_device(device)?;
1256
1257        for idx in [beginning_of_pass_write_index, end_of_pass_write_index]
1258            .into_iter()
1259            .flatten()
1260        {
1261            query_set.validate_query(SimplifiedQueryType::Timestamp, idx, None)?;
1262        }
1263
1264        if let Some((begin, end)) = beginning_of_pass_write_index.zip(end_of_pass_write_index) {
1265            if begin == end {
1266                return Err(TimestampWritesError::IndicesEqual { idx: begin }.into());
1267            }
1268        }
1269
1270        if beginning_of_pass_write_index
1271            .or(end_of_pass_write_index)
1272            .is_none()
1273        {
1274            return Err(TimestampWritesError::IndicesMissing.into());
1275        }
1276
1277        Ok(ArcPassTimestampWrites {
1278            query_set,
1279            beginning_of_pass_write_index,
1280            end_of_pass_write_index,
1281        })
1282    }
1283}
1284
1285fn push_constant_clear<PushFn>(offset: u32, size_bytes: u32, mut push_fn: PushFn)
1286where
1287    PushFn: FnMut(u32, &[u32]),
1288{
1289    let mut count_words = 0_u32;
1290    let size_words = size_bytes / wgt::PUSH_CONSTANT_ALIGNMENT;
1291    while count_words < size_words {
1292        let count_bytes = count_words * wgt::PUSH_CONSTANT_ALIGNMENT;
1293        let size_to_write_words =
1294            (size_words - count_words).min(PUSH_CONSTANT_CLEAR_ARRAY.len() as u32);
1295
1296        push_fn(
1297            offset + count_bytes,
1298            &PUSH_CONSTANT_CLEAR_ARRAY[0..size_to_write_words as usize],
1299        );
1300
1301        count_words += size_to_write_words;
1302    }
1303}
1304
1305#[derive(Debug, Copy, Clone)]
1306struct StateChange<T> {
1307    last_state: Option<T>,
1308}
1309
1310impl<T: Copy + PartialEq> StateChange<T> {
1311    fn new() -> Self {
1312        Self { last_state: None }
1313    }
1314    fn set_and_check_redundant(&mut self, new_state: T) -> bool {
1315        let already_set = self.last_state == Some(new_state);
1316        self.last_state = Some(new_state);
1317        already_set
1318    }
1319    fn reset(&mut self) {
1320        self.last_state = None;
1321    }
1322}
1323
1324impl<T: Copy + PartialEq> Default for StateChange<T> {
1325    fn default() -> Self {
1326        Self::new()
1327    }
1328}
1329
1330#[derive(Debug)]
1331struct BindGroupStateChange {
1332    last_states: [StateChange<Option<id::BindGroupId>>; hal::MAX_BIND_GROUPS],
1333}
1334
1335impl BindGroupStateChange {
1336    fn new() -> Self {
1337        Self {
1338            last_states: [StateChange::new(); hal::MAX_BIND_GROUPS],
1339        }
1340    }
1341
1342    fn set_and_check_redundant(
1343        &mut self,
1344        bind_group_id: Option<id::BindGroupId>,
1345        index: u32,
1346        dynamic_offsets: &mut Vec<u32>,
1347        offsets: &[wgt::DynamicOffset],
1348    ) -> bool {
1349        // For now never deduplicate bind groups with dynamic offsets.
1350        if offsets.is_empty() {
1351            // If this get returns None, that means we're well over the limit,
1352            // so let the call through to get a proper error
1353            if let Some(current_bind_group) = self.last_states.get_mut(index as usize) {
1354                // Bail out if we're binding the same bind group.
1355                if current_bind_group.set_and_check_redundant(bind_group_id) {
1356                    return true;
1357                }
1358            }
1359        } else {
1360            // We intentionally remove the memory of this bind group if we have dynamic offsets,
1361            // such that if you try to bind this bind group later with _no_ dynamic offsets it
1362            // tries to bind it again and gives a proper validation error.
1363            if let Some(current_bind_group) = self.last_states.get_mut(index as usize) {
1364                current_bind_group.reset();
1365            }
1366            dynamic_offsets.extend_from_slice(offsets);
1367        }
1368        false
1369    }
1370    fn reset(&mut self) {
1371        self.last_states = [StateChange::new(); hal::MAX_BIND_GROUPS];
1372    }
1373}
1374
1375impl Default for BindGroupStateChange {
1376    fn default() -> Self {
1377        Self::new()
1378    }
1379}
1380
1381/// Helper to attach [`PassErrorScope`] to errors.
1382trait MapPassErr<T> {
1383    fn map_pass_err(self, scope: PassErrorScope) -> T;
1384}
1385
1386impl<T, E, F> MapPassErr<Result<T, F>> for Result<T, E>
1387where
1388    E: MapPassErr<F>,
1389{
1390    fn map_pass_err(self, scope: PassErrorScope) -> Result<T, F> {
1391        self.map_err(|err| err.map_pass_err(scope))
1392    }
1393}
1394
1395impl MapPassErr<PassStateError> for EncoderStateError {
1396    fn map_pass_err(self, scope: PassErrorScope) -> PassStateError {
1397        PassStateError { scope, inner: self }
1398    }
1399}
1400
1401#[derive(Clone, Copy, Debug)]
1402pub enum DrawKind {
1403    Draw,
1404    DrawIndirect,
1405    MultiDrawIndirect,
1406    MultiDrawIndirectCount,
1407}
1408
1409/// A command that can be recorded in a pass or bundle.
1410///
1411/// This is used to provide context for errors during command recording.
1412/// [`MapPassErr`] is used as a helper to attach a `PassErrorScope` to
1413/// an error.
1414///
1415/// The [`PassErrorScope::Bundle`] and [`PassErrorScope::Pass`] variants
1416/// are used when the error occurs during the opening or closing of the
1417/// pass or bundle.
1418#[derive(Clone, Copy, Debug, Error)]
1419pub enum PassErrorScope {
1420    // TODO: Extract out the 2 error variants below so that we can always
1421    // include the ResourceErrorIdent of the pass around all inner errors
1422    #[error("In a bundle parameter")]
1423    Bundle,
1424    #[error("In a pass parameter")]
1425    Pass,
1426    #[error("In a set_bind_group command")]
1427    SetBindGroup,
1428    #[error("In a set_pipeline command")]
1429    SetPipelineRender,
1430    #[error("In a set_pipeline command")]
1431    SetPipelineCompute,
1432    #[error("In a set_push_constant command")]
1433    SetPushConstant,
1434    #[error("In a set_vertex_buffer command")]
1435    SetVertexBuffer,
1436    #[error("In a set_index_buffer command")]
1437    SetIndexBuffer,
1438    #[error("In a set_blend_constant command")]
1439    SetBlendConstant,
1440    #[error("In a set_stencil_reference command")]
1441    SetStencilReference,
1442    #[error("In a set_viewport command")]
1443    SetViewport,
1444    #[error("In a set_scissor_rect command")]
1445    SetScissorRect,
1446    #[error("In a draw command, kind: {kind:?}")]
1447    Draw { kind: DrawKind, indexed: bool },
1448    #[error("In a write_timestamp command")]
1449    WriteTimestamp,
1450    #[error("In a begin_occlusion_query command")]
1451    BeginOcclusionQuery,
1452    #[error("In a end_occlusion_query command")]
1453    EndOcclusionQuery,
1454    #[error("In a begin_pipeline_statistics_query command")]
1455    BeginPipelineStatisticsQuery,
1456    #[error("In a end_pipeline_statistics_query command")]
1457    EndPipelineStatisticsQuery,
1458    #[error("In a execute_bundle command")]
1459    ExecuteBundle,
1460    #[error("In a dispatch command, indirect:{indirect}")]
1461    Dispatch { indirect: bool },
1462    #[error("In a push_debug_group command")]
1463    PushDebugGroup,
1464    #[error("In a pop_debug_group command")]
1465    PopDebugGroup,
1466    #[error("In a insert_debug_marker command")]
1467    InsertDebugMarker,
1468}
1469
1470/// Variant of `EncoderStateError` that includes the pass scope.
1471#[derive(Clone, Debug, Error)]
1472#[error("{scope}")]
1473pub struct PassStateError {
1474    pub scope: PassErrorScope,
1475    #[source]
1476    pub(super) inner: EncoderStateError,
1477}
1478
1479impl WebGpuError for PassStateError {
1480    fn webgpu_error_type(&self) -> ErrorType {
1481        let Self { scope: _, inner } = self;
1482        inner.webgpu_error_type()
1483    }
1484}