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
21const BUFFER_SIZE: wgt::BufferSize = wgt::BufferSize::new(1_048_560).unwrap();
36
37#[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 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 #[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 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 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 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; 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 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
670fn 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 if buffer_rem <= binding_rem {
684 max_storage_buffer_binding_size - binding_rem + buffer_rem
685 }
686 else {
688 max_storage_buffer_binding_size - binding_rem - min_storage_buffer_offset_alignment
689 + buffer_rem
690 }
691 }
692}
693
694fn 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; 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 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 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
792pub(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#[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; let src_offset = src_offset | ((indexed as u32) << 31);
929
930 let max_limit = U32_MAX_AS_U64 + U32_MAX_AS_U64; 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; let vertex_or_index_limit = vertex_or_index_limit as u32; let instance_limit = instance_limit.min(max_limit);
938 let instance_limit_bit_32 = (instance_limit >> 32) as u32; let instance_limit = instance_limit as u32; let dst_offset = u64_offset_to_u32_offset(dst_offset);
942 let dst_offset = dst_offset / 4; 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 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
983pub(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 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 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
1060fn 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; const MBIS: u64 = 128 << 20; #[rustfmt::skip]
1077 let cases: &[(u64, u64, u32, u64, u64, u64, u64)] = &[
1078 (MBUS, MBIS, 32, 16, 0, 0, 0),
1082 (MBUS, MBIS, 32, 16, MBUS - 16, MBIS, MBIS - 16),
1084 (MBUS + 4, MBUS, 32, 16, MBUS + 4 - 16, 32, MBUS - 32 + 4 - 16),
1086 (512, 256, 32, 16, 240, 224, 16), (512, 256, 32, 16, 248, 224, 24), (512, 256, 32, 16, 256, 224, 32), (512, 256, 64, 16, 240, 192, 48), (512, 256, 64, 16, 248, 192, 56), (512, 256, 64, 16, 256, 192, 64), (512, 256, 128, 16, 240, 128, 112), (512, 256, 128, 16, 248, 128, 120), (512, 256, 128, 16, 256, 256, 0), (512, 256, 32, 20, 236, 224, 12), (512, 256, 32, 20, 244, 224, 20), (512, 256, 32, 20, 252, 224, 28), (512, 256, 64, 20, 236, 192, 44), (512, 256, 64, 20, 244, 192, 52), (512, 256, 64, 20, 252, 192, 60), (512, 256, 128, 20, 236, 128, 108), (512, 256, 128, 20, 244, 128, 116), (512, 256, 128, 20, 252, 128, 124), ];
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 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 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}