diff --git a/CHANGELOG.md b/CHANGELOG.md index 0dd1696c0a6..bf92318ee98 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -40,6 +40,33 @@ Bottom level categories: ## Unreleased +### Major Changes + +#### Multiview on all major platforms and support for multiview bitmasks + +Multiview has been reworked, adding support for DX12 and Metal, and adding testing and validation to wgpu itself. +This change also introduces a view bitmask, a new field in `RenderPassDescriptor` that allows a render pass to render multiple to non-adjacent layers. Note that this also influences apps that don't use multiview, as they have to set this field to `None`. +```diff +- wgpu::RenderPassDescriptor { +- label: None, +- color_attachments: &color_attachments, +- depth_stencil_attachment: None, +- timestamp_writes: None, +- occlusion_query_set: None, +- } ++ wgpu::RenderPassDescriptor { ++ label: None, ++ color_attachments: &color_attachments, ++ depth_stencil_attachment: None, ++ timestamp_writes: None, ++ occlusion_query_set: None, ++ multiview_mask: NonZero::new(3), ++ } +``` +One other breaking change worth noting is that `@builtin(view_index)` now requires a type of `u32`, where previously it required `i32`. + +By @SupaMaggie70Incorporated in [#8206](https://github.com/gfx-rs/wgpu/pull/8206). + ## v27.0.0 (2025-10-01) ### Major Changes @@ -161,7 +188,6 @@ by if the `Feature::MULTI_DRAW_INDIRECT_COUNT` feature is available on the devic By @cwfitzgerald in [#8162](https://github.com/gfx-rs/wgpu/pull/8162). - #### `wgpu::PollType::Wait` has now an optional timeout We removed `wgpu::PollType::WaitForSubmissionIndex` and added fields to `wgpu::PollType::Wait` in order to express timeouts. diff --git a/benches/benches/wgpu-benchmark/renderpass.rs b/benches/benches/wgpu-benchmark/renderpass.rs index 07700d68fce..a733804a44e 100644 --- a/benches/benches/wgpu-benchmark/renderpass.rs +++ b/benches/benches/wgpu-benchmark/renderpass.rs @@ -370,6 +370,7 @@ impl RenderpassState { occlusion_query_set: None, timestamp_writes: None, depth_stencil_attachment: None, + multiview_mask: None, }); let start_idx = pass_number * draws_per_pass; @@ -417,6 +418,7 @@ impl RenderpassState { occlusion_query_set: None, timestamp_writes: None, depth_stencil_attachment: None, + multiview_mask: None, }); render_pass.set_pipeline(self.bindless_pipeline.as_ref().unwrap()); diff --git a/deno_webgpu/command_encoder.rs b/deno_webgpu/command_encoder.rs index 3c9a37c9907..f0cdb99b523 100644 --- a/deno_webgpu/command_encoder.rs +++ b/deno_webgpu/command_encoder.rs @@ -2,6 +2,7 @@ use std::borrow::Cow; use std::cell::RefCell; +use std::num::NonZero; use deno_core::cppgc::Ptr; use deno_core::op2; @@ -142,6 +143,7 @@ impl GPUCommandEncoder { occlusion_query_set: descriptor .occlusion_query_set .map(|query_set| query_set.id), + multiview_mask: NonZero::new(descriptor.multiview_mask), }; let (render_pass, err) = self diff --git a/deno_webgpu/render_pass.rs b/deno_webgpu/render_pass.rs index 61236edcd29..31e75e58987 100644 --- a/deno_webgpu/render_pass.rs +++ b/deno_webgpu/render_pass.rs @@ -460,6 +460,9 @@ pub(crate) struct GPURenderPassDescriptor { /*#[webidl(default = 50000000)] #[options(enforce_range = true)] pub max_draw_count: u64,*/ + #[webidl(default = 0)] + #[options(enforce_range = true)] + pub multiview_mask: u32, } #[derive(WebIDL)] diff --git a/examples/features/src/boids/mod.rs b/examples/features/src/boids/mod.rs index d272442275a..be79435b70d 100644 --- a/examples/features/src/boids/mod.rs +++ b/examples/features/src/boids/mod.rs @@ -276,6 +276,7 @@ impl crate::framework::Example for Example { depth_stencil_attachment: None, timestamp_writes: None, occlusion_query_set: None, + multiview_mask: None, }; // get command encoder diff --git a/examples/features/src/bunnymark/mod.rs b/examples/features/src/bunnymark/mod.rs index 233fe8ba539..512137fd30f 100644 --- a/examples/features/src/bunnymark/mod.rs +++ b/examples/features/src/bunnymark/mod.rs @@ -122,6 +122,7 @@ impl Example { depth_stencil_attachment: None, timestamp_writes: None, occlusion_query_set: None, + multiview_mask: None, }); rpass.set_pipeline(&self.pipeline); rpass.set_bind_group(0, &self.global_group, &[]); diff --git a/examples/features/src/conservative_raster/mod.rs b/examples/features/src/conservative_raster/mod.rs index d2f5c74fdec..2f1f417b161 100644 --- a/examples/features/src/conservative_raster/mod.rs +++ b/examples/features/src/conservative_raster/mod.rs @@ -273,6 +273,7 @@ impl crate::framework::Example for Example { depth_stencil_attachment: None, timestamp_writes: None, occlusion_query_set: None, + multiview_mask: None, }); rpass.set_pipeline(&self.pipeline_triangle_conservative); @@ -295,6 +296,7 @@ impl crate::framework::Example for Example { depth_stencil_attachment: None, timestamp_writes: None, occlusion_query_set: None, + multiview_mask: None, }); rpass.set_pipeline(&self.pipeline_upscale); diff --git a/examples/features/src/cube/mod.rs b/examples/features/src/cube/mod.rs index 686dca3782e..ccdd31af08d 100644 --- a/examples/features/src/cube/mod.rs +++ b/examples/features/src/cube/mod.rs @@ -356,6 +356,7 @@ impl crate::framework::Example for Example { depth_stencil_attachment: None, timestamp_writes: None, occlusion_query_set: None, + multiview_mask: None, }); rpass.push_debug_group("Prepare data for draw."); rpass.set_pipeline(&self.pipeline); diff --git a/examples/features/src/hello_triangle/mod.rs b/examples/features/src/hello_triangle/mod.rs index eca99f6bb37..cf5d40e7659 100644 --- a/examples/features/src/hello_triangle/mod.rs +++ b/examples/features/src/hello_triangle/mod.rs @@ -129,6 +129,7 @@ async fn run(event_loop: EventLoop<()>, window: Window) { depth_stencil_attachment: None, timestamp_writes: None, occlusion_query_set: None, + multiview_mask: None, }); rpass.set_pipeline(&render_pipeline); rpass.draw(0..3, 0..1); diff --git a/examples/features/src/hello_windows/mod.rs b/examples/features/src/hello_windows/mod.rs index fbd69f0bff2..c1234e54998 100644 --- a/examples/features/src/hello_windows/mod.rs +++ b/examples/features/src/hello_windows/mod.rs @@ -133,6 +133,7 @@ async fn run(event_loop: EventLoop<()>, viewports: Vec<(Arc, wgpu::Color depth_stencil_attachment: None, timestamp_writes: None, occlusion_query_set: None, + multiview_mask: None, }); } diff --git a/examples/features/src/mesh_shader/mod.rs b/examples/features/src/mesh_shader/mod.rs index 50ddff39a07..4b9f735c24e 100644 --- a/examples/features/src/mesh_shader/mod.rs +++ b/examples/features/src/mesh_shader/mod.rs @@ -144,6 +144,7 @@ impl crate::framework::Example for Example { depth_stencil_attachment: None, timestamp_writes: None, occlusion_query_set: None, + multiview_mask: None, }); rpass.push_debug_group("Prepare data for draw."); rpass.set_pipeline(&self.pipeline); diff --git a/examples/features/src/mipmap/mod.rs b/examples/features/src/mipmap/mod.rs index 3875bc3a76a..61d3bbdc107 100644 --- a/examples/features/src/mipmap/mod.rs +++ b/examples/features/src/mipmap/mod.rs @@ -170,6 +170,7 @@ impl Example { depth_stencil_attachment: None, timestamp_writes: None, occlusion_query_set: None, + multiview_mask: None, }); if let Some(ref query_sets) = query_sets { rpass.write_timestamp(&query_sets.timestamp, timestamp_query_index_base); @@ -492,6 +493,7 @@ impl crate::framework::Example for Example { depth_stencil_attachment: None, timestamp_writes: None, occlusion_query_set: None, + multiview_mask: None, }); rpass.set_pipeline(&self.draw_pipeline); rpass.set_bind_group(0, &self.bind_group, &[]); diff --git a/examples/features/src/msaa_line/mod.rs b/examples/features/src/msaa_line/mod.rs index 71c6b277c4b..7dd4eb2ec06 100644 --- a/examples/features/src/msaa_line/mod.rs +++ b/examples/features/src/msaa_line/mod.rs @@ -307,6 +307,7 @@ impl crate::framework::Example for Example { depth_stencil_attachment: None, timestamp_writes: None, occlusion_query_set: None, + multiview_mask: None, }) .execute_bundles(iter::once(&self.bundle)); } diff --git a/examples/features/src/multiple_render_targets/mod.rs b/examples/features/src/multiple_render_targets/mod.rs index d708f701f0b..31c4481cd74 100644 --- a/examples/features/src/multiple_render_targets/mod.rs +++ b/examples/features/src/multiple_render_targets/mod.rs @@ -182,6 +182,7 @@ impl MultiTargetRenderer { depth_stencil_attachment: None, timestamp_writes: None, occlusion_query_set: None, + multiview_mask: None, }); rpass.set_pipeline(&self.pipeline); rpass.set_bind_group(0, &self.bindgroup, &[]); @@ -339,6 +340,7 @@ impl TargetRenderer { depth_stencil_attachment: None, timestamp_writes: None, occlusion_query_set: None, + multiview_mask: None, }); rpass.set_pipeline(&self.pipeline); rpass.set_bind_group(0, &self.bindgroup_left, &[]); diff --git a/examples/features/src/ray_cube_compute/mod.rs b/examples/features/src/ray_cube_compute/mod.rs index 3d875b1c745..2e60e5fd956 100644 --- a/examples/features/src/ray_cube_compute/mod.rs +++ b/examples/features/src/ray_cube_compute/mod.rs @@ -469,6 +469,7 @@ impl crate::framework::Example for Example { depth_stencil_attachment: None, timestamp_writes: None, occlusion_query_set: None, + multiview_mask: None, }); rpass.set_pipeline(&self.blit_pipeline); diff --git a/examples/features/src/ray_cube_fragment/mod.rs b/examples/features/src/ray_cube_fragment/mod.rs index dde26e02832..6643152a937 100644 --- a/examples/features/src/ray_cube_fragment/mod.rs +++ b/examples/features/src/ray_cube_fragment/mod.rs @@ -355,6 +355,7 @@ impl crate::framework::Example for Example { depth_stencil_attachment: None, timestamp_writes: None, occlusion_query_set: None, + multiview_mask: None, }); rpass.set_pipeline(&self.pipeline); diff --git a/examples/features/src/ray_cube_normals/mod.rs b/examples/features/src/ray_cube_normals/mod.rs index 4620b7551c0..a5553c7fa79 100644 --- a/examples/features/src/ray_cube_normals/mod.rs +++ b/examples/features/src/ray_cube_normals/mod.rs @@ -455,6 +455,7 @@ impl crate::framework::Example for Example { depth_stencil_attachment: None, timestamp_writes: None, occlusion_query_set: None, + multiview_mask: None, }); rpass.set_pipeline(&self.blit_pipeline); diff --git a/examples/features/src/ray_scene/mod.rs b/examples/features/src/ray_scene/mod.rs index 21c2aada802..eb367cc53a2 100644 --- a/examples/features/src/ray_scene/mod.rs +++ b/examples/features/src/ray_scene/mod.rs @@ -535,6 +535,7 @@ impl crate::framework::Example for Example { depth_stencil_attachment: None, timestamp_writes: None, occlusion_query_set: None, + multiview_mask: None, }); rpass.set_pipeline(&self.pipeline); diff --git a/examples/features/src/ray_shadows/mod.rs b/examples/features/src/ray_shadows/mod.rs index d4d1cabe4b6..d273c63ed0d 100644 --- a/examples/features/src/ray_shadows/mod.rs +++ b/examples/features/src/ray_shadows/mod.rs @@ -348,6 +348,7 @@ impl crate::framework::Example for Example { depth_stencil_attachment: None, timestamp_writes: None, occlusion_query_set: None, + multiview_mask: None, }); rpass.set_pipeline(&self.pipeline); diff --git a/examples/features/src/ray_traced_triangle/mod.rs b/examples/features/src/ray_traced_triangle/mod.rs index 0df2e829d2b..eefe0ce6d1f 100644 --- a/examples/features/src/ray_traced_triangle/mod.rs +++ b/examples/features/src/ray_traced_triangle/mod.rs @@ -414,6 +414,7 @@ impl crate::framework::Example for Example { depth_stencil_attachment: None, timestamp_writes: None, occlusion_query_set: None, + multiview_mask: None, }); rpass.set_pipeline(&self.blit_pipeline); diff --git a/examples/features/src/render_to_texture/mod.rs b/examples/features/src/render_to_texture/mod.rs index 923434dbae6..8dfb1b5c459 100644 --- a/examples/features/src/render_to_texture/mod.rs +++ b/examples/features/src/render_to_texture/mod.rs @@ -95,6 +95,7 @@ async fn run(_path: Option) { depth_stencil_attachment: None, occlusion_query_set: None, timestamp_writes: None, + multiview_mask: None, }); render_pass.set_pipeline(&pipeline); render_pass.draw(0..3, 0..1); diff --git a/examples/features/src/shadow/mod.rs b/examples/features/src/shadow/mod.rs index b89d2c902dc..477f6a7429e 100644 --- a/examples/features/src/shadow/mod.rs +++ b/examples/features/src/shadow/mod.rs @@ -771,6 +771,7 @@ impl crate::framework::Example for Example { }), timestamp_writes: None, occlusion_query_set: None, + multiview_mask: None, }); pass.set_pipeline(&self.shadow_pass.pipeline); pass.set_bind_group(0, &self.shadow_pass.bind_group, &[]); @@ -816,6 +817,7 @@ impl crate::framework::Example for Example { }), timestamp_writes: None, occlusion_query_set: None, + multiview_mask: None, }); pass.set_pipeline(&self.forward_pass.pipeline); pass.set_bind_group(0, &self.forward_pass.bind_group, &[]); diff --git a/examples/features/src/skybox/mod.rs b/examples/features/src/skybox/mod.rs index 699f6615ba9..3847a18824d 100644 --- a/examples/features/src/skybox/mod.rs +++ b/examples/features/src/skybox/mod.rs @@ -444,6 +444,7 @@ impl crate::framework::Example for Example { }), timestamp_writes: None, occlusion_query_set: None, + multiview_mask: None, }); rpass.set_bind_group(0, &self.bind_group, &[]); diff --git a/examples/features/src/srgb_blend/mod.rs b/examples/features/src/srgb_blend/mod.rs index fcd59265b76..dbc1455dfce 100644 --- a/examples/features/src/srgb_blend/mod.rs +++ b/examples/features/src/srgb_blend/mod.rs @@ -196,6 +196,7 @@ impl crate::framework::Example for Example { depth_stencil_attachment: None, timestamp_writes: None, occlusion_query_set: None, + multiview_mask: None, }); rpass.push_debug_group("Prepare data for draw."); rpass.set_pipeline(&self.pipeline); diff --git a/examples/features/src/stencil_triangles/mod.rs b/examples/features/src/stencil_triangles/mod.rs index e1e44acf40f..c39deb1c2f6 100644 --- a/examples/features/src/stencil_triangles/mod.rs +++ b/examples/features/src/stencil_triangles/mod.rs @@ -222,6 +222,7 @@ impl crate::framework::Example for Example { }), timestamp_writes: None, occlusion_query_set: None, + multiview_mask: None, }); rpass.set_stencil_reference(1); diff --git a/examples/features/src/texture_arrays/mod.rs b/examples/features/src/texture_arrays/mod.rs index e7615444200..151bd0b2ca8 100644 --- a/examples/features/src/texture_arrays/mod.rs +++ b/examples/features/src/texture_arrays/mod.rs @@ -402,6 +402,7 @@ impl crate::framework::Example for Example { depth_stencil_attachment: None, timestamp_writes: None, occlusion_query_set: None, + multiview_mask: None, }); rpass.set_pipeline(&self.pipeline); diff --git a/examples/features/src/timestamp_queries/mod.rs b/examples/features/src/timestamp_queries/mod.rs index 9d7b6cbc62f..aac9332e19c 100644 --- a/examples/features/src/timestamp_queries/mod.rs +++ b/examples/features/src/timestamp_queries/mod.rs @@ -395,6 +395,7 @@ fn render_pass( end_of_pass_write_index: Some(*next_unused_query + 1), }), occlusion_query_set: None, + multiview_mask: None, }); *next_unused_query += 2; diff --git a/examples/features/src/uniform_values/mod.rs b/examples/features/src/uniform_values/mod.rs index 6a42f2635e1..344b3469471 100644 --- a/examples/features/src/uniform_values/mod.rs +++ b/examples/features/src/uniform_values/mod.rs @@ -319,6 +319,7 @@ async fn run(event_loop: EventLoop<()>, window: Arc) { depth_stencil_attachment: None, occlusion_query_set: None, timestamp_writes: None, + multiview_mask: None, }); render_pass.set_pipeline(&wgpu_context_ref.pipeline); // (9) diff --git a/examples/features/src/water/mod.rs b/examples/features/src/water/mod.rs index 5d1241fce39..2f29bd27bba 100644 --- a/examples/features/src/water/mod.rs +++ b/examples/features/src/water/mod.rs @@ -750,6 +750,7 @@ impl crate::framework::Example for Example { }), timestamp_writes: None, occlusion_query_set: None, + multiview_mask: None, }); rpass.execute_bundles([&self.terrain_bundle]); @@ -778,6 +779,7 @@ impl crate::framework::Example for Example { }), timestamp_writes: None, occlusion_query_set: None, + multiview_mask: None, }); rpass.set_pipeline(&self.terrain_pipeline); rpass.set_bind_group(0, &self.terrain_normal_bind_group, &[]); @@ -805,6 +807,7 @@ impl crate::framework::Example for Example { }), timestamp_writes: None, occlusion_query_set: None, + multiview_mask: None, }); rpass.set_pipeline(&self.water_pipeline); diff --git a/examples/standalone/02_hello_window/src/main.rs b/examples/standalone/02_hello_window/src/main.rs index fc0d278a942..6da07c9f750 100644 --- a/examples/standalone/02_hello_window/src/main.rs +++ b/examples/standalone/02_hello_window/src/main.rs @@ -107,6 +107,7 @@ impl State { depth_stencil_attachment: None, timestamp_writes: None, occlusion_query_set: None, + multiview_mask: None, }); // If you wanted to call any drawing commands, they would go here. diff --git a/naga/src/back/glsl/mod.rs b/naga/src/back/glsl/mod.rs index 4c5a9d8cbcb..c764430a568 100644 --- a/naga/src/back/glsl/mod.rs +++ b/naga/src/back/glsl/mod.rs @@ -5204,8 +5204,13 @@ const fn glsl_built_in(built_in: crate::BuiltIn, options: VaryingOptions) -> &'s "gl_FragCoord" } } - Bi::ViewIndex if options.targeting_webgl => "int(gl_ViewID_OVR)", - Bi::ViewIndex => "gl_ViewIndex", + Bi::ViewIndex => { + if options.targeting_webgl { + "gl_ViewID_OVR" + } else { + "uint(gl_ViewIndex)" + } + } // vertex Bi::BaseInstance => "uint(gl_BaseInstance)", Bi::BaseVertex => "uint(gl_BaseVertex)", diff --git a/naga/src/back/hlsl/conv.rs b/naga/src/back/hlsl/conv.rs index ed40cbe5102..0aad453130d 100644 --- a/naga/src/back/hlsl/conv.rs +++ b/naga/src/back/hlsl/conv.rs @@ -172,6 +172,7 @@ impl crate::BuiltIn { // to this field will get replaced with references to `SPECIAL_CBUF_VAR` // in `Writer::write_expr`. Self::NumWorkGroups => "SV_GroupID", + Self::ViewIndex => "SV_ViewID", // These builtins map to functions Self::SubgroupSize | Self::SubgroupInvocationId @@ -180,7 +181,7 @@ impl crate::BuiltIn { Self::BaseInstance | Self::BaseVertex | Self::WorkGroupSize => { return Err(Error::Unimplemented(format!("builtin {self:?}"))) } - Self::PointSize | Self::ViewIndex | Self::PointCoord | Self::DrawID => { + Self::PointSize | Self::PointCoord | Self::DrawID => { return Err(Error::Custom(format!("Unsupported builtin {self:?}"))) } }) diff --git a/naga/src/back/hlsl/mod.rs b/naga/src/back/hlsl/mod.rs index c7747eb3fac..7fca9670921 100644 --- a/naga/src/back/hlsl/mod.rs +++ b/naga/src/back/hlsl/mod.rs @@ -650,6 +650,8 @@ pub enum Error { ResolveArraySizeError(#[from] proc::ResolveArraySizeError), #[error("entry point with stage {0:?} and name '{1}' not found")] EntryPointNotFound(ir::ShaderStage, String), + #[error("requires shader model {1:?} for reason: {0}")] + ShaderModelTooLow(String, ShaderModel), } #[derive(PartialEq, Eq, Hash)] diff --git a/naga/src/back/hlsl/writer.rs b/naga/src/back/hlsl/writer.rs index ab95b9327f9..419a65fe5b6 100644 --- a/naga/src/back/hlsl/writer.rs +++ b/naga/src/back/hlsl/writer.rs @@ -569,6 +569,14 @@ impl<'a, W: fmt::Write> super::Writer<'a, W> { ) -> BackendResult { match *binding { Some(crate::Binding::BuiltIn(builtin)) if !is_subgroup_builtin_binding(binding) => { + if builtin == crate::BuiltIn::ViewIndex + && self.options.shader_model < ShaderModel::V6_1 + { + return Err(Error::ShaderModelTooLow( + "used @builtin(view_index) or SV_ViewID".to_string(), + ShaderModel::V6_1, + )); + } let builtin_str = builtin.to_hlsl_str()?; write!(self.out, " : {builtin_str}")?; } diff --git a/naga/src/back/msl/mod.rs b/naga/src/back/msl/mod.rs index 44aedf686c4..bf17db89281 100644 --- a/naga/src/back/msl/mod.rs +++ b/naga/src/back/msl/mod.rs @@ -530,6 +530,12 @@ impl Options { crate::BuiltIn::PrimitiveIndex if self.lang_version < (2, 2) => { return Err(Error::UnsupportedAttribute("primitive_id".to_string())); } + // macOS: since Metal 2.3 + // iOS: Since Metal 2.2 + // https://developer.apple.com/metal/Metal-Shading-Language-Specification.pdf#page=114 + crate::BuiltIn::ViewIndex if self.lang_version < (2, 2) => { + return Err(Error::UnsupportedAttribute("amplification_id".to_string())); + } _ => {} } @@ -668,6 +674,7 @@ impl ResolvedBinding { let name = match built_in { Bi::Position { invariant: false } => "position", Bi::Position { invariant: true } => "position, invariant", + Bi::ViewIndex => "amplification_id", // vertex Bi::BaseInstance => "base_instance", Bi::BaseVertex => "base_vertex", @@ -694,7 +701,7 @@ impl ResolvedBinding { Bi::SubgroupId => "simdgroup_index_in_threadgroup", Bi::SubgroupSize => "threads_per_simdgroup", Bi::SubgroupInvocationId => "thread_index_in_simdgroup", - Bi::CullDistance | Bi::ViewIndex | Bi::DrawID => { + Bi::CullDistance | Bi::DrawID => { return Err(Error::UnsupportedBuiltIn(built_in)) } }; diff --git a/naga/src/valid/interface.rs b/naga/src/valid/interface.rs index 7c8cc903139..a8a0e0920c6 100644 --- a/naga/src/valid/interface.rs +++ b/naga/src/valid/interface.rs @@ -253,7 +253,7 @@ impl VaryingContext<'_> { St::Compute => false, St::Task | St::Mesh => unreachable!(), }, - *ty_inner == Ti::Scalar(crate::Scalar::I32), + *ty_inner == Ti::Scalar(crate::Scalar::U32), ), Bi::FragDepth => ( self.stage == St::Fragment && self.output, diff --git a/naga/tests/in/wgsl/multiview.toml b/naga/tests/in/wgsl/multiview.toml index 690600e0957..04c49286f6c 100644 --- a/naga/tests/in/wgsl/multiview.toml +++ b/naga/tests/in/wgsl/multiview.toml @@ -1,3 +1,8 @@ glsl_multiview = 2 god_mode = true -targets = "SPIRV | GLSL | WGSL" + +[msl] +lang_version = [2, 3] + +[hlsl] +shader_model = "V6_1" diff --git a/naga/tests/in/wgsl/multiview.wgsl b/naga/tests/in/wgsl/multiview.wgsl index 0eedd087866..ae01746ef24 100644 --- a/naga/tests/in/wgsl/multiview.wgsl +++ b/naga/tests/in/wgsl/multiview.wgsl @@ -1,2 +1,2 @@ @fragment -fn main(@builtin(view_index) view_index: i32) {} +fn main(@builtin(view_index) view_index: u32) {} diff --git a/naga/tests/in/wgsl/multiview_webgl.wgsl b/naga/tests/in/wgsl/multiview_webgl.wgsl index 0eedd087866..ae01746ef24 100644 --- a/naga/tests/in/wgsl/multiview_webgl.wgsl +++ b/naga/tests/in/wgsl/multiview_webgl.wgsl @@ -1,2 +1,2 @@ @fragment -fn main(@builtin(view_index) view_index: i32) {} +fn main(@builtin(view_index) view_index: u32) {} diff --git a/naga/tests/out/glsl/wgsl-multiview.main.Fragment.glsl b/naga/tests/out/glsl/wgsl-multiview.main.Fragment.glsl index 466aea062ff..4ae52f27810 100644 --- a/naga/tests/out/glsl/wgsl-multiview.main.Fragment.glsl +++ b/naga/tests/out/glsl/wgsl-multiview.main.Fragment.glsl @@ -6,7 +6,7 @@ precision highp int; void main() { - int view_index = gl_ViewIndex; + uint view_index = uint(gl_ViewIndex); return; } diff --git a/naga/tests/out/glsl/wgsl-multiview_webgl.main.Fragment.glsl b/naga/tests/out/glsl/wgsl-multiview_webgl.main.Fragment.glsl index 30515289c95..0e0e1246ccd 100644 --- a/naga/tests/out/glsl/wgsl-multiview_webgl.main.Fragment.glsl +++ b/naga/tests/out/glsl/wgsl-multiview_webgl.main.Fragment.glsl @@ -6,7 +6,7 @@ precision highp int; void main() { - int view_index = int(gl_ViewID_OVR); + uint view_index = gl_ViewID_OVR; return; } diff --git a/naga/tests/out/hlsl/wgsl-multiview.hlsl b/naga/tests/out/hlsl/wgsl-multiview.hlsl new file mode 100644 index 00000000000..dc0997cd093 --- /dev/null +++ b/naga/tests/out/hlsl/wgsl-multiview.hlsl @@ -0,0 +1,9 @@ +struct FragmentInput_main { + uint view_index_1 : SV_ViewID; +}; + +void main(FragmentInput_main fragmentinput_main) +{ + uint view_index = fragmentinput_main.view_index_1; + return; +} diff --git a/naga/tests/out/hlsl/wgsl-multiview.ron b/naga/tests/out/hlsl/wgsl-multiview.ron new file mode 100644 index 00000000000..ff856788943 --- /dev/null +++ b/naga/tests/out/hlsl/wgsl-multiview.ron @@ -0,0 +1,12 @@ +( + vertex:[ + ], + fragment:[ + ( + entry_point:"main", + target_profile:"ps_6_1", + ), + ], + compute:[ + ], +) diff --git a/naga/tests/out/msl/wgsl-multiview.msl b/naga/tests/out/msl/wgsl-multiview.msl new file mode 100644 index 00000000000..e6c845ee232 --- /dev/null +++ b/naga/tests/out/msl/wgsl-multiview.msl @@ -0,0 +1,14 @@ +// language: metal2.3 +#include +#include + +using metal::uint; + + +struct main_Input { +}; +fragment void main_( + uint view_index [[amplification_id]] +) { + return; +} diff --git a/naga/tests/out/spv/wgsl-multiview.spvasm b/naga/tests/out/spv/wgsl-multiview.spvasm index 792dea5593c..93a218d62be 100644 --- a/naga/tests/out/spv/wgsl-multiview.spvasm +++ b/naga/tests/out/spv/wgsl-multiview.spvasm @@ -12,7 +12,7 @@ OpExecutionMode %8 OriginUpperLeft OpDecorate %5 BuiltIn ViewIndex OpDecorate %5 Flat %2 = OpTypeVoid -%3 = OpTypeInt 32 1 +%3 = OpTypeInt 32 0 %6 = OpTypePointer Input %3 %5 = OpVariable %6 Input %9 = OpTypeFunction %2 diff --git a/naga/tests/out/wgsl/wgsl-multiview.wgsl b/naga/tests/out/wgsl/wgsl-multiview.wgsl index 51192d2f7a6..64830243586 100644 --- a/naga/tests/out/wgsl/wgsl-multiview.wgsl +++ b/naga/tests/out/wgsl/wgsl-multiview.wgsl @@ -1,4 +1,4 @@ @fragment -fn main(@builtin(view_index) view_index: i32) { +fn main(@builtin(view_index) view_index: u32) { return; } diff --git a/player/src/lib.rs b/player/src/lib.rs index 437f51b14bb..6a80081111d 100644 --- a/player/src/lib.rs +++ b/player/src/lib.rs @@ -111,6 +111,7 @@ impl GlobalPlay for wgc::global::Global { target_depth_stencil, timestamp_writes, occlusion_query_set_id, + multiview_mask, } => { self.render_pass_end_with_unresolved_commands( encoder, @@ -119,6 +120,7 @@ impl GlobalPlay for wgc::global::Global { target_depth_stencil.as_ref(), timestamp_writes.as_ref(), occlusion_query_set_id, + multiview_mask, ); } Command::BuildAccelerationStructures { blas, tlas } => { diff --git a/tests/tests/wgpu-gpu/binding_array/sampled_textures.rs b/tests/tests/wgpu-gpu/binding_array/sampled_textures.rs index c445857a92f..5c6bcd79019 100644 --- a/tests/tests/wgpu-gpu/binding_array/sampled_textures.rs +++ b/tests/tests/wgpu-gpu/binding_array/sampled_textures.rs @@ -228,6 +228,7 @@ async fn binding_array_sampled_textures(ctx: TestingContext, partially_bound: bo depth_stencil_attachment: None, timestamp_writes: None, occlusion_query_set: None, + multiview_mask: None, }); render_pass.set_pipeline(&pipeline); render_pass.set_bind_group(0, &bind_group, &[]); diff --git a/tests/tests/wgpu-gpu/clip_distances.rs b/tests/tests/wgpu-gpu/clip_distances.rs index bbdd2d539ff..9ca2f284312 100644 --- a/tests/tests/wgpu-gpu/clip_distances.rs +++ b/tests/tests/wgpu-gpu/clip_distances.rs @@ -87,6 +87,7 @@ async fn clip_distances(ctx: TestingContext) { depth_stencil_attachment: None, timestamp_writes: None, occlusion_query_set: None, + multiview_mask: None, }); rpass.set_pipeline(&pipeline); rpass.draw(0..3, 0..1); diff --git a/tests/tests/wgpu-gpu/device.rs b/tests/tests/wgpu-gpu/device.rs index 7e9d4204ccd..fd57f774433 100644 --- a/tests/tests/wgpu-gpu/device.rs +++ b/tests/tests/wgpu-gpu/device.rs @@ -346,6 +346,7 @@ static DEVICE_DESTROY_THEN_MORE: GpuTestConfiguration = GpuTestConfiguration::ne depth_stencil_attachment: None, timestamp_writes: None, occlusion_query_set: None, + multiview_mask: None, }); drop(pass); ctx.queue.submit([encoder_for_render_pass.finish()]); diff --git a/tests/tests/wgpu-gpu/draw_indirect.rs b/tests/tests/wgpu-gpu/draw_indirect.rs index e33ccb83ab7..33f312c0351 100644 --- a/tests/tests/wgpu-gpu/draw_indirect.rs +++ b/tests/tests/wgpu-gpu/draw_indirect.rs @@ -272,6 +272,7 @@ async fn run_test(ctx: TestingContext, test_data: TestData, expect_noop: bool) { depth_stencil_attachment: None, timestamp_writes: None, occlusion_query_set: None, + multiview_mask: None, }); rpass.set_pipeline(&pipeline); @@ -747,6 +748,7 @@ async fn indirect_buffer_offsets(ctx: TestingContext) { depth_stencil_attachment: None, timestamp_writes: None, occlusion_query_set: None, + multiview_mask: None, }); rpass.set_pipeline(&pipeline); diff --git a/tests/tests/wgpu-gpu/encoder.rs b/tests/tests/wgpu-gpu/encoder.rs index 426ef3c808e..ab1c10594ca 100644 --- a/tests/tests/wgpu-gpu/encoder.rs +++ b/tests/tests/wgpu-gpu/encoder.rs @@ -77,6 +77,7 @@ static DROP_ENCODER_AFTER_ERROR: GpuTestConfiguration = GpuTestConfiguration::ne depth_stencil_attachment: None, timestamp_writes: None, occlusion_query_set: None, + multiview_mask: None, }); // This viewport is invalid because it has negative size. diff --git a/tests/tests/wgpu-gpu/main.rs b/tests/tests/wgpu-gpu/main.rs index b52e5088ebb..2c5bb2e0789 100644 --- a/tests/tests/wgpu-gpu/main.rs +++ b/tests/tests/wgpu-gpu/main.rs @@ -36,6 +36,7 @@ mod instance; mod life_cycle; mod mem_leaks; mod mesh_shader; +mod multiview; mod occlusion_query; mod oob_indexing; mod oom; @@ -97,6 +98,7 @@ fn all_tests() -> Vec { life_cycle::all_tests(&mut tests); mem_leaks::all_tests(&mut tests); mesh_shader::all_tests(&mut tests); + multiview::all_tests(&mut tests); occlusion_query::all_tests(&mut tests); oob_indexing::all_tests(&mut tests); oom::all_tests(&mut tests); diff --git a/tests/tests/wgpu-gpu/mem_leaks.rs b/tests/tests/wgpu-gpu/mem_leaks.rs index ada016b1f7e..42b01238103 100644 --- a/tests/tests/wgpu-gpu/mem_leaks.rs +++ b/tests/tests/wgpu-gpu/mem_leaks.rs @@ -207,6 +207,7 @@ async fn draw_test_with_reports( depth_stencil_attachment: None, timestamp_writes: None, occlusion_query_set: None, + multiview_mask: None, }); rpass.set_pipeline(&pipeline); diff --git a/tests/tests/wgpu-gpu/mesh_shader/mod.rs b/tests/tests/wgpu-gpu/mesh_shader/mod.rs index 3d7f6df6068..a4f0243b1be 100644 --- a/tests/tests/wgpu-gpu/mesh_shader/mod.rs +++ b/tests/tests/wgpu-gpu/mesh_shader/mod.rs @@ -218,6 +218,7 @@ fn mesh_pipeline_build(ctx: &TestingContext, info: MeshPipelineTestInfo) { }), timestamp_writes: None, occlusion_query_set: None, + multiview_mask: None, }); pass.set_pipeline(&pipeline); pass.draw_mesh_tasks(1, 1, 1); @@ -316,6 +317,7 @@ fn mesh_draw(ctx: &TestingContext, draw_type: DrawType) { }), timestamp_writes: None, occlusion_query_set: None, + multiview_mask: None, }); pass.set_pipeline(&pipeline); match draw_type { diff --git a/tests/tests/wgpu-gpu/multiview.rs b/tests/tests/wgpu-gpu/multiview.rs new file mode 100644 index 00000000000..59093067d15 --- /dev/null +++ b/tests/tests/wgpu-gpu/multiview.rs @@ -0,0 +1,197 @@ +use std::num::NonZero; + +use wgpu::{ + util::{BufferInitDescriptor, DeviceExt}, + vertex_attr_array, Features, Limits, +}; +use wgpu_test::{ + gpu_test, GpuTestConfiguration, GpuTestInitializer, TestParameters, TestingContext, +}; + +pub fn all_tests(vec: &mut Vec) { + vec.push(DRAW_MULTIVIEW); +} + +#[gpu_test] +static DRAW_MULTIVIEW: GpuTestConfiguration = GpuTestConfiguration::new() + .parameters( + TestParameters::default() + .features(Features::MULTIVIEW) + .limits(Limits { + max_multiview_view_count: 2, + max_multiview_instance_index: 1, + ..Limits::defaults() + }), + ) + .run_async(run_test); + +async fn run_test(ctx: TestingContext) { + let vertex_buffer_content: &[f32; 12] = &[ + // Triangle 1 + -1.0, -1.0, // Bottom left + 1.0, 1.0, // Top right + -1.0, 1.0, // Top left + // Triangle 2 + -1.0, -1.0, // Bottom left + 1.0, -1.0, // Bottom right + 1.0, 1.0, // Top right + ]; + let vertex_buffer = ctx.device.create_buffer_init(&BufferInitDescriptor { + label: None, + contents: bytemuck::cast_slice(vertex_buffer_content), + usage: wgpu::BufferUsages::VERTEX, + }); + + let shader_src = " + @vertex + fn vs_main(@location(0) position: vec2f) -> @builtin(position) vec4f { + return vec4f(position, 0.0, 1.0); + } + + @fragment + fn fs_main(@builtin(view_index) view_index: u32) -> @location(0) vec4f { + return vec4f(f32(view_index) * 0.25 + 0.125); + } + "; + + let shader = ctx + .device + .create_shader_module(wgpu::ShaderModuleDescriptor { + label: None, + source: wgpu::ShaderSource::Wgsl(shader_src.into()), + }); + + let pipeline_desc = wgpu::RenderPipelineDescriptor { + label: None, + vertex: wgpu::VertexState { + buffers: &[wgpu::VertexBufferLayout { + array_stride: 8, + step_mode: wgpu::VertexStepMode::Vertex, + attributes: &vertex_attr_array![0 => Float32x2], + }], + module: &shader, + entry_point: Some("vs_main"), + compilation_options: Default::default(), + }, + primitive: wgpu::PrimitiveState::default(), + fragment: Some(wgpu::FragmentState { + module: &shader, + entry_point: Some("fs_main"), + compilation_options: Default::default(), + targets: &[Some(wgpu::ColorTargetState { + format: wgpu::TextureFormat::R8Unorm, + blend: None, + write_mask: wgpu::ColorWrites::ALL, + })], + }), + multiview: NonZero::new(2), + multisample: Default::default(), + layout: None, + depth_stencil: None, + cache: None, + }; + const TEXTURE_SIZE: u32 = 512; + let pipeline = ctx.device.create_render_pipeline(&pipeline_desc); + let texture = ctx.device.create_texture(&wgpu::TextureDescriptor { + label: None, + size: wgpu::Extent3d { + width: TEXTURE_SIZE, + height: TEXTURE_SIZE, + depth_or_array_layers: 2, + }, + mip_level_count: 1, + sample_count: 1, + dimension: wgpu::TextureDimension::D2, + format: wgpu::TextureFormat::R8Unorm, + usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::COPY_SRC, + view_formats: &[], + }); + let view = texture.create_view(&wgpu::TextureViewDescriptor { + label: None, + format: Some(wgpu::TextureFormat::R8Unorm), + dimension: Some(wgpu::TextureViewDimension::D2Array), + usage: Some(wgpu::TextureUsages::RENDER_ATTACHMENT), + aspect: wgpu::TextureAspect::All, + base_mip_level: 0, + mip_level_count: None, + base_array_layer: 0, + array_layer_count: Some(2), + }); + let readback_buffer = ctx.device.create_buffer(&wgpu::BufferDescriptor { + label: None, + size: TEXTURE_SIZE as u64 * TEXTURE_SIZE as u64 * 2, + usage: wgpu::BufferUsages::COPY_DST | wgpu::BufferUsages::MAP_READ, + mapped_at_creation: false, + }); + + let mut encoder = ctx + .device + .create_command_encoder(&wgpu::CommandEncoderDescriptor::default()); + { + let mut rpass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { + label: None, + color_attachments: &[Some(wgpu::RenderPassColorAttachment { + view: &view, + depth_slice: None, + resolve_target: None, + ops: wgpu::Operations { + load: wgpu::LoadOp::Clear(wgpu::Color::TRANSPARENT), + store: wgpu::StoreOp::Store, + }, + })], + depth_stencil_attachment: None, + timestamp_writes: None, + occlusion_query_set: None, + multiview_mask: NonZero::new(3), + }); + rpass.set_pipeline(&pipeline); + rpass.set_vertex_buffer(0, vertex_buffer.slice(..)); + rpass.draw(0..6, 0..1); + } + encoder.copy_texture_to_buffer( + wgpu::TexelCopyTextureInfo { + texture: &texture, + mip_level: 0, + origin: wgpu::Origin3d { x: 0, y: 0, z: 0 }, + aspect: wgpu::TextureAspect::All, + }, + wgpu::TexelCopyBufferInfo { + buffer: &readback_buffer, + layout: wgpu::TexelCopyBufferLayout { + offset: 0, + bytes_per_row: Some(TEXTURE_SIZE), + rows_per_image: Some(TEXTURE_SIZE), + }, + }, + wgpu::Extent3d { + width: TEXTURE_SIZE, + height: TEXTURE_SIZE, + depth_or_array_layers: 2, + }, + ); + ctx.queue.submit([encoder.finish()]); + + let slice = readback_buffer.slice(..); + slice.map_async(wgpu::MapMode::Read, |_| ()); + + ctx.async_poll(wgpu::PollType::Wait { + submission_index: None, + timeout: None, + }) + .await + .unwrap(); + + let data = slice.get_mapped_range(); + let each_texture_size = (TEXTURE_SIZE * TEXTURE_SIZE) as usize; + assert!(data.len() == each_texture_size * 2); + eprintln!("View values: {}, {}", data[0], data[each_texture_size]); + for view_idx in 0..2 { + // Some metal devices automatically initialize stuff to 255, so I decided to use 128 instead of that + let target_value = 32 + view_idx as u8 * 64; + let failed_value = data[each_texture_size * view_idx..each_texture_size * (view_idx + 1)] + .iter() + .copied() + .find(|b| *b != target_value); + assert_eq!(failed_value, None, "Expected {target_value}"); + } +} diff --git a/tests/tests/wgpu-gpu/occlusion_query/mod.rs b/tests/tests/wgpu-gpu/occlusion_query/mod.rs index caa4f7e9d76..46c33fde154 100644 --- a/tests/tests/wgpu-gpu/occlusion_query/mod.rs +++ b/tests/tests/wgpu-gpu/occlusion_query/mod.rs @@ -84,6 +84,7 @@ static OCCLUSION_QUERY: GpuTestConfiguration = GpuTestConfiguration::new() }), timestamp_writes: None, occlusion_query_set: Some(&query_set), + multiview_mask: None, }); render_pass.set_pipeline(&pipeline); diff --git a/tests/tests/wgpu-gpu/planar_texture/mod.rs b/tests/tests/wgpu-gpu/planar_texture/mod.rs index 961d4b43f64..b491f9cc2da 100644 --- a/tests/tests/wgpu-gpu/planar_texture/mod.rs +++ b/tests/tests/wgpu-gpu/planar_texture/mod.rs @@ -100,6 +100,7 @@ fn test_planar_texture_creation_sampling( depth_stencil_attachment: None, timestamp_writes: None, occlusion_query_set: None, + multiview_mask: None, }); rpass.set_pipeline(&pipeline); rpass.set_bind_group(0, &bind_group, &[]); diff --git a/tests/tests/wgpu-gpu/regression/issue_3349.rs b/tests/tests/wgpu-gpu/regression/issue_3349.rs index adee4eb6527..8637f6c3217 100644 --- a/tests/tests/wgpu-gpu/regression/issue_3349.rs +++ b/tests/tests/wgpu-gpu/regression/issue_3349.rs @@ -166,6 +166,7 @@ async fn multi_stage_data_binding_test(ctx: TestingContext) { depth_stencil_attachment: None, timestamp_writes: None, occlusion_query_set: None, + multiview_mask: None, }); rpass.set_pipeline(&pipeline); diff --git a/tests/tests/wgpu-gpu/regression/issue_3457.rs b/tests/tests/wgpu-gpu/regression/issue_3457.rs index 495ad029933..cb693a2fc1d 100644 --- a/tests/tests/wgpu-gpu/regression/issue_3457.rs +++ b/tests/tests/wgpu-gpu/regression/issue_3457.rs @@ -156,6 +156,7 @@ static PASS_RESET_VERTEX_BUFFER: GpuTestConfiguration = GpuTestConfiguration::ne depth_stencil_attachment: None, timestamp_writes: None, occlusion_query_set: None, + multiview_mask: None, }); double_rpass.set_pipeline(&double_pipeline); @@ -192,6 +193,7 @@ static PASS_RESET_VERTEX_BUFFER: GpuTestConfiguration = GpuTestConfiguration::ne depth_stencil_attachment: None, timestamp_writes: None, occlusion_query_set: None, + multiview_mask: None, }); single_rpass.set_pipeline(&single_pipeline); diff --git a/tests/tests/wgpu-gpu/regression/issue_4485.rs b/tests/tests/wgpu-gpu/regression/issue_4485.rs index 5c814377822..006b2fcba21 100644 --- a/tests/tests/wgpu-gpu/regression/issue_4485.rs +++ b/tests/tests/wgpu-gpu/regression/issue_4485.rs @@ -98,6 +98,7 @@ async fn test_impl(ctx: &TestingContext) { depth_stencil_attachment: None, timestamp_writes: None, occlusion_query_set: None, + multiview_mask: None, }); render_pass.set_pipeline(&pipeline); render_pass.draw(0..3, 0..1); diff --git a/tests/tests/wgpu-gpu/regression/issue_4514.rs b/tests/tests/wgpu-gpu/regression/issue_4514.rs index f3ad2051a90..90d126dc3e8 100644 --- a/tests/tests/wgpu-gpu/regression/issue_4514.rs +++ b/tests/tests/wgpu-gpu/regression/issue_4514.rs @@ -98,6 +98,7 @@ async fn test_impl(ctx: &TestingContext) { depth_stencil_attachment: None, timestamp_writes: None, occlusion_query_set: None, + multiview_mask: None, }); render_pass.set_pipeline(&pipeline); render_pass.draw(0..3, 0..1); diff --git a/tests/tests/wgpu-gpu/render_pass_ownership.rs b/tests/tests/wgpu-gpu/render_pass_ownership.rs index 8d1f65972e5..56aef55b93f 100644 --- a/tests/tests/wgpu-gpu/render_pass_ownership.rs +++ b/tests/tests/wgpu-gpu/render_pass_ownership.rs @@ -91,6 +91,7 @@ async fn render_pass_resource_ownership(ctx: TestingContext) { }), timestamp_writes: None, occlusion_query_set: Some(&occlusion_query_set), + multiview_mask: None, }); // Drop render pass attachments right away. diff --git a/tests/tests/wgpu-gpu/render_target.rs b/tests/tests/wgpu-gpu/render_target.rs index e45b8630a4e..3611986d856 100644 --- a/tests/tests/wgpu-gpu/render_target.rs +++ b/tests/tests/wgpu-gpu/render_target.rs @@ -203,6 +203,7 @@ async fn run_test( depth_stencil_attachment: None, timestamp_writes: None, occlusion_query_set: None, + multiview_mask: None, }); rpass.set_pipeline(&pipeline); rpass.set_vertex_buffer(0, vertex_buffer.slice(..)); @@ -386,6 +387,7 @@ async fn run_test_3d(ctx: TestingContext) { depth_stencil_attachment: None, timestamp_writes: None, occlusion_query_set: None, + multiview_mask: None, }); rpass.set_pipeline(&pipeline); rpass.set_vertex_buffer(0, vertex_buffer.slice(..)); diff --git a/tests/tests/wgpu-gpu/scissor_tests/mod.rs b/tests/tests/wgpu-gpu/scissor_tests/mod.rs index 14b81eb4337..fdb8b4fcb5f 100644 --- a/tests/tests/wgpu-gpu/scissor_tests/mod.rs +++ b/tests/tests/wgpu-gpu/scissor_tests/mod.rs @@ -98,6 +98,7 @@ async fn scissor_test_impl( depth_stencil_attachment: None, timestamp_writes: None, occlusion_query_set: None, + multiview_mask: None, }); render_pass.set_pipeline(&pipeline); render_pass.set_scissor_rect( diff --git a/tests/tests/wgpu-gpu/shader_primitive_index/mod.rs b/tests/tests/wgpu-gpu/shader_primitive_index/mod.rs index eb6e6fa1d23..2fce464fdb5 100644 --- a/tests/tests/wgpu-gpu/shader_primitive_index/mod.rs +++ b/tests/tests/wgpu-gpu/shader_primitive_index/mod.rs @@ -193,6 +193,7 @@ async fn pulling_common( depth_stencil_attachment: None, timestamp_writes: None, occlusion_query_set: None, + multiview_mask: None, }); rpass.set_pipeline(&pipeline); diff --git a/tests/tests/wgpu-gpu/shader_view_format/mod.rs b/tests/tests/wgpu-gpu/shader_view_format/mod.rs index 0087ff35adc..3891c48fcb5 100644 --- a/tests/tests/wgpu-gpu/shader_view_format/mod.rs +++ b/tests/tests/wgpu-gpu/shader_view_format/mod.rs @@ -151,6 +151,7 @@ async fn reinterpret( depth_stencil_attachment: None, timestamp_writes: None, occlusion_query_set: None, + multiview_mask: None, }); rpass.set_pipeline(&pipeline); rpass.set_bind_group(0, &bind_group, &[]); diff --git a/tests/tests/wgpu-gpu/vertex_formats/mod.rs b/tests/tests/wgpu-gpu/vertex_formats/mod.rs index 89f8db03d62..23d8c319a61 100644 --- a/tests/tests/wgpu-gpu/vertex_formats/mod.rs +++ b/tests/tests/wgpu-gpu/vertex_formats/mod.rs @@ -360,6 +360,7 @@ async fn vertex_formats_common(ctx: TestingContext, tests: &[Test<'_>]) { depth_stencil_attachment: None, timestamp_writes: None, occlusion_query_set: None, + multiview_mask: None, }); rpass.set_vertex_buffer(0, buffer_input.slice(..)); diff --git a/tests/tests/wgpu-gpu/vertex_indices/mod.rs b/tests/tests/wgpu-gpu/vertex_indices/mod.rs index 4fc63631f1e..1ea8023fbd2 100644 --- a/tests/tests/wgpu-gpu/vertex_indices/mod.rs +++ b/tests/tests/wgpu-gpu/vertex_indices/mod.rs @@ -366,6 +366,7 @@ async fn vertex_index_common(ctx: TestingContext) { depth_stencil_attachment: None, timestamp_writes: None, occlusion_query_set: None, + multiview_mask: None, }); { diff --git a/tests/tests/wgpu-gpu/vertex_state.rs b/tests/tests/wgpu-gpu/vertex_state.rs index 73cc0ecb78c..cdb806883f2 100644 --- a/tests/tests/wgpu-gpu/vertex_state.rs +++ b/tests/tests/wgpu-gpu/vertex_state.rs @@ -148,6 +148,7 @@ async fn set_array_stride_to_0(ctx: TestingContext) { depth_stencil_attachment: None, timestamp_writes: None, occlusion_query_set: None, + multiview_mask: None, }); // The D3D12 backend used to not set the stride of vertex buffers if it was 0. diff --git a/tests/tests/wgpu-gpu/zero_init_texture_after_discard.rs b/tests/tests/wgpu-gpu/zero_init_texture_after_discard.rs index 5d52b6e982a..193971f61c4 100644 --- a/tests/tests/wgpu-gpu/zero_init_texture_after_discard.rs +++ b/tests/tests/wgpu-gpu/zero_init_texture_after_discard.rs @@ -173,6 +173,7 @@ impl<'ctx> TestCase<'ctx> { }), timestamp_writes: None, occlusion_query_set: None, + multiview_mask: None, }); ctx.queue.submit([encoder.finish()]); } else { @@ -259,6 +260,7 @@ impl<'ctx> TestCase<'ctx> { ), timestamp_writes: None, occlusion_query_set: None, + multiview_mask: None, }); } @@ -284,6 +286,7 @@ impl<'ctx> TestCase<'ctx> { ), timestamp_writes: None, occlusion_query_set: None, + multiview_mask: None, }); } @@ -309,6 +312,7 @@ impl<'ctx> TestCase<'ctx> { ), timestamp_writes: None, occlusion_query_set: None, + multiview_mask: None, }); } diff --git a/tests/tests/wgpu-validation/api/external_texture.rs b/tests/tests/wgpu-validation/api/external_texture.rs index 989fe5ac8ae..0750cdf22e5 100644 --- a/tests/tests/wgpu-validation/api/external_texture.rs +++ b/tests/tests/wgpu-validation/api/external_texture.rs @@ -685,6 +685,7 @@ var tex: texture_external; depth_stencil_attachment: None, timestamp_writes: None, occlusion_query_set: None, + multiview_mask: None, }); pass.set_pipeline(&pipeline); diff --git a/wgpu-core/src/command/clear.rs b/wgpu-core/src/command/clear.rs index 5c8838f69e5..6b7b8936c76 100644 --- a/wgpu-core/src/command/clear.rs +++ b/wgpu-core/src/command/clear.rs @@ -547,7 +547,7 @@ fn clear_texture_via_render_passes( sample_count: dst_texture.desc.sample_count, color_attachments, depth_stencil_attachment, - multiview: None, + multiview_mask: None, timestamp_writes: None, occlusion_query_set: None, }) diff --git a/wgpu-core/src/command/draw.rs b/wgpu-core/src/command/draw.rs index 7a57077a4fa..77c22f089c8 100644 --- a/wgpu-core/src/command/draw.rs +++ b/wgpu-core/src/command/draw.rs @@ -44,6 +44,13 @@ pub enum DrawError { instance_limit: u64, slot: u32, }, + #[error( + "Instance {last_instance} extends beyond limit {instance_limit} imposed by multiview restrictions on the render pass." + )] + InstanceBeyondMultiviewLimit { + last_instance: u64, + instance_limit: u64, + }, #[error("Index {last_index} extends beyond limit {index_limit}. Did you bind the correct index buffer?")] IndexBeyondLimit { last_index: u64, index_limit: u64 }, #[error( @@ -71,6 +78,13 @@ pub enum DrawError { limit: u32, max_total: u32, }, + #[error( + "Mesh shader calls in multiview render passes require `EXPERIMENTAL_MESH_SHADER_MULTIVIEW`, and the view count ({views_given}) must be <= `Limits::max_multiview_view_count` ({max_multiviews})" + )] + MeshPipelineMultiviewLimitsViolated { + views_given: u32, + max_multiviews: u32, + }, } impl WebGpuError for DrawError { diff --git a/wgpu-core/src/command/encoder_command.rs b/wgpu-core/src/command/encoder_command.rs index 092228099bc..0713cc55971 100644 --- a/wgpu-core/src/command/encoder_command.rs +++ b/wgpu-core/src/command/encoder_command.rs @@ -1,4 +1,4 @@ -use core::convert::Infallible; +use core::{convert::Infallible, num::NonZero}; use alloc::{string::String, sync::Arc, vec::Vec}; @@ -65,6 +65,7 @@ pub enum Command { target_depth_stencil: Option, timestamp_writes: Option, occlusion_query_set_id: Option, + multiview_mask: Option>, }, BuildAccelerationStructures { blas: Vec, @@ -129,6 +130,7 @@ pub enum ArcCommand { depth_stencil_attachment: Option, timestamp_writes: Option, occlusion_query_set: Option>, + multiview_mask: Option>, }, BuildAccelerationStructures { blas: Vec, diff --git a/wgpu-core/src/command/mod.rs b/wgpu-core/src/command/mod.rs index 887c19fb357..cd5adac4691 100644 --- a/wgpu-core/src/command/mod.rs +++ b/wgpu-core/src/command/mod.rs @@ -1329,6 +1329,7 @@ impl Global { depth_stencil_attachment, timestamp_writes, occlusion_query_set, + multiview_mask, } => { encode_render_pass( &mut state, @@ -1337,6 +1338,7 @@ impl Global { depth_stencil_attachment, timestamp_writes, occlusion_query_set, + multiview_mask, )?; } ArcCommand::RunComputePass { diff --git a/wgpu-core/src/command/render.rs b/wgpu-core/src/command/render.rs index 51db9634ea4..b1376c18fff 100644 --- a/wgpu-core/src/command/render.rs +++ b/wgpu-core/src/command/render.rs @@ -235,6 +235,7 @@ pub struct RenderPassDescriptor<'a> { pub timestamp_writes: Option<&'a PassTimestampWrites>, /// Defines where the occlusion query results will be stored for this pass. pub occlusion_query_set: Option, + pub multiview_mask: Option, } /// Describes the attachments of a render pass. @@ -249,6 +250,8 @@ struct ArcRenderPassDescriptor<'a> { pub timestamp_writes: Option, /// Defines where the occlusion query results will be stored for this pass. pub occlusion_query_set: Option>, + /// The multiview array layers that will be used + pub multiview_mask: Option, } pub type RenderBasePass = BasePass; @@ -276,6 +279,7 @@ pub struct RenderPass { depth_stencil_attachment: Option, timestamp_writes: Option, occlusion_query_set: Option>, + multiview_mask: Option, // Resource binding dedupe state. current_bind_groups: BindGroupStateChange, @@ -291,6 +295,7 @@ impl RenderPass { color_attachments, depth_stencil_attachment, occlusion_query_set, + multiview_mask, } = desc; Self { @@ -300,6 +305,7 @@ impl RenderPass { depth_stencil_attachment, timestamp_writes, occlusion_query_set, + multiview_mask, current_bind_groups: BindGroupStateChange::new(), current_pipeline: StateChange::new(), @@ -314,6 +320,7 @@ impl RenderPass { depth_stencil_attachment: None, timestamp_writes: None, occlusion_query_set: None, + multiview_mask: None, current_bind_groups: BindGroupStateChange::new(), current_pipeline: StateChange::new(), } @@ -337,6 +344,7 @@ impl fmt::Debug for RenderPass { "push constant u32 count", &self.base.push_constant_data.len(), ) + .field("multiview mask", &self.multiview_mask) .finish() } } @@ -779,6 +787,8 @@ pub enum RenderPassErrorInner { "Multiview pass texture views with more than one array layer must have D2Array dimension" )] MultiViewDimensionMismatch, + #[error("Multiview view count limit violated")] + TooManyMultiviewViews, #[error("missing occlusion query set")] MissingOcclusionQuerySet, #[error(transparent)] @@ -880,6 +890,7 @@ impl WebGpuError for RenderPassError { | RenderPassErrorInner::PushConstantOutOfMemory | RenderPassErrorInner::MultiViewMismatch | RenderPassErrorInner::MultiViewDimensionMismatch + | RenderPassErrorInner::TooManyMultiviewViews | RenderPassErrorInner::MissingOcclusionQuerySet | RenderPassErrorInner::PassEnded => return ErrorType::Validation, }; @@ -916,6 +927,7 @@ struct RenderPassInfo { divergent_discarded_depth_stencil_aspect: Option<(wgt::TextureAspect, Arc)>, multiview: Option, + multiview_mask: Option, } impl RenderPassInfo { @@ -967,6 +979,7 @@ impl RenderPassInfo { pending_query_resets: &mut QueryResetMap, pending_discard_init_fixups: &mut SurfacesInDiscardState, snatch_guard: &SnatchGuard<'_>, + multiview_mask: Option, ) -> Result { profiling::scope!("RenderPassInfo::start"); @@ -1011,8 +1024,11 @@ impl RenderPassInfo { } } else { // Multiview is only supported if the feature is enabled - if this_multiview.is_some() { + if let Some(this_multiview) = this_multiview { device.require_features(wgt::Features::MULTIVIEW)?; + if this_multiview.get() > device.limits.max_multiview_view_count { + return Err(RenderPassErrorInner::TooManyMultiviewViews); + } } detected_multiview = Some(this_multiview); @@ -1417,7 +1433,7 @@ impl RenderPassInfo { sample_count, color_attachments: &color_attachments_hal, depth_stencil_attachment: depth_stencil, - multiview, + multiview_mask, timestamp_writes: timestamp_writes_hal, occlusion_query_set: occlusion_query_set_hal, }; @@ -1453,6 +1469,7 @@ impl RenderPassInfo { extent, divergent_discarded_depth_stencil_aspect, multiview, + multiview_mask, }) } @@ -1519,7 +1536,7 @@ impl RenderPassInfo { stencil_ops, clear_value: (0.0, 0), }), - multiview: self.multiview, + multiview_mask: self.multiview_mask, timestamp_writes: None, occlusion_query_set: None, }; @@ -1668,6 +1685,8 @@ impl Global { None }; + arc_desc.multiview_mask = desc.multiview_mask; + Ok(()) } @@ -1686,6 +1705,7 @@ impl Global { color_attachments: ArrayVec::new(), depth_stencil_attachment: None, occlusion_query_set: None, + multiview_mask: None, }; match fill_arc_desc(hub, desc, &mut arc_desc, &cmd_enc.device) { Ok(()) => (RenderPass::new(cmd_enc, arc_desc), None), @@ -1745,6 +1765,7 @@ impl Global { depth_stencil_attachment: Option<&RenderPassDepthStencilAttachment>, timestamp_writes: Option<&PassTimestampWrites>, occlusion_query_set: Option, + multiview_mask: Option, ) { #[cfg(feature = "trace")] { @@ -1766,6 +1787,7 @@ impl Global { target_depth_stencil: depth_stencil_attachment.cloned(), timestamp_writes: timestamp_writes.cloned(), occlusion_query_set_id: occlusion_query_set, + multiview_mask, }); } } @@ -1787,6 +1809,7 @@ impl Global { depth_stencil_attachment, timestamp_writes, occlusion_query_set, + multiview_mask, }, ); if let Some(err) = encoder_error { @@ -1844,6 +1867,7 @@ impl Global { depth_stencil_attachment: pass.depth_stencil_attachment.take(), timestamp_writes: pass.timestamp_writes.take(), occlusion_query_set: pass.occlusion_query_set.take(), + multiview_mask: pass.multiview_mask, }) }) } @@ -1856,6 +1880,7 @@ pub(super) fn encode_render_pass( mut depth_stencil_attachment: Option, mut timestamp_writes: Option, occlusion_query_set: Option>, + multiview_mask: Option, ) -> Result<(), RenderPassError> { let pass_scope = PassErrorScope::Pass; @@ -1894,6 +1919,7 @@ pub(super) fn encode_render_pass( &mut pending_query_resets, &mut pending_discard_init_fixups, parent_state.snatch_guard, + multiview_mask, ) .map_pass_err(pass_scope)?; @@ -2645,6 +2671,16 @@ fn draw_indexed( .limits .validate_instance_limit(first_instance, instance_count)?; + if state.info.multiview.is_some() + && first_instance + instance_count + > state.pass.base.device.limits.max_multiview_instance_index + 1 + { + return Err(DrawError::InstanceBeyondMultiviewLimit { + last_instance: (first_instance + instance_count) as u64, + instance_limit: (state.pass.base.device.limits.max_multiview_instance_index + 1) as u64, + }); + } + unsafe { if instance_count > 0 && index_count > 0 { state.pass.base.raw_encoder.draw_indexed( @@ -2668,6 +2704,21 @@ fn draw_mesh_tasks( api_log!("RenderPass::draw_mesh_tasks {group_count_x} {group_count_y} {group_count_z}"); state.is_ready(DrawCommandFamily::DrawMeshTasks)?; + if let Some(mv) = state.info.multiview { + if !state + .pass + .base + .device + .features + .contains(wgt::Features::EXPERIMENTAL_MESH_SHADER_MULTIVIEW) + || mv.get() > state.pass.base.device.limits.max_mesh_multiview_view_count + { + return Err(DrawError::MeshPipelineMultiviewLimitsViolated { + views_given: mv.get(), + max_multiviews: state.pass.base.device.limits.max_mesh_multiview_view_count, + }); + } + } let groups_size_limit = state .pass diff --git a/wgpu-hal/Cargo.toml b/wgpu-hal/Cargo.toml index 3b8c37c861e..ed272e9ef90 100644 --- a/wgpu-hal/Cargo.toml +++ b/wgpu-hal/Cargo.toml @@ -99,7 +99,6 @@ vulkan = [ "dep:ordered-float", "dep:parking_lot", "dep:profiling", - "dep:smallvec", "dep:windows", "windows/Win32", ] @@ -233,7 +232,7 @@ glow = { workspace = true, optional = true } ash = { workspace = true, optional = true } gpu-alloc = { workspace = true, optional = true } gpu-descriptor = { workspace = true, optional = true } -smallvec = { workspace = true, optional = true, features = ["union"] } +smallvec = { workspace = true, features = ["union"] } # Backend: GLES khronos-egl = { workspace = true, features = ["dynamic"], optional = true } libloading = { workspace = true, optional = true } diff --git a/wgpu-hal/examples/halmark/main.rs b/wgpu-hal/examples/halmark/main.rs index c70e37bc12b..7b8f44b6550 100644 --- a/wgpu-hal/examples/halmark/main.rs +++ b/wgpu-hal/examples/halmark/main.rs @@ -727,7 +727,7 @@ impl Example { }, })], depth_stencil_attachment: None, - multiview: None, + multiview_mask: None, timestamp_writes: None, occlusion_query_set: None, }; diff --git a/wgpu-hal/examples/raw-gles.rs b/wgpu-hal/examples/raw-gles.rs index e90561efe19..bda0d345875 100644 --- a/wgpu-hal/examples/raw-gles.rs +++ b/wgpu-hal/examples/raw-gles.rs @@ -329,7 +329,7 @@ fn fill_screen(exposed: &hal::ExposedAdapter, width: u32, height clear_value: wgpu_types::Color::BLUE, })], depth_stencil_attachment: None, - multiview: None, + multiview_mask: None, timestamp_writes: None, occlusion_query_set: None, }; diff --git a/wgpu-hal/src/dx12/adapter.rs b/wgpu-hal/src/dx12/adapter.rs index 140dcf60a80..bd4405009b2 100644 --- a/wgpu-hal/src/dx12/adapter.rs +++ b/wgpu-hal/src/dx12/adapter.rs @@ -172,9 +172,9 @@ impl super::Adapter { && features2.DepthBoundsTestSupported.as_bool() }; - let casting_fully_typed_format_supported = { + let (casting_fully_typed_format_supported, view_instancing) = { let mut features3 = Direct3D12::D3D12_FEATURE_DATA_D3D12_OPTIONS3::default(); - unsafe { + if unsafe { device.CheckFeatureSupport( Direct3D12::D3D12_FEATURE_D3D12_OPTIONS3, <*mut _>::cast(&mut features3), @@ -182,7 +182,14 @@ impl super::Adapter { ) } .is_ok() - && features3.CastingFullyTypedFormatSupported.as_bool() + { + ( + features3.CastingFullyTypedFormatSupported.as_bool(), + features3.ViewInstancingTier.0 >= Direct3D12::D3D12_VIEW_INSTANCING_TIER_1.0, + ) + } else { + (false, false) + } }; let heap_create_not_zeroed = { @@ -545,6 +552,8 @@ impl super::Adapter { mesh_shader_supported, ); + features.set(wgt::Features::MULTIVIEW, view_instancing); + // TODO: Determine if IPresentationManager is supported let presentation_timer = auxil::dxgi::time::PresentationTimer::new_dxgi(); @@ -670,7 +679,7 @@ impl super::Adapter { max_task_workgroups_per_dimension: Direct3D12::D3D12_CS_DISPATCH_MAX_THREAD_GROUPS_PER_DIMENSION, // Multiview not supported by WGPU yet - max_mesh_multiview_count: 0, + max_mesh_multiview_view_count: 0, // This seems to be right, and I can't find anything to suggest it would be less than the 2048 provided here max_mesh_output_layers: Direct3D12::D3D12_REQ_TEXTURE2D_ARRAY_AXIS_DIMENSION, @@ -694,6 +703,13 @@ impl super::Adapter { } else { 0 }, + + // See https://microsoft.github.io/DirectX-Specs/d3d/ViewInstancing.html#maximum-viewinstancecount + // This is really frickin annoying, 6 (probably 8) for cube mapping would be really nice. But they + // arbitrarily chose 4, eliminating tons of use cases. + max_multiview_view_count: if view_instancing { 4 } else { 0 }, + // This limit is specific to vulkan + max_multiview_instance_index: if view_instancing { u32::MAX } else { 0 }, }, alignments: crate::Alignments { buffer_copy_offset: wgt::BufferSize::new( diff --git a/wgpu-hal/src/dx12/command.rs b/wgpu-hal/src/dx12/command.rs index 19b06867f84..3717bc9dd35 100644 --- a/wgpu-hal/src/dx12/command.rs +++ b/wgpu-hal/src/dx12/command.rs @@ -887,6 +887,14 @@ impl crate::CommandEncoder for super::CommandEncoder { } } + if let Some(multiview_mask) = desc.multiview_mask { + unsafe { + list.cast::() + .unwrap() + .SetViewInstanceMask(multiview_mask.get()); + } + } + let raw_vp = Direct3D12::D3D12_VIEWPORT { TopLeftX: 0.0, TopLeftY: 0.0, diff --git a/wgpu-hal/src/dynamic/command.rs b/wgpu-hal/src/dynamic/command.rs index 43205c01d2f..a4fbfdbf64e 100644 --- a/wgpu-hal/src/dynamic/command.rs +++ b/wgpu-hal/src/dynamic/command.rs @@ -415,7 +415,7 @@ impl DynCommandEncoder for C { .depth_stencil_attachment .as_ref() .map(|ds| ds.expect_downcast()), - multiview: desc.multiview, + multiview_mask: desc.multiview_mask, timestamp_writes: desc .timestamp_writes .as_ref() diff --git a/wgpu-hal/src/gles/adapter.rs b/wgpu-hal/src/gles/adapter.rs index 806f5567ba2..e3507b3ad09 100644 --- a/wgpu-hal/src/gles/adapter.rs +++ b/wgpu-hal/src/gles/adapter.rs @@ -803,13 +803,16 @@ impl super::Adapter { max_task_workgroup_total_count: 0, max_task_workgroups_per_dimension: 0, - max_mesh_multiview_count: 0, + max_mesh_multiview_view_count: 0, max_mesh_output_layers: 0, max_blas_primitive_count: 0, max_blas_geometry_count: 0, max_tlas_instance_count: 0, max_acceleration_structures_per_shader_stage: 0, + + max_multiview_view_count: 0, + max_multiview_instance_index: 0, }; let mut workarounds = super::Workarounds::empty(); diff --git a/wgpu-hal/src/lib.rs b/wgpu-hal/src/lib.rs index a614098240d..0031f831884 100644 --- a/wgpu-hal/src/lib.rs +++ b/wgpu-hal/src/lib.rs @@ -2491,7 +2491,7 @@ pub struct RenderPassDescriptor<'a, Q: DynQuerySet + ?Sized, T: DynTextureView + pub sample_count: u32, pub color_attachments: &'a [Option>], pub depth_stencil_attachment: Option>, - pub multiview: Option, + pub multiview_mask: Option, pub timestamp_writes: Option>, pub occlusion_query_set: Option<&'a Q>, } diff --git a/wgpu-hal/src/metal/adapter.rs b/wgpu-hal/src/metal/adapter.rs index 47ffbd3c6c1..30d899672db 100644 --- a/wgpu-hal/src/metal/adapter.rs +++ b/wgpu-hal/src/metal/adapter.rs @@ -902,6 +902,18 @@ impl super::PrivateCapabilities { && (device.supports_family(MTLGPUFamily::Apple7) || device.supports_family(MTLGPUFamily::Mac2)), supports_shared_event: version.at_least((10, 14), (12, 0), os_is_mac), + supported_vertex_amplification_factor: { + let mut factor = 1; + // https://developer.apple.com/metal/Metal-Feature-Set-Tables.pdf#page=8 + // The table specifies either none, 2, 8, or unsupported, implying it is a relatively small power of 2 + // The bitmask only uses 32 bits, so it can't be higher even if the device for some reason claims to support that. + loop { + if factor >= 32 || !device.supports_vertex_amplification_count(factor * 2) { + break factor as u32; + } + factor *= 2 + } + }, } } @@ -1001,6 +1013,10 @@ impl super::PrivateCapabilities { features.insert(F::SUBGROUP | F::SUBGROUP_BARRIER); } + if self.supported_vertex_amplification_factor > 1 { + features.insert(F::MULTIVIEW); + } + features } @@ -1031,7 +1047,6 @@ impl super::PrivateCapabilities { downlevel .flags .set(wgt::DownlevelFlags::ANISOTROPIC_FILTERING, true); - let base = wgt::Limits::default(); crate::Capabilities { limits: wgt::Limits { @@ -1079,7 +1094,7 @@ impl super::PrivateCapabilities { max_task_workgroup_total_count: 0, max_task_workgroups_per_dimension: 0, - max_mesh_multiview_count: 0, + max_mesh_multiview_view_count: 0, max_mesh_output_layers: 0, max_blas_primitive_count: 0, // When added: 2^28 from https://developer.apple.com/documentation/metal/mtlaccelerationstructureusage/extendedlimits @@ -1092,6 +1107,17 @@ impl super::PrivateCapabilities { // > [Acceleration structures] are opaque objects that can be bound directly using // buffer binding points or via argument buffers max_acceleration_structures_per_shader_stage: 0, + + max_multiview_view_count: if self.supported_vertex_amplification_factor > 1 { + self.supported_vertex_amplification_factor + } else { + 0 + }, + max_multiview_instance_index: if self.supported_vertex_amplification_factor > 1 { + u32::MAX + } else { + 0 + }, }, alignments: crate::Alignments { buffer_copy_offset: wgt::BufferSize::new(self.buffer_alignment).unwrap(), diff --git a/wgpu-hal/src/metal/command.rs b/wgpu-hal/src/metal/command.rs index 5888eb4e909..c79af76ae67 100644 --- a/wgpu-hal/src/metal/command.rs +++ b/wgpu-hal/src/metal/command.rs @@ -9,6 +9,7 @@ use metal::{ MTLIndexType, MTLLoadAction, MTLPrimitiveType, MTLScissorRect, MTLSize, MTLStoreAction, MTLViewport, MTLVisibilityResultMode, NSRange, }; +use smallvec::SmallVec; // has to match `Temp::binding_sizes` const WORD_SIZE: usize = 4; @@ -653,9 +654,30 @@ impl crate::CommandEncoder for super::CommandEncoder { descriptor .set_visibility_result_buffer(Some(occlusion_query_set.raw_buffer.as_ref())) } - + if let Some(mv) = desc.multiview_mask { + descriptor.set_render_target_array_length(32 - mv.leading_zeros() as u64); + } let raw = self.raw_cmd_buf.as_ref().unwrap(); let encoder = raw.new_render_command_encoder(descriptor); + if let Some(mv) = desc.multiview_mask { + // Here we unpack the multiview bitmask. I'm not entirely sure why Apple makes us do this. + // Most likely the API just wasn't thought about enough. It's not like they ever allow you + // to use enough views to overflow a 32-bit bitmask. + let mv = mv.get(); + let msb = 31 - mv.leading_zeros(); + let mut maps: SmallVec<[metal::VertexAmplificationViewMapping; 32]> = + SmallVec::new(); + for i in 0..=msb { + if (mv & (1 << i)) != 0 { + maps.push(metal::VertexAmplificationViewMapping { + renderTargetArrayIndexOffset: i, + // WGPU doesn't allow rendering to multiple viewports in a single pass + viewportArrayIndexOffset: 0, + }); + } + } + encoder.set_vertex_amplification_count(mv.count_ones() as u64, Some(&maps)); + } if let Some(label) = desc.label { encoder.set_label(label); } diff --git a/wgpu-hal/src/metal/device.rs b/wgpu-hal/src/metal/device.rs index 9f994e6302f..2c744470792 100644 --- a/wgpu-hal/src/metal/device.rs +++ b/wgpu-hal/src/metal/device.rs @@ -1345,6 +1345,10 @@ impl crate::Device for super::Device { descriptor.set_label(name); } + if let Some(mv) = desc.multiview { + descriptor.set_max_vertex_amplification_count(mv.get() as u64); + } + let raw = self .shared .device diff --git a/wgpu-hal/src/metal/mod.rs b/wgpu-hal/src/metal/mod.rs index 00223b2f778..83c5994766c 100644 --- a/wgpu-hal/src/metal/mod.rs +++ b/wgpu-hal/src/metal/mod.rs @@ -300,6 +300,7 @@ struct PrivateCapabilities { int64_atomics: bool, float_atomics: bool, supports_shared_event: bool, + supported_vertex_amplification_factor: u32, } #[derive(Clone, Debug)] diff --git a/wgpu-hal/src/noop/mod.rs b/wgpu-hal/src/noop/mod.rs index 4d06e04331e..92cc1cb91be 100644 --- a/wgpu-hal/src/noop/mod.rs +++ b/wgpu-hal/src/noop/mod.rs @@ -192,15 +192,18 @@ pub const CAPABILITIES: crate::Capabilities = { max_push_constant_size: ALLOC_MAX_U32, max_non_sampler_bindings: ALLOC_MAX_U32, - max_task_workgroup_total_count: 0, - max_task_workgroups_per_dimension: 0, - max_mesh_multiview_count: 0, - max_mesh_output_layers: 0, + max_task_workgroup_total_count: ALLOC_MAX_U32, + max_task_workgroups_per_dimension: ALLOC_MAX_U32, + max_mesh_multiview_view_count: ALLOC_MAX_U32, + max_mesh_output_layers: ALLOC_MAX_U32, max_blas_primitive_count: ALLOC_MAX_U32, max_blas_geometry_count: ALLOC_MAX_U32, max_tlas_instance_count: ALLOC_MAX_U32, max_acceleration_structures_per_shader_stage: ALLOC_MAX_U32, + + max_multiview_view_count: ALLOC_MAX_U32, + max_multiview_instance_index: ALLOC_MAX_U32, }, alignments: crate::Alignments { // All maximally permissive diff --git a/wgpu-hal/src/vulkan/adapter.rs b/wgpu-hal/src/vulkan/adapter.rs index 9c007bf6ef9..c7715c2b51b 100644 --- a/wgpu-hal/src/vulkan/adapter.rs +++ b/wgpu-hal/src/vulkan/adapter.rs @@ -962,6 +962,10 @@ pub struct PhysicalDeviceProperties { /// `VK_EXT_mesh_shader` extension. mesh_shader: Option>, + /// Additional `vk::PhysicalDevice` properties from the + /// `VK_KHR_multiview` extension. + multiview: Option>, + /// The device API version. /// /// Which is the version of Vulkan supported for device-level functionality. @@ -1191,7 +1195,7 @@ impl PhysicalDeviceProperties { let ( max_task_workgroup_total_count, max_task_workgroups_per_dimension, - max_mesh_multiview_count, + max_mesh_multiview_view_count, max_mesh_output_layers, ) = match self.mesh_shader { Some(m) => ( @@ -1253,6 +1257,17 @@ impl PhysicalDeviceProperties { properties.max_per_stage_descriptor_acceleration_structures; } + let (max_multiview_view_count, max_multiview_instance_index) = + if let Some(properties) = self.multiview { + ( + // The bitmask only uses 32 bits, so it can't be higher even if the device for some reason claims to support that. + properties.max_multiview_view_count.min(32), + properties.max_multiview_instance_index, + ) + } else { + (0, 0) + }; + wgt::Limits { max_texture_dimension_1d: limits.max_image_dimension1_d, max_texture_dimension_2d: limits.max_image_dimension2_d, @@ -1313,13 +1328,16 @@ impl PhysicalDeviceProperties { max_task_workgroup_total_count, max_task_workgroups_per_dimension, - max_mesh_multiview_count, + max_mesh_multiview_view_count, max_mesh_output_layers, max_blas_primitive_count, max_blas_geometry_count, max_tlas_instance_count, max_acceleration_structures_per_shader_stage, + + max_multiview_view_count, + max_multiview_instance_index, } } @@ -1378,6 +1396,9 @@ impl super::InstanceShared { capabilities.properties = unsafe { self.raw.get_physical_device_properties(phd) }; capabilities.device_api_version = capabilities.properties.api_version; + let supports_multiview = capabilities.device_api_version >= vk::API_VERSION_1_1 + || capabilities.supports_extension(khr::multiview::NAME); + if let Some(ref get_device_properties) = self.get_physical_device_properties { // Get these now to avoid borrowing conflicts later let supports_maintenance3 = capabilities.device_api_version >= vk::API_VERSION_1_1 @@ -1455,6 +1476,13 @@ impl super::InstanceShared { properties2 = properties2.push_next(next); } + if supports_multiview { + let next = capabilities + .multiview + .insert(vk::PhysicalDeviceMultiviewProperties::default()); + properties2 = properties2.push_next(next); + } + unsafe { get_device_properties.get_physical_device_properties2(phd, &mut properties2) }; diff --git a/wgpu-hal/src/vulkan/command.rs b/wgpu-hal/src/vulkan/command.rs index 651fdcf0cc6..fea5667f9a8 100644 --- a/wgpu-hal/src/vulkan/command.rs +++ b/wgpu-hal/src/vulkan/command.rs @@ -784,7 +784,7 @@ impl crate::CommandEncoder for super::CommandEncoder { colors: ArrayVec::default(), depth_stencil: None, sample_count: desc.sample_count, - multiview: desc.multiview, + multiview_mask: desc.multiview_mask, }; let mut fb_key = super::FramebufferKey { raw_pass: vk::RenderPass::null(), @@ -825,15 +825,6 @@ impl crate::CommandEncoder for super::CommandEncoder { vk_clear_values.push(unsafe { mem::zeroed() }); fb_key.push_view(at.view.identified_raw_view()); } - - // Assert this attachment is valid for the detected multiview, as a sanity check - // The driver crash for this is really bad on AMD, so the check is worth it - if let Some(multiview) = desc.multiview { - assert_eq!(cat.target.view.layers, multiview); - if let Some(ref resolve_target) = cat.resolve_target { - assert_eq!(resolve_target.view.layers, multiview); - } - } } else { rp_key.colors.push(None); } @@ -850,12 +841,6 @@ impl crate::CommandEncoder for super::CommandEncoder { stencil_ops: ds.stencil_ops, }); fb_key.push_view(ds.target.view.identified_raw_view()); - - // Assert this attachment is valid for the detected multiview, as a sanity check - // The driver crash for this is really bad on AMD, so the check is worth it - if let Some(multiview) = desc.multiview { - assert_eq!(ds.target.view.layers, multiview); - } } let render_area = vk::Rect2D { diff --git a/wgpu-hal/src/vulkan/device.rs b/wgpu-hal/src/vulkan/device.rs index 1a710cb2d99..7049261308a 100644 --- a/wgpu-hal/src/vulkan/device.rs +++ b/wgpu-hal/src/vulkan/device.rs @@ -84,7 +84,7 @@ impl super::DeviceShared { ref colors, ref depth_stencil, sample_count, - multiview, + multiview_mask, } = *e.key(); let mut vk_attachments = Vec::new(); @@ -209,15 +209,11 @@ impl super::DeviceShared { let mut multiview_info; let mask; - if let Some(multiview) = multiview { - // Sanity checks, better to panic here than cause a driver crash - assert!(multiview.get() <= 8); - assert!(multiview.get() > 1); - + if let Some(multiview_mask) = multiview_mask { // Right now we enable all bits on the view masks and correlation masks. // This means we're rendering to all views in the subpass, and that all views // can be rendered concurrently. - mask = [(1 << multiview.get()) - 1]; + mask = [multiview_mask.get()]; // On Vulkan 1.1 or later, this is an alias for core functionality multiview_info = vk::RenderPassMultiviewCreateInfoKHR::default() @@ -1367,7 +1363,7 @@ impl crate::Device for super::Device { Ok(super::TextureView { raw_texture: texture.raw, raw, - layers, + _layers: layers, format: desc.format, raw_format, base_mip_level: desc.range.base_mip_level, @@ -1991,9 +1987,12 @@ impl crate::Device for super::Device { vk::DynamicState::BLEND_CONSTANTS, vk::DynamicState::STENCIL_REFERENCE, ]; + let multiview_mask = desc + .multiview + .map(|v| NonZeroU32::new((1u32 << v.get()) - 1).unwrap()); let mut compatible_rp_key = super::RenderPassKey { sample_count: desc.multisample.count, - multiview: desc.multiview, + multiview_mask, ..Default::default() }; let mut stages = ArrayVec::<_, { crate::MAX_CONCURRENT_SHADER_STAGES }>::new(); diff --git a/wgpu-hal/src/vulkan/mod.rs b/wgpu-hal/src/vulkan/mod.rs index 183950b71f0..c82a06d7a7a 100644 --- a/wgpu-hal/src/vulkan/mod.rs +++ b/wgpu-hal/src/vulkan/mod.rs @@ -707,7 +707,7 @@ struct RenderPassKey { colors: ArrayVec, { crate::MAX_COLOR_ATTACHMENTS }>, depth_stencil: Option, sample_count: u32, - multiview: Option, + multiview_mask: Option, } struct DeviceShared { @@ -975,7 +975,7 @@ impl Texture { pub struct TextureView { raw_texture: vk::Image, raw: vk::ImageView, - layers: NonZeroU32, + _layers: NonZeroU32, format: wgt::TextureFormat, raw_format: vk::Format, base_mip_level: u32, diff --git a/wgpu-info/src/human.rs b/wgpu-info/src/human.rs index f0930bd0212..b1e4e7db1e4 100644 --- a/wgpu-info/src/human.rs +++ b/wgpu-info/src/human.rs @@ -163,13 +163,16 @@ fn print_adapter(output: &mut impl io::Write, report: &AdapterReport, idx: usize max_task_workgroup_total_count, max_task_workgroups_per_dimension, - max_mesh_multiview_count, + max_mesh_multiview_view_count: max_mesh_multiview_count, max_mesh_output_layers, max_blas_primitive_count, max_blas_geometry_count, max_tlas_instance_count, max_acceleration_structures_per_shader_stage, + + max_multiview_view_count, + max_multiview_instance_index } = limits; writeln!(output, "\t\t Max Texture Dimension 1d: {max_texture_dimension_1d}")?; writeln!(output, "\t\t Max Texture Dimension 2d: {max_texture_dimension_2d}")?; @@ -209,13 +212,16 @@ fn print_adapter(output: &mut impl io::Write, report: &AdapterReport, idx: usize writeln!(output, "\t\t Max Task Workgroup Total Count: {max_task_workgroup_total_count}")?; writeln!(output, "\t\t Max Task Workgroups Per Dimension: {max_task_workgroups_per_dimension}")?; - writeln!(output, "\t\t Max Mesh Multiview Count: {max_mesh_multiview_count}")?; + writeln!(output, "\t\t Max Mesh Multiview View Count: {max_mesh_multiview_count}")?; writeln!(output, "\t\t Max Mesh Output Layers: {max_mesh_output_layers}")?; writeln!(output, "\t\t Max BLAS Primitive count: {max_blas_primitive_count}")?; writeln!(output, "\t\t Max BLAS Geometry count: {max_blas_geometry_count}")?; writeln!(output, "\t\t Max TLAS Instance count: {max_tlas_instance_count}")?; writeln!(output, "\t\t Max Acceleration Structures Per Shader Stage: {max_acceleration_structures_per_shader_stage}")?; + + writeln!(output, "\t\t Max Multiview View Count: {max_multiview_view_count}")?; + writeln!(output, "\t\t Max Multiview Instance Index: {max_multiview_instance_index}")?; // This one reflects more of a wgpu implementation limitations than a hardware limit // so don't show it here. let _ = max_non_sampler_bindings; diff --git a/wgpu-types/src/lib.rs b/wgpu-types/src/lib.rs index f397bed3616..cb467e7a7a6 100644 --- a/wgpu-types/src/lib.rs +++ b/wgpu-types/src/lib.rs @@ -519,12 +519,15 @@ macro_rules! with_limits { $macro_name!(max_task_workgroup_total_count, Ordering::Less); $macro_name!(max_task_workgroups_per_dimension, Ordering::Less); - $macro_name!(max_mesh_multiview_count, Ordering::Less); + $macro_name!(max_mesh_multiview_view_count, Ordering::Less); $macro_name!(max_mesh_output_layers, Ordering::Less); $macro_name!(max_blas_primitive_count, Ordering::Less); $macro_name!(max_blas_geometry_count, Ordering::Less); $macro_name!(max_tlas_instance_count, Ordering::Less); + + $macro_name!(max_multiview_view_count, Ordering::Less); + $macro_name!(max_multiview_instance_index, Ordering::Less); }; } @@ -694,8 +697,8 @@ pub struct Limits { pub max_task_workgroups_per_dimension: u32, /// The maximum number of layers that can be output from a mesh shader pub max_mesh_output_layers: u32, - /// The maximum number of views that can be used by a mesh shader - pub max_mesh_multiview_count: u32, + /// The maximum number of views that can be used by a mesh shader in multiview rendering + pub max_mesh_multiview_view_count: u32, /// The maximum number of primitive (ex: triangles, aabbs) a BLAS is allowed to have. Requesting /// more than 0 during device creation only makes sense if [`Features::EXPERIMENTAL_RAY_QUERY`] @@ -713,6 +716,13 @@ pub struct Limits { /// Requesting more than 0 during device creation only makes sense if [`Features::EXPERIMENTAL_RAY_QUERY`] /// is enabled. pub max_acceleration_structures_per_shader_stage: u32, + + /// The maximum number of views that can be used in multiview rendering + pub max_multiview_view_count: u32, + /// For an instanced draw call using multiview, the maximum instance index. For example, + /// if draw is called with instances a..b, then b must be <= this limit + 1. + /// Note that this is NOT just the maximum total number of instances. + pub max_multiview_instance_index: u32, } impl Default for Limits { @@ -767,12 +777,14 @@ impl Limits { /// max_non_sampler_bindings: 1_000_000, /// max_task_workgroup_total_count: 0, /// max_task_workgroups_per_dimension: 0, - /// max_mesh_multiview_count: 0, + /// max_mesh_multiview_view_count: 0, /// max_mesh_output_layers: 0, /// max_blas_primitive_count: 0, /// max_blas_geometry_count: 0, /// max_tlas_instance_count: 0, /// max_acceleration_structures_per_shader_stage: 0, + /// max_multiview_view_count: 0, + /// max_multiview_instance_index: 0, /// }); /// ``` /// @@ -820,13 +832,16 @@ impl Limits { max_task_workgroup_total_count: 0, max_task_workgroups_per_dimension: 0, - max_mesh_multiview_count: 0, + max_mesh_multiview_view_count: 0, max_mesh_output_layers: 0, max_blas_primitive_count: 0, max_blas_geometry_count: 0, max_tlas_instance_count: 0, max_acceleration_structures_per_shader_stage: 0, + + max_multiview_view_count: 0, + max_multiview_instance_index: 0, } } @@ -875,13 +890,16 @@ impl Limits { /// /// max_task_workgroup_total_count: 0, /// max_task_workgroups_per_dimension: 0, - /// max_mesh_multiview_count: 0, + /// max_mesh_multiview_view_count: 0, /// max_mesh_output_layers: 0, /// /// max_blas_primitive_count: 0, /// max_blas_geometry_count: 0, /// max_tlas_instance_count: 0, /// max_acceleration_structures_per_shader_stage: 0, + /// + /// max_multiview_view_count: 0, + /// max_multiview_instance_index: 0, /// }); /// ``` #[must_use] @@ -898,7 +916,7 @@ impl Limits { max_task_workgroups_per_dimension: 0, max_task_workgroup_total_count: 0, - max_mesh_multiview_count: 0, + max_mesh_multiview_view_count: 0, max_mesh_output_layers: 0, ..Self::defaults() } @@ -950,13 +968,16 @@ impl Limits { /// /// max_task_workgroup_total_count: 0, /// max_task_workgroups_per_dimension: 0, - /// max_mesh_multiview_count: 0, + /// max_mesh_multiview_view_count: 0, /// max_mesh_output_layers: 0, /// /// max_blas_primitive_count: 0, /// max_blas_geometry_count: 0, /// max_tlas_instance_count: 0, /// max_acceleration_structures_per_shader_stage: 0, + /// + /// max_multiview_view_count: 0, + /// max_multiview_instance_index: 0, /// }); /// ``` #[must_use] @@ -1051,7 +1072,7 @@ impl Limits { max_task_workgroup_total_count: 65536, max_task_workgroups_per_dimension: 256, // llvmpipe reports 0 multiview count, which just means no multiview is allowed - max_mesh_multiview_count: 0, + max_mesh_multiview_view_count: 0, // llvmpipe once again requires this to be 8. An RTX 3060 supports well over 1024. max_mesh_output_layers: 8, ..self diff --git a/wgpu/src/api/render_pass.rs b/wgpu/src/api/render_pass.rs index c8cc56018eb..5d4f6635278 100644 --- a/wgpu/src/api/render_pass.rs +++ b/wgpu/src/api/render_pass.rs @@ -1,4 +1,4 @@ -use core::ops::Range; +use core::{num::NonZeroU32, ops::Range}; use crate::{ api::{impl_deferred_command_buffer_actions, SharedDeferredCommandBufferActions}, @@ -654,6 +654,8 @@ pub struct RenderPassDescriptor<'a> { pub timestamp_writes: Option>, /// Defines where the occlusion query results will be stored for this pass. pub occlusion_query_set: Option<&'a QuerySet>, + /// The mask of multiview image layers to use for this render pass + pub multiview_mask: Option, } #[cfg(send_sync)] static_assertions::assert_impl_all!(RenderPassDescriptor<'_>: Send, Sync); diff --git a/wgpu/src/backend/webgpu.rs b/wgpu/src/backend/webgpu.rs index 920ad58ba17..8de2e4a9901 100644 --- a/wgpu/src/backend/webgpu.rs +++ b/wgpu/src/backend/webgpu.rs @@ -828,13 +828,16 @@ fn map_wgt_limits(limits: webgpu_sys::GpuSupportedLimits) -> wgt::Limits { max_task_workgroup_total_count: wgt::Limits::default().max_task_workgroup_total_count, max_task_workgroups_per_dimension: wgt::Limits::default().max_task_workgroups_per_dimension, max_mesh_output_layers: wgt::Limits::default().max_mesh_output_layers, - max_mesh_multiview_count: wgt::Limits::default().max_mesh_multiview_count, + max_mesh_multiview_view_count: wgt::Limits::default().max_mesh_multiview_view_count, max_blas_primitive_count: wgt::Limits::default().max_blas_primitive_count, max_blas_geometry_count: wgt::Limits::default().max_blas_geometry_count, max_tlas_instance_count: wgt::Limits::default().max_tlas_instance_count, max_acceleration_structures_per_shader_stage: wgt::Limits::default() .max_acceleration_structures_per_shader_stage, + + max_multiview_view_count: wgt::Limits::default().max_multiview_view_count, + max_multiview_instance_index: wgt::Limits::default().max_multiview_instance_index, } } diff --git a/wgpu/src/backend/wgpu_core.rs b/wgpu/src/backend/wgpu_core.rs index 074b1239029..cffb8826481 100644 --- a/wgpu/src/backend/wgpu_core.rs +++ b/wgpu/src/backend/wgpu_core.rs @@ -2536,6 +2536,7 @@ impl dispatch::CommandEncoderInterface for CoreCommandEncoder { color_attachments: Borrowed(&colors), depth_stencil_attachment: depth_stencil.as_ref(), occlusion_query_set: desc.occlusion_query_set.map(|qs| qs.inner.as_core().id), + multiview_mask: desc.multiview_mask, }, ); diff --git a/wgpu/src/util/texture_blitter.rs b/wgpu/src/util/texture_blitter.rs index e6b46e02859..edabd905be6 100644 --- a/wgpu/src/util/texture_blitter.rs +++ b/wgpu/src/util/texture_blitter.rs @@ -209,6 +209,7 @@ impl TextureBlitter { depth_stencil_attachment: None, timestamp_writes: None, occlusion_query_set: None, + multiview_mask: None, }); pass.set_pipeline(&self.pipeline); pass.set_bind_group(0, &bind_group, &[]);