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

View File

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

View File

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

View File

@ -372,6 +372,8 @@ impl super::Adapter {
} else {
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()
| wgt::DownlevelFlags::NON_POWER_OF_TWO_MIPMAPPED_TEXTURES
@ -383,10 +385,7 @@ impl super::Adapter {
wgt::DownlevelFlags::FRAGMENT_WRITABLE_STORAGE,
max_storage_block_size != 0,
);
downlevel_flags.set(
wgt::DownlevelFlags::INDIRECT_EXECUTION,
supported((3, 1), (4, 3)) || extensions.contains("GL_ARB_multi_draw_indirect"),
);
downlevel_flags.set(wgt::DownlevelFlags::INDIRECT_EXECUTION, indirect_execution);
downlevel_flags.set(wgt::DownlevelFlags::BASE_VERTEX, supported((3, 2), (3, 2)));
downlevel_flags.set(
wgt::DownlevelFlags::INDEPENDENT_BLEND,
@ -471,6 +470,8 @@ impl super::Adapter {
wgt::Features::SHADER_EARLY_DEPTH_TEST,
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") {
features.set(wgt::Features::TIMESTAMP_QUERY, 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.
///
/// Supported platforms:
/// Natively Supported Platforms:
/// - DX12
/// - 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_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 {
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 {
match wasm_bindgen::JsValue::from(web_feat).as_string() {
Some(value) if supported_features.has(&value) => features |= wgpu_feat,