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::{
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 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 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 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 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 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 unsafe {
181 state.scope.merge_bind_group(&bind_group.used)?;
182 }
183 }
184 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
205pub(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}