wgpu_core/command/
pass.rs

1//! Generic pass functions that both compute and render passes need.
2
3use crate::binding_model::{BindError, BindGroup, ImmediateUploadError};
4use crate::command::encoder::EncodingState;
5use crate::command::{
6    bind::Binder, memory_init::SurfacesInDiscardState, query::QueryResetMap, DebugGroupError,
7    QueryUseError,
8};
9use crate::device::{Device, DeviceError, MissingFeatures};
10use crate::pipeline::LateSizedBufferGroup;
11use crate::resource::{DestroyedResourceError, Labeled, ParentDevice, QuerySet};
12use crate::track::{ResourceUsageCompatibilityError, UsageScope};
13use crate::{api_log, binding_model};
14use alloc::sync::Arc;
15use alloc::vec::Vec;
16use core::str;
17use thiserror::Error;
18use wgt::error::{ErrorType, WebGpuError};
19use wgt::DynamicOffset;
20
21#[derive(Clone, Debug, Error)]
22#[error(
23    "Bind group index {index} is greater than the device's configured `max_bind_groups` limit {max}"
24)]
25pub struct BindGroupIndexOutOfRange {
26    pub index: u32,
27    pub max: u32,
28}
29
30#[derive(Clone, Debug, Error)]
31#[error("Pipeline must be set")]
32pub struct MissingPipeline;
33
34#[derive(Clone, Debug, Error)]
35#[error("Setting `values_offset` to be `None` is only for internal use in render bundles")]
36pub struct InvalidValuesOffset;
37
38impl WebGpuError for InvalidValuesOffset {
39    fn webgpu_error_type(&self) -> ErrorType {
40        ErrorType::Validation
41    }
42}
43
44pub(crate) struct PassState<'scope, 'snatch_guard, 'cmd_enc> {
45    pub(crate) base: EncodingState<'snatch_guard, 'cmd_enc>,
46
47    /// Immediate texture inits required because of prior discards. Need to
48    /// be inserted before texture reads.
49    pub(crate) pending_discard_init_fixups: SurfacesInDiscardState,
50
51    pub(crate) scope: UsageScope<'scope>,
52
53    pub(crate) binder: Binder,
54
55    pub(crate) temp_offsets: Vec<u32>,
56
57    pub(crate) dynamic_offset_count: usize,
58
59    pub(crate) string_offset: usize,
60}
61
62pub(crate) fn set_bind_group<E>(
63    state: &mut PassState,
64    device: &Arc<Device>,
65    dynamic_offsets: &[DynamicOffset],
66    index: u32,
67    num_dynamic_offsets: usize,
68    bind_group: Option<Arc<BindGroup>>,
69    merge_bind_groups: bool,
70) -> Result<(), E>
71where
72    E: From<DeviceError>
73        + From<BindGroupIndexOutOfRange>
74        + From<ResourceUsageCompatibilityError>
75        + From<DestroyedResourceError>
76        + From<BindError>,
77{
78    if let Some(ref bind_group) = bind_group {
79        api_log!("Pass::set_bind_group {index} {}", bind_group.error_ident());
80    } else {
81        api_log!("Pass::set_bind_group {index} None");
82    }
83
84    let max_bind_groups = state.base.device.limits.max_bind_groups;
85    if index >= max_bind_groups {
86        return Err(BindGroupIndexOutOfRange {
87            index,
88            max: max_bind_groups,
89        }
90        .into());
91    }
92
93    state.temp_offsets.clear();
94    state.temp_offsets.extend_from_slice(
95        &dynamic_offsets
96            [state.dynamic_offset_count..state.dynamic_offset_count + num_dynamic_offsets],
97    );
98    state.dynamic_offset_count += num_dynamic_offsets;
99
100    if let Some(bind_group) = bind_group {
101        // Add the bind group to the tracker. This is done for both compute and
102        // render passes, and is used to fail submission of the command buffer if
103        // any resource in any of the bind groups has been destroyed, whether or
104        // not the bind group is actually used by the pipeline.
105        let bind_group = state.base.tracker.bind_groups.insert_single(bind_group);
106
107        bind_group.same_device(device)?;
108
109        bind_group.validate_dynamic_bindings(index, &state.temp_offsets)?;
110
111        if merge_bind_groups {
112            // Merge the bind group's resources into the tracker. We only do this
113            // for render passes. For compute passes it is done per dispatch in
114            // [`flush_bindings`].
115            unsafe {
116                state.scope.merge_bind_group(&bind_group.used)?;
117            }
118        }
119        //Note: stateless trackers are not merged: the lifetime reference
120        // is held to the bind group itself.
121
122        state
123            .binder
124            .assign_group(index as usize, bind_group, &state.temp_offsets);
125    } else {
126        if !state.temp_offsets.is_empty() {
127            return Err(BindError::DynamicOffsetCountNotZero {
128                group: index,
129                actual: state.temp_offsets.len(),
130            }
131            .into());
132        }
133
134        state.binder.clear_group(index as usize);
135    };
136
137    Ok(())
138}
139
140/// Implementation of `flush_bindings` for both compute and render passes.
141///
142/// See the compute pass version of `State::flush_bindings` for an explanation
143/// of some differences in handling the two types of passes.
144pub(super) fn flush_bindings_helper(state: &mut PassState) -> Result<(), DestroyedResourceError> {
145    let start = state.binder.take_rebind_start_index();
146    let entries = state.binder.list_valid_with_start(start);
147    let pipeline_layout = state.binder.pipeline_layout.as_ref().unwrap();
148
149    for (i, bind_group, dynamic_offsets) in entries {
150        state.base.buffer_memory_init_actions.extend(
151            bind_group.buffer_init_actions.iter().filter_map(|action| {
152                action
153                    .buffer
154                    .initialization_status
155                    .read()
156                    .check_action(action)
157            }),
158        );
159        for action in bind_group.texture_init_actions.iter() {
160            state.pending_discard_init_fixups.extend(
161                state
162                    .base
163                    .texture_memory_actions
164                    .register_init_action(action),
165            );
166        }
167
168        let used_resource = bind_group
169            .used
170            .acceleration_structures
171            .into_iter()
172            .map(|tlas| crate::ray_tracing::AsAction::UseTlas(tlas.clone()));
173
174        state.base.as_actions.extend(used_resource);
175
176        let raw_bg = bind_group.try_raw(state.base.snatch_guard)?;
177        unsafe {
178            state.base.raw_encoder.set_bind_group(
179                pipeline_layout
180                    .raw()
181                    .expect("Pipeline layout should be valid at this point"),
182                i as u32,
183                raw_bg,
184                dynamic_offsets,
185            );
186        }
187    }
188
189    Ok(())
190}
191
192pub(super) fn change_pipeline_layout<E, F: FnOnce()>(
193    state: &mut PassState,
194    pipeline_layout: &Arc<binding_model::PipelineLayout>,
195    late_sized_buffer_groups: &[LateSizedBufferGroup],
196    f: F,
197) -> Result<(), E>
198where
199    E: From<DestroyedResourceError>,
200{
201    if state
202        .binder
203        .change_pipeline_layout(pipeline_layout, late_sized_buffer_groups)
204    {
205        f();
206
207        super::immediates_clear(
208            0,
209            pipeline_layout.immediate_size,
210            |clear_offset, clear_data| unsafe {
211                state.base.raw_encoder.set_immediates(
212                    pipeline_layout
213                        .raw()
214                        .expect("Pipeline layout should be valid at this point"),
215                    clear_offset,
216                    clear_data,
217                );
218            },
219        );
220    }
221    Ok(())
222}
223
224pub(crate) fn validate_immediates_alignment(
225    offset: u32,
226    size_bytes: u32,
227) -> Result<(), ImmediateUploadError> {
228    if !offset.is_multiple_of(wgt::IMMEDIATE_DATA_ALIGNMENT) {
229        return Err(ImmediateUploadError::StartOffsetUnaligned(offset));
230    }
231
232    if !size_bytes.is_multiple_of(wgt::IMMEDIATE_DATA_ALIGNMENT) {
233        return Err(ImmediateUploadError::SizeUnaligned(size_bytes));
234    }
235
236    Ok(())
237}
238
239pub(crate) fn set_immediates<E, F: FnOnce(&[u32])>(
240    state: &mut PassState,
241    immediates_data: &[u32],
242    offset: u32,
243    size_bytes: u32,
244    values_offset: Option<u32>,
245    f: F,
246) -> Result<(), E>
247where
248    E: From<ImmediateUploadError> + From<InvalidValuesOffset> + From<MissingPipeline>,
249{
250    api_log!("Pass::set_immediates");
251
252    // Alignment has been validated by `validate_immediates_alignment` when pushing `SetImmediate` commands.
253
254    let values_offset = values_offset.ok_or(InvalidValuesOffset)?;
255
256    let pipeline_layout = state
257        .binder
258        .pipeline_layout
259        .as_ref()
260        .ok_or(MissingPipeline)?;
261
262    pipeline_layout.validate_immediates_ranges(offset, size_bytes)?;
263
264    let values_offset_usize = usize::try_from(values_offset)
265        .expect("`values_offset` is outside the bounds of `usize` (!?)");
266    if values_offset_usize > immediates_data.len() {
267        panic!(
268            "Internal error: `set_immediates` values offset ({}) \
269            overruns the immediates data length ({})",
270            values_offset,
271            immediates_data.len()
272        );
273    }
274
275    let size_immediate_elements = size_bytes / wgt::IMMEDIATE_DATA_ALIGNMENT;
276    let size_immediate_elements_usize = usize::try_from(size_immediate_elements)
277        .expect("`size_immediate_elements` is outside the bounds of `usize` (!?)");
278    if size_immediate_elements_usize > immediates_data.len() - values_offset_usize {
279        panic!(
280            "Internal error: `set_immediates` values offset + count ({} + {}) \
281            overruns the immediates data length ({})",
282            values_offset,
283            size_immediate_elements,
284            immediates_data.len()
285        );
286    }
287
288    // NOTE: These additions are will not overflow, because we've validated the range above.
289    let values_end_offset = values_offset_usize + size_immediate_elements_usize;
290    let data_slice = &immediates_data[(values_offset_usize)..values_end_offset];
291
292    f(data_slice);
293
294    unsafe {
295        state.base.raw_encoder.set_immediates(
296            pipeline_layout
297                .raw()
298                .expect("Pipeline layout should be valid at this point"),
299            offset,
300            data_slice,
301        )
302    }
303    Ok(())
304}
305
306pub(crate) fn write_timestamp<E>(
307    state: &mut PassState,
308    device: &Arc<Device>,
309    pending_query_resets: Option<&mut QueryResetMap>,
310    query_set: Arc<QuerySet>,
311    query_index: u32,
312) -> Result<(), E>
313where
314    E: From<MissingFeatures> + From<QueryUseError> + From<DeviceError>,
315{
316    api_log!(
317        "Pass::write_timestamps {query_index} {}",
318        query_set.error_ident()
319    );
320
321    query_set.same_device(device)?;
322
323    state
324        .base
325        .device
326        .require_features(wgt::Features::TIMESTAMP_QUERY_INSIDE_PASSES)?;
327
328    let query_set = state.base.tracker.query_sets.insert_single(query_set);
329
330    query_set.validate_and_write_timestamp(
331        state.base.raw_encoder,
332        query_index,
333        pending_query_resets,
334        state.base.snatch_guard,
335        state.base.query_set_writes,
336    )?;
337    Ok(())
338}
339
340pub(crate) fn push_debug_group(state: &mut PassState, string_data: &[u8], len: usize) {
341    *state.base.debug_scope_depth += 1;
342    if !state
343        .base
344        .device
345        .instance_flags
346        .contains(wgt::InstanceFlags::DISCARD_HAL_LABELS)
347    {
348        let label =
349            str::from_utf8(&string_data[state.string_offset..state.string_offset + len]).unwrap();
350
351        api_log!("Pass::push_debug_group {label:?}");
352        unsafe {
353            state.base.raw_encoder.begin_debug_marker(label);
354        }
355    }
356    state.string_offset += len;
357}
358
359pub(crate) fn pop_debug_group<E>(state: &mut PassState) -> Result<(), E>
360where
361    E: From<DebugGroupError>,
362{
363    api_log!("Pass::pop_debug_group");
364
365    if *state.base.debug_scope_depth == 0 {
366        return Err(DebugGroupError::InvalidPop.into());
367    }
368    *state.base.debug_scope_depth -= 1;
369    if !state
370        .base
371        .device
372        .instance_flags
373        .contains(wgt::InstanceFlags::DISCARD_HAL_LABELS)
374    {
375        unsafe {
376            state.base.raw_encoder.end_debug_marker();
377        }
378    }
379    Ok(())
380}
381
382pub(crate) fn insert_debug_marker(state: &mut PassState, string_data: &[u8], len: usize) {
383    if !state
384        .base
385        .device
386        .instance_flags
387        .contains(wgt::InstanceFlags::DISCARD_HAL_LABELS)
388    {
389        let label =
390            str::from_utf8(&string_data[state.string_offset..state.string_offset + len]).unwrap();
391        api_log!("Pass::insert_debug_marker {label:?}");
392        unsafe {
393            state.base.raw_encoder.insert_debug_marker(label);
394        }
395    }
396    state.string_offset += len;
397}