Skip to main content

wgpu_core/indirect_validation/
draw.rs

1use super::{
2    utils::{BufferBarrierScratch, BufferBarriers, UniqueIndexExt as _, UniqueIndexScratch},
3    CreateIndirectValidationPipelineError,
4};
5use crate::{
6    command::{get_src_stride_of_indirect_args, RenderPassErrorInner},
7    device::{queue::TempResource, Device, DeviceError},
8    hal_label,
9    lock::{rank, Mutex},
10    pipeline::{CreatePipelineError, CreateShaderModuleError},
11    resource::{RawResourceAccess as _, StagingBuffer, Trackable},
12    snatch::SnatchGuard,
13    track::TrackerIndex,
14    FastHashMap,
15};
16use alloc::{boxed::Box, string::ToString, sync::Arc, vec, vec::Vec};
17use core::{mem::size_of, num::NonZeroU64};
18use scopeguard::{guard, ScopeGuard};
19use wgt::Limits;
20
21/// Note: This needs to be under:
22///
23/// default max_compute_workgroups_per_dimension * size_of::<wgt::DrawIndirectArgs>() * `workgroup_size` used by the shader
24///
25/// = (2^16 - 1) * 2^4 * 2^6
26///
27/// It is currently set to:
28///
29/// = (2^16 - 1) * 2^4
30///
31/// This is enough space for:
32///
33/// - 65535 [`wgt::DrawIndirectArgs`] / [`MetadataEntry`]
34/// - 52428 [`wgt::DrawIndexedIndirectArgs`]
35const BUFFER_SIZE: wgt::BufferSize = wgt::BufferSize::new(1_048_560).unwrap();
36
37/// Holds all device-level resources that are needed to validate indirect draws.
38///
39/// This machinery requires the following limits:
40///
41/// - max_bind_groups: 3,
42/// - max_dynamic_storage_buffers_per_pipeline_layout: 1,
43/// - max_storage_buffers_per_shader_stage: 3,
44/// - max_immediate_size: 8,
45///
46/// These are all indirectly satisfied by `DownlevelFlags::INDIRECT_EXECUTION`, which is also
47/// required for this module's functionality to work.
48#[derive(Debug)]
49pub(crate) struct Draw {
50    module: Box<dyn hal::DynShaderModule>,
51    metadata_bind_group_layout: Box<dyn hal::DynBindGroupLayout>,
52    src_bind_group_layout: Box<dyn hal::DynBindGroupLayout>,
53    dst_bind_group_layout: Box<dyn hal::DynBindGroupLayout>,
54    pipeline_layout: Box<dyn hal::DynPipelineLayout>,
55    pipeline: Box<dyn hal::DynComputePipeline>,
56
57    free_indirect_entries: Mutex<Vec<BufferPoolEntry>>,
58    free_metadata_entries: Mutex<Vec<BufferPoolEntry>>,
59}
60
61impl Draw {
62    pub(super) fn new(
63        device: &dyn hal::DynDevice,
64        required_features: &wgt::Features,
65        instance_flags: wgt::InstanceFlags,
66        backend: wgt::Backend,
67        limits: &Limits,
68    ) -> Result<Self, CreateIndirectValidationPipelineError> {
69        // Indirect draw validation doesn't support buffer sizes higher than u32
70        // since its offsets in the shader and dynamic offsets are u32.
71        //
72        // See also: `u64_offset_to_u32_offset`.
73        assert!(limits.max_buffer_size <= u32::MAX as u64);
74
75        let module = create_validation_module(device, instance_flags)?;
76        let module = guard(module, |module| unsafe {
77            device.destroy_shader_module(module)
78        });
79
80        let metadata_bind_group_layout = create_bind_group_layout(
81            device,
82            true,
83            false,
84            BUFFER_SIZE,
85            hal_label(
86                Some("(wgpu internal) Indirect draw validation metadata bind group layout"),
87                instance_flags,
88            ),
89        )?;
90        let metadata_bind_group_layout = guard(metadata_bind_group_layout, |bgl| unsafe {
91            device.destroy_bind_group_layout(bgl)
92        });
93
94        let src_bind_group_layout = create_bind_group_layout(
95            device,
96            true,
97            true,
98            wgt::BufferSize::new(4 * 4).unwrap(),
99            hal_label(
100                Some("(wgpu internal) Indirect draw validation source bind group layout"),
101                instance_flags,
102            ),
103        )?;
104        let src_bind_group_layout = guard(src_bind_group_layout, |bgl| unsafe {
105            device.destroy_bind_group_layout(bgl)
106        });
107
108        let dst_bind_group_layout = create_bind_group_layout(
109            device,
110            false,
111            false,
112            BUFFER_SIZE,
113            hal_label(
114                Some("(wgpu internal) Indirect draw validation destination bind group layout"),
115                instance_flags,
116            ),
117        )?;
118        let dst_bind_group_layout = guard(dst_bind_group_layout, |bgl| unsafe {
119            device.destroy_bind_group_layout(bgl)
120        });
121
122        let pipeline_layout_desc = hal::PipelineLayoutDescriptor {
123            label: hal_label(
124                Some("(wgpu internal) Indirect draw validation pipeline layout"),
125                instance_flags,
126            ),
127            flags: hal::PipelineLayoutFlags::empty(),
128            bind_group_layouts: &[
129                Some(metadata_bind_group_layout.as_ref()),
130                Some(src_bind_group_layout.as_ref()),
131                Some(dst_bind_group_layout.as_ref()),
132            ],
133            immediate_size: 8,
134        };
135        let pipeline_layout = unsafe {
136            device
137                .create_pipeline_layout(&pipeline_layout_desc)
138                .map_err(DeviceError::from_hal)?
139        };
140        let pipeline_layout = guard(pipeline_layout, |pipeline_layout| unsafe {
141            device.destroy_pipeline_layout(pipeline_layout)
142        });
143
144        let supports_indirect_first_instance =
145            required_features.contains(wgt::Features::INDIRECT_FIRST_INSTANCE);
146        let write_d3d12_special_constants = backend == wgt::Backend::Dx12;
147        let pipeline = create_validation_pipeline(
148            device,
149            module.as_ref(),
150            pipeline_layout.as_ref(),
151            supports_indirect_first_instance,
152            write_d3d12_special_constants,
153            instance_flags,
154        )?;
155        let pipeline = guard(pipeline, |pipeline| unsafe {
156            device.destroy_compute_pipeline(pipeline)
157        });
158
159        // Error returns after we start consuming guards could bypass resource cleanup.
160        #[deny(clippy::question_mark_used)]
161        Ok(Self {
162            module: ScopeGuard::into_inner(module),
163            metadata_bind_group_layout: ScopeGuard::into_inner(metadata_bind_group_layout),
164            src_bind_group_layout: ScopeGuard::into_inner(src_bind_group_layout),
165            dst_bind_group_layout: ScopeGuard::into_inner(dst_bind_group_layout),
166            pipeline_layout: ScopeGuard::into_inner(pipeline_layout),
167            pipeline: ScopeGuard::into_inner(pipeline),
168
169            free_indirect_entries: Mutex::new(rank::BUFFER_POOL, Vec::new()),
170            free_metadata_entries: Mutex::new(rank::BUFFER_POOL, Vec::new()),
171        })
172    }
173
174    /// `Ok(None)` will only be returned if `buffer_size` is `0`.
175    pub(super) fn create_src_bind_group(
176        &self,
177        device: &dyn hal::DynDevice,
178        limits: &Limits,
179        buffer_size: u64,
180        buffer: &dyn hal::DynBuffer,
181        instance_flags: wgt::InstanceFlags,
182    ) -> Result<Option<Box<dyn hal::DynBindGroup>>, DeviceError> {
183        let binding_size = calculate_src_buffer_binding_size(buffer_size, limits);
184        let Some(binding_size) = NonZeroU64::new(binding_size) else {
185            return Ok(None);
186        };
187        let hal_desc = hal::BindGroupDescriptor {
188            label: hal_label(
189                Some("(wgpu internal) Indirect draw validation source bind group"),
190                instance_flags,
191            ),
192            layout: self.src_bind_group_layout.as_ref(),
193            entries: &[hal::BindGroupEntry {
194                binding: 0,
195                resource_index: 0,
196                count: 1,
197            }],
198            // SAFETY: We calculated the binding size to fit within the buffer.
199            buffers: &[hal::BufferBinding::new_unchecked(buffer, 0, binding_size)],
200            samplers: &[],
201            textures: &[],
202            acceleration_structures: &[],
203            external_textures: &[],
204        };
205        unsafe {
206            device
207                .create_bind_group(&hal_desc)
208                .map(Some)
209                .map_err(DeviceError::from_hal)
210        }
211    }
212
213    fn acquire_dst_entry(
214        &self,
215        device: &dyn hal::DynDevice,
216        instance_flags: wgt::InstanceFlags,
217    ) -> Result<BufferPoolEntry, hal::DeviceError> {
218        let mut free_buffers = self.free_indirect_entries.lock();
219        match free_buffers.pop() {
220            Some(buffer) => Ok(buffer),
221            None => {
222                let usage = wgt::BufferUses::INDIRECT | wgt::BufferUses::STORAGE_READ_WRITE;
223                create_buffer_and_bind_group(
224                    device,
225                    usage,
226                    self.dst_bind_group_layout.as_ref(),
227                    hal_label(Some("(wgpu internal) Indirect draw validation destination buffer"), instance_flags),
228                    hal_label(Some("(wgpu internal) Indirect draw validation destination bind group layout"), instance_flags),
229                )
230            }
231        }
232    }
233
234    fn release_dst_entries(&self, entries: impl Iterator<Item = BufferPoolEntry>) {
235        self.free_indirect_entries.lock().extend(entries);
236    }
237
238    fn acquire_metadata_entry(
239        &self,
240        device: &dyn hal::DynDevice,
241        instance_flags: wgt::InstanceFlags,
242    ) -> Result<BufferPoolEntry, hal::DeviceError> {
243        let mut free_buffers = self.free_metadata_entries.lock();
244        match free_buffers.pop() {
245            Some(buffer) => Ok(buffer),
246            None => {
247                let usage = wgt::BufferUses::COPY_DST | wgt::BufferUses::STORAGE_READ_ONLY;
248                create_buffer_and_bind_group(
249                    device,
250                    usage,
251                    self.metadata_bind_group_layout.as_ref(),
252                    hal_label(
253                        Some("(wgpu internal) Indirect draw validation metadata buffer"),
254                        instance_flags,
255                    ),
256                    hal_label(
257                        Some("(wgpu internal) Indirect draw validation metadata bind group layout"),
258                        instance_flags,
259                    ),
260                )
261            }
262        }
263    }
264
265    fn release_metadata_entries(&self, entries: impl Iterator<Item = BufferPoolEntry>) {
266        self.free_metadata_entries.lock().extend(entries);
267    }
268
269    /// Injects a compute pass that will validate all indirect draws in the current render pass.
270    pub(crate) fn inject_validation_pass(
271        &self,
272        device: &Arc<Device>,
273        snatch_guard: &SnatchGuard,
274        resources: &mut DrawResources,
275        temp_resources: &mut Vec<TempResource>,
276        encoder: &mut dyn hal::DynCommandEncoder,
277        batcher: DrawBatcher,
278    ) -> Result<(), RenderPassErrorInner> {
279        let mut batches = batcher.batches;
280
281        if batches.is_empty() {
282            return Ok(());
283        }
284
285        let max_staging_buffer_size = 1 << 26; // ~67MiB
286
287        let mut staging_buffers = Vec::new();
288
289        let mut current_size = 0;
290        for batch in batches.values_mut() {
291            let data = batch.metadata();
292            let offset = if current_size + data.len() > max_staging_buffer_size {
293                let staging_buffer =
294                    StagingBuffer::new(device, NonZeroU64::new(current_size as u64).unwrap())?;
295                staging_buffers.push(staging_buffer);
296                current_size = data.len();
297                0
298            } else {
299                let offset = current_size;
300                current_size += data.len();
301                offset as u64
302            };
303            batch.staging_buffer_index = staging_buffers.len();
304            batch.staging_buffer_offset = offset;
305        }
306        if current_size != 0 {
307            let staging_buffer =
308                StagingBuffer::new(device, NonZeroU64::new(current_size as u64).unwrap())?;
309            staging_buffers.push(staging_buffer);
310        }
311
312        for batch in batches.values() {
313            let data = batch.metadata();
314            let staging_buffer = &mut staging_buffers[batch.staging_buffer_index];
315            unsafe {
316                staging_buffer.write_with_offset(
317                    data,
318                    0,
319                    batch.staging_buffer_offset as isize,
320                    data.len(),
321                )
322            };
323        }
324
325        let staging_buffers: Vec<_> = staging_buffers
326            .into_iter()
327            .map(|buffer| buffer.flush())
328            .collect();
329
330        let mut current_metadata_entry = None;
331        for batch in batches.values_mut() {
332            let data = batch.metadata();
333            let (metadata_resource_index, metadata_buffer_offset) =
334                resources.get_metadata_subrange(data.len() as u64, &mut current_metadata_entry)?;
335            batch.metadata_resource_index = metadata_resource_index;
336            batch.metadata_buffer_offset = metadata_buffer_offset;
337        }
338
339        let buffer_barrier_scratch = &mut BufferBarrierScratch::new();
340        let unique_index_scratch = &mut UniqueIndexScratch::new();
341
342        BufferBarriers::new(buffer_barrier_scratch)
343            .extend(
344                batches
345                    .values()
346                    .map(|batch| batch.staging_buffer_index)
347                    .unique(unique_index_scratch)
348                    .map(|index| hal::BufferBarrier {
349                        buffer: staging_buffers[index].raw(),
350                        usage: hal::StateTransition {
351                            from: wgt::BufferUses::MAP_WRITE,
352                            to: wgt::BufferUses::COPY_SRC,
353                        },
354                    }),
355            )
356            .extend(
357                batches
358                    .values()
359                    .map(|batch| batch.metadata_resource_index)
360                    .unique(unique_index_scratch)
361                    .map(|index| hal::BufferBarrier {
362                        buffer: resources.get_metadata_buffer(index),
363                        usage: hal::StateTransition {
364                            from: wgt::BufferUses::STORAGE_READ_ONLY,
365                            to: wgt::BufferUses::COPY_DST,
366                        },
367                    }),
368            )
369            .encode(encoder);
370
371        for batch in batches.values() {
372            let data = batch.metadata();
373            let data_size = NonZeroU64::new(data.len() as u64).unwrap();
374
375            let staging_buffer = &staging_buffers[batch.staging_buffer_index];
376
377            let metadata_buffer = resources.get_metadata_buffer(batch.metadata_resource_index);
378
379            unsafe {
380                encoder.copy_buffer_to_buffer(
381                    staging_buffer.raw(),
382                    metadata_buffer,
383                    &[hal::BufferCopy {
384                        src_offset: batch.staging_buffer_offset,
385                        dst_offset: batch.metadata_buffer_offset,
386                        size: data_size,
387                    }],
388                );
389            }
390        }
391
392        for staging_buffer in staging_buffers {
393            temp_resources.push(TempResource::StagingBuffer(staging_buffer));
394        }
395
396        BufferBarriers::new(buffer_barrier_scratch)
397            .extend(
398                batches
399                    .values()
400                    .map(|batch| batch.metadata_resource_index)
401                    .unique(unique_index_scratch)
402                    .map(|index| hal::BufferBarrier {
403                        buffer: resources.get_metadata_buffer(index),
404                        usage: hal::StateTransition {
405                            from: wgt::BufferUses::COPY_DST,
406                            to: wgt::BufferUses::STORAGE_READ_ONLY,
407                        },
408                    }),
409            )
410            .extend(
411                batches
412                    .values()
413                    .map(|batch| batch.dst_resource_index)
414                    .unique(unique_index_scratch)
415                    .map(|index| hal::BufferBarrier {
416                        buffer: resources.get_dst_buffer(index),
417                        usage: hal::StateTransition {
418                            from: wgt::BufferUses::INDIRECT,
419                            to: wgt::BufferUses::STORAGE_READ_WRITE,
420                        },
421                    }),
422            )
423            .encode(encoder);
424
425        let desc = hal::ComputePassDescriptor {
426            label: hal_label(
427                Some("(wgpu internal) Indirect draw validation pass"),
428                device.instance_flags,
429            ),
430            timestamp_writes: None,
431        };
432        unsafe {
433            encoder.begin_compute_pass(&desc);
434        }
435        unsafe {
436            encoder.set_compute_pipeline(self.pipeline.as_ref());
437        }
438
439        for batch in batches.values() {
440            let pipeline_layout = self.pipeline_layout.as_ref();
441
442            let metadata_start =
443                (batch.metadata_buffer_offset / size_of::<MetadataEntry>() as u64) as u32;
444            let metadata_count = batch.entries.len() as u32;
445            unsafe {
446                encoder.set_immediates(pipeline_layout, 0, &[metadata_start, metadata_count]);
447            }
448
449            let metadata_bind_group =
450                resources.get_metadata_bind_group(batch.metadata_resource_index);
451            unsafe {
452                encoder.set_bind_group(pipeline_layout, 0, metadata_bind_group, &[]);
453            }
454
455            // Make sure the indirect buffer is still valid.
456            batch.src_buffer.try_raw(snatch_guard)?;
457
458            let src_bind_group = batch
459                .src_buffer
460                .indirect_validation_bind_groups
461                .get(snatch_guard)
462                .unwrap()
463                .draw
464                .as_ref();
465            unsafe {
466                encoder.set_bind_group(
467                    pipeline_layout,
468                    1,
469                    src_bind_group,
470                    &[u64_offset_to_u32_offset(batch.src_dynamic_offset)],
471                );
472            }
473
474            let dst_bind_group = resources.get_dst_bind_group(batch.dst_resource_index);
475            unsafe {
476                encoder.set_bind_group(pipeline_layout, 2, dst_bind_group, &[]);
477            }
478
479            unsafe {
480                encoder.dispatch_workgroups([(batch.entries.len() as u32).div_ceil(64), 1, 1]);
481            }
482        }
483
484        unsafe {
485            encoder.end_compute_pass();
486        }
487
488        BufferBarriers::new(buffer_barrier_scratch)
489            .extend(
490                batches
491                    .values()
492                    .map(|batch| batch.dst_resource_index)
493                    .unique(unique_index_scratch)
494                    .map(|index| hal::BufferBarrier {
495                        buffer: resources.get_dst_buffer(index),
496                        usage: hal::StateTransition {
497                            from: wgt::BufferUses::STORAGE_READ_WRITE,
498                            to: wgt::BufferUses::INDIRECT,
499                        },
500                    }),
501            )
502            .encode(encoder);
503
504        Ok(())
505    }
506
507    pub(super) fn dispose(self, device: &dyn hal::DynDevice) {
508        let Draw {
509            module,
510            metadata_bind_group_layout,
511            src_bind_group_layout,
512            dst_bind_group_layout,
513            pipeline_layout,
514            pipeline,
515
516            free_indirect_entries,
517            free_metadata_entries,
518        } = self;
519
520        for entry in free_indirect_entries.into_inner().drain(..) {
521            unsafe {
522                device.destroy_bind_group(entry.bind_group);
523                device.destroy_buffer(entry.buffer);
524            }
525        }
526
527        for entry in free_metadata_entries.into_inner().drain(..) {
528            unsafe {
529                device.destroy_bind_group(entry.bind_group);
530                device.destroy_buffer(entry.buffer);
531            }
532        }
533
534        unsafe {
535            device.destroy_compute_pipeline(pipeline);
536            device.destroy_pipeline_layout(pipeline_layout);
537            device.destroy_bind_group_layout(metadata_bind_group_layout);
538            device.destroy_bind_group_layout(src_bind_group_layout);
539            device.destroy_bind_group_layout(dst_bind_group_layout);
540            device.destroy_shader_module(module);
541        }
542    }
543}
544
545fn create_validation_module(
546    device: &dyn hal::DynDevice,
547    instance_flags: wgt::InstanceFlags,
548) -> Result<Box<dyn hal::DynShaderModule>, CreateIndirectValidationPipelineError> {
549    let src = include_str!("./validate_draw.wgsl");
550
551    #[cfg(feature = "wgsl")]
552    let module = naga::front::wgsl::parse_str(src).map_err(|inner| {
553        CreateShaderModuleError::Parsing(naga::error::ShaderError {
554            source: src.to_string(),
555            label: None,
556            inner: Box::new(inner),
557        })
558    })?;
559    #[cfg(not(feature = "wgsl"))]
560    #[allow(clippy::diverging_sub_expression)]
561    let module = panic!("Indirect validation requires the wgsl feature flag to be enabled!");
562
563    let info = crate::device::create_validator(
564        wgt::Features::IMMEDIATES,
565        wgt::DownlevelFlags::empty(),
566        naga::valid::ValidationFlags::all(),
567    )
568    .validate(&module)
569    .map_err(|inner| {
570        CreateShaderModuleError::Validation(naga::error::ShaderError {
571            source: src.to_string(),
572            label: None,
573            inner,
574        })
575    })?;
576    let hal_shader = hal::ShaderInput::Naga(hal::NagaShader {
577        module: alloc::borrow::Cow::Owned(module),
578        info,
579        debug_source: None,
580    });
581    let hal_desc = hal::ShaderModuleDescriptor {
582        label: hal_label(
583            Some("(wgpu internal) Indirect draw validation shader module"),
584            instance_flags,
585        ),
586        runtime_checks: wgt::ShaderRuntimeChecks::unchecked(),
587    };
588    let module = unsafe { device.create_shader_module(&hal_desc, hal_shader) }.map_err(
589        |error| match error {
590            hal::ShaderError::Device(error) => {
591                CreateShaderModuleError::Device(DeviceError::from_hal(error))
592            }
593            hal::ShaderError::Compilation(ref msg) => {
594                log::error!("Shader error: {msg}");
595                CreateShaderModuleError::Generation
596            }
597        },
598    )?;
599
600    Ok(module)
601}
602
603fn create_validation_pipeline(
604    device: &dyn hal::DynDevice,
605    module: &dyn hal::DynShaderModule,
606    pipeline_layout: &dyn hal::DynPipelineLayout,
607    supports_indirect_first_instance: bool,
608    write_d3d12_special_constants: bool,
609    instance_flags: wgt::InstanceFlags,
610) -> Result<Box<dyn hal::DynComputePipeline>, CreateIndirectValidationPipelineError> {
611    let pipeline_desc = hal::ComputePipelineDescriptor {
612        label: hal_label(
613            Some("(wgpu internal) Indirect draw validation pipeline"),
614            instance_flags,
615        ),
616        layout: pipeline_layout,
617        stage: hal::ProgrammableStage {
618            module,
619            entry_point: "main",
620            constants: &hashbrown::HashMap::from([
621                (
622                    "supports_indirect_first_instance".to_string(),
623                    f64::from(supports_indirect_first_instance),
624                ),
625                (
626                    "write_d3d12_special_constants".to_string(),
627                    f64::from(write_d3d12_special_constants),
628                ),
629            ]),
630            zero_initialize_workgroup_memory: false,
631        },
632        cache: None,
633    };
634    let pipeline = unsafe { device.create_compute_pipeline(&pipeline_desc) }
635        .map_err(CreatePipelineError::from_hal_without_device_loss)?;
636
637    Ok(pipeline)
638}
639
640fn create_bind_group_layout(
641    device: &dyn hal::DynDevice,
642    read_only: bool,
643    has_dynamic_offset: bool,
644    min_binding_size: wgt::BufferSize,
645    label: Option<&'static str>,
646) -> Result<Box<dyn hal::DynBindGroupLayout>, CreateIndirectValidationPipelineError> {
647    let bind_group_layout_desc = hal::BindGroupLayoutDescriptor {
648        label,
649        flags: hal::BindGroupLayoutFlags::empty(),
650        entries: &[wgt::BindGroupLayoutEntry {
651            binding: 0,
652            visibility: wgt::ShaderStages::COMPUTE,
653            ty: wgt::BindingType::Buffer {
654                ty: wgt::BufferBindingType::Storage { read_only },
655                has_dynamic_offset,
656                min_binding_size: Some(min_binding_size),
657            },
658            count: None,
659        }],
660    };
661    let bind_group_layout = unsafe {
662        device
663            .create_bind_group_layout(&bind_group_layout_desc)
664            .map_err(DeviceError::from_hal)?
665    };
666
667    Ok(bind_group_layout)
668}
669
670/// Returns the largest binding size that when combined with dynamic offsets can address the whole buffer.
671fn calculate_src_buffer_binding_size(buffer_size: u64, limits: &Limits) -> u64 {
672    let max_storage_buffer_binding_size = limits.max_storage_buffer_binding_size;
673    let min_storage_buffer_offset_alignment = limits.min_storage_buffer_offset_alignment as u64;
674
675    if buffer_size <= max_storage_buffer_binding_size {
676        buffer_size
677    } else {
678        let buffer_rem = buffer_size % min_storage_buffer_offset_alignment;
679        let binding_rem = max_storage_buffer_binding_size % min_storage_buffer_offset_alignment;
680
681        // Can the buffer remainder fit in the binding remainder?
682        // If so, align max binding size and add buffer remainder
683        if buffer_rem <= binding_rem {
684            max_storage_buffer_binding_size - binding_rem + buffer_rem
685        }
686        // If not, align max binding size, shorten it by a chunk and add buffer remainder
687        else {
688            max_storage_buffer_binding_size - binding_rem - min_storage_buffer_offset_alignment
689                + buffer_rem
690        }
691    }
692}
693
694/// Splits the given `offset` into a dynamic offset & offset.
695fn calculate_src_offsets(
696    buffer_size: u64,
697    limits: &Limits,
698    offset: u64,
699    data_size: u64,
700) -> (u64, u64) {
701    const MAX_DATA_SIZE: u64 = 20; // indexed indirect draw params are 20B
702    let binding_size = calculate_src_buffer_binding_size(buffer_size, limits);
703    let min_storage_buffer_offset_alignment = limits.min_storage_buffer_offset_alignment as u64;
704
705    assert!([16, MAX_DATA_SIZE].contains(&data_size));
706    assert!([32, 64, 128, 256].contains(&min_storage_buffer_offset_alignment));
707    assert!(buffer_size >= data_size);
708    assert!(offset <= buffer_size - data_size);
709    assert!(binding_size <= buffer_size);
710
711    // Invariants that the outputs of this function must satisfy:
712    // - out_dynamic_offset + out_offset = offset
713    // - out_dynamic_offset % min_storage_buffer_offset_alignment = 0
714    // - out_dynamic_offset + binding_size <= buffer_size
715    // - out_offset + data_size <= binding_size
716
717    // Align the max offset in the binding and treat it as the stride between
718    // dynamic offsets.
719    //
720    // `dynamic_offset_stride` could just be `min_storage_buffer_offset_alignment`
721    // but we want to make it as large as possible since setting dynamic
722    // offsets requires extra calls to setBindGroup and then to dispatch,
723    // calls which we want to minimize.
724    //
725    // Use `MAX_DATA_SIZE` instead of the actual `data_size` so that the
726    // resulting stride is the same for both indexed and non-indexed draw calls,
727    // reducing the likelihood of `out_dynamic_offset` being different.
728    let dynamic_offset_stride = binding_size.saturating_sub(MAX_DATA_SIZE)
729        / min_storage_buffer_offset_alignment
730        * min_storage_buffer_offset_alignment;
731    if dynamic_offset_stride == 0 {
732        return (0, offset);
733    }
734
735    let max_dynamic_offset = buffer_size - binding_size;
736    let out_dynamic_offset =
737        max_dynamic_offset.min(offset / dynamic_offset_stride * dynamic_offset_stride);
738    let out_offset = offset - out_dynamic_offset;
739
740    (out_dynamic_offset, out_offset)
741}
742
743#[derive(Debug)]
744struct BufferPoolEntry {
745    buffer: Box<dyn hal::DynBuffer>,
746    bind_group: Box<dyn hal::DynBindGroup>,
747}
748
749fn create_buffer_and_bind_group(
750    device: &dyn hal::DynDevice,
751    usage: wgt::BufferUses,
752    bind_group_layout: &dyn hal::DynBindGroupLayout,
753    buffer_label: Option<&'static str>,
754    bind_group_label: Option<&'static str>,
755) -> Result<BufferPoolEntry, hal::DeviceError> {
756    let buffer_desc = hal::BufferDescriptor {
757        label: buffer_label,
758        size: BUFFER_SIZE.get(),
759        usage,
760        memory_flags: hal::MemoryFlags::empty(),
761    };
762    let (buffer, _) = unsafe { device.create_buffer(&buffer_desc) }?;
763    let bind_group_desc = hal::BindGroupDescriptor {
764        label: bind_group_label,
765        layout: bind_group_layout,
766        entries: &[hal::BindGroupEntry {
767            binding: 0,
768            resource_index: 0,
769            count: 1,
770        }],
771        // SAFETY: We just created the buffer with this size.
772        buffers: &[hal::BufferBinding::new_unchecked(
773            buffer.as_ref(),
774            0,
775            BUFFER_SIZE,
776        )],
777        samplers: &[],
778        textures: &[],
779        acceleration_structures: &[],
780        external_textures: &[],
781    };
782    let bind_group = unsafe { device.create_bind_group(&bind_group_desc) }?;
783    Ok(BufferPoolEntry { buffer, bind_group })
784}
785
786#[derive(Clone)]
787struct CurrentEntry {
788    index: usize,
789    offset: u64,
790}
791
792/// Holds all command buffer-level resources that are needed to validate indirect draws.
793pub(crate) struct DrawResources {
794    device: Arc<Device>,
795    dst_entries: Vec<BufferPoolEntry>,
796    metadata_entries: Vec<BufferPoolEntry>,
797}
798
799impl Drop for DrawResources {
800    fn drop(&mut self) {
801        if let Some(ref indirect_validation) = self.device.indirect_validation {
802            let indirect_draw_validation = &indirect_validation.draw;
803            indirect_draw_validation.release_dst_entries(self.dst_entries.drain(..));
804            indirect_draw_validation.release_metadata_entries(self.metadata_entries.drain(..));
805        }
806    }
807}
808
809impl DrawResources {
810    pub(crate) fn new(device: Arc<Device>) -> Self {
811        DrawResources {
812            device,
813            dst_entries: Vec::new(),
814            metadata_entries: Vec::new(),
815        }
816    }
817
818    pub(crate) fn get_dst_buffer(&self, index: usize) -> &dyn hal::DynBuffer {
819        self.dst_entries.get(index).unwrap().buffer.as_ref()
820    }
821
822    fn get_dst_bind_group(&self, index: usize) -> &dyn hal::DynBindGroup {
823        self.dst_entries.get(index).unwrap().bind_group.as_ref()
824    }
825
826    fn get_metadata_buffer(&self, index: usize) -> &dyn hal::DynBuffer {
827        self.metadata_entries.get(index).unwrap().buffer.as_ref()
828    }
829
830    fn get_metadata_bind_group(&self, index: usize) -> &dyn hal::DynBindGroup {
831        self.metadata_entries
832            .get(index)
833            .unwrap()
834            .bind_group
835            .as_ref()
836    }
837
838    fn get_dst_subrange(
839        &mut self,
840        size: u64,
841        current_entry: &mut Option<CurrentEntry>,
842    ) -> Result<(usize, u64), DeviceError> {
843        let indirect_draw_validation = &self.device.indirect_validation.as_ref().unwrap().draw;
844        let ensure_entry = |index: usize| {
845            if self.dst_entries.len() <= index {
846                let entry = indirect_draw_validation
847                    .acquire_dst_entry(self.device.raw(), self.device.instance_flags)?;
848                self.dst_entries.push(entry);
849            }
850            Ok(())
851        };
852        let entry_data = Self::get_subrange_impl(ensure_entry, current_entry, size)?;
853        Ok((entry_data.index, entry_data.offset))
854    }
855
856    fn get_metadata_subrange(
857        &mut self,
858        size: u64,
859        current_entry: &mut Option<CurrentEntry>,
860    ) -> Result<(usize, u64), DeviceError> {
861        let indirect_draw_validation = &self.device.indirect_validation.as_ref().unwrap().draw;
862        let ensure_entry = |index: usize| {
863            if self.metadata_entries.len() <= index {
864                let entry = indirect_draw_validation
865                    .acquire_metadata_entry(self.device.raw(), self.device.instance_flags)?;
866                self.metadata_entries.push(entry);
867            }
868            Ok(())
869        };
870        let entry_data = Self::get_subrange_impl(ensure_entry, current_entry, size)?;
871        Ok((entry_data.index, entry_data.offset))
872    }
873
874    fn get_subrange_impl(
875        ensure_entry: impl FnOnce(usize) -> Result<(), hal::DeviceError>,
876        current_entry: &mut Option<CurrentEntry>,
877        size: u64,
878    ) -> Result<CurrentEntry, DeviceError> {
879        let index = if let Some(current_entry) = current_entry.as_mut() {
880            if current_entry.offset + size <= BUFFER_SIZE.get() {
881                let entry_data = current_entry.clone();
882                current_entry.offset += size;
883                return Ok(entry_data);
884            } else {
885                current_entry.index + 1
886            }
887        } else {
888            0
889        };
890
891        ensure_entry(index).map_err(DeviceError::from_hal)?;
892
893        let entry_data = CurrentEntry { index, offset: 0 };
894
895        *current_entry = Some(CurrentEntry {
896            index,
897            offset: size,
898        });
899
900        Ok(entry_data)
901    }
902}
903
904/// This must match the `MetadataEntry` struct used by the shader.
905#[repr(C)]
906struct MetadataEntry {
907    src_offset: u32,
908    dst_offset: u32,
909    vertex_or_index_limit: u32,
910    instance_limit: u32,
911}
912
913impl MetadataEntry {
914    fn new(
915        indexed: bool,
916        src_offset: u64,
917        dst_offset: u64,
918        vertex_or_index_limit: u64,
919        instance_limit: u64,
920    ) -> Self {
921        const U32_MAX_AS_U64: u64 = u32::MAX as u64;
922
923        let src_offset = u64_offset_to_u32_offset(src_offset);
924        let src_offset = src_offset / 4; // translate byte offset to offset in u32's
925
926        // `src_offset` needs at most 30 bits,
927        // pack `indexed` in bit 31 of `src_offset`
928        let src_offset = src_offset | ((indexed as u32) << 31);
929
930        // max value for limits since first_X and X_count indirect draw arguments are u32
931        let max_limit = U32_MAX_AS_U64 + U32_MAX_AS_U64; // 1 11111111 11111111 11111111 11111110
932
933        let vertex_or_index_limit = vertex_or_index_limit.min(max_limit);
934        let vertex_or_index_limit_bit_32 = (vertex_or_index_limit >> 32) as u32; // extract bit 32
935        let vertex_or_index_limit = vertex_or_index_limit as u32; // truncate the limit to a u32
936
937        let instance_limit = instance_limit.min(max_limit);
938        let instance_limit_bit_32 = (instance_limit >> 32) as u32; // extract bit 32
939        let instance_limit = instance_limit as u32; // truncate the limit to a u32
940
941        let dst_offset = u64_offset_to_u32_offset(dst_offset);
942        let dst_offset = dst_offset / 4; // translate byte offset to offset in u32's
943
944        // `dst_offset` needs at most 30 bits,
945        // pack `vertex_or_index_limit_bit_32` in bit 30 of `dst_offset` and
946        // pack `instance_limit_bit_32` in bit 31 of `dst_offset`
947        let dst_offset =
948            dst_offset | (vertex_or_index_limit_bit_32 << 30) | (instance_limit_bit_32 << 31);
949
950        Self {
951            src_offset,
952            dst_offset,
953            vertex_or_index_limit,
954            instance_limit,
955        }
956    }
957}
958
959struct DrawIndirectValidationBatch {
960    src_buffer: Arc<crate::resource::Buffer>,
961    src_dynamic_offset: u64,
962    dst_resource_index: usize,
963    entries: Vec<MetadataEntry>,
964
965    staging_buffer_index: usize,
966    staging_buffer_offset: u64,
967    metadata_resource_index: usize,
968    metadata_buffer_offset: u64,
969}
970
971impl DrawIndirectValidationBatch {
972    /// Data to be written to the metadata buffer.
973    fn metadata(&self) -> &[u8] {
974        unsafe {
975            core::slice::from_raw_parts(
976                self.entries.as_ptr().cast::<u8>(),
977                self.entries.len() * size_of::<MetadataEntry>(),
978            )
979        }
980    }
981}
982
983/// Accumulates all needed data needed to validate indirect draws.
984pub(crate) struct DrawBatcher {
985    batches: FastHashMap<(TrackerIndex, u64, usize), DrawIndirectValidationBatch>,
986    current_dst_entry: Option<CurrentEntry>,
987}
988
989impl DrawBatcher {
990    pub(crate) fn new() -> Self {
991        Self {
992            batches: FastHashMap::default(),
993            current_dst_entry: None,
994        }
995    }
996
997    /// Add an indirect draw to be validated.
998    ///
999    /// Returns the index of the indirect buffer in `indirect_draw_validation_resources`
1000    /// and the offset to be used for the draw.
1001    pub(crate) fn add<'a>(
1002        &mut self,
1003        indirect_draw_validation_resources: &'a mut DrawResources,
1004        device: &Device,
1005        src_buffer: &Arc<crate::resource::Buffer>,
1006        offset: u64,
1007        family: crate::command::DrawCommandFamily,
1008        vertex_or_index_limit: u64,
1009        instance_limit: u64,
1010    ) -> Result<(usize, u64), DeviceError> {
1011        let stride = crate::command::get_dst_stride_of_indirect_args(device.backend(), family);
1012
1013        let (dst_resource_index, dst_offset) = indirect_draw_validation_resources
1014            .get_dst_subrange(stride, &mut self.current_dst_entry)?;
1015
1016        let buffer_size = src_buffer.size;
1017        let limits = device.adapter.limits();
1018        let data_size = get_src_stride_of_indirect_args(family);
1019        let (src_dynamic_offset, src_offset) =
1020            calculate_src_offsets(buffer_size, &limits, offset, data_size);
1021
1022        let src_buffer_tracker_index = src_buffer.tracker_index();
1023
1024        let entry = MetadataEntry::new(
1025            family == crate::command::DrawCommandFamily::DrawIndexed,
1026            src_offset,
1027            dst_offset,
1028            vertex_or_index_limit,
1029            instance_limit,
1030        );
1031
1032        match self.batches.entry((
1033            src_buffer_tracker_index,
1034            src_dynamic_offset,
1035            dst_resource_index,
1036        )) {
1037            hashbrown::hash_map::Entry::Occupied(mut occupied_entry) => {
1038                occupied_entry.get_mut().entries.push(entry)
1039            }
1040            hashbrown::hash_map::Entry::Vacant(vacant_entry) => {
1041                vacant_entry.insert(DrawIndirectValidationBatch {
1042                    src_buffer: src_buffer.clone(),
1043                    src_dynamic_offset,
1044                    dst_resource_index,
1045                    entries: vec![entry],
1046
1047                    // these will be initialized once we accumulated all entries for the batch
1048                    staging_buffer_index: 0,
1049                    staging_buffer_offset: 0,
1050                    metadata_resource_index: 0,
1051                    metadata_buffer_offset: 0,
1052                });
1053            }
1054        }
1055
1056        Ok((dst_resource_index, dst_offset))
1057    }
1058}
1059
1060/// Indirect draw validation doesn't support u64 offsets.
1061///
1062/// This fn should never panic due to the assert in [`Draw::new`].
1063fn u64_offset_to_u32_offset(offset: u64) -> u32 {
1064    offset.try_into().unwrap()
1065}
1066
1067#[cfg(test)]
1068mod tests {
1069    use super::*;
1070
1071    #[test]
1072    fn calculate_src_offsets_test() {
1073        const MBUS: u64 = 256 << 20; // default max_buffer_size
1074        const MBIS: u64 = 128 << 20; // default max_storage_buffer_binding_size
1075
1076        #[rustfmt::skip]
1077        let cases: &[(u64, u64, u32, u64, u64, u64, u64)] = &[
1078            // (buffer_size, max_binding_size, offset_alignment, data_size, offset, out_dynamic_offset, out_offset)
1079
1080            // data at start of buffer
1081            (MBUS, MBIS, 32,  16, 0, 0, 0),
1082            // data at end of buffer
1083            (MBUS, MBIS, 32,  16, MBUS - 16, MBIS, MBIS - 16),
1084            // data at end of buffer, where buffer_size % alignment != 0
1085            (MBUS + 4, MBUS, 32, 16, MBUS + 4 - 16, 32, MBUS - 32 + 4 - 16),
1086            // data before/straddling/after middle of the buffer with
1087            // max binding size limit being half of the buffer size
1088            // alignment = 32
1089            (512, 256, 32,  16, 240, 224, 16), // before middle
1090            (512, 256, 32,  16, 248, 224, 24), // straddling middle
1091            (512, 256, 32,  16, 256, 224, 32), // after middle
1092            // alignment = 64
1093            (512, 256, 64,  16, 240, 192, 48), // before middle
1094            (512, 256, 64,  16, 248, 192, 56), // straddling middle
1095            (512, 256, 64,  16, 256, 192, 64), // after middle
1096            // alignment = 128
1097            (512, 256, 128, 16, 240, 128, 112), // before middle
1098            (512, 256, 128, 16, 248, 128, 120), // straddling middle
1099            (512, 256, 128, 16, 256, 256, 0), // after middle
1100            // as above but with data_size = 20
1101            // alignment = 32
1102            (512, 256, 32,  20, 236, 224, 12), // before middle
1103            (512, 256, 32,  20, 244, 224, 20), // straddling middle
1104            (512, 256, 32,  20, 252, 224, 28), // after middle
1105            // alignment = 64
1106            (512, 256, 64,  20, 236, 192, 44), // before middle
1107            (512, 256, 64,  20, 244, 192, 52), // straddling middle
1108            (512, 256, 64,  20, 252, 192, 60), // after middle
1109            // alignment = 128
1110            (512, 256, 128, 20, 236, 128, 108), // before middle
1111            (512, 256, 128, 20, 244, 128, 116), // straddling middle
1112            (512, 256, 128, 20, 252, 128, 124), // after middle
1113        ];
1114
1115        for &(
1116            buffer_size,
1117            max_storage_buffer_binding_size,
1118            min_storage_buffer_offset_alignment,
1119            data_size,
1120            offset,
1121            expected_out_dynamic_offset,
1122            expected_out_offset,
1123        ) in cases
1124        {
1125            let limits = Limits {
1126                max_storage_buffer_binding_size,
1127                min_storage_buffer_offset_alignment,
1128                ..Limits::default()
1129            };
1130            let (out_dynamic_offset, out_offset) =
1131                calculate_src_offsets(buffer_size, &limits, offset, data_size);
1132            let binding_size = calculate_src_buffer_binding_size(buffer_size, &limits);
1133            // check invariants
1134            assert_eq!(out_dynamic_offset + out_offset, offset);
1135            assert_eq!(
1136                out_dynamic_offset % min_storage_buffer_offset_alignment as u64,
1137                0
1138            );
1139            assert!(out_dynamic_offset + binding_size <= buffer_size);
1140            assert!(out_offset + data_size <= binding_size);
1141            // check output matches
1142            assert_eq!(
1143                (out_dynamic_offset, out_offset),
1144                (expected_out_dynamic_offset, expected_out_offset),
1145                "buffer_size={buffer_size} \
1146                 max_binding_size={max_storage_buffer_binding_size} \
1147                 offset_alignment={min_storage_buffer_offset_alignment} \
1148                 data_size={data_size} \
1149                 offset={offset}"
1150            );
1151        }
1152    }
1153}