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 render_command;
12mod timestamp_writes;
13mod transfer;
14
15use std::sync::Arc;
16
17pub(crate) use self::clear::clear_texture;
18pub use self::{
19    bundle::*, clear::ClearError, compute::*, compute_command::ComputeCommand, draw::*, query::*,
20    render::*, render_command::RenderCommand, transfer::*,
21};
22pub(crate) use allocator::CommandAllocator;
23
24pub(crate) use timestamp_writes::ArcPassTimestampWrites;
25pub use timestamp_writes::PassTimestampWrites;
26
27use self::memory_init::CommandBufferTextureMemoryActions;
28
29use crate::device::{Device, DeviceError};
30use crate::lock::{rank, Mutex};
31use crate::snatch::SnatchGuard;
32
33use crate::init_tracker::BufferInitTrackerAction;
34use crate::resource::{InvalidResourceError, Labeled};
35use crate::track::{DeviceTracker, Tracker, UsageScope};
36use crate::LabelHelpers;
37use crate::{api_log, global::Global, id, resource_log, Label};
38
39use thiserror::Error;
40
41#[cfg(feature = "trace")]
42use crate::device::trace::Command as TraceCommand;
43
44const PUSH_CONSTANT_CLEAR_ARRAY: &[u32] = &[0_u32; 64];
45
46/// The current state of a [`CommandBuffer`].
47#[derive(Debug)]
48pub(crate) enum CommandEncoderStatus {
49    /// Ready to record commands. An encoder's initial state.
50    ///
51    /// Command building methods like [`command_encoder_clear_buffer`] and
52    /// [`compute_pass_end`] require the encoder to be in this
53    /// state.
54    ///
55    /// This corresponds to WebGPU's "open" state.
56    /// See <https://www.w3.org/TR/webgpu/#encoder-state-open>
57    ///
58    /// [`command_encoder_clear_buffer`]: Global::command_encoder_clear_buffer
59    /// [`compute_pass_end`]: Global::compute_pass_end
60    Recording,
61
62    /// Locked by a render or compute pass.
63    ///
64    /// This state is entered when a render/compute pass is created,
65    /// and exited when the pass is ended.
66    ///
67    /// As long as the command encoder is locked, any command building operation on it will fail
68    /// and put the encoder into the [`CommandEncoderStatus::Error`] state.
69    /// See <https://www.w3.org/TR/webgpu/#encoder-state-locked>
70    Locked,
71
72    /// Command recording is complete, and the buffer is ready for submission.
73    ///
74    /// [`Global::command_encoder_finish`] transitions a
75    /// `CommandBuffer` from the `Recording` state into this state.
76    ///
77    /// [`Global::queue_submit`] drops command buffers unless they are
78    /// in this state.
79    Finished,
80
81    /// An error occurred while recording a compute or render pass.
82    ///
83    /// When a `CommandEncoder` is left in this state, we have also
84    /// returned an error result from the function that encountered
85    /// the problem. Future attempts to use the encoder (for example,
86    /// calls to [`CommandBufferMutable::check_recording`]) will also return
87    /// errors.
88    ///
89    /// Calling [`Global::command_encoder_finish`] in this state
90    /// discards the command buffer under construction.
91    Error,
92}
93
94/// A raw [`CommandEncoder`][rce], and the raw [`CommandBuffer`][rcb]s built from it.
95///
96/// Each wgpu-core [`CommandBuffer`] owns an instance of this type, which is
97/// where the commands are actually stored.
98///
99/// This holds a `Vec` of raw [`CommandBuffer`][rcb]s, not just one. We are not
100/// always able to record commands in the order in which they must ultimately be
101/// submitted to the queue, but raw command buffers don't permit inserting new
102/// commands into the middle of a recorded stream. However, hal queue submission
103/// accepts a series of command buffers at once, so we can simply break the
104/// stream up into multiple buffers, and then reorder the buffers. See
105/// [`CommandEncoder::close_and_swap`] for a specific example of this.
106///
107/// Note that a [`CommandEncoderId`] actually refers to a [`CommandBuffer`].
108/// Methods that take a command encoder id actually look up the command buffer,
109/// and then use its encoder.
110///
111/// [rce]: hal::Api::CommandEncoder
112/// [rcb]: hal::Api::CommandBuffer
113/// [`CommandEncoderId`]: crate::id::CommandEncoderId
114pub(crate) struct CommandEncoder {
115    /// The underlying `wgpu_hal` [`CommandEncoder`].
116    ///
117    /// Successfully executed command buffers' encoders are saved in a
118    /// [`CommandAllocator`] for recycling.
119    ///
120    /// [`CommandEncoder`]: hal::Api::CommandEncoder
121    /// [`CommandAllocator`]: crate::command::CommandAllocator
122    raw: Box<dyn hal::DynCommandEncoder>,
123
124    /// All the raw command buffers for our owning [`CommandBuffer`], in
125    /// submission order.
126    ///
127    /// These command buffers were all constructed with `raw`. The
128    /// [`wgpu_hal::CommandEncoder`] trait forbids these from outliving `raw`,
129    /// and requires that we provide all of these when we call
130    /// [`raw.reset_all()`][CE::ra], so the encoder and its buffers travel
131    /// together.
132    ///
133    /// [CE::ra]: hal::CommandEncoder::reset_all
134    /// [`wgpu_hal::CommandEncoder`]: hal::CommandEncoder
135    list: Vec<Box<dyn hal::DynCommandBuffer>>,
136
137    /// True if `raw` is in the "recording" state.
138    ///
139    /// See the documentation for [`wgpu_hal::CommandEncoder`] for
140    /// details on the states `raw` can be in.
141    ///
142    /// [`wgpu_hal::CommandEncoder`]: hal::CommandEncoder
143    is_open: bool,
144
145    hal_label: Option<String>,
146}
147
148//TODO: handle errors better
149impl CommandEncoder {
150    /// Finish the current command buffer, if any, and place it
151    /// at the second-to-last position in our list.
152    ///
153    /// If we have opened this command encoder, finish its current
154    /// command buffer, and insert it just before the last element in
155    /// [`self.list`][l]. If this command buffer is closed, do nothing.
156    ///
157    /// On return, the underlying hal encoder is closed.
158    ///
159    /// What is this for?
160    ///
161    /// The `wgpu_hal` contract requires that each render or compute pass's
162    /// commands be preceded by calls to [`transition_buffers`] and
163    /// [`transition_textures`], to put the resources the pass operates on in
164    /// the appropriate state. Unfortunately, we don't know which transitions
165    /// are needed until we're done recording the pass itself. Rather than
166    /// iterating over the pass twice, we note the necessary transitions as we
167    /// record its commands, finish the raw command buffer for the actual pass,
168    /// record a new raw command buffer for the transitions, and jam that buffer
169    /// in just before the pass's. This is the function that jams in the
170    /// transitions' command buffer.
171    ///
172    /// [l]: CommandEncoder::list
173    /// [`transition_buffers`]: hal::CommandEncoder::transition_buffers
174    /// [`transition_textures`]: hal::CommandEncoder::transition_textures
175    fn close_and_swap(&mut self, device: &Device) -> Result<(), DeviceError> {
176        if self.is_open {
177            self.is_open = false;
178            let new = unsafe { self.raw.end_encoding() }.map_err(|e| device.handle_hal_error(e))?;
179            self.list.insert(self.list.len() - 1, new);
180        }
181
182        Ok(())
183    }
184
185    /// Finish the current command buffer, if any, and add it to the
186    /// end of [`self.list`][l].
187    ///
188    /// If we have opened this command encoder, finish its current
189    /// command buffer, and push it onto the end of [`self.list`][l].
190    /// If this command buffer is closed, do nothing.
191    ///
192    /// On return, the underlying hal encoder is closed.
193    ///
194    /// [l]: CommandEncoder::list
195    fn close(&mut self, device: &Device) -> Result<(), DeviceError> {
196        if self.is_open {
197            self.is_open = false;
198            let cmd_buf =
199                unsafe { self.raw.end_encoding() }.map_err(|e| device.handle_hal_error(e))?;
200            self.list.push(cmd_buf);
201        }
202
203        Ok(())
204    }
205
206    /// Discard the command buffer under construction, if any.
207    ///
208    /// The underlying hal encoder is closed, if it was recording.
209    pub(crate) fn discard(&mut self) {
210        if self.is_open {
211            self.is_open = false;
212            unsafe { self.raw.discard_encoding() };
213        }
214    }
215
216    /// Begin recording a new command buffer, if we haven't already.
217    ///
218    /// The underlying hal encoder is put in the "recording" state.
219    pub(crate) fn open(
220        &mut self,
221        device: &Device,
222    ) -> Result<&mut dyn hal::DynCommandEncoder, DeviceError> {
223        if !self.is_open {
224            self.is_open = true;
225            let hal_label = self.hal_label.as_deref();
226            unsafe { self.raw.begin_encoding(hal_label) }
227                .map_err(|e| device.handle_hal_error(e))?;
228        }
229
230        Ok(self.raw.as_mut())
231    }
232
233    /// Begin recording a new command buffer for a render pass, with
234    /// its own label.
235    ///
236    /// The underlying hal encoder is put in the "recording" state.
237    fn open_pass(&mut self, hal_label: Option<&str>, device: &Device) -> Result<(), DeviceError> {
238        self.is_open = true;
239        unsafe { self.raw.begin_encoding(hal_label) }.map_err(|e| device.handle_hal_error(e))?;
240
241        Ok(())
242    }
243}
244
245pub(crate) struct BakedCommands {
246    pub(crate) encoder: Box<dyn hal::DynCommandEncoder>,
247    pub(crate) list: Vec<Box<dyn hal::DynCommandBuffer>>,
248    pub(crate) trackers: Tracker,
249    buffer_memory_init_actions: Vec<BufferInitTrackerAction>,
250    texture_memory_actions: CommandBufferTextureMemoryActions,
251}
252
253/// The mutable state of a [`CommandBuffer`].
254pub struct CommandBufferMutable {
255    /// The [`wgpu_hal::Api::CommandBuffer`]s we've built so far, and the encoder
256    /// they belong to.
257    ///
258    /// [`wgpu_hal::Api::CommandBuffer`]: hal::Api::CommandBuffer
259    pub(crate) encoder: CommandEncoder,
260
261    /// The current state of this command buffer's encoder.
262    status: CommandEncoderStatus,
263
264    /// All the resources that the commands recorded so far have referred to.
265    pub(crate) trackers: Tracker,
266
267    /// The regions of buffers and textures these commands will read and write.
268    ///
269    /// This is used to determine which portions of which
270    /// buffers/textures we actually need to initialize. If we're
271    /// definitely going to write to something before we read from it,
272    /// we don't need to clear its contents.
273    buffer_memory_init_actions: Vec<BufferInitTrackerAction>,
274    texture_memory_actions: CommandBufferTextureMemoryActions,
275
276    pub(crate) pending_query_resets: QueryResetMap,
277    #[cfg(feature = "trace")]
278    pub(crate) commands: Option<Vec<TraceCommand>>,
279}
280
281impl CommandBufferMutable {
282    pub(crate) fn open_encoder_and_tracker(
283        &mut self,
284        device: &Device,
285    ) -> Result<(&mut dyn hal::DynCommandEncoder, &mut Tracker), DeviceError> {
286        let encoder = self.encoder.open(device)?;
287        let tracker = &mut self.trackers;
288
289        Ok((encoder, tracker))
290    }
291
292    fn lock_encoder_impl(&mut self, lock: bool) -> Result<(), CommandEncoderError> {
293        match self.status {
294            CommandEncoderStatus::Recording => {
295                if lock {
296                    self.status = CommandEncoderStatus::Locked;
297                }
298                Ok(())
299            }
300            CommandEncoderStatus::Locked => {
301                // Any operation on a locked encoder is required to put it into the invalid/error state.
302                // See https://www.w3.org/TR/webgpu/#encoder-state-locked
303                self.encoder.discard();
304                self.status = CommandEncoderStatus::Error;
305                Err(CommandEncoderError::Locked)
306            }
307            CommandEncoderStatus::Finished => Err(CommandEncoderError::NotRecording),
308            CommandEncoderStatus::Error => Err(CommandEncoderError::Invalid),
309        }
310    }
311
312    /// Checks that the encoder is in the [`CommandEncoderStatus::Recording`] state.
313    fn check_recording(&mut self) -> Result<(), CommandEncoderError> {
314        self.lock_encoder_impl(false)
315    }
316
317    /// Locks the encoder by putting it in the [`CommandEncoderStatus::Locked`] state.
318    ///
319    /// Call [`CommandBufferMutable::unlock_encoder`] to put the [`CommandBuffer`] back into the [`CommandEncoderStatus::Recording`] state.
320    fn lock_encoder(&mut self) -> Result<(), CommandEncoderError> {
321        self.lock_encoder_impl(true)
322    }
323
324    /// Unlocks the [`CommandBuffer`] and puts it back into the [`CommandEncoderStatus::Recording`] state.
325    ///
326    /// This function is the counterpart to [`CommandBufferMutable::lock_encoder`].
327    /// It is only valid to call this function if the encoder is in the [`CommandEncoderStatus::Locked`] state.
328    fn unlock_encoder(&mut self) -> Result<(), CommandEncoderError> {
329        match self.status {
330            CommandEncoderStatus::Recording => Err(CommandEncoderError::Invalid),
331            CommandEncoderStatus::Locked => {
332                self.status = CommandEncoderStatus::Recording;
333                Ok(())
334            }
335            CommandEncoderStatus::Finished => Err(CommandEncoderError::Invalid),
336            CommandEncoderStatus::Error => Err(CommandEncoderError::Invalid),
337        }
338    }
339
340    pub fn check_finished(&self) -> Result<(), CommandEncoderError> {
341        match self.status {
342            CommandEncoderStatus::Finished => Ok(()),
343            _ => Err(CommandEncoderError::Invalid),
344        }
345    }
346
347    pub(crate) fn finish(&mut self, device: &Device) -> Result<(), CommandEncoderError> {
348        match self.status {
349            CommandEncoderStatus::Recording => {
350                if let Err(e) = self.encoder.close(device) {
351                    Err(e.into())
352                } else {
353                    self.status = CommandEncoderStatus::Finished;
354                    // Note: if we want to stop tracking the swapchain texture view,
355                    // this is the place to do it.
356                    Ok(())
357                }
358            }
359            CommandEncoderStatus::Locked => {
360                self.encoder.discard();
361                self.status = CommandEncoderStatus::Error;
362                Err(CommandEncoderError::Locked)
363            }
364            CommandEncoderStatus::Finished => Err(CommandEncoderError::NotRecording),
365            CommandEncoderStatus::Error => {
366                self.encoder.discard();
367                Err(CommandEncoderError::Invalid)
368            }
369        }
370    }
371
372    pub(crate) fn into_baked_commands(self) -> BakedCommands {
373        BakedCommands {
374            encoder: self.encoder.raw,
375            list: self.encoder.list,
376            trackers: self.trackers,
377            buffer_memory_init_actions: self.buffer_memory_init_actions,
378            texture_memory_actions: self.texture_memory_actions,
379        }
380    }
381
382    pub(crate) fn destroy(mut self, device: &Device) {
383        self.encoder.discard();
384        unsafe {
385            self.encoder.raw.reset_all(self.encoder.list);
386        }
387        unsafe {
388            device.raw().destroy_command_encoder(self.encoder.raw);
389        }
390    }
391}
392
393/// A buffer of commands to be submitted to the GPU for execution.
394///
395/// Whereas the WebGPU API uses two separate types for command buffers and
396/// encoders, this type is a fusion of the two:
397///
398/// - During command recording, this holds a [`CommandEncoder`] accepting this
399///   buffer's commands. In this state, the [`CommandBuffer`] type behaves like
400///   a WebGPU `GPUCommandEncoder`.
401///
402/// - Once command recording is finished by calling
403///   [`Global::command_encoder_finish`], no further recording is allowed. The
404///   internal [`CommandEncoder`] is retained solely as a storage pool for the
405///   raw command buffers. In this state, the value behaves like a WebGPU
406///   `GPUCommandBuffer`.
407///
408/// - Once a command buffer is submitted to the queue, it is removed from the id
409///   registry, and its contents are taken to construct a [`BakedCommands`],
410///   whose contents eventually become the property of the submission queue.
411pub struct CommandBuffer {
412    pub(crate) device: Arc<Device>,
413    support_clear_texture: bool,
414    /// The `label` from the descriptor used to create the resource.
415    label: String,
416
417    /// The mutable state of this command buffer.
418    ///
419    /// This `Option` is populated when the command buffer is first created.
420    /// When this is submitted, dropped, or destroyed, its contents are
421    /// extracted into a [`BakedCommands`] by
422    /// [`CommandBufferMutable::into_baked_commands`].
423    pub(crate) data: Mutex<Option<CommandBufferMutable>>,
424}
425
426impl Drop for CommandBuffer {
427    fn drop(&mut self) {
428        resource_log!("Drop {}", self.error_ident());
429        if let Some(data) = self.data.lock().take() {
430            data.destroy(&self.device);
431        }
432    }
433}
434
435impl CommandBuffer {
436    pub(crate) fn new(
437        encoder: Box<dyn hal::DynCommandEncoder>,
438        device: &Arc<Device>,
439        label: &Label,
440    ) -> Self {
441        CommandBuffer {
442            device: device.clone(),
443            support_clear_texture: device.features.contains(wgt::Features::CLEAR_TEXTURE),
444            label: label.to_string(),
445            data: Mutex::new(
446                rank::COMMAND_BUFFER_DATA,
447                Some(CommandBufferMutable {
448                    encoder: CommandEncoder {
449                        raw: encoder,
450                        is_open: false,
451                        list: Vec::new(),
452                        hal_label: label.to_hal(device.instance_flags).map(str::to_owned),
453                    },
454                    status: CommandEncoderStatus::Recording,
455                    trackers: Tracker::new(),
456                    buffer_memory_init_actions: Default::default(),
457                    texture_memory_actions: Default::default(),
458                    pending_query_resets: QueryResetMap::new(),
459                    #[cfg(feature = "trace")]
460                    commands: if device.trace.lock().is_some() {
461                        Some(Vec::new())
462                    } else {
463                        None
464                    },
465                }),
466            ),
467        }
468    }
469
470    pub(crate) fn new_invalid(device: &Arc<Device>, label: &Label) -> Self {
471        CommandBuffer {
472            device: device.clone(),
473            support_clear_texture: device.features.contains(wgt::Features::CLEAR_TEXTURE),
474            label: label.to_string(),
475            data: Mutex::new(rank::COMMAND_BUFFER_DATA, None),
476        }
477    }
478
479    pub(crate) fn insert_barriers_from_tracker(
480        raw: &mut dyn hal::DynCommandEncoder,
481        base: &mut Tracker,
482        head: &Tracker,
483        snatch_guard: &SnatchGuard,
484    ) {
485        profiling::scope!("insert_barriers");
486
487        base.buffers.set_from_tracker(&head.buffers);
488        base.textures.set_from_tracker(&head.textures);
489
490        Self::drain_barriers(raw, base, snatch_guard);
491    }
492
493    pub(crate) fn insert_barriers_from_scope(
494        raw: &mut dyn hal::DynCommandEncoder,
495        base: &mut Tracker,
496        head: &UsageScope,
497        snatch_guard: &SnatchGuard,
498    ) {
499        profiling::scope!("insert_barriers");
500
501        base.buffers.set_from_usage_scope(&head.buffers);
502        base.textures.set_from_usage_scope(&head.textures);
503
504        Self::drain_barriers(raw, base, snatch_guard);
505    }
506
507    pub(crate) fn drain_barriers(
508        raw: &mut dyn hal::DynCommandEncoder,
509        base: &mut Tracker,
510        snatch_guard: &SnatchGuard,
511    ) {
512        profiling::scope!("drain_barriers");
513
514        let buffer_barriers = base
515            .buffers
516            .drain_transitions(snatch_guard)
517            .collect::<Vec<_>>();
518        let (transitions, textures) = base.textures.drain_transitions(snatch_guard);
519        let texture_barriers = transitions
520            .into_iter()
521            .enumerate()
522            .map(|(i, p)| p.into_hal(textures[i].unwrap().raw()))
523            .collect::<Vec<_>>();
524
525        unsafe {
526            raw.transition_buffers(&buffer_barriers);
527            raw.transition_textures(&texture_barriers);
528        }
529    }
530
531    pub(crate) fn insert_barriers_from_device_tracker(
532        raw: &mut dyn hal::DynCommandEncoder,
533        base: &mut DeviceTracker,
534        head: &Tracker,
535        snatch_guard: &SnatchGuard,
536    ) {
537        profiling::scope!("insert_barriers_from_device_tracker");
538
539        let buffer_barriers = base
540            .buffers
541            .set_from_tracker_and_drain_transitions(&head.buffers, snatch_guard)
542            .collect::<Vec<_>>();
543
544        let texture_barriers = base
545            .textures
546            .set_from_tracker_and_drain_transitions(&head.textures, snatch_guard)
547            .collect::<Vec<_>>();
548
549        unsafe {
550            raw.transition_buffers(&buffer_barriers);
551            raw.transition_textures(&texture_barriers);
552        }
553    }
554}
555
556impl CommandBuffer {
557    pub fn try_get<'a>(
558        &'a self,
559    ) -> Result<parking_lot::MappedMutexGuard<'a, CommandBufferMutable>, InvalidResourceError> {
560        let g = self.data.lock();
561        crate::lock::MutexGuard::try_map(g, |data| data.as_mut())
562            .map_err(|_| InvalidResourceError(self.error_ident()))
563    }
564
565    pub fn try_take<'a>(&'a self) -> Result<CommandBufferMutable, InvalidResourceError> {
566        self.data
567            .lock()
568            .take()
569            .ok_or_else(|| InvalidResourceError(self.error_ident()))
570    }
571}
572
573crate::impl_resource_type!(CommandBuffer);
574crate::impl_labeled!(CommandBuffer);
575crate::impl_parent_device!(CommandBuffer);
576crate::impl_storage_item!(CommandBuffer);
577
578/// A stream of commands for a render pass or compute pass.
579///
580/// This also contains side tables referred to by certain commands,
581/// like dynamic offsets for [`SetBindGroup`] or string data for
582/// [`InsertDebugMarker`].
583///
584/// Render passes use `BasePass<RenderCommand>`, whereas compute
585/// passes use `BasePass<ComputeCommand>`.
586///
587/// [`SetBindGroup`]: RenderCommand::SetBindGroup
588/// [`InsertDebugMarker`]: RenderCommand::InsertDebugMarker
589#[doc(hidden)]
590#[derive(Debug, Clone)]
591#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
592pub struct BasePass<C> {
593    pub label: Option<String>,
594
595    /// The stream of commands.
596    pub commands: Vec<C>,
597
598    /// Dynamic offsets consumed by [`SetBindGroup`] commands in `commands`.
599    ///
600    /// Each successive `SetBindGroup` consumes the next
601    /// [`num_dynamic_offsets`] values from this list.
602    pub dynamic_offsets: Vec<wgt::DynamicOffset>,
603
604    /// Strings used by debug instructions.
605    ///
606    /// Each successive [`PushDebugGroup`] or [`InsertDebugMarker`]
607    /// instruction consumes the next `len` bytes from this vector.
608    pub string_data: Vec<u8>,
609
610    /// Data used by `SetPushConstant` instructions.
611    ///
612    /// See the documentation for [`RenderCommand::SetPushConstant`]
613    /// and [`ComputeCommand::SetPushConstant`] for details.
614    pub push_constant_data: Vec<u32>,
615}
616
617impl<C: Clone> BasePass<C> {
618    fn new(label: &Label) -> Self {
619        Self {
620            label: label.as_ref().map(|cow| cow.to_string()),
621            commands: Vec::new(),
622            dynamic_offsets: Vec::new(),
623            string_data: Vec::new(),
624            push_constant_data: Vec::new(),
625        }
626    }
627}
628
629#[derive(Clone, Debug, Error)]
630#[non_exhaustive]
631pub enum CommandEncoderError {
632    #[error("Command encoder is invalid")]
633    Invalid,
634    #[error("Command encoder must be active")]
635    NotRecording,
636    #[error(transparent)]
637    Device(#[from] DeviceError),
638    #[error("Command encoder is locked by a previously created render/compute pass. Before recording any new commands, the pass must be ended.")]
639    Locked,
640
641    #[error(transparent)]
642    InvalidColorAttachment(#[from] ColorAttachmentError),
643    #[error(transparent)]
644    InvalidResource(#[from] InvalidResourceError),
645}
646
647impl Global {
648    pub fn command_encoder_finish(
649        &self,
650        encoder_id: id::CommandEncoderId,
651        _desc: &wgt::CommandBufferDescriptor<Label>,
652    ) -> (id::CommandBufferId, Option<CommandEncoderError>) {
653        profiling::scope!("CommandEncoder::finish");
654
655        let hub = &self.hub;
656
657        let cmd_buf = hub.command_buffers.get(encoder_id.into_command_buffer_id());
658
659        let error = match cmd_buf
660            .try_get()
661            .map_err(|e| e.into())
662            .and_then(|mut cmd_buf_data| cmd_buf_data.finish(&cmd_buf.device))
663        {
664            Ok(_) => None,
665            Err(e) => Some(e),
666        };
667
668        (encoder_id.into_command_buffer_id(), error)
669    }
670
671    pub fn command_encoder_push_debug_group(
672        &self,
673        encoder_id: id::CommandEncoderId,
674        label: &str,
675    ) -> Result<(), CommandEncoderError> {
676        profiling::scope!("CommandEncoder::push_debug_group");
677        api_log!("CommandEncoder::push_debug_group {label}");
678
679        let hub = &self.hub;
680
681        let cmd_buf = hub.command_buffers.get(encoder_id.into_command_buffer_id());
682        let mut cmd_buf_data = cmd_buf.try_get()?;
683        cmd_buf_data.check_recording()?;
684
685        #[cfg(feature = "trace")]
686        if let Some(ref mut list) = cmd_buf_data.commands {
687            list.push(TraceCommand::PushDebugGroup(label.to_string()));
688        }
689
690        let cmd_buf_raw = cmd_buf_data.encoder.open(&cmd_buf.device)?;
691        if !cmd_buf
692            .device
693            .instance_flags
694            .contains(wgt::InstanceFlags::DISCARD_HAL_LABELS)
695        {
696            unsafe {
697                cmd_buf_raw.begin_debug_marker(label);
698            }
699        }
700        Ok(())
701    }
702
703    pub fn command_encoder_insert_debug_marker(
704        &self,
705        encoder_id: id::CommandEncoderId,
706        label: &str,
707    ) -> Result<(), CommandEncoderError> {
708        profiling::scope!("CommandEncoder::insert_debug_marker");
709        api_log!("CommandEncoder::insert_debug_marker {label}");
710
711        let hub = &self.hub;
712
713        let cmd_buf = hub.command_buffers.get(encoder_id.into_command_buffer_id());
714        let mut cmd_buf_data = cmd_buf.try_get()?;
715        cmd_buf_data.check_recording()?;
716
717        #[cfg(feature = "trace")]
718        if let Some(ref mut list) = cmd_buf_data.commands {
719            list.push(TraceCommand::InsertDebugMarker(label.to_string()));
720        }
721
722        if !cmd_buf
723            .device
724            .instance_flags
725            .contains(wgt::InstanceFlags::DISCARD_HAL_LABELS)
726        {
727            let cmd_buf_raw = cmd_buf_data.encoder.open(&cmd_buf.device)?;
728            unsafe {
729                cmd_buf_raw.insert_debug_marker(label);
730            }
731        }
732        Ok(())
733    }
734
735    pub fn command_encoder_pop_debug_group(
736        &self,
737        encoder_id: id::CommandEncoderId,
738    ) -> Result<(), CommandEncoderError> {
739        profiling::scope!("CommandEncoder::pop_debug_marker");
740        api_log!("CommandEncoder::pop_debug_group");
741
742        let hub = &self.hub;
743
744        let cmd_buf = hub.command_buffers.get(encoder_id.into_command_buffer_id());
745        let mut cmd_buf_data = cmd_buf.try_get()?;
746        cmd_buf_data.check_recording()?;
747
748        #[cfg(feature = "trace")]
749        if let Some(ref mut list) = cmd_buf_data.commands {
750            list.push(TraceCommand::PopDebugGroup);
751        }
752
753        let cmd_buf_raw = cmd_buf_data.encoder.open(&cmd_buf.device)?;
754        if !cmd_buf
755            .device
756            .instance_flags
757            .contains(wgt::InstanceFlags::DISCARD_HAL_LABELS)
758        {
759            unsafe {
760                cmd_buf_raw.end_debug_marker();
761            }
762        }
763        Ok(())
764    }
765}
766
767fn push_constant_clear<PushFn>(offset: u32, size_bytes: u32, mut push_fn: PushFn)
768where
769    PushFn: FnMut(u32, &[u32]),
770{
771    let mut count_words = 0_u32;
772    let size_words = size_bytes / wgt::PUSH_CONSTANT_ALIGNMENT;
773    while count_words < size_words {
774        let count_bytes = count_words * wgt::PUSH_CONSTANT_ALIGNMENT;
775        let size_to_write_words =
776            (size_words - count_words).min(PUSH_CONSTANT_CLEAR_ARRAY.len() as u32);
777
778        push_fn(
779            offset + count_bytes,
780            &PUSH_CONSTANT_CLEAR_ARRAY[0..size_to_write_words as usize],
781        );
782
783        count_words += size_to_write_words;
784    }
785}
786
787#[derive(Debug, Copy, Clone)]
788struct StateChange<T> {
789    last_state: Option<T>,
790}
791
792impl<T: Copy + PartialEq> StateChange<T> {
793    fn new() -> Self {
794        Self { last_state: None }
795    }
796    fn set_and_check_redundant(&mut self, new_state: T) -> bool {
797        let already_set = self.last_state == Some(new_state);
798        self.last_state = Some(new_state);
799        already_set
800    }
801    fn reset(&mut self) {
802        self.last_state = None;
803    }
804}
805
806impl<T: Copy + PartialEq> Default for StateChange<T> {
807    fn default() -> Self {
808        Self::new()
809    }
810}
811
812#[derive(Debug)]
813struct BindGroupStateChange {
814    last_states: [StateChange<Option<id::BindGroupId>>; hal::MAX_BIND_GROUPS],
815}
816
817impl BindGroupStateChange {
818    fn new() -> Self {
819        Self {
820            last_states: [StateChange::new(); hal::MAX_BIND_GROUPS],
821        }
822    }
823
824    fn set_and_check_redundant(
825        &mut self,
826        bind_group_id: Option<id::BindGroupId>,
827        index: u32,
828        dynamic_offsets: &mut Vec<u32>,
829        offsets: &[wgt::DynamicOffset],
830    ) -> bool {
831        // For now never deduplicate bind groups with dynamic offsets.
832        if offsets.is_empty() {
833            // If this get returns None, that means we're well over the limit,
834            // so let the call through to get a proper error
835            if let Some(current_bind_group) = self.last_states.get_mut(index as usize) {
836                // Bail out if we're binding the same bind group.
837                if current_bind_group.set_and_check_redundant(bind_group_id) {
838                    return true;
839                }
840            }
841        } else {
842            // We intentionally remove the memory of this bind group if we have dynamic offsets,
843            // such that if you try to bind this bind group later with _no_ dynamic offsets it
844            // tries to bind it again and gives a proper validation error.
845            if let Some(current_bind_group) = self.last_states.get_mut(index as usize) {
846                current_bind_group.reset();
847            }
848            dynamic_offsets.extend_from_slice(offsets);
849        }
850        false
851    }
852    fn reset(&mut self) {
853        self.last_states = [StateChange::new(); hal::MAX_BIND_GROUPS];
854    }
855}
856
857impl Default for BindGroupStateChange {
858    fn default() -> Self {
859        Self::new()
860    }
861}
862
863trait MapPassErr<T, O> {
864    fn map_pass_err(self, scope: PassErrorScope) -> Result<T, O>;
865}
866
867#[derive(Clone, Copy, Debug)]
868pub enum DrawKind {
869    Draw,
870    DrawIndirect,
871    MultiDrawIndirect,
872    MultiDrawIndirectCount,
873}
874
875#[derive(Clone, Copy, Debug, Error)]
876pub enum PassErrorScope {
877    // TODO: Extract out the 2 error variants below so that we can always
878    // include the ResourceErrorIdent of the pass around all inner errors
879    #[error("In a bundle parameter")]
880    Bundle,
881    #[error("In a pass parameter")]
882    Pass,
883    #[error("In a set_bind_group command")]
884    SetBindGroup,
885    #[error("In a set_pipeline command")]
886    SetPipelineRender,
887    #[error("In a set_pipeline command")]
888    SetPipelineCompute,
889    #[error("In a set_push_constant command")]
890    SetPushConstant,
891    #[error("In a set_vertex_buffer command")]
892    SetVertexBuffer,
893    #[error("In a set_index_buffer command")]
894    SetIndexBuffer,
895    #[error("In a set_blend_constant command")]
896    SetBlendConstant,
897    #[error("In a set_stencil_reference command")]
898    SetStencilReference,
899    #[error("In a set_viewport command")]
900    SetViewport,
901    #[error("In a set_scissor_rect command")]
902    SetScissorRect,
903    #[error("In a draw command, kind: {kind:?}")]
904    Draw { kind: DrawKind, indexed: bool },
905    #[error("While resetting queries after the renderpass was ran")]
906    QueryReset,
907    #[error("In a write_timestamp command")]
908    WriteTimestamp,
909    #[error("In a begin_occlusion_query command")]
910    BeginOcclusionQuery,
911    #[error("In a end_occlusion_query command")]
912    EndOcclusionQuery,
913    #[error("In a begin_pipeline_statistics_query command")]
914    BeginPipelineStatisticsQuery,
915    #[error("In a end_pipeline_statistics_query command")]
916    EndPipelineStatisticsQuery,
917    #[error("In a execute_bundle command")]
918    ExecuteBundle,
919    #[error("In a dispatch command, indirect:{indirect}")]
920    Dispatch { indirect: bool },
921    #[error("In a push_debug_group command")]
922    PushDebugGroup,
923    #[error("In a pop_debug_group command")]
924    PopDebugGroup,
925    #[error("In a insert_debug_marker command")]
926    InsertDebugMarker,
927}