1use 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 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 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 unsafe {
116 state.scope.merge_bind_group(&bind_group.used)?;
117 }
118 }
119 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
140pub(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 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 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}