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::{CreateComputePipelineError, 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 =
635 unsafe { device.create_compute_pipeline(&pipeline_desc) }.map_err(|err| match err {
636 hal::PipelineError::Device(error) => {
637 CreateComputePipelineError::Device(DeviceError::from_hal(error))
638 }
639 hal::PipelineError::Linkage(_stages, msg) => CreateComputePipelineError::Internal(msg),
640 hal::PipelineError::EntryPoint(_stage) => CreateComputePipelineError::Internal(
641 crate::device::ENTRYPOINT_FAILURE_ERROR.to_string(),
642 ),
643 hal::PipelineError::PipelineConstants(_, error) => {
644 CreateComputePipelineError::PipelineConstants(error)
645 }
646 })?;
647
648 Ok(pipeline)
649}
650
651fn create_bind_group_layout(
652 device: &dyn hal::DynDevice,
653 read_only: bool,
654 has_dynamic_offset: bool,
655 min_binding_size: wgt::BufferSize,
656 label: Option<&'static str>,
657) -> Result<Box<dyn hal::DynBindGroupLayout>, CreateIndirectValidationPipelineError> {
658 let bind_group_layout_desc = hal::BindGroupLayoutDescriptor {
659 label,
660 flags: hal::BindGroupLayoutFlags::empty(),
661 entries: &[wgt::BindGroupLayoutEntry {
662 binding: 0,
663 visibility: wgt::ShaderStages::COMPUTE,
664 ty: wgt::BindingType::Buffer {
665 ty: wgt::BufferBindingType::Storage { read_only },
666 has_dynamic_offset,
667 min_binding_size: Some(min_binding_size),
668 },
669 count: None,
670 }],
671 };
672 let bind_group_layout = unsafe {
673 device
674 .create_bind_group_layout(&bind_group_layout_desc)
675 .map_err(DeviceError::from_hal)?
676 };
677
678 Ok(bind_group_layout)
679}
680
681fn calculate_src_buffer_binding_size(buffer_size: u64, limits: &Limits) -> u64 {
683 let max_storage_buffer_binding_size = limits.max_storage_buffer_binding_size;
684 let min_storage_buffer_offset_alignment = limits.min_storage_buffer_offset_alignment as u64;
685
686 if buffer_size <= max_storage_buffer_binding_size {
687 buffer_size
688 } else {
689 let buffer_rem = buffer_size % min_storage_buffer_offset_alignment;
690 let binding_rem = max_storage_buffer_binding_size % min_storage_buffer_offset_alignment;
691
692 if buffer_rem <= binding_rem {
695 max_storage_buffer_binding_size - binding_rem + buffer_rem
696 }
697 else {
699 max_storage_buffer_binding_size - binding_rem - min_storage_buffer_offset_alignment
700 + buffer_rem
701 }
702 }
703}
704
705fn calculate_src_offsets(
707 buffer_size: u64,
708 limits: &Limits,
709 offset: u64,
710 data_size: u64,
711) -> (u64, u64) {
712 const MAX_DATA_SIZE: u64 = 20; let binding_size = calculate_src_buffer_binding_size(buffer_size, limits);
714 let min_storage_buffer_offset_alignment = limits.min_storage_buffer_offset_alignment as u64;
715
716 assert!([16, MAX_DATA_SIZE].contains(&data_size));
717 assert!([32, 64, 128, 256].contains(&min_storage_buffer_offset_alignment));
718 assert!(buffer_size >= data_size);
719 assert!(offset <= buffer_size - data_size);
720 assert!(binding_size <= buffer_size);
721
722 let dynamic_offset_stride = binding_size.saturating_sub(MAX_DATA_SIZE)
740 / min_storage_buffer_offset_alignment
741 * min_storage_buffer_offset_alignment;
742 if dynamic_offset_stride == 0 {
743 return (0, offset);
744 }
745
746 let max_dynamic_offset = buffer_size - binding_size;
747 let out_dynamic_offset =
748 max_dynamic_offset.min(offset / dynamic_offset_stride * dynamic_offset_stride);
749 let out_offset = offset - out_dynamic_offset;
750
751 (out_dynamic_offset, out_offset)
752}
753
754#[derive(Debug)]
755struct BufferPoolEntry {
756 buffer: Box<dyn hal::DynBuffer>,
757 bind_group: Box<dyn hal::DynBindGroup>,
758}
759
760fn create_buffer_and_bind_group(
761 device: &dyn hal::DynDevice,
762 usage: wgt::BufferUses,
763 bind_group_layout: &dyn hal::DynBindGroupLayout,
764 buffer_label: Option<&'static str>,
765 bind_group_label: Option<&'static str>,
766) -> Result<BufferPoolEntry, hal::DeviceError> {
767 let buffer_desc = hal::BufferDescriptor {
768 label: buffer_label,
769 size: BUFFER_SIZE.get(),
770 usage,
771 memory_flags: hal::MemoryFlags::empty(),
772 };
773 let buffer = unsafe { device.create_buffer(&buffer_desc) }?;
774 let bind_group_desc = hal::BindGroupDescriptor {
775 label: bind_group_label,
776 layout: bind_group_layout,
777 entries: &[hal::BindGroupEntry {
778 binding: 0,
779 resource_index: 0,
780 count: 1,
781 }],
782 buffers: &[hal::BufferBinding::new_unchecked(
784 buffer.as_ref(),
785 0,
786 BUFFER_SIZE,
787 )],
788 samplers: &[],
789 textures: &[],
790 acceleration_structures: &[],
791 external_textures: &[],
792 };
793 let bind_group = unsafe { device.create_bind_group(&bind_group_desc) }?;
794 Ok(BufferPoolEntry { buffer, bind_group })
795}
796
797#[derive(Clone)]
798struct CurrentEntry {
799 index: usize,
800 offset: u64,
801}
802
803pub(crate) struct DrawResources {
805 device: Arc<Device>,
806 dst_entries: Vec<BufferPoolEntry>,
807 metadata_entries: Vec<BufferPoolEntry>,
808}
809
810impl Drop for DrawResources {
811 fn drop(&mut self) {
812 if let Some(ref indirect_validation) = self.device.indirect_validation {
813 let indirect_draw_validation = &indirect_validation.draw;
814 indirect_draw_validation.release_dst_entries(self.dst_entries.drain(..));
815 indirect_draw_validation.release_metadata_entries(self.metadata_entries.drain(..));
816 }
817 }
818}
819
820impl DrawResources {
821 pub(crate) fn new(device: Arc<Device>) -> Self {
822 DrawResources {
823 device,
824 dst_entries: Vec::new(),
825 metadata_entries: Vec::new(),
826 }
827 }
828
829 pub(crate) fn get_dst_buffer(&self, index: usize) -> &dyn hal::DynBuffer {
830 self.dst_entries.get(index).unwrap().buffer.as_ref()
831 }
832
833 fn get_dst_bind_group(&self, index: usize) -> &dyn hal::DynBindGroup {
834 self.dst_entries.get(index).unwrap().bind_group.as_ref()
835 }
836
837 fn get_metadata_buffer(&self, index: usize) -> &dyn hal::DynBuffer {
838 self.metadata_entries.get(index).unwrap().buffer.as_ref()
839 }
840
841 fn get_metadata_bind_group(&self, index: usize) -> &dyn hal::DynBindGroup {
842 self.metadata_entries
843 .get(index)
844 .unwrap()
845 .bind_group
846 .as_ref()
847 }
848
849 fn get_dst_subrange(
850 &mut self,
851 size: u64,
852 current_entry: &mut Option<CurrentEntry>,
853 ) -> Result<(usize, u64), DeviceError> {
854 let indirect_draw_validation = &self.device.indirect_validation.as_ref().unwrap().draw;
855 let ensure_entry = |index: usize| {
856 if self.dst_entries.len() <= index {
857 let entry = indirect_draw_validation
858 .acquire_dst_entry(self.device.raw(), self.device.instance_flags)?;
859 self.dst_entries.push(entry);
860 }
861 Ok(())
862 };
863 let entry_data = Self::get_subrange_impl(ensure_entry, current_entry, size)?;
864 Ok((entry_data.index, entry_data.offset))
865 }
866
867 fn get_metadata_subrange(
868 &mut self,
869 size: u64,
870 current_entry: &mut Option<CurrentEntry>,
871 ) -> Result<(usize, u64), DeviceError> {
872 let indirect_draw_validation = &self.device.indirect_validation.as_ref().unwrap().draw;
873 let ensure_entry = |index: usize| {
874 if self.metadata_entries.len() <= index {
875 let entry = indirect_draw_validation
876 .acquire_metadata_entry(self.device.raw(), self.device.instance_flags)?;
877 self.metadata_entries.push(entry);
878 }
879 Ok(())
880 };
881 let entry_data = Self::get_subrange_impl(ensure_entry, current_entry, size)?;
882 Ok((entry_data.index, entry_data.offset))
883 }
884
885 fn get_subrange_impl(
886 ensure_entry: impl FnOnce(usize) -> Result<(), hal::DeviceError>,
887 current_entry: &mut Option<CurrentEntry>,
888 size: u64,
889 ) -> Result<CurrentEntry, DeviceError> {
890 let index = if let Some(current_entry) = current_entry.as_mut() {
891 if current_entry.offset + size <= BUFFER_SIZE.get() {
892 let entry_data = current_entry.clone();
893 current_entry.offset += size;
894 return Ok(entry_data);
895 } else {
896 current_entry.index + 1
897 }
898 } else {
899 0
900 };
901
902 ensure_entry(index).map_err(DeviceError::from_hal)?;
903
904 let entry_data = CurrentEntry { index, offset: 0 };
905
906 *current_entry = Some(CurrentEntry {
907 index,
908 offset: size,
909 });
910
911 Ok(entry_data)
912 }
913}
914
915#[repr(C)]
917struct MetadataEntry {
918 src_offset: u32,
919 dst_offset: u32,
920 vertex_or_index_limit: u32,
921 instance_limit: u32,
922}
923
924impl MetadataEntry {
925 fn new(
926 indexed: bool,
927 src_offset: u64,
928 dst_offset: u64,
929 vertex_or_index_limit: u64,
930 instance_limit: u64,
931 ) -> Self {
932 const U32_MAX_AS_U64: u64 = u32::MAX as u64;
933
934 let src_offset = u64_offset_to_u32_offset(src_offset);
935 let src_offset = src_offset / 4; let src_offset = src_offset | ((indexed as u32) << 31);
940
941 let max_limit = U32_MAX_AS_U64 + U32_MAX_AS_U64; let vertex_or_index_limit = vertex_or_index_limit.min(max_limit);
945 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);
949 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);
953 let dst_offset = dst_offset / 4; let dst_offset =
959 dst_offset | (vertex_or_index_limit_bit_32 << 30) | (instance_limit_bit_32 << 31);
960
961 Self {
962 src_offset,
963 dst_offset,
964 vertex_or_index_limit,
965 instance_limit,
966 }
967 }
968}
969
970struct DrawIndirectValidationBatch {
971 src_buffer: Arc<crate::resource::Buffer>,
972 src_dynamic_offset: u64,
973 dst_resource_index: usize,
974 entries: Vec<MetadataEntry>,
975
976 staging_buffer_index: usize,
977 staging_buffer_offset: u64,
978 metadata_resource_index: usize,
979 metadata_buffer_offset: u64,
980}
981
982impl DrawIndirectValidationBatch {
983 fn metadata(&self) -> &[u8] {
985 unsafe {
986 core::slice::from_raw_parts(
987 self.entries.as_ptr().cast::<u8>(),
988 self.entries.len() * size_of::<MetadataEntry>(),
989 )
990 }
991 }
992}
993
994pub(crate) struct DrawBatcher {
996 batches: FastHashMap<(TrackerIndex, u64, usize), DrawIndirectValidationBatch>,
997 current_dst_entry: Option<CurrentEntry>,
998}
999
1000impl DrawBatcher {
1001 pub(crate) fn new() -> Self {
1002 Self {
1003 batches: FastHashMap::default(),
1004 current_dst_entry: None,
1005 }
1006 }
1007
1008 pub(crate) fn add<'a>(
1013 &mut self,
1014 indirect_draw_validation_resources: &'a mut DrawResources,
1015 device: &Device,
1016 src_buffer: &Arc<crate::resource::Buffer>,
1017 offset: u64,
1018 family: crate::command::DrawCommandFamily,
1019 vertex_or_index_limit: u64,
1020 instance_limit: u64,
1021 ) -> Result<(usize, u64), DeviceError> {
1022 let stride = crate::command::get_dst_stride_of_indirect_args(device.backend(), family);
1023
1024 let (dst_resource_index, dst_offset) = indirect_draw_validation_resources
1025 .get_dst_subrange(stride, &mut self.current_dst_entry)?;
1026
1027 let buffer_size = src_buffer.size;
1028 let limits = device.adapter.limits();
1029 let data_size = get_src_stride_of_indirect_args(family);
1030 let (src_dynamic_offset, src_offset) =
1031 calculate_src_offsets(buffer_size, &limits, offset, data_size);
1032
1033 let src_buffer_tracker_index = src_buffer.tracker_index();
1034
1035 let entry = MetadataEntry::new(
1036 family == crate::command::DrawCommandFamily::DrawIndexed,
1037 src_offset,
1038 dst_offset,
1039 vertex_or_index_limit,
1040 instance_limit,
1041 );
1042
1043 match self.batches.entry((
1044 src_buffer_tracker_index,
1045 src_dynamic_offset,
1046 dst_resource_index,
1047 )) {
1048 hashbrown::hash_map::Entry::Occupied(mut occupied_entry) => {
1049 occupied_entry.get_mut().entries.push(entry)
1050 }
1051 hashbrown::hash_map::Entry::Vacant(vacant_entry) => {
1052 vacant_entry.insert(DrawIndirectValidationBatch {
1053 src_buffer: src_buffer.clone(),
1054 src_dynamic_offset,
1055 dst_resource_index,
1056 entries: vec![entry],
1057
1058 staging_buffer_index: 0,
1060 staging_buffer_offset: 0,
1061 metadata_resource_index: 0,
1062 metadata_buffer_offset: 0,
1063 });
1064 }
1065 }
1066
1067 Ok((dst_resource_index, dst_offset))
1068 }
1069}
1070
1071fn u64_offset_to_u32_offset(offset: u64) -> u32 {
1075 offset.try_into().unwrap()
1076}
1077
1078#[cfg(test)]
1079mod tests {
1080 use super::*;
1081
1082 #[test]
1083 fn calculate_src_offsets_test() {
1084 const MBUS: u64 = 256 << 20; const MBIS: u64 = 128 << 20; #[rustfmt::skip]
1088 let cases: &[(u64, u64, u32, u64, u64, u64, u64)] = &[
1089 (MBUS, MBIS, 32, 16, 0, 0, 0),
1093 (MBUS, MBIS, 32, 16, MBUS - 16, MBIS, MBIS - 16),
1095 (MBUS + 4, MBUS, 32, 16, MBUS + 4 - 16, 32, MBUS - 32 + 4 - 16),
1097 (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), ];
1125
1126 for &(
1127 buffer_size,
1128 max_storage_buffer_binding_size,
1129 min_storage_buffer_offset_alignment,
1130 data_size,
1131 offset,
1132 expected_out_dynamic_offset,
1133 expected_out_offset,
1134 ) in cases
1135 {
1136 let limits = Limits {
1137 max_storage_buffer_binding_size,
1138 min_storage_buffer_offset_alignment,
1139 ..Limits::default()
1140 };
1141 let (out_dynamic_offset, out_offset) =
1142 calculate_src_offsets(buffer_size, &limits, offset, data_size);
1143 let binding_size = calculate_src_buffer_binding_size(buffer_size, &limits);
1144 assert_eq!(out_dynamic_offset + out_offset, offset);
1146 assert_eq!(
1147 out_dynamic_offset % min_storage_buffer_offset_alignment as u64,
1148 0
1149 );
1150 assert!(out_dynamic_offset + binding_size <= buffer_size);
1151 assert!(out_offset + data_size <= binding_size);
1152 assert_eq!(
1154 (out_dynamic_offset, out_offset),
1155 (expected_out_dynamic_offset, expected_out_offset),
1156 "buffer_size={buffer_size} \
1157 max_binding_size={max_storage_buffer_binding_size} \
1158 offset_alignment={min_storage_buffer_offset_alignment} \
1159 data_size={data_size} \
1160 offset={offset}"
1161 );
1162 }
1163 }
1164}