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