Refine Multi-Draw-Indirect (#6870)

This commit is contained in:
Connor Fitzgerald 2025-01-07 07:52:42 -05:00 committed by GitHub
parent 78e35c4a7e
commit fabcba8f9a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 55 additions and 55 deletions

View File

@ -484,10 +484,10 @@ impl RenderBundleEncoder {
) )
.map_pass_err(scope)?; .map_pass_err(scope)?;
} }
RenderCommand::MultiDrawIndirect { RenderCommand::DrawIndirect {
buffer_id, buffer_id,
offset, offset,
count: None, count: 1,
indexed, indexed,
} => { } => {
let scope = PassErrorScope::Draw { let scope = PassErrorScope::Draw {
@ -504,7 +504,7 @@ impl RenderBundleEncoder {
) )
.map_pass_err(scope)?; .map_pass_err(scope)?;
} }
RenderCommand::MultiDrawIndirect { .. } RenderCommand::DrawIndirect { .. }
| RenderCommand::MultiDrawIndirectCount { .. } => unimplemented!(), | RenderCommand::MultiDrawIndirectCount { .. } => unimplemented!(),
RenderCommand::PushDebugGroup { color: _, len: _ } => unimplemented!(), RenderCommand::PushDebugGroup { color: _, len: _ } => unimplemented!(),
RenderCommand::InsertDebugMarker { color: _, len: _ } => unimplemented!(), RenderCommand::InsertDebugMarker { color: _, len: _ } => unimplemented!(),
@ -887,10 +887,10 @@ fn multi_draw_indirect(
state.flush_vertices(); state.flush_vertices();
state.flush_binds(used_bind_groups, dynamic_offsets); state.flush_binds(used_bind_groups, dynamic_offsets);
state.commands.push(ArcRenderCommand::MultiDrawIndirect { state.commands.push(ArcRenderCommand::DrawIndirect {
buffer, buffer,
offset, offset,
count: None, count: 1,
indexed, indexed,
}); });
Ok(()) Ok(())
@ -1101,25 +1101,25 @@ impl RenderBundle {
) )
}; };
} }
Cmd::MultiDrawIndirect { Cmd::DrawIndirect {
buffer, buffer,
offset, offset,
count: None, count: 1,
indexed: false, indexed: false,
} => { } => {
let buffer = buffer.try_raw(snatch_guard)?; let buffer = buffer.try_raw(snatch_guard)?;
unsafe { raw.draw_indirect(buffer, *offset, 1) }; unsafe { raw.draw_indirect(buffer, *offset, 1) };
} }
Cmd::MultiDrawIndirect { Cmd::DrawIndirect {
buffer, buffer,
offset, offset,
count: None, count: 1,
indexed: true, indexed: true,
} => { } => {
let buffer = buffer.try_raw(snatch_guard)?; let buffer = buffer.try_raw(snatch_guard)?;
unsafe { raw.draw_indexed_indirect(buffer, *offset, 1) }; unsafe { raw.draw_indexed_indirect(buffer, *offset, 1) };
} }
Cmd::MultiDrawIndirect { .. } | Cmd::MultiDrawIndirectCount { .. } => { Cmd::DrawIndirect { .. } | Cmd::MultiDrawIndirectCount { .. } => {
return Err(ExecutionError::Unimplemented("multi-draw-indirect")) return Err(ExecutionError::Unimplemented("multi-draw-indirect"))
} }
Cmd::PushDebugGroup { .. } | Cmd::InsertDebugMarker { .. } | Cmd::PopDebugGroup => { Cmd::PushDebugGroup { .. } | Cmd::InsertDebugMarker { .. } | Cmd::PopDebugGroup => {
@ -1727,10 +1727,10 @@ pub mod bundle_ffi {
buffer_id: id::BufferId, buffer_id: id::BufferId,
offset: BufferAddress, offset: BufferAddress,
) { ) {
bundle.base.commands.push(RenderCommand::MultiDrawIndirect { bundle.base.commands.push(RenderCommand::DrawIndirect {
buffer_id, buffer_id,
offset, offset,
count: None, count: 1,
indexed: false, indexed: false,
}); });
} }
@ -1740,10 +1740,10 @@ pub mod bundle_ffi {
buffer_id: id::BufferId, buffer_id: id::BufferId,
offset: BufferAddress, offset: BufferAddress,
) { ) {
bundle.base.commands.push(RenderCommand::MultiDrawIndirect { bundle.base.commands.push(RenderCommand::DrawIndirect {
buffer_id, buffer_id,
offset, offset,
count: None, count: 1,
indexed: true, indexed: true,
}); });
} }

View File

