Skip to main content

wgpu_core/command/
compute.rs

1use parking_lot::Mutex;
2use thiserror::Error;
3use wgt::{
4    error::{ErrorType, WebGpuError},
5    BufferAddress, DynamicOffset,
6};
7
8use alloc::{borrow::Cow, boxed::Box, sync::Arc, vec::Vec};
9use core::{convert::Infallible, fmt, str};
10
11use crate::{
12    api_log,
13    binding_model::{BindError, ImmediateUploadError, LateMinBufferBindingSizeMismatch},
14    command::{
15        bind::{Binder, BinderError},
16        compute_command::ArcComputeCommand,
17        encoder::EncodingState,
18        memory_init::{fixup_discarded_surfaces, SurfacesInDiscardState},
19        pass::{self, flush_bindings_helper},
20        pass_base, pass_try,
21        query::{
22            end_pipeline_statistics_query, record_pass_timestamp_writes,
23            validate_and_begin_pipeline_statistics_query,
24        },
25        ArcCommand, ArcPassTimestampWrites, BasePass, BindGroupStateChange, CommandEncoder,
26        CommandEncoderError, DebugGroupError, EncoderStateError, InnerCommandEncoder, MapPassErr,
27        PassErrorScope, PassStateError, PassTimestampWrites, QueryUseError, StateChange,
28        TimestampWritesError, TransitionResourcesError,
29    },
30    device::{Device, DeviceError, MissingDownlevelFlags, MissingFeatures},
31    global::Global,
32    hal_label, id, impl_resource_type,
33    init_tracker::MemoryInitKind,
34    pipeline::ComputePipeline,
35    resource::{
36        self, Buffer, DestroyedResourceError, Fallible, InvalidResourceError, Labeled,
37        MissingBufferUsageError, ParentDevice, RawResourceAccess, TextureView, Trackable,
38    },
39    track::{ResourceUsageCompatibilityError, TextureViewBindGroupState, Tracker},
40    Label,
41};
42
43pub type ComputeBasePass = BasePass<ArcComputeCommand, ComputePassError>;
44
45/// A pass's [encoder state](https://www.w3.org/TR/webgpu/#encoder-state) and
46/// its validity are two distinct conditions, i.e., the full matrix of
47/// (open, ended) x (valid, invalid) is possible.
48///
49/// The presence or absence of the `parent` `Option` indicates the pass's state.
50/// The presence or absence of an error in `base.error` indicates the pass's
51/// validity.
52pub struct ComputePass {
53    /// All pass data & records is stored here.
54    base: ComputeBasePass,
55
56    /// Parent command encoder that this pass records commands into.
57    ///
58    /// If this is `Some`, then the pass is in WebGPU's "open" state. If it is
59    /// `None`, then the pass is in the "ended" state.
60    /// See <https://www.w3.org/TR/webgpu/#encoder-state>
61    parent: Option<Arc<CommandEncoder>>,
62
63    timestamp_writes: Option<ArcPassTimestampWrites>,
64
65    // Resource binding dedupe state.
66    current_bind_groups: BindGroupStateChange,
67    current_pipeline: StateChange<id::ComputePipelineId>,
68}
69
70impl_resource_type!(ComputePass);
71
72impl crate::storage::StorageItem for ComputePass {
73    type Marker = id::markers::ComputePassEncoder;
74}
75
76impl ComputePass {
77    /// If the parent command encoder is invalid, the returned pass will be invalid.
78    fn new(parent: Arc<CommandEncoder>, desc: ArcComputePassDescriptor) -> Self {
79        let ArcComputePassDescriptor {
80            label,
81            timestamp_writes,
82        } = desc;
83
84        Self {
85            base: BasePass::new(&label),
86            parent: Some(parent),
87            timestamp_writes,
88
89            current_bind_groups: BindGroupStateChange::new(),
90            current_pipeline: StateChange::new(),
91        }
92    }
93
94    fn new_invalid(parent: Arc<CommandEncoder>, label: &Label, err: ComputePassError) -> Self {
95        Self {
96            base: BasePass::new_invalid(label, err),
97            parent: Some(parent),
98            timestamp_writes: None,
99            current_bind_groups: BindGroupStateChange::new(),
100            current_pipeline: StateChange::new(),
101        }
102    }
103
104    #[inline]
105    pub fn label(&self) -> Option<&str> {
106        self.base.label.as_deref()
107    }
108}
109
110impl fmt::Debug for ComputePass {
111    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
112        match self.parent {
113            Some(ref cmd_enc) => write!(f, "ComputePass {{ parent: {} }}", cmd_enc.error_ident()),
114            None => write!(f, "ComputePass {{ parent: None }}"),
115        }
116    }
117}
118
119#[derive(Clone, Debug, Default)]
120#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
121pub struct ComputePassDescriptor<'a, PTW = PassTimestampWrites> {
122    pub label: Label<'a>,
123    /// Defines where and when timestamp values will be written for this pass.
124    pub timestamp_writes: Option<PTW>,
125}
126
127/// cbindgen:ignore
128type ArcComputePassDescriptor<'a> = ComputePassDescriptor<'a, ArcPassTimestampWrites>;
129
130#[derive(Clone, Debug, Error)]
131#[non_exhaustive]
132pub enum DispatchError {
133    #[error("Compute pipeline must be set")]
134    MissingPipeline(pass::MissingPipeline),
135    #[error(transparent)]
136    IncompatibleBindGroup(#[from] Box<BinderError>),
137    #[error(
138        "Each current dispatch group size dimension ({current:?}) must be less or equal to {limit}"
139    )]
140    InvalidGroupSize { current: [u32; 3], limit: u32 },
141    #[error(transparent)]
142    BindingSizeTooSmall(#[from] LateMinBufferBindingSizeMismatch),
143    #[error("Not all immediate data required by the pipeline has been set via set_immediates (missing byte ranges: {missing})")]
144    MissingImmediateData {
145        missing: naga::valid::ImmediateSlots,
146    },
147}
148
149impl WebGpuError for DispatchError {
150    fn webgpu_error_type(&self) -> ErrorType {
151        ErrorType::Validation
152    }
153}
154
155/// Error encountered when performing a compute pass.
156#[derive(Clone, Debug, Error)]
157pub enum ComputePassErrorInner {
158    #[error(transparent)]
159    Device(#[from] DeviceError),
160    #[error(transparent)]
161    EncoderState(#[from] EncoderStateError),
162    #[error("Parent encoder is invalid")]
163    InvalidParentEncoder,
164    #[error(transparent)]
165    DebugGroupError(#[from] DebugGroupError),
166    #[error(transparent)]
167    BindGroupIndexOutOfRange(#[from] pass::BindGroupIndexOutOfRange),
168    #[error(transparent)]
169    DestroyedResource(#[from] DestroyedResourceError),
170    #[error("Indirect buffer offset {0:?} is not a multiple of 4")]
171    UnalignedIndirectBufferOffset(BufferAddress),
172    #[error("Indirect buffer of {args_size} bytes starting at offset {offset} would overrun buffer of size {buffer_size}")]
173    IndirectBufferOverrun {
174        args_size: u64,
175        offset: u64,
176        buffer_size: u64,
177    },
178    #[error(transparent)]
179    ResourceUsageCompatibility(#[from] ResourceUsageCompatibilityError),
180    #[error(transparent)]
181    MissingBufferUsage(#[from] MissingBufferUsageError),
182    #[error(transparent)]
183    Dispatch(#[from] DispatchError),
184    #[error(transparent)]
185    Bind(#[from] BindError),
186    #[error(transparent)]
187    ImmediateData(#[from] ImmediateUploadError),
188    #[error(transparent)]
189    QueryUse(#[from] QueryUseError),
190    #[error(transparent)]
191    TransitionResources(#[from] TransitionResourcesError),
192    #[error(transparent)]
193    MissingFeatures(#[from] MissingFeatures),
194    #[error(transparent)]
195    MissingDownlevelFlags(#[from] MissingDownlevelFlags),
196    #[error("The compute pass has already been ended and no further commands can be recorded")]
197    PassEnded,
198    #[error(transparent)]
199    InvalidResource(#[from] InvalidResourceError),
200    #[error(transparent)]
201    TimestampWrites(#[from] TimestampWritesError),
202    // This one is unreachable, but required for generic pass support
203    #[error(transparent)]
204    InvalidValuesOffset(#[from] pass::InvalidValuesOffset),
205}
206
207/// Error encountered when performing a compute pass, stored for later reporting
208/// when encoding ends.
209#[derive(Clone, Debug, Error)]
210#[error("{scope}")]
211pub struct ComputePassError {
212    pub scope: PassErrorScope,
213    #[source]
214    pub(super) inner: ComputePassErrorInner,
215}
216
217impl From<pass::MissingPipeline> for ComputePassErrorInner {
218    fn from(value: pass::MissingPipeline) -> Self {
219        Self::Dispatch(DispatchError::MissingPipeline(value))
220    }
221}
222
223impl<E> MapPassErr<ComputePassError> for E
224where
225    E: Into<ComputePassErrorInner>,
226{
227    fn map_pass_err(self, scope: PassErrorScope) -> ComputePassError {
228        ComputePassError {
229            scope,
230            inner: self.into(),
231        }
232    }
233}
234
235impl WebGpuError for ComputePassError {
236    fn webgpu_error_type(&self) -> ErrorType {
237        let Self { scope: _, inner } = self;
238        match inner {
239            ComputePassErrorInner::Device(e) => e.webgpu_error_type(),
240            ComputePassErrorInner::EncoderState(e) => e.webgpu_error_type(),
241            ComputePassErrorInner::DebugGroupError(e) => e.webgpu_error_type(),
242            ComputePassErrorInner::DestroyedResource(e) => e.webgpu_error_type(),
243            ComputePassErrorInner::ResourceUsageCompatibility(e) => e.webgpu_error_type(),
244            ComputePassErrorInner::MissingBufferUsage(e) => e.webgpu_error_type(),
245            ComputePassErrorInner::Dispatch(e) => e.webgpu_error_type(),
246            ComputePassErrorInner::Bind(e) => e.webgpu_error_type(),
247            ComputePassErrorInner::ImmediateData(e) => e.webgpu_error_type(),
248            ComputePassErrorInner::QueryUse(e) => e.webgpu_error_type(),
249            ComputePassErrorInner::TransitionResources(e) => e.webgpu_error_type(),
250            ComputePassErrorInner::MissingFeatures(e) => e.webgpu_error_type(),
251            ComputePassErrorInner::MissingDownlevelFlags(e) => e.webgpu_error_type(),
252            ComputePassErrorInner::InvalidResource(e) => e.webgpu_error_type(),
253            ComputePassErrorInner::TimestampWrites(e) => e.webgpu_error_type(),
254            ComputePassErrorInner::InvalidValuesOffset(e) => e.webgpu_error_type(),
255
256            ComputePassErrorInner::InvalidParentEncoder
257            | ComputePassErrorInner::BindGroupIndexOutOfRange { .. }
258            | ComputePassErrorInner::UnalignedIndirectBufferOffset(_)
259            | ComputePassErrorInner::IndirectBufferOverrun { .. }
260            | ComputePassErrorInner::PassEnded => ErrorType::Validation,
261        }
262    }
263}
264
265struct State<'scope, 'snatch_guard, 'cmd_enc> {
266    pipeline: Option<Arc<ComputePipeline>>,
267
268    pass: pass::PassState<'scope, 'snatch_guard, 'cmd_enc>,
269
270    active_query: Option<(Arc<resource::QuerySet>, u32)>,
271
272    immediates: Vec<u32>,
273
274    /// A bitmask, tracking which 4-byte slots have been written via `set_immediates`.
275    /// Checked against the pipeline's required slots before each dispatch.
276    immediate_slots_set: naga::valid::ImmediateSlots,
277
278    intermediate_trackers: Tracker,
279}
280
281impl<'scope, 'snatch_guard, 'cmd_enc> State<'scope, 'snatch_guard, 'cmd_enc> {
282    fn is_ready(&self) -> Result<(), DispatchError> {
283        if let Some(pipeline) = self.pipeline.as_ref() {
284            self.pass.binder.check_compatibility(pipeline.as_ref())?;
285            self.pass.binder.check_late_buffer_bindings()?;
286            if !self
287                .immediate_slots_set
288                .contains(pipeline.immediate_slots_required)
289            {
290                return Err(DispatchError::MissingImmediateData {
291                    missing: pipeline
292                        .immediate_slots_required
293                        .difference(self.immediate_slots_set),
294                });
295            }
296            Ok(())
297        } else {
298            Err(DispatchError::MissingPipeline(pass::MissingPipeline))
299        }
300    }
301
302    /// Flush binding state in preparation for a dispatch.
303    ///
304    /// # Differences between render and compute passes
305    ///
306    /// There are differences between the `flush_bindings` implementations for
307    /// render and compute passes, because render passes have a single usage
308    /// scope for the entire pass, and compute passes have a separate usage
309    /// scope for each dispatch.
310    ///
311    /// For compute passes, bind groups are merged into a fresh usage scope
312    /// here, not into the pass usage scope within calls to `set_bind_group`. As
313    /// specified by WebGPU, for compute passes, we merge only the bind groups
314    /// that are actually used by the pipeline, unlike render passes, which
315    /// merge every bind group that is ever set, even if it is not ultimately
316    /// used by the pipeline.
317    ///
318    /// For compute passes, we call `drain_barriers` here, because barriers may
319    /// be needed before each dispatch if a previous dispatch had a conflicting
320    /// usage. For render passes, barriers are emitted once at the start of the
321    /// render pass.
322    ///
323    /// # Indirect buffer handling
324    ///
325    /// The `indirect_buffer` argument should be passed for any indirect
326    /// dispatch (with or without validation). It will be checked for
327    /// conflicting usages according to WebGPU rules. For the purpose of
328    /// these rules, the fact that we have actually processed the buffer in
329    /// the validation pass is an implementation detail.
330    ///
331    /// The `track_indirect_buffer` argument should be set when doing indirect
332    /// dispatch *without* validation. In this case, the indirect buffer will
333    /// be added to the tracker in order to generate any necessary transitions
334    /// for that usage.
335    ///
336    /// When doing indirect dispatch *with* validation, the indirect buffer is
337    /// processed by the validation pass and is not used by the actual dispatch.
338    /// The indirect validation code handles transitions for the validation
339    /// pass.
340    fn flush_bindings(
341        &mut self,
342        indirect_buffer: Option<&Arc<Buffer>>,
343        track_indirect_buffer: bool,
344    ) -> Result<(), ComputePassErrorInner> {
345        for bind_group in self.pass.binder.list_active() {
346            unsafe { self.pass.scope.merge_bind_group(&bind_group.used)? };
347        }
348
349        // Add the indirect buffer. Because usage scopes are per-dispatch, this
350        // is the only place where INDIRECT usage could be added, and it is safe
351        // for us to remove it below.
352        if let Some(buffer) = indirect_buffer {
353            self.pass
354                .scope
355                .buffers
356                .merge_single(buffer, wgt::BufferUses::INDIRECT)?;
357        }
358
359        // For compute, usage scopes are associated with each dispatch and not
360        // with the pass as a whole. However, because the cost of creating and
361        // dropping `UsageScope`s is significant (even with the pool), we
362        // add and then remove usage from a single usage scope.
363
364        for bind_group in self.pass.binder.list_active() {
365            self.intermediate_trackers
366                .set_and_remove_from_usage_scope_sparse(&mut self.pass.scope, &bind_group.used);
367        }
368
369        if track_indirect_buffer {
370            self.intermediate_trackers
371                .buffers
372                .set_and_remove_from_usage_scope_sparse(
373                    &mut self.pass.scope.buffers,
374                    indirect_buffer.map(|buf| buf.tracker_index()),
375                );
376        } else if let Some(buffer) = indirect_buffer {
377            self.pass
378                .scope
379                .buffers
380                .remove_usage(buffer, wgt::BufferUses::INDIRECT);
381        }
382
383        flush_bindings_helper(&mut self.pass)?;
384
385        CommandEncoder::drain_barriers(
386            self.pass.base.raw_encoder,
387            &mut self.intermediate_trackers,
388            self.pass.base.snatch_guard,
389        );
390        Ok(())
391    }
392}
393
394/// Compute pass version of [`command::transition_resources`](crate::command::transition_resources).
395/// See also `State::flush_bindings` for details on the implementation.
396fn transition_resources(
397    state: &mut State,
398    buffer_transitions: Vec<wgt::BufferTransition<Arc<Buffer>>>,
399    texture_transitions: Vec<wgt::TextureTransition<Arc<TextureView>>>,
400) -> Result<(), TransitionResourcesError> {
401    let indices = &state.pass.base.device.tracker_indices;
402    state.pass.scope.buffers.set_size(indices.buffers.size());
403    state.pass.scope.textures.set_size(indices.textures.size());
404
405    let mut buffer_ids = Vec::with_capacity(buffer_transitions.len());
406    let mut textures = TextureViewBindGroupState::new();
407
408    // Process buffer transitions
409    for buffer_transition in buffer_transitions {
410        buffer_transition
411            .buffer
412            .same_device(state.pass.base.device)?;
413
414        state
415            .pass
416            .scope
417            .buffers
418            .merge_single(&buffer_transition.buffer, buffer_transition.state)?;
419        buffer_ids.push(buffer_transition.buffer.tracker_index());
420    }
421
422    state
423        .intermediate_trackers
424        .buffers
425        .set_and_remove_from_usage_scope_sparse(&mut state.pass.scope.buffers, buffer_ids);
426
427    // Process texture transitions
428    for texture_transition in texture_transitions {
429        texture_transition
430            .texture
431            .same_device(state.pass.base.device)?;
432
433        unsafe {
434            state.pass.scope.textures.merge_single(
435                &texture_transition.texture.parent,
436                texture_transition.selector,
437                texture_transition.state,
438            )
439        }?;
440
441        textures.insert_single(texture_transition.texture, texture_transition.state);
442    }
443
444    state
445        .intermediate_trackers
446        .textures
447        .set_and_remove_from_usage_scope_sparse(&mut state.pass.scope.textures, &textures);
448
449    // Record any needed barriers based on tracker data
450    CommandEncoder::drain_barriers(
451        state.pass.base.raw_encoder,
452        &mut state.intermediate_trackers,
453        state.pass.base.snatch_guard,
454    );
455    Ok(())
456}
457
458impl CommandEncoder {
459    fn begin_compute_pass(
460        self: &Arc<Self>,
461        desc: &ComputePassDescriptor<'_, PassTimestampWrites<Fallible<resource::QuerySet>>>,
462    ) -> (ComputePass, Option<CommandEncoderError>) {
463        use EncoderStateError as SErr;
464
465        let scope = PassErrorScope::Pass;
466
467        let label = desc.label.as_deref().map(Cow::Borrowed);
468
469        let mut cmd_buf_data = self.data.lock();
470
471        match cmd_buf_data.lock_encoder() {
472            Ok(()) => {
473                drop(cmd_buf_data);
474                if let Err(err) = self.device.check_is_valid() {
475                    return (
476                        ComputePass::new_invalid(Arc::clone(self), &label, err.map_pass_err(scope)),
477                        None,
478                    );
479                }
480
481                match desc
482                    .timestamp_writes
483                    .as_ref()
484                    .map(|tw| {
485                        Self::validate_pass_timestamp_writes::<ComputePassErrorInner>(
486                            &self.device,
487                            tw,
488                        )
489                    })
490                    .transpose()
491                {
492                    Ok(timestamp_writes) => {
493                        let arc_desc = ArcComputePassDescriptor {
494                            label,
495                            timestamp_writes,
496                        };
497                        (ComputePass::new(Arc::clone(self), arc_desc), None)
498                    }
499                    Err(err) => (
500                        ComputePass::new_invalid(Arc::clone(self), &label, err.map_pass_err(scope)),
501                        None,
502                    ),
503                }
504            }
505            Err(err @ SErr::Locked) => {
506                // Attempting to open a new pass while the encoder is locked
507                // invalidates the encoder, but does not generate a validation
508                // error.
509                cmd_buf_data.invalidate(err.clone());
510                drop(cmd_buf_data);
511                (
512                    ComputePass::new_invalid(Arc::clone(self), &label, err.map_pass_err(scope)),
513                    None,
514                )
515            }
516            Err(err @ (SErr::Ended | SErr::Submitted)) => {
517                // Attempting to open a new pass after the encode has ended
518                // generates an immediate validation error.
519                drop(cmd_buf_data);
520                (
521                    ComputePass::new_invalid(
522                        Arc::clone(self),
523                        &label,
524                        err.clone().map_pass_err(scope),
525                    ),
526                    Some(err.into()),
527                )
528            }
529            Err(err @ SErr::Invalid) => {
530                // Passes can be opened even on an invalid encoder. Such passes
531                // are even valid, but since there's no visible side-effect of
532                // the pass being valid and there's no point in storing recorded
533                // commands that will ultimately be discarded, we open an
534                // invalid pass to save that work.
535                drop(cmd_buf_data);
536                (
537                    ComputePass::new_invalid(Arc::clone(self), &label, err.map_pass_err(scope)),
538                    None,
539                )
540            }
541            Err(SErr::Unlocked) => {
542                unreachable!("lock_encoder cannot fail due to the encoder being unlocked")
543            }
544        }
545    }
546}
547
548// Running the compute pass.
549
550impl Global {
551    /// Creates a compute pass.
552    ///
553    /// If creation fails, an invalid pass is returned. Attempting to record
554    /// commands into an invalid pass is permitted, but a validation error will
555    /// ultimately be generated when the parent encoder is finished, and it is
556    /// not possible to run any commands from the invalid pass.
557    ///
558    /// If successful, puts the encoder into the [`Locked`] state.
559    ///
560    /// [`Locked`]: crate::command::CommandEncoderStatus::Locked
561    pub fn command_encoder_begin_compute_pass(
562        &self,
563        encoder_id: id::CommandEncoderId,
564        desc: &ComputePassDescriptor<'_>,
565    ) -> (ComputePass, Option<CommandEncoderError>) {
566        let hub = &self.hub;
567
568        let cmd_enc = hub.command_encoders.get(encoder_id);
569
570        let desc = ComputePassDescriptor {
571            label: desc.label.as_deref().map(Cow::Borrowed),
572            timestamp_writes: desc
573                .timestamp_writes
574                .as_ref()
575                .map(|tw| PassTimestampWrites {
576                    query_set: hub.query_sets.get(tw.query_set),
577                    beginning_of_pass_write_index: tw.beginning_of_pass_write_index,
578                    end_of_pass_write_index: tw.end_of_pass_write_index,
579                }),
580        };
581
582        cmd_enc.begin_compute_pass(&desc)
583    }
584
585    pub fn command_encoder_begin_compute_pass_with_id(
586        &self,
587        encoder_id: id::CommandEncoderId,
588        desc: &ComputePassDescriptor<'_>,
589        id_in: Option<id::ComputePassEncoderId>,
590    ) -> (id::ComputePassEncoderId, Option<CommandEncoderError>) {
591        let fid = self.hub.compute_passes.prepare(id_in);
592
593        let (pass, err) = self.command_encoder_begin_compute_pass(encoder_id, desc);
594
595        // no lock rank here because only one thread should be using compute pass
596        // and it's only used by id variants of compute pass methods on global
597        // so no deadlock (or concurrent lock) should happen in practise
598        let id = fid.assign(Arc::new(Mutex::new(pass)));
599
600        (id, err)
601    }
602
603    #[inline(never)]
604    pub fn compute_pass_end(&self, pass: &mut ComputePass) -> Result<(), EncoderStateError> {
605        profiling::scope!(
606            "CommandEncoder::run_compute_pass {}",
607            pass.base.label.as_deref().unwrap_or("")
608        );
609
610        let cmd_enc = pass.parent.take().ok_or(EncoderStateError::Ended)?;
611        let mut cmd_buf_data = cmd_enc.data.lock();
612
613        cmd_buf_data.unlock_encoder()?;
614
615        let base = pass.base.take();
616
617        if let Err(ComputePassError {
618            inner:
619                ComputePassErrorInner::EncoderState(
620                    err @ (EncoderStateError::Locked | EncoderStateError::Ended),
621                ),
622            scope: _,
623        }) = base
624        {
625            // Most encoding errors are detected and raised within `finish()`.
626            //
627            // However, we raise a validation error here if the pass was opened
628            // within another pass, or on a finished encoder. The latter is
629            // particularly important, because in that case reporting errors via
630            // `CommandEncoder::finish` is not possible.
631            return Err(err.clone());
632        }
633
634        cmd_buf_data.push_with(|| -> Result<_, ComputePassError> {
635            Ok(ArcCommand::RunComputePass {
636                pass: base?,
637                timestamp_writes: pass.timestamp_writes.take(),
638            })
639        })
640    }
641
642    pub fn compute_pass_end_with_id(
643        &self,
644        pass_id: id::ComputePassEncoderId,
645    ) -> Result<(), EncoderStateError> {
646        let pass = self.hub.compute_passes.get(pass_id);
647        let mut pass = pass
648            .try_lock()
649            .expect("ComputePasses should not be accessed concurrently");
650        self.compute_pass_end(&mut pass)
651    }
652
653    pub fn compute_pass_drop(&self, pass_id: id::ComputePassEncoderId) {
654        self.hub.compute_passes.remove(pass_id);
655    }
656}
657
658#[inline(never)]
659pub(super) fn encode_compute_pass(
660    parent_state: &mut EncodingState<InnerCommandEncoder>,
661    mut base: BasePass<ArcComputeCommand, Infallible>,
662    mut timestamp_writes: Option<ArcPassTimestampWrites>,
663) -> Result<(), ComputePassError> {
664    let pass_scope = PassErrorScope::Pass;
665
666    let device = parent_state.device;
667
668    // We automatically keep extending command buffers over time, and because
669    // we want to insert a command buffer _before_ what we're about to record,
670    // we need to make sure to close the previous one.
671    parent_state
672        .raw_encoder
673        .close_if_open()
674        .map_pass_err(pass_scope)?;
675    let raw_encoder = parent_state
676        .raw_encoder
677        .open_pass(base.label.as_deref())
678        .map_pass_err(pass_scope)?;
679
680    let mut debug_scope_depth = 0;
681
682    let mut state = State {
683        pipeline: None,
684
685        pass: pass::PassState {
686            base: EncodingState {
687                device,
688                raw_encoder,
689                tracker: parent_state.tracker,
690                buffer_memory_init_actions: parent_state.buffer_memory_init_actions,
691                texture_memory_actions: parent_state.texture_memory_actions,
692                as_actions: parent_state.as_actions,
693                temp_resources: parent_state.temp_resources,
694                indirect_draw_validation_resources: parent_state.indirect_draw_validation_resources,
695                snatch_guard: parent_state.snatch_guard,
696                debug_scope_depth: &mut debug_scope_depth,
697                query_set_writes: parent_state.query_set_writes,
698                deferred_query_set_resolves: parent_state.deferred_query_set_resolves,
699            },
700            binder: Binder::new(),
701            temp_offsets: Vec::new(),
702            dynamic_offset_count: 0,
703            pending_discard_init_fixups: SurfacesInDiscardState::new(),
704            scope: device.new_usage_scope(),
705            string_offset: 0,
706        },
707        active_query: None,
708
709        immediates: Vec::new(),
710
711        immediate_slots_set: Default::default(),
712
713        intermediate_trackers: Tracker::new(
714            device.ordered_buffer_usages,
715            device.ordered_texture_usages,
716        ),
717    };
718
719    let indices = &device.tracker_indices;
720    state
721        .pass
722        .base
723        .tracker
724        .buffers
725        .set_size(indices.buffers.size());
726    state
727        .pass
728        .base
729        .tracker
730        .textures
731        .set_size(indices.textures.size());
732
733    let timestamp_writes: Option<hal::PassTimestampWrites<'_, dyn hal::DynQuerySet>> =
734        if let Some(tw) = timestamp_writes.take() {
735            tw.query_set.same_device(device).map_pass_err(pass_scope)?;
736
737            record_pass_timestamp_writes(&tw, state.pass.base.query_set_writes);
738
739            let query_set = state
740                .pass
741                .base
742                .tracker
743                .query_sets
744                .insert_single(tw.query_set);
745
746            // Unlike in render passes we can't delay resetting the query sets since
747            // there is no auxiliary pass.
748            let range = if let (Some(index_a), Some(index_b)) =
749                (tw.beginning_of_pass_write_index, tw.end_of_pass_write_index)
750            {
751                Some(index_a.min(index_b)..index_a.max(index_b) + 1)
752            } else {
753                tw.beginning_of_pass_write_index
754                    .or(tw.end_of_pass_write_index)
755                    .map(|i| i..i + 1)
756            };
757            let raw_query_set = query_set
758                .try_raw(parent_state.snatch_guard)
759                .map_pass_err(pass_scope)?;
760            // Range should always be Some, both values being None should lead to a validation error.
761            // But no point in erroring over that nuance here!
762            if let Some(range) = range {
763                unsafe {
764                    state
765                        .pass
766                        .base
767                        .raw_encoder
768                        .reset_queries(raw_query_set, range);
769                }
770            }
771
772            Some(hal::PassTimestampWrites {
773                query_set: raw_query_set,
774                beginning_of_pass_write_index: tw.beginning_of_pass_write_index,
775                end_of_pass_write_index: tw.end_of_pass_write_index,
776            })
777        } else {
778            None
779        };
780
781    let hal_desc = hal::ComputePassDescriptor {
782        label: hal_label(base.label.as_deref(), device.instance_flags),
783        timestamp_writes,
784    };
785
786    unsafe {
787        state.pass.base.raw_encoder.begin_compute_pass(&hal_desc);
788    }
789
790    for command in base.commands.drain(..) {
791        match command {
792            ArcComputeCommand::SetBindGroup {
793                index,
794                num_dynamic_offsets,
795                bind_group,
796            } => {
797                let scope = PassErrorScope::SetBindGroup;
798                pass::set_bind_group::<ComputePassErrorInner>(
799                    &mut state.pass,
800                    device,
801                    &base.dynamic_offsets,
802                    index,
803                    num_dynamic_offsets,
804                    bind_group,
805                    false,
806                )
807                .map_pass_err(scope)?;
808            }
809            ArcComputeCommand::SetPipeline(pipeline) => {
810                let scope = PassErrorScope::SetPipelineCompute;
811                set_pipeline(&mut state, device, pipeline).map_pass_err(scope)?;
812            }
813            ArcComputeCommand::SetImmediate {
814                offset,
815                size_bytes,
816                values_offset,
817            } => {
818                let scope = PassErrorScope::SetImmediate;
819                pass::set_immediates::<ComputePassErrorInner, _>(
820                    &mut state.pass,
821                    &base.immediates_data,
822                    offset,
823                    size_bytes,
824                    Some(values_offset),
825                    |data_slice| {
826                        let offset_in_elements = (offset / wgt::IMMEDIATE_DATA_ALIGNMENT) as usize;
827                        let size_in_elements =
828                            (size_bytes / wgt::IMMEDIATE_DATA_ALIGNMENT) as usize;
829                        state.immediates[offset_in_elements..][..size_in_elements]
830                            .copy_from_slice(data_slice);
831                    },
832                )
833                .map_pass_err(scope)?;
834                state.immediate_slots_set |=
835                    naga::valid::ImmediateSlots::from_range(offset, size_bytes);
836            }
837            ArcComputeCommand::DispatchWorkgroups(groups) => {
838                let scope = PassErrorScope::Dispatch { indirect: false };
839                dispatch_workgroups(&mut state, groups).map_pass_err(scope)?;
840            }
841            ArcComputeCommand::DispatchWorkgroupsIndirect { buffer, offset } => {
842                let scope = PassErrorScope::Dispatch { indirect: true };
843                dispatch_workgroups_indirect(&mut state, device, buffer, offset)
844                    .map_pass_err(scope)?;
845            }
846            ArcComputeCommand::PushDebugGroup { color: _, len } => {
847                pass::push_debug_group(&mut state.pass, &base.string_data, len);
848            }
849            ArcComputeCommand::PopDebugGroup => {
850                let scope = PassErrorScope::PopDebugGroup;
851                pass::pop_debug_group::<ComputePassErrorInner>(&mut state.pass)
852                    .map_pass_err(scope)?;
853            }
854            ArcComputeCommand::InsertDebugMarker { color: _, len } => {
855                pass::insert_debug_marker(&mut state.pass, &base.string_data, len);
856            }
857            ArcComputeCommand::WriteTimestamp {
858                query_set,
859                query_index,
860            } => {
861                let scope = PassErrorScope::WriteTimestamp;
862                pass::write_timestamp::<ComputePassErrorInner>(
863                    &mut state.pass,
864                    device,
865                    None, // compute passes do not attempt to coalesce query resets
866                    query_set,
867                    query_index,
868                )
869                .map_pass_err(scope)?;
870            }
871            ArcComputeCommand::BeginPipelineStatisticsQuery {
872                query_set,
873                query_index,
874            } => {
875                let scope = PassErrorScope::BeginPipelineStatisticsQuery;
876                validate_and_begin_pipeline_statistics_query(
877                    query_set,
878                    state.pass.base.raw_encoder,
879                    &mut state.pass.base.tracker.query_sets,
880                    device,
881                    query_index,
882                    None,
883                    &mut state.active_query,
884                    state.pass.base.snatch_guard,
885                )
886                .map_pass_err(scope)?;
887            }
888            ArcComputeCommand::EndPipelineStatisticsQuery => {
889                let scope = PassErrorScope::EndPipelineStatisticsQuery;
890                end_pipeline_statistics_query(
891                    state.pass.base.raw_encoder,
892                    &mut state.active_query,
893                    state.pass.base.snatch_guard,
894                    state.pass.base.query_set_writes,
895                )
896                .map_pass_err(scope)?;
897            }
898            ArcComputeCommand::TransitionResources {
899                buffer_transitions,
900                texture_transitions,
901            } => {
902                let scope = PassErrorScope::TransitionResources;
903                transition_resources(&mut state, buffer_transitions, texture_transitions)
904                    .map_pass_err(scope)?;
905            }
906        }
907    }
908
909    if *state.pass.base.debug_scope_depth > 0 {
910        Err(
911            ComputePassErrorInner::DebugGroupError(DebugGroupError::MissingPop)
912                .map_pass_err(pass_scope),
913        )?;
914    }
915
916    unsafe {
917        state.pass.base.raw_encoder.end_compute_pass();
918    }
919
920    let State {
921        pass: pass::PassState {
922            pending_discard_init_fixups,
923            ..
924        },
925        intermediate_trackers,
926        ..
927    } = state;
928
929    // Stop the current command encoder.
930    parent_state.raw_encoder.close().map_pass_err(pass_scope)?;
931
932    // Create a new command encoder, which we will insert _before_ the body of the compute pass.
933    //
934    // Use that buffer to insert barriers and clear discarded images.
935    let transit = parent_state
936        .raw_encoder
937        .open_pass(hal_label(
938            Some("(wgpu internal) Pre Pass"),
939            device.instance_flags,
940        ))
941        .map_pass_err(pass_scope)?;
942    fixup_discarded_surfaces(
943        pending_discard_init_fixups.into_iter(),
944        transit,
945        &mut parent_state.tracker.textures,
946        device,
947        parent_state.snatch_guard,
948    );
949    CommandEncoder::insert_barriers_from_tracker(
950        transit,
951        parent_state.tracker,
952        &intermediate_trackers,
953        parent_state.snatch_guard,
954    );
955    // Close the command encoder, and swap it with the previous.
956    parent_state
957        .raw_encoder
958        .close_and_swap()
959        .map_pass_err(pass_scope)?;
960
961    Ok(())
962}
963
964fn set_pipeline(
965    state: &mut State,
966    device: &Arc<Device>,
967    pipeline: Arc<ComputePipeline>,
968) -> Result<(), ComputePassErrorInner> {
969    pipeline.same_device(device)?;
970
971    state.pipeline = Some(pipeline.clone());
972
973    let pipeline = state
974        .pass
975        .base
976        .tracker
977        .compute_pipelines
978        .insert_single(pipeline)
979        .clone();
980
981    unsafe {
982        state
983            .pass
984            .base
985            .raw_encoder
986            .set_compute_pipeline(pipeline.raw()?);
987    }
988
989    // Rebind resources
990    let pipeline_layout = pipeline.layout()?;
991    pass::change_pipeline_layout::<ComputePassErrorInner, _>(
992        &mut state.pass,
993        pipeline_layout,
994        &pipeline.late_sized_buffer_groups,
995        || {
996            // This only needs to be here for compute pipelines because they use immediates for
997            // validating indirect draws.
998            state.immediates.clear();
999            // Note that can only be one range for each stage. See the `MoreThanOneImmediateRangePerStage` error.
1000            if pipeline_layout.immediate_size != 0 {
1001                // Note that non-0 range start doesn't work anyway https://github.com/gfx-rs/wgpu/issues/4502
1002                let len = pipeline_layout.immediate_size as usize
1003                    / wgt::IMMEDIATE_DATA_ALIGNMENT as usize;
1004                state.immediates.extend(core::iter::repeat_n(0, len));
1005            }
1006        },
1007    )
1008}
1009
1010fn dispatch_workgroups(state: &mut State, groups: [u32; 3]) -> Result<(), ComputePassErrorInner> {
1011    api_log!("ComputePass::dispatch {groups:?}");
1012
1013    state.is_ready()?;
1014
1015    state.flush_bindings(None, false)?;
1016
1017    let groups_size_limit = state
1018        .pass
1019        .base
1020        .device
1021        .limits
1022        .max_compute_workgroups_per_dimension;
1023
1024    if groups.iter().copied().any(|g| g > groups_size_limit) {
1025        return Err(ComputePassErrorInner::Dispatch(
1026            DispatchError::InvalidGroupSize {
1027                current: groups,
1028                limit: groups_size_limit,
1029            },
1030        ));
1031    }
1032
1033    unsafe {
1034        state.pass.base.raw_encoder.dispatch_workgroups(groups);
1035    }
1036    Ok(())
1037}
1038
1039fn dispatch_workgroups_indirect(
1040    state: &mut State,
1041    device: &Arc<Device>,
1042    buffer: Arc<Buffer>,
1043    offset: u64,
1044) -> Result<(), ComputePassErrorInner> {
1045    api_log!("ComputePass::dispatch_indirect");
1046
1047    buffer.same_device(device)?;
1048
1049    state.is_ready()?;
1050
1051    state
1052        .pass
1053        .base
1054        .device
1055        .require_downlevel_flags(wgt::DownlevelFlags::INDIRECT_EXECUTION)?;
1056
1057    buffer.check_usage(wgt::BufferUsages::INDIRECT)?;
1058
1059    if !offset.is_multiple_of(4) {
1060        return Err(ComputePassErrorInner::UnalignedIndirectBufferOffset(offset));
1061    }
1062
1063    let args_size = size_of::<wgt::DispatchIndirectArgs>() as u64;
1064    if buffer.size < args_size || buffer.size - args_size < offset {
1065        return Err(ComputePassErrorInner::IndirectBufferOverrun {
1066            args_size,
1067            offset,
1068            buffer_size: buffer.size,
1069        });
1070    }
1071
1072    buffer.check_destroyed(state.pass.base.snatch_guard)?;
1073
1074    let stride = 3 * 4; // 3 integers, x/y/z group size
1075    state.pass.base.buffer_memory_init_actions.extend(
1076        buffer.initialization_status.read().create_action(
1077            &buffer,
1078            offset..(offset + stride),
1079            MemoryInitKind::NeedsInitializedMemory,
1080        ),
1081    );
1082
1083    if let Some(ref indirect_validation) = state.pass.base.device.indirect_validation {
1084        let params = indirect_validation.dispatch.params(
1085            &state.pass.base.device.limits,
1086            offset,
1087            buffer.size,
1088        );
1089
1090        unsafe {
1091            state
1092                .pass
1093                .base
1094                .raw_encoder
1095                .set_compute_pipeline(params.pipeline);
1096        }
1097
1098        unsafe {
1099            state.pass.base.raw_encoder.set_immediates(
1100                params.pipeline_layout,
1101                0,
1102                &[params.offset_remainder as u32 / 4],
1103            );
1104        }
1105
1106        unsafe {
1107            state.pass.base.raw_encoder.set_bind_group(
1108                params.pipeline_layout,
1109                0,
1110                params.dst_bind_group,
1111                &[],
1112            );
1113        }
1114        unsafe {
1115            state.pass.base.raw_encoder.set_bind_group(
1116                params.pipeline_layout,
1117                1,
1118                buffer
1119                    .indirect_validation_bind_groups
1120                    .get(state.pass.base.snatch_guard)
1121                    .unwrap()
1122                    .dispatch
1123                    .as_ref(),
1124                &[params.aligned_offset as u32],
1125            );
1126        }
1127
1128        let src_transition = state
1129            .intermediate_trackers
1130            .buffers
1131            .set_single(&buffer, wgt::BufferUses::STORAGE_READ_ONLY);
1132        let src_barrier = src_transition
1133            .map(|transition| transition.into_hal(&buffer, state.pass.base.snatch_guard));
1134        unsafe {
1135            state
1136                .pass
1137                .base
1138                .raw_encoder
1139                .transition_buffers(src_barrier.as_slice());
1140        }
1141
1142        unsafe {
1143            state
1144                .pass
1145                .base
1146                .raw_encoder
1147                .transition_buffers(&[hal::BufferBarrier {
1148                    buffer: params.dst_buffer,
1149                    usage: hal::StateTransition {
1150                        from: wgt::BufferUses::INDIRECT,
1151                        to: wgt::BufferUses::STORAGE_READ_WRITE,
1152                    },
1153                }]);
1154        }
1155
1156        unsafe {
1157            state.pass.base.raw_encoder.dispatch_workgroups([1, 1, 1]);
1158        }
1159
1160        // reset state
1161        {
1162            let pipeline = state.pipeline.as_ref().unwrap();
1163
1164            unsafe {
1165                state
1166                    .pass
1167                    .base
1168                    .raw_encoder
1169                    .set_compute_pipeline(pipeline.raw()?);
1170            }
1171
1172            let pipeline_layout = pipeline.layout()?;
1173
1174            if !state.immediates.is_empty() {
1175                unsafe {
1176                    state.pass.base.raw_encoder.set_immediates(
1177                        pipeline_layout.raw()?,
1178                        0,
1179                        &state.immediates,
1180                    );
1181                }
1182            }
1183
1184            for (i, group, dynamic_offsets) in state.pass.binder.list_valid() {
1185                let raw_bg = group.try_raw(state.pass.base.snatch_guard)?;
1186                unsafe {
1187                    state.pass.base.raw_encoder.set_bind_group(
1188                        pipeline_layout.raw()?,
1189                        i as u32,
1190                        raw_bg,
1191                        dynamic_offsets,
1192                    );
1193                }
1194            }
1195        }
1196
1197        unsafe {
1198            state
1199                .pass
1200                .base
1201                .raw_encoder
1202                .transition_buffers(&[hal::BufferBarrier {
1203                    buffer: params.dst_buffer,
1204                    usage: hal::StateTransition {
1205                        from: wgt::BufferUses::STORAGE_READ_WRITE,
1206                        to: wgt::BufferUses::INDIRECT,
1207                    },
1208                }]);
1209        }
1210
1211        state.flush_bindings(Some(&buffer), false)?;
1212        unsafe {
1213            state
1214                .pass
1215                .base
1216                .raw_encoder
1217                .dispatch_workgroups_indirect(params.dst_buffer, 0);
1218        }
1219    } else {
1220        state.flush_bindings(Some(&buffer), true)?;
1221
1222        let buf_raw = buffer.try_raw(state.pass.base.snatch_guard)?;
1223        unsafe {
1224            state
1225                .pass
1226                .base
1227                .raw_encoder
1228                .dispatch_workgroups_indirect(buf_raw, offset);
1229        }
1230    }
1231
1232    Ok(())
1233}
1234
1235// Recording a compute pass.
1236//
1237// The only error that should be returned from these methods is
1238// `EncoderStateError::Ended`, when the pass has already ended and an immediate
1239// validation error is raised.
1240//
1241// All other errors should be stored in the pass for later reporting when
1242// `CommandEncoder.finish()` is called.
1243//
1244// The `pass_try!` macro should be used to handle errors appropriately. Note
1245// that the `pass_try!` and `pass_base!` macros may return early from the
1246// function that invokes them, like the `?` operator.
1247impl Global {
1248    pub fn compute_pass_set_bind_group(
1249        &self,
1250        pass: &mut ComputePass,
1251        index: u32,
1252        bind_group_id: Option<id::BindGroupId>,
1253        offsets: &[DynamicOffset],
1254    ) -> Result<(), PassStateError> {
1255        let scope = PassErrorScope::SetBindGroup;
1256
1257        // This statement will return an error if the pass is ended. It's
1258        // important the error check comes before the early-out for
1259        // `set_and_check_redundant`.
1260        let base = pass_base!(pass, scope);
1261
1262        if pass.current_bind_groups.set_and_check_redundant(
1263            bind_group_id,
1264            index,
1265            &mut base.dynamic_offsets,
1266            offsets,
1267        ) {
1268            return Ok(());
1269        }
1270
1271        let mut bind_group = None;
1272        if let Some(bind_group_id) = bind_group_id {
1273            let hub = &self.hub;
1274            bind_group = Some(pass_try!(
1275                base,
1276                scope,
1277                hub.bind_groups.get(bind_group_id).get(),
1278            ));
1279        }
1280
1281        base.commands.push(ArcComputeCommand::SetBindGroup {
1282            index,
1283            num_dynamic_offsets: offsets.len(),
1284            bind_group,
1285        });
1286
1287        Ok(())
1288    }
1289
1290    pub fn compute_pass_set_bind_group_with_id(
1291        &self,
1292        pass_id: id::ComputePassEncoderId,
1293        index: u32,
1294        bind_group_id: Option<id::BindGroupId>,
1295        offsets: &[DynamicOffset],
1296    ) -> Result<(), PassStateError> {
1297        let pass = self.hub.compute_passes.get(pass_id);
1298        let mut pass = pass
1299            .try_lock()
1300            .expect("ComputePasses should not be accessed concurrently");
1301        self.compute_pass_set_bind_group(&mut pass, index, bind_group_id, offsets)
1302    }
1303
1304    pub fn compute_pass_set_pipeline(
1305        &self,
1306        pass: &mut ComputePass,
1307        pipeline_id: id::ComputePipelineId,
1308    ) -> Result<(), PassStateError> {
1309        let redundant = pass.current_pipeline.set_and_check_redundant(pipeline_id);
1310
1311        let scope = PassErrorScope::SetPipelineCompute;
1312
1313        // This statement will return an error if the pass is ended.
1314        // Its important the error check comes before the early-out for `redundant`.
1315        let base = pass_base!(pass, scope);
1316
1317        if redundant {
1318            return Ok(());
1319        }
1320
1321        let hub = &self.hub;
1322        let compute_pipeline = hub.compute_pipelines.get(pipeline_id);
1323        pass_try!(base, scope, compute_pipeline.check_valid());
1324
1325        base.commands
1326            .push(ArcComputeCommand::SetPipeline(compute_pipeline));
1327
1328        Ok(())
1329    }
1330
1331    pub fn compute_pass_set_pipeline_with_id(
1332        &self,
1333        pass_id: id::ComputePassEncoderId,
1334        pipeline_id: id::ComputePipelineId,
1335    ) -> Result<(), PassStateError> {
1336        let pass = self.hub.compute_passes.get(pass_id);
1337        let mut pass = pass
1338            .try_lock()
1339            .expect("ComputePasses should not be accessed concurrently");
1340        self.compute_pass_set_pipeline(&mut pass, pipeline_id)
1341    }
1342
1343    pub fn compute_pass_set_immediates(
1344        &self,
1345        pass: &mut ComputePass,
1346        offset: u32,
1347        data: &[u8],
1348    ) -> Result<(), PassStateError> {
1349        let scope = PassErrorScope::SetImmediate;
1350        let base = pass_base!(pass, scope);
1351
1352        let size_bytes = pass_try!(
1353            base,
1354            scope,
1355            u32::try_from(data.len()).map_err(|_| ImmediateUploadError::ImmediateOutOfMemory)
1356        );
1357        pass_try!(
1358            base,
1359            scope,
1360            pass::validate_immediates_alignment(offset, size_bytes)
1361        );
1362
1363        let values_offset = base.immediates_data.len().try_into().unwrap();
1364
1365        base.immediates_data.extend(
1366            data.chunks_exact(wgt::IMMEDIATE_DATA_ALIGNMENT as usize)
1367                .map(|arr| u32::from_ne_bytes([arr[0], arr[1], arr[2], arr[3]])),
1368        );
1369
1370        base.commands.push(ArcComputeCommand::SetImmediate {
1371            offset,
1372            size_bytes: data.len() as u32,
1373            values_offset,
1374        });
1375
1376        Ok(())
1377    }
1378
1379    pub fn compute_pass_set_immediates_with_id(
1380        &self,
1381        pass_id: id::ComputePassEncoderId,
1382        offset: u32,
1383        data: &[u8],
1384    ) -> Result<(), PassStateError> {
1385        let pass = self.hub.compute_passes.get(pass_id);
1386        let mut pass = pass
1387            .try_lock()
1388            .expect("ComputePasses should not be accessed concurrently");
1389        self.compute_pass_set_immediates(&mut pass, offset, data)
1390    }
1391
1392    pub fn compute_pass_dispatch_workgroups(
1393        &self,
1394        pass: &mut ComputePass,
1395        groups_x: u32,
1396        groups_y: u32,
1397        groups_z: u32,
1398    ) -> Result<(), PassStateError> {
1399        let scope = PassErrorScope::Dispatch { indirect: false };
1400
1401        pass_base!(pass, scope)
1402            .commands
1403            .push(ArcComputeCommand::DispatchWorkgroups([
1404                groups_x, groups_y, groups_z,
1405            ]));
1406
1407        Ok(())
1408    }
1409
1410    pub fn compute_pass_dispatch_workgroups_with_id(
1411        &self,
1412        pass_id: id::ComputePassEncoderId,
1413        groups_x: u32,
1414        groups_y: u32,
1415        groups_z: u32,
1416    ) -> Result<(), PassStateError> {
1417        let pass = self.hub.compute_passes.get(pass_id);
1418        let mut pass = pass
1419            .try_lock()
1420            .expect("ComputePasses should not be accessed concurrently");
1421        self.compute_pass_dispatch_workgroups(&mut pass, groups_x, groups_y, groups_z)
1422    }
1423
1424    pub fn compute_pass_dispatch_workgroups_indirect(
1425        &self,
1426        pass: &mut ComputePass,
1427        buffer_id: id::BufferId,
1428        offset: BufferAddress,
1429    ) -> Result<(), PassStateError> {
1430        let hub = &self.hub;
1431        let scope = PassErrorScope::Dispatch { indirect: true };
1432        let base = pass_base!(pass, scope);
1433
1434        let buffer = pass_try!(base, scope, hub.buffers.get(buffer_id).get());
1435
1436        base.commands
1437            .push(ArcComputeCommand::DispatchWorkgroupsIndirect { buffer, offset });
1438
1439        Ok(())
1440    }
1441
1442    pub fn compute_pass_dispatch_workgroups_indirect_with_id(
1443        &self,
1444        pass_id: id::ComputePassEncoderId,
1445        buffer_id: id::BufferId,
1446        offset: BufferAddress,
1447    ) -> Result<(), PassStateError> {
1448        let pass = self.hub.compute_passes.get(pass_id);
1449        let mut pass = pass
1450            .try_lock()
1451            .expect("ComputePasses should not be accessed concurrently");
1452        self.compute_pass_dispatch_workgroups_indirect(&mut pass, buffer_id, offset)
1453    }
1454
1455    pub fn compute_pass_push_debug_group(
1456        &self,
1457        pass: &mut ComputePass,
1458        label: &str,
1459        color: u32,
1460    ) -> Result<(), PassStateError> {
1461        let base = pass_base!(pass, PassErrorScope::PushDebugGroup);
1462
1463        let bytes = label.as_bytes();
1464        base.string_data.extend_from_slice(bytes);
1465
1466        base.commands.push(ArcComputeCommand::PushDebugGroup {
1467            color,
1468            len: bytes.len(),
1469        });
1470
1471        Ok(())
1472    }
1473
1474    pub fn compute_pass_push_debug_group_with_id(
1475        &self,
1476        pass_id: id::ComputePassEncoderId,
1477        label: &str,
1478        color: u32,
1479    ) -> Result<(), PassStateError> {
1480        let pass = self.hub.compute_passes.get(pass_id);
1481        let mut pass = pass
1482            .try_lock()
1483            .expect("ComputePasses should not be accessed concurrently");
1484        self.compute_pass_push_debug_group(&mut pass, label, color)
1485    }
1486
1487    pub fn compute_pass_pop_debug_group(
1488        &self,
1489        pass: &mut ComputePass,
1490    ) -> Result<(), PassStateError> {
1491        let base = pass_base!(pass, PassErrorScope::PopDebugGroup);
1492
1493        base.commands.push(ArcComputeCommand::PopDebugGroup);
1494
1495        Ok(())
1496    }
1497
1498    pub fn compute_pass_pop_debug_group_with_id(
1499        &self,
1500        pass_id: id::ComputePassEncoderId,
1501    ) -> Result<(), PassStateError> {
1502        let pass = self.hub.compute_passes.get(pass_id);
1503        let mut pass = pass
1504            .try_lock()
1505            .expect("ComputePasses should not be accessed concurrently");
1506        self.compute_pass_pop_debug_group(&mut pass)
1507    }
1508
1509    pub fn compute_pass_insert_debug_marker(
1510        &self,
1511        pass: &mut ComputePass,
1512        label: &str,
1513        color: u32,
1514    ) -> Result<(), PassStateError> {
1515        let base = pass_base!(pass, PassErrorScope::InsertDebugMarker);
1516
1517        let bytes = label.as_bytes();
1518        base.string_data.extend_from_slice(bytes);
1519
1520        base.commands.push(ArcComputeCommand::InsertDebugMarker {
1521            color,
1522            len: bytes.len(),
1523        });
1524
1525        Ok(())
1526    }
1527
1528    pub fn compute_pass_insert_debug_marker_with_id(
1529        &self,
1530        pass_id: id::ComputePassEncoderId,
1531        label: &str,
1532        color: u32,
1533    ) -> Result<(), PassStateError> {
1534        let pass = self.hub.compute_passes.get(pass_id);
1535        let mut pass = pass
1536            .try_lock()
1537            .expect("ComputePasses should not be accessed concurrently");
1538        self.compute_pass_insert_debug_marker(&mut pass, label, color)
1539    }
1540
1541    pub fn compute_pass_write_timestamp(
1542        &self,
1543        pass: &mut ComputePass,
1544        query_set_id: id::QuerySetId,
1545        query_index: u32,
1546    ) -> Result<(), PassStateError> {
1547        let scope = PassErrorScope::WriteTimestamp;
1548        let base = pass_base!(pass, scope);
1549
1550        let hub = &self.hub;
1551        let query_set = pass_try!(base, scope, hub.query_sets.get(query_set_id).get());
1552
1553        base.commands.push(ArcComputeCommand::WriteTimestamp {
1554            query_set,
1555            query_index,
1556        });
1557
1558        Ok(())
1559    }
1560
1561    pub fn compute_pass_write_timestamp_with_id(
1562        &self,
1563        pass_id: id::ComputePassEncoderId,
1564        query_set_id: id::QuerySetId,
1565        query_index: u32,
1566    ) -> Result<(), PassStateError> {
1567        let pass = self.hub.compute_passes.get(pass_id);
1568        let mut pass = pass
1569            .try_lock()
1570            .expect("ComputePasses should not be accessed concurrently");
1571        self.compute_pass_write_timestamp(&mut pass, query_set_id, query_index)
1572    }
1573
1574    pub fn compute_pass_begin_pipeline_statistics_query(
1575        &self,
1576        pass: &mut ComputePass,
1577        query_set_id: id::QuerySetId,
1578        query_index: u32,
1579    ) -> Result<(), PassStateError> {
1580        let scope = PassErrorScope::BeginPipelineStatisticsQuery;
1581        let base = pass_base!(pass, scope);
1582
1583        let hub = &self.hub;
1584        let query_set = pass_try!(base, scope, hub.query_sets.get(query_set_id).get());
1585
1586        base.commands
1587            .push(ArcComputeCommand::BeginPipelineStatisticsQuery {
1588                query_set,
1589                query_index,
1590            });
1591
1592        Ok(())
1593    }
1594
1595    pub fn compute_pass_begin_pipeline_statistics_query_with_id(
1596        &self,
1597        pass_id: id::ComputePassEncoderId,
1598        query_set_id: id::QuerySetId,
1599        query_index: u32,
1600    ) -> Result<(), PassStateError> {
1601        let pass = self.hub.compute_passes.get(pass_id);
1602        let mut pass = pass
1603            .try_lock()
1604            .expect("ComputePasses should not be accessed concurrently");
1605        self.compute_pass_begin_pipeline_statistics_query(&mut pass, query_set_id, query_index)
1606    }
1607
1608    pub fn compute_pass_end_pipeline_statistics_query(
1609        &self,
1610        pass: &mut ComputePass,
1611    ) -> Result<(), PassStateError> {
1612        pass_base!(pass, PassErrorScope::EndPipelineStatisticsQuery)
1613            .commands
1614            .push(ArcComputeCommand::EndPipelineStatisticsQuery);
1615
1616        Ok(())
1617    }
1618
1619    pub fn compute_pass_end_pipeline_statistics_query_with_id(
1620        &self,
1621        pass_id: id::ComputePassEncoderId,
1622    ) -> Result<(), PassStateError> {
1623        let pass = self.hub.compute_passes.get(pass_id);
1624        let mut pass = pass
1625            .try_lock()
1626            .expect("ComputePasses should not be accessed concurrently");
1627        self.compute_pass_end_pipeline_statistics_query(&mut pass)
1628    }
1629
1630    pub fn compute_pass_transition_resources(
1631        &self,
1632        pass: &mut ComputePass,
1633        buffer_transitions: impl Iterator<Item = wgt::BufferTransition<id::BufferId>>,
1634        texture_transitions: impl Iterator<Item = wgt::TextureTransition<id::TextureViewId>>,
1635    ) -> Result<(), PassStateError> {
1636        let scope = PassErrorScope::TransitionResources;
1637        let base = pass_base!(pass, scope);
1638
1639        let hub = &self.hub;
1640        let buffer_transitions = pass_try!(
1641            base,
1642            scope,
1643            buffer_transitions
1644                .map(|buffer_transition| -> Result<_, InvalidResourceError> {
1645                    Ok(wgt::BufferTransition {
1646                        buffer: hub.buffers.get(buffer_transition.buffer).get()?,
1647                        state: buffer_transition.state,
1648                    })
1649                })
1650                .collect::<Result<Vec<_>, _>>()
1651        );
1652
1653        let texture_transitions = pass_try!(
1654            base,
1655            scope,
1656            texture_transitions
1657                .map(|texture_transition| -> Result<_, InvalidResourceError> {
1658                    Ok(wgt::TextureTransition {
1659                        texture: hub.texture_views.get(texture_transition.texture).get()?,
1660                        selector: texture_transition.selector,
1661                        state: texture_transition.state,
1662                    })
1663                })
1664                .collect::<Result<Vec<_>, _>>()
1665        );
1666
1667        base.commands.push(ArcComputeCommand::TransitionResources {
1668            buffer_transitions,
1669            texture_transitions,
1670        });
1671
1672        Ok(())
1673    }
1674}