Skip to content

Commit

Permalink
Refine Multi-Draw-Indirect (gfx-rs#6870)
Browse files Browse the repository at this point in the history
  • Loading branch information
cwfitzgerald authored Jan 7, 2025
1 parent 78e35c4 commit fabcba8
Show file tree
Hide file tree
Showing 6 changed files with 55 additions and 55 deletions.
28 changes: 14 additions & 14 deletions wgpu-core/src/command/bundle.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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!(),
Expand Down Expand Up @@ -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(())
Expand Down Expand Up @@ -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 => {
Expand Down Expand Up @@ -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,
});
}
Expand All @@ -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,
});
}
Expand Down
41 changes: 18 additions & 23 deletions wgpu-core/src/command/render.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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!(
Expand All @@ -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)?;
Expand All @@ -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,
Expand All @@ -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(())
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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,
});

Expand All @@ -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,
});

Expand All @@ -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,
});

Expand All @@ -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,
});

Expand Down
18 changes: 8 additions & 10 deletions wgpu-core/src/command/render_command.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};

Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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 {
Expand Down
9 changes: 5 additions & 4 deletions wgpu-hal/src/gles/adapter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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,
Expand Down Expand Up @@ -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);
Expand Down
11 changes: 8 additions & 3 deletions wgpu-types/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
3 changes: 2 additions & 1 deletion wgpu/src/backend/webgpu.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down

0 comments on commit fabcba8

Please sign in to comment.