@ -676,10 +676,9 @@ pub enum RenderPassErrorInner {
MissingDownlevelFlags(#[from] MissingDownlevelFlags), MissingDownlevelFlags(#[from] MissingDownlevelFlags),
#[error("Indirect buffer offset {0:?} is not a multiple of 4")] #[error("Indirect buffer offset {0:?} is not a multiple of 4")]
UnalignedIndirectBufferOffset(BufferAddress), UnalignedIndirectBufferOffset(BufferAddress),
#[error("Indirect draw uses bytes {offset}..{end_offset} {} which overruns indirect buffer of size {buffer_size}", #[error("Indirect draw uses bytes {offset}..{end_offset} using count {count} which overruns indirect buffer of size {buffer_size}")]
count.map_or_else(String::new, |v| format!("(using count {v})")))]
IndirectBufferOverrun { IndirectBufferOverrun {
count: Option<NonZeroU32>, count: u32,
offset: u64, offset: u64,
end_offset: u64, end_offset: u64,
buffer_size: u64, buffer_size: u64,
@ -1787,14 +1786,14 @@ impl Global {
) )
.map_pass_err(scope)?; .map_pass_err(scope)?;
} }
ArcRenderCommand::MultiDrawIndirect { ArcRenderCommand::DrawIndirect {
buffer, buffer,
offset, offset,
count, count,
indexed, indexed,
} => { } => {
let scope = PassErrorScope::Draw { let scope = PassErrorScope::Draw {
kind: if count.is_some() { kind: if count != 1 {
DrawKind::MultiDrawIndirect DrawKind::MultiDrawIndirect
} else { } else {
DrawKind::DrawIndirect DrawKind::DrawIndirect
@ -2467,7 +2466,7 @@ fn multi_draw_indirect(
cmd_buf: &Arc<CommandBuffer>, cmd_buf: &Arc<CommandBuffer>,
indirect_buffer: Arc<crate::resource::Buffer>, indirect_buffer: Arc<crate::resource::Buffer>,
offset: u64, offset: u64,
count: Option<NonZeroU32>, count: u32,
indexed: bool, indexed: bool,
) -> Result<(), RenderPassErrorInner> { ) -> Result<(), RenderPassErrorInner> {
api_log!( api_log!(
@ -2482,7 +2481,7 @@ fn multi_draw_indirect(
true => size_of::<wgt::DrawIndexedIndirectArgs>(), true => size_of::<wgt::DrawIndexedIndirectArgs>(),
}; };
if count.is_some() { if count != 1 {
state state
.device .device
.require_features(wgt::Features::MULTI_DRAW_INDIRECT)?; .require_features(wgt::Features::MULTI_DRAW_INDIRECT)?;
@ -2502,13 +2501,11 @@ fn multi_draw_indirect(
indirect_buffer.check_usage(BufferUsages::INDIRECT)?; indirect_buffer.check_usage(BufferUsages::INDIRECT)?;
let indirect_raw = indirect_buffer.try_raw(state.snatch_guard)?; let indirect_raw = indirect_buffer.try_raw(state.snatch_guard)?;
let actual_count = count.map_or(1, |c| c.get());
if offset % 4 != 0 { if offset % 4 != 0 {
return Err(RenderPassErrorInner::UnalignedIndirectBufferOffset(offset)); return Err(RenderPassErrorInner::UnalignedIndirectBufferOffset(offset));
} }
let end_offset = offset + stride as u64 * actual_count as u64; let end_offset = offset + stride as u64 * count as u64;
if end_offset > indirect_buffer.size { if end_offset > indirect_buffer.size {
return Err(RenderPassErrorInner::IndirectBufferOverrun { return Err(RenderPassErrorInner::IndirectBufferOverrun {
count, count,
@ -2528,14 +2525,12 @@ fn multi_draw_indirect(
match indexed { match indexed {
false => unsafe { false => unsafe {
state state.raw_encoder.draw_indirect(indirect_raw, offset, count);
.raw_encoder
.draw_indirect(indirect_raw, offset, actual_count);
}, },
true => unsafe { true => unsafe {
state state
.raw_encoder .raw_encoder
.draw_indexed_indirect(indirect_raw, offset, actual_count); .draw_indexed_indirect(indirect_raw, offset, count);
}, },
} }
Ok(()) Ok(())
@ -2599,7 +2594,7 @@ fn multi_draw_indirect_count(
let end_offset = offset + stride * max_count as u64; let end_offset = offset + stride * max_count as u64;
if end_offset > indirect_buffer.size { if end_offset > indirect_buffer.size {
return Err(RenderPassErrorInner::IndirectBufferOverrun { return Err(RenderPassErrorInner::IndirectBufferOverrun {
count: None, count: 1,
offset, offset,
end_offset, end_offset,
buffer_size: indirect_buffer.size, buffer_size: indirect_buffer.size,
@ -3103,10 +3098,10 @@ impl Global {
}; };
let base = pass.base_mut(scope)?; let base = pass.base_mut(scope)?;
base.commands.push(ArcRenderCommand::MultiDrawIndirect { base.commands.push(ArcRenderCommand::DrawIndirect {
buffer: self.resolve_render_pass_buffer_id(scope, buffer_id)?, buffer: self.resolve_render_pass_buffer_id(scope, buffer_id)?,
offset, offset,
count: None, count: 1,
indexed: false, indexed: false,
}); });
@ -3125,10 +3120,10 @@ impl Global {
}; };
let base = pass.base_mut(scope)?; let base = pass.base_mut(scope)?;
base.commands.push(ArcRenderCommand::MultiDrawIndirect { base.commands.push(ArcRenderCommand::DrawIndirect {
buffer: self.resolve_render_pass_buffer_id(scope, buffer_id)?, buffer: self.resolve_render_pass_buffer_id(scope, buffer_id)?,
offset, offset,
count: None, count: 1,
indexed: true, indexed: true,
}); });
@ -3148,10 +3143,10 @@ impl Global {
}; };
let base = pass.base_mut(scope)?; let base = pass.base_mut(scope)?;
base.commands.push(ArcRenderCommand::MultiDrawIndirect { base.commands.push(ArcRenderCommand::DrawIndirect {
buffer: self.resolve_render_pass_buffer_id(scope, buffer_id)?, buffer: self.resolve_render_pass_buffer_id(scope, buffer_id)?,
offset, offset,
count: NonZeroU32::new(count), count,
indexed: false, indexed: false,
}); });
@ -3171,10 +3166,10 @@ impl Global {
}; };
let base = pass.base_mut(scope)?; let base = pass.base_mut(scope)?;
base.commands.push(ArcRenderCommand::MultiDrawIndirect { base.commands.push(ArcRenderCommand::DrawIndirect {
buffer: self.resolve_render_pass_buffer_id(scope, buffer_id)?, buffer: self.resolve_render_pass_buffer_id(scope, buffer_id)?,
offset, offset,
count: NonZeroU32::new(count), count,
indexed: true, indexed: true,
}); });

View File

@ -6,7 +6,7 @@ use crate::{
}; };
use wgt::{BufferAddress, BufferSize, Color}; use wgt::{BufferAddress, BufferSize, Color};
use std::{num::NonZeroU32, sync::Arc}; use std::sync::Arc;
use super::{Rect, RenderBundle}; use super::{Rect, RenderBundle};
@ -82,11 +82,10 @@ pub enum RenderCommand {
base_vertex: i32, base_vertex: i32,
first_instance: u32, first_instance: u32,
}, },
MultiDrawIndirect { DrawIndirect {
buffer_id: id::BufferId, buffer_id: id::BufferId,
offset: BufferAddress, offset: BufferAddress,
/// Count of `None` represents a non-multi call. count: u32,
count: Option<NonZeroU32>,
indexed: bool, indexed: bool,
}, },
MultiDrawIndirectCount { MultiDrawIndirectCount {
@ -311,16 +310,16 @@ impl RenderCommand {
first_instance, first_instance,
}, },
RenderCommand::MultiDrawIndirect { RenderCommand::DrawIndirect {
buffer_id, buffer_id,
offset, offset,
count, count,
indexed, indexed,
} => ArcRenderCommand::MultiDrawIndirect { } => ArcRenderCommand::DrawIndirect {
buffer: buffers_guard.get(buffer_id).get().map_err(|e| { buffer: buffers_guard.get(buffer_id).get().map_err(|e| {
RenderPassError { RenderPassError {
scope: PassErrorScope::Draw { scope: PassErrorScope::Draw {
kind: if count.is_some() { kind: if count != 1 {
DrawKind::MultiDrawIndirect DrawKind::MultiDrawIndirect
} else { } else {
DrawKind::DrawIndirect DrawKind::DrawIndirect
@ -459,11 +458,10 @@ pub enum ArcRenderCommand {
base_vertex: i32, base_vertex: i32,
first_instance: u32, first_instance: u32,
}, },
MultiDrawIndirect { DrawIndirect {
buffer: Arc<Buffer>, buffer: Arc<Buffer>,
offset: BufferAddress, offset: BufferAddress,
/// Count of `None` represents a non-multi call. count: u32,
count: Option<NonZeroU32>,
indexed: bool, indexed: bool,
}, },
MultiDrawIndirectCount { MultiDrawIndirectCount {

View File

@ -372,6 +372,8 @@ impl super::Adapter {
} else { } else {
vertex_shader_storage_textures.min(fragment_shader_storage_textures) vertex_shader_storage_textures.min(fragment_shader_storage_textures)
}; };
let indirect_execution =
supported((3, 1), (4, 3)) || extensions.contains("GL_ARB_multi_draw_indirect");
let mut downlevel_flags = wgt::DownlevelFlags::empty() let mut downlevel_flags = wgt::DownlevelFlags::empty()
| wgt::DownlevelFlags::NON_POWER_OF_TWO_MIPMAPPED_TEXTURES | wgt::DownlevelFlags::NON_POWER_OF_TWO_MIPMAPPED_TEXTURES
@ -383,10 +385,7 @@ impl super::Adapter {
wgt::DownlevelFlags::FRAGMENT_WRITABLE_STORAGE, wgt::DownlevelFlags::FRAGMENT_WRITABLE_STORAGE,
max_storage_block_size != 0, max_storage_block_size != 0,
); );
downlevel_flags.set( downlevel_flags.set(wgt::DownlevelFlags::INDIRECT_EXECUTION, indirect_execution);
wgt::DownlevelFlags::INDIRECT_EXECUTION,
supported((3, 1), (4, 3)) || extensions.contains("GL_ARB_multi_draw_indirect"),
);
downlevel_flags.set(wgt::DownlevelFlags::BASE_VERTEX, supported((3, 2), (3, 2))); downlevel_flags.set(wgt::DownlevelFlags::BASE_VERTEX, supported((3, 2), (3, 2)));
downlevel_flags.set( downlevel_flags.set(
wgt::DownlevelFlags::INDEPENDENT_BLEND, wgt::DownlevelFlags::INDEPENDENT_BLEND,
@ -471,6 +470,8 @@ impl super::Adapter {
wgt::Features::SHADER_EARLY_DEPTH_TEST, wgt::Features::SHADER_EARLY_DEPTH_TEST,
supported((3, 1), (4, 2)) || extensions.contains("GL_ARB_shader_image_load_store"), supported((3, 1), (4, 2)) || extensions.contains("GL_ARB_shader_image_load_store"),
); );
// We emulate MDI with a loop of draw calls.
features.set(wgt::Features::MULTI_DRAW_INDIRECT, indirect_execution);
if extensions.contains("GL_ARB_timer_query") { if extensions.contains("GL_ARB_timer_query") {
features.set(wgt::Features::TIMESTAMP_QUERY, true); features.set(wgt::Features::TIMESTAMP_QUERY, true);
features.set(wgt::Features::TIMESTAMP_QUERY_INSIDE_ENCODERS, true); features.set(wgt::Features::TIMESTAMP_QUERY_INSIDE_ENCODERS, true);

View File

@ -638,12 +638,17 @@ bitflags::bitflags! {
/// ///
/// Allows multiple indirect calls to be dispatched from a single buffer. /// Allows multiple indirect calls to be dispatched from a single buffer.
/// ///
/// Supported platforms: /// Natively Supported Platforms:
/// - DX12 /// - DX12
/// - Vulkan /// - Vulkan
/// - Metal on Apple3+ or Mac1+ (Emulated on top of `draw_indirect` and `draw_indexed_indirect`)
/// ///
/// This is a native only feature. /// Emulated Platforms:
/// - Metal
/// - OpenGL
/// - WebGPU
///
/// Emulation is preformed by looping over the individual indirect draw calls in the backend. This is still significantly
/// faster than enulating it yourself, as wgpu only does draw call validation once.
/// ///
/// [`RenderPass::multi_draw_indirect`]: ../wgpu/struct.RenderPass.html#method.multi_draw_indirect /// [`RenderPass::multi_draw_indirect`]: ../wgpu/struct.RenderPass.html#method.multi_draw_indirect
/// [`RenderPass::multi_draw_indexed_indirect`]: ../wgpu/struct.RenderPass.html#method.multi_draw_indexed_indirect /// [`RenderPass::multi_draw_indexed_indirect`]: ../wgpu/struct.RenderPass.html#method.multi_draw_indexed_indirect

View File

@ -783,7 +783,8 @@ const FEATURES_MAPPING: [(wgt::Features, webgpu_sys::GpuFeatureName); 12] = [
]; ];
fn map_wgt_features(supported_features: webgpu_sys::GpuSupportedFeatures) -> wgt::Features { fn map_wgt_features(supported_features: webgpu_sys::GpuSupportedFeatures) -> wgt::Features {
let mut features = wgt::Features::empty(); // We emulate MDI.
let mut features = wgt::Features::MULTI_DRAW_INDIRECT;
for (wgpu_feat, web_feat) in FEATURES_MAPPING { for (wgpu_feat, web_feat) in FEATURES_MAPPING {
match wasm_bindgen::JsValue::from(web_feat).as_string() { match wasm_bindgen::JsValue::from(web_feat).as_string() {
Some(value) if supported_features.has(&value) => features |= wgpu_feat, Some(value) if supported_features.has(&value) => features |= wgpu_feat,