From fabcba8f9af42c5e6b51da771fd2df0c4a01910f Mon Sep 17 00:00:00 2001 From: Connor Fitzgerald Date: Tue, 7 Jan 2025 07:52:42 -0500 Subject: [PATCH] Refine Multi-Draw-Indirect (#6870) --- wgpu-core/src/command/bundle.rs | 28 ++++++++--------- wgpu-core/src/command/render.rs | 41 +++++++++++-------------- wgpu-core/src/command/render_command.rs | 18 +++++------ wgpu-hal/src/gles/adapter.rs | 9 +++--- wgpu-types/src/lib.rs | 11 +++++-- wgpu/src/backend/webgpu.rs | 3 +- 6 files changed, 55 insertions(+), 55 deletions(-) diff --git a/wgpu-core/src/command/bundle.rs b/wgpu-core/src/command/bundle.rs index ac7a5280bf..c7f433c3a0 100644 --- a/wgpu-core/src/command/bundle.rs +++ b/wgpu-core/src/command/bundle.rs @@ -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, }); } diff --git a/wgpu-core/src/command/render.rs b/wgpu-core/src/command/render.rs index 31b4dd8218..abbbcfb46a 100644 --- a/wgpu-core/src/command/render.rs +++ b/wgpu-core/src/command/render.rs @@ -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, + 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, indirect_buffer: Arc, offset: u64, - count: Option, + count: u32, indexed: bool, ) -> Result<(), RenderPassErrorInner> { api_log!( @@ -2482,7 +2481,7 @@ fn multi_draw_indirect( true => size_of::(), }; - 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, }); diff --git a/wgpu-core/src/command/render_command.rs b/wgpu-core/src/command/render_command.rs index d4e2689d27..549d140bb5 100644 --- a/wgpu-core/src/command/render_command.rs +++ b/wgpu-core/src/command/render_command.rs @@ -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, + 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, offset: BufferAddress, - /// Count of `None` represents a non-multi call. - count: Option, + count: u32, indexed: bool, }, MultiDrawIndirectCount { diff --git a/wgpu-hal/src/gles/adapter.rs b/wgpu-hal/src/gles/adapter.rs index 15d171f55a..d901324205 100644 --- a/wgpu-hal/src/gles/adapter.rs +++ b/wgpu-hal/src/gles/adapter.rs @@ -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); diff --git a/wgpu-types/src/lib.rs b/wgpu-types/src/lib.rs index 9d3605f18e..7753de289a 100644 --- a/wgpu-types/src/lib.rs +++ b/wgpu-types/src/lib.rs @@ -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 diff --git a/wgpu/src/backend/webgpu.rs b/wgpu/src/backend/webgpu.rs index 8511aff5ed..73bb72bebf 100644 --- a/wgpu/src/backend/webgpu.rs +++ b/wgpu/src/backend/webgpu.rs @@ -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,