diff --git a/CHANGELOG.md b/CHANGELOG.md index 95c279bba3d..c004f6d1dec 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -40,7 +40,7 @@ Bottom level categories: ## Unreleased -### Major changes +### Major Changes #### 'wgpu::Instance::enumerate_adapters` is now `async` & available on WebGPU @@ -72,6 +72,32 @@ SamplerDescriptor { ... } ``` + +#### 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 to multiple 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). + ### Changes #### General diff --git a/benches/benches/wgpu-benchmark/renderpass.rs b/benches/benches/wgpu-benchmark/renderpass.rs index 07700d68fce..ba387b7016b 100644 --- a/benches/benches/wgpu-benchmark/renderpass.rs +++ b/benches/benches/wgpu-benchmark/renderpass.rs @@ -224,7 +224,7 @@ impl RenderpassState { })], compilation_options: wgpu::PipelineCompilationOptions::default(), }), - multiview: None, + multiview_mask: None, cache: None, }); @@ -322,7 +322,7 @@ impl RenderpassState { })], compilation_options: wgpu::PipelineCompilationOptions::default(), }), - multiview: None, + multiview_mask: None, cache: None, }, )); @@ -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/device.rs b/deno_webgpu/device.rs index b59fc633cd4..74fcbc8fa93 100644 --- a/deno_webgpu/device.rs +++ b/deno_webgpu/device.rs @@ -879,7 +879,7 @@ impl GPUDevice { multisample, fragment, cache: None, - multiview: None, + multiview_mask: None, }; let (id, err) = self.instance.device_create_render_pipeline( 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..f4fabae008d 100644 --- a/examples/features/src/boids/mod.rs +++ b/examples/features/src/boids/mod.rs @@ -148,7 +148,7 @@ impl crate::framework::Example for Example { primitive: wgpu::PrimitiveState::default(), depth_stencil: None, multisample: wgpu::MultisampleState::default(), - multiview: None, + multiview_mask: None, cache: None, }); @@ -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 797a8b90519..86c39d87d0e 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, &[]); @@ -229,7 +230,7 @@ impl crate::framework::Example for Example { }, depth_stencil: None, multisample: wgpu::MultisampleState::default(), - multiview: None, + multiview_mask: None, cache: None, }); diff --git a/examples/features/src/conservative_raster/mod.rs b/examples/features/src/conservative_raster/mod.rs index d2f5c74fdec..751f4b2f6bf 100644 --- a/examples/features/src/conservative_raster/mod.rs +++ b/examples/features/src/conservative_raster/mod.rs @@ -106,7 +106,7 @@ impl crate::framework::Example for Example { }, depth_stencil: None, multisample: wgpu::MultisampleState::default(), - multiview: None, + multiview_mask: None, cache: None, }); @@ -129,7 +129,7 @@ impl crate::framework::Example for Example { primitive: wgpu::PrimitiveState::default(), depth_stencil: None, multisample: wgpu::MultisampleState::default(), - multiview: None, + multiview_mask: None, cache: None, }); @@ -160,7 +160,7 @@ impl crate::framework::Example for Example { }, depth_stencil: None, multisample: wgpu::MultisampleState::default(), - multiview: None, + multiview_mask: None, cache: None, }), ) @@ -217,7 +217,7 @@ impl crate::framework::Example for Example { primitive: wgpu::PrimitiveState::default(), depth_stencil: None, multisample: wgpu::MultisampleState::default(), - multiview: None, + multiview_mask: None, cache: None, }), bind_group_layout, @@ -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..b0421978d23 100644 --- a/examples/features/src/cube/mod.rs +++ b/examples/features/src/cube/mod.rs @@ -256,7 +256,7 @@ impl crate::framework::Example for Example { }, depth_stencil: None, multisample: wgpu::MultisampleState::default(), - multiview: None, + multiview_mask: None, cache: None, }); @@ -298,7 +298,7 @@ impl crate::framework::Example for Example { }, depth_stencil: None, multisample: wgpu::MultisampleState::default(), - multiview: None, + multiview_mask: None, cache: None, }); Some(pipeline_wire) @@ -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..66696dbe815 100644 --- a/examples/features/src/hello_triangle/mod.rs +++ b/examples/features/src/hello_triangle/mod.rs @@ -71,7 +71,7 @@ async fn run(event_loop: EventLoop<()>, window: Window) { primitive: wgpu::PrimitiveState::default(), depth_stencil: None, multisample: wgpu::MultisampleState::default(), - multiview: None, + multiview_mask: None, cache: None, }); @@ -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/lib.rs b/examples/features/src/lib.rs index baacf6a6b39..2299dded725 100644 --- a/examples/features/src/lib.rs +++ b/examples/features/src/lib.rs @@ -17,6 +17,7 @@ pub mod mesh_shader; pub mod mipmap; pub mod msaa_line; pub mod multiple_render_targets; +pub mod multiview; pub mod ray_cube_compute; pub mod ray_cube_fragment; pub mod ray_cube_normals; diff --git a/examples/features/src/main.rs b/examples/features/src/main.rs index c9fc31259c9..c28c7ad5f58 100644 --- a/examples/features/src/main.rs +++ b/examples/features/src/main.rs @@ -188,6 +188,12 @@ const EXAMPLES: &[ExampleDesc] = &[ webgl: false, webgpu: false, }, + ExampleDesc { + name: "multiview", + function: wgpu_examples::multiview::main, + webgl: false, + webgpu: false, + }, ]; fn get_example_name() -> Option { 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 82c965d29d4..67f4a01ff89 100644 --- a/examples/features/src/mipmap/mod.rs +++ b/examples/features/src/mipmap/mod.rs @@ -104,7 +104,7 @@ impl Example { }, depth_stencil: None, multisample: wgpu::MultisampleState::default(), - multiview: None, + multiview_mask: None, cache: None, }); @@ -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); @@ -305,7 +306,7 @@ impl crate::framework::Example for Example { }, depth_stencil: None, multisample: wgpu::MultisampleState::default(), - multiview: None, + multiview_mask: None, cache: None, }); @@ -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..a7a908fd56b 100644 --- a/examples/features/src/msaa_line/mod.rs +++ b/examples/features/src/msaa_line/mod.rs @@ -77,7 +77,7 @@ impl Example { count: sample_count, ..Default::default() }, - multiview: None, + multiview_mask: None, cache: None, }); let mut encoder = @@ -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 0f4d5aeb68d..fbc47258c0f 100644 --- a/examples/features/src/multiple_render_targets/mod.rs +++ b/examples/features/src/multiple_render_targets/mod.rs @@ -161,7 +161,7 @@ impl MultiTargetRenderer { primitive: wgpu::PrimitiveState::default(), depth_stencil: None, multisample: wgpu::MultisampleState::default(), - multiview: None, + multiview_mask: None, cache: None, }); @@ -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, &[]); @@ -266,7 +267,7 @@ impl TargetRenderer { primitive: wgpu::PrimitiveState::default(), depth_stencil: None, multisample: wgpu::MultisampleState::default(), - multiview: None, + multiview_mask: None, cache: None, }); @@ -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/multiview/mod.rs b/examples/features/src/multiview/mod.rs new file mode 100644 index 00000000000..1c7f4861afb --- /dev/null +++ b/examples/features/src/multiview/mod.rs @@ -0,0 +1,214 @@ +use std::{num::NonZero, time::Instant}; + +use wgpu::{ + util::{BufferInitDescriptor, DeviceExt, TextureBlitter}, + vertex_attr_array, +}; + +const TEXTURE_SIZE: u32 = 512; + +// Change this to demonstrate non-contiguous multiview functionality +const LAYERS: u32 = 2; + +pub struct Example { + pipeline: wgpu::RenderPipeline, + vertex_buffer: wgpu::Buffer, + view: wgpu::TextureView, + view1: wgpu::TextureView, + view2: wgpu::TextureView, + start_time: Instant, + blitter: TextureBlitter, +} +impl crate::framework::Example for Example { + fn init( + config: &wgpu::SurfaceConfiguration, + _adapter: &wgpu::Adapter, + device: &wgpu::Device, + _queue: &wgpu::Queue, + ) -> Self { + 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 = device.create_shader_module(wgpu::ShaderModuleDescriptor { + label: None, + source: wgpu::ShaderSource::Wgsl(shader_src.into()), + }); + + let pipeline = device.create_render_pipeline(&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_mask: NonZero::new(1 | (1 << (LAYERS - 1))), + multisample: Default::default(), + layout: None, + depth_stencil: None, + cache: None, + }); + 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 = device.create_buffer_init(&BufferInitDescriptor { + label: None, + contents: bytemuck::cast_slice(vertex_buffer_content), + usage: wgpu::BufferUsages::VERTEX, + }); + let texture = device.create_texture(&wgpu::TextureDescriptor { + label: None, + size: wgpu::Extent3d { + width: TEXTURE_SIZE, + height: TEXTURE_SIZE, + depth_or_array_layers: LAYERS, + }, + mip_level_count: 1, + sample_count: 1, + dimension: wgpu::TextureDimension::D2, + format: wgpu::TextureFormat::R8Unorm, + usage: wgpu::TextureUsages::RENDER_ATTACHMENT + | wgpu::TextureUsages::COPY_SRC + | wgpu::TextureUsages::TEXTURE_BINDING, + 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(LAYERS), + }); + let view1 = texture.create_view(&wgpu::TextureViewDescriptor { + label: None, + format: Some(wgpu::TextureFormat::R8Unorm), + dimension: Some(wgpu::TextureViewDimension::D2), + usage: Some(wgpu::TextureUsages::TEXTURE_BINDING), + aspect: wgpu::TextureAspect::All, + base_mip_level: 0, + mip_level_count: None, + base_array_layer: 0, + array_layer_count: Some(1), + }); + let view2 = texture.create_view(&wgpu::TextureViewDescriptor { + label: None, + format: Some(wgpu::TextureFormat::R8Unorm), + dimension: Some(wgpu::TextureViewDimension::D2), + usage: Some(wgpu::TextureUsages::TEXTURE_BINDING), + aspect: wgpu::TextureAspect::All, + base_mip_level: 0, + mip_level_count: None, + base_array_layer: LAYERS - 1, + array_layer_count: Some(1), + }); + let blitter = wgpu::util::TextureBlitter::new(device, config.format); + Self { + pipeline, + vertex_buffer, + view, + view1, + view2, + blitter, + start_time: Instant::now(), + } + } + fn render(&mut self, view: &wgpu::TextureView, device: &wgpu::Device, queue: &wgpu::Queue) { + let mut encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor::default()); + { + let multiview_mask = 1 | (1 << (LAYERS - 1)); + let mut rpass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { + label: None, + color_attachments: &[Some(wgpu::RenderPassColorAttachment { + view: &self.view, + depth_slice: None, + resolve_target: None, + ops: wgpu::Operations { + load: wgpu::LoadOp::Clear(wgpu::Color { + r: 0.02, + ..wgpu::Color::RED + }), + store: wgpu::StoreOp::Store, + }, + })], + depth_stencil_attachment: None, + timestamp_writes: None, + occlusion_query_set: None, + multiview_mask: NonZero::new(multiview_mask), + }); + rpass.set_pipeline(&self.pipeline); + rpass.set_vertex_buffer(0, self.vertex_buffer.slice(..)); + rpass.draw(0..6, 0..1); + } + if !(Instant::now() - self.start_time) + .as_secs() + .is_multiple_of(2) + { + self.blitter.copy(device, &mut encoder, &self.view1, view); + } else { + self.blitter.copy(device, &mut encoder, &self.view2, view); + } + queue.submit(Some(encoder.finish())); + } + fn required_downlevel_capabilities() -> wgpu::DownlevelCapabilities { + Default::default() + } + fn required_features() -> wgpu::Features { + wgpu::Features::MULTIVIEW + } + fn required_limits() -> wgpu::Limits { + wgpu::Limits { + max_multiview_view_count: LAYERS, + ..Default::default() + } + } + fn resize( + &mut self, + _config: &wgpu::SurfaceConfiguration, + _device: &wgpu::Device, + _queue: &wgpu::Queue, + ) { + // empty + } + fn update(&mut self, _event: winit::event::WindowEvent) { + // empty + } +} + +pub fn main() { + crate::framework::run::("multiview"); +} diff --git a/examples/features/src/ray_cube_compute/mod.rs b/examples/features/src/ray_cube_compute/mod.rs index a120aae6c76..0ba6277577d 100644 --- a/examples/features/src/ray_cube_compute/mod.rs +++ b/examples/features/src/ray_cube_compute/mod.rs @@ -326,7 +326,7 @@ impl crate::framework::Example for Example { }, depth_stencil: None, multisample: wgpu::MultisampleState::default(), - multiview: None, + multiview_mask: None, cache: None, }); @@ -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..6cd8fc48839 100644 --- a/examples/features/src/ray_cube_fragment/mod.rs +++ b/examples/features/src/ray_cube_fragment/mod.rs @@ -216,7 +216,7 @@ impl crate::framework::Example for Example { }, depth_stencil: None, multisample: wgpu::MultisampleState::default(), - multiview: None, + multiview_mask: None, cache: None, }); @@ -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 664bdfec2ed..1c7acf7c377 100644 --- a/examples/features/src/ray_cube_normals/mod.rs +++ b/examples/features/src/ray_cube_normals/mod.rs @@ -317,7 +317,7 @@ impl crate::framework::Example for Example { }, depth_stencil: None, multisample: wgpu::MultisampleState::default(), - multiview: None, + multiview_mask: None, cache: None, }); @@ -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..ab399624099 100644 --- a/examples/features/src/ray_scene/mod.rs +++ b/examples/features/src/ray_scene/mod.rs @@ -395,7 +395,7 @@ impl crate::framework::Example for Example { }, depth_stencil: None, multisample: wgpu::MultisampleState::default(), - multiview: None, + multiview_mask: None, cache: None, }); @@ -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..5a3305ee38f 100644 --- a/examples/features/src/ray_shadows/mod.rs +++ b/examples/features/src/ray_shadows/mod.rs @@ -239,7 +239,7 @@ impl crate::framework::Example for Example { }, depth_stencil: None, multisample: wgpu::MultisampleState::default(), - multiview: None, + multiview_mask: None, cache: None, }); @@ -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 b61d0cc3a1f..698ccfab1df 100644 --- a/examples/features/src/ray_traced_triangle/mod.rs +++ b/examples/features/src/ray_traced_triangle/mod.rs @@ -309,7 +309,7 @@ impl crate::framework::Example for Example { write_mask: Default::default(), })], }), - multiview: None, + multiview_mask: None, cache: None, }); @@ -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..e06c8d638d5 100644 --- a/examples/features/src/render_to_texture/mod.rs +++ b/examples/features/src/render_to_texture/mod.rs @@ -68,7 +68,7 @@ async fn run(_path: Option) { primitive: wgpu::PrimitiveState::default(), depth_stencil: None, multisample: wgpu::MultisampleState::default(), - multiview: None, + multiview_mask: None, cache: None, }); @@ -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 a112e6bd1ae..77be87823ad 100644 --- a/examples/features/src/shadow/mod.rs +++ b/examples/features/src/shadow/mod.rs @@ -518,7 +518,7 @@ impl crate::framework::Example for Example { }, }), multisample: wgpu::MultisampleState::default(), - multiview: None, + multiview_mask: None, cache: None, }); @@ -653,7 +653,7 @@ impl crate::framework::Example for Example { bias: wgpu::DepthBiasState::default(), }), multisample: wgpu::MultisampleState::default(), - multiview: None, + multiview_mask: None, cache: None, }); @@ -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 c45779a37c2..0cca4c6f59f 100644 --- a/examples/features/src/skybox/mod.rs +++ b/examples/features/src/skybox/mod.rs @@ -218,7 +218,7 @@ impl crate::framework::Example for Example { bias: wgpu::DepthBiasState::default(), }), multisample: wgpu::MultisampleState::default(), - multiview: None, + multiview_mask: None, cache: None, }); let entity_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { @@ -252,7 +252,7 @@ impl crate::framework::Example for Example { bias: wgpu::DepthBiasState::default(), }), multisample: wgpu::MultisampleState::default(), - multiview: None, + multiview_mask: None, cache: None, }); @@ -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..cabf4cec52d 100644 --- a/examples/features/src/srgb_blend/mod.rs +++ b/examples/features/src/srgb_blend/mod.rs @@ -146,7 +146,7 @@ impl crate::framework::Example for Example { }, depth_stencil: None, multisample: wgpu::MultisampleState::default(), - multiview: None, + multiview_mask: None, cache: None, }); @@ -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..1a20669e91a 100644 --- a/examples/features/src/stencil_triangles/mod.rs +++ b/examples/features/src/stencil_triangles/mod.rs @@ -100,7 +100,7 @@ impl crate::framework::Example for Example { bias: Default::default(), }), multisample: wgpu::MultisampleState::default(), - multiview: None, + multiview_mask: None, cache: None, }); @@ -136,7 +136,7 @@ impl crate::framework::Example for Example { bias: Default::default(), }), multisample: wgpu::MultisampleState::default(), - multiview: None, + multiview_mask: None, cache: None, }); @@ -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..45942f9a191 100644 --- a/examples/features/src/texture_arrays/mod.rs +++ b/examples/features/src/texture_arrays/mod.rs @@ -358,7 +358,7 @@ impl crate::framework::Example for Example { }, depth_stencil: None, multisample: wgpu::MultisampleState::default(), - multiview: None, + multiview_mask: None, cache: None }); @@ -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..b15977156fe 100644 --- a/examples/features/src/timestamp_queries/mod.rs +++ b/examples/features/src/timestamp_queries/mod.rs @@ -358,7 +358,7 @@ fn render_pass( primitive: wgpu::PrimitiveState::default(), depth_stencil: None, multisample: wgpu::MultisampleState::default(), - multiview: None, + multiview_mask: None, cache: None, }); let render_target = device.create_texture(&wgpu::TextureDescriptor { @@ -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..e4510ab4eb1 100644 --- a/examples/features/src/uniform_values/mod.rs +++ b/examples/features/src/uniform_values/mod.rs @@ -186,7 +186,7 @@ impl WgpuContext { primitive: wgpu::PrimitiveState::default(), depth_stencil: None, multisample: wgpu::MultisampleState::default(), - multiview: None, + multiview_mask: None, cache: None, }); let surface_config = surface @@ -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 e287f6edf61..dc8bccfa441 100644 --- a/examples/features/src/water/mod.rs +++ b/examples/features/src/water/mod.rs @@ -567,7 +567,7 @@ impl crate::framework::Example for Example { }), // No multisampling is used. multisample: wgpu::MultisampleState::default(), - multiview: None, + multiview_mask: None, // No pipeline caching is used cache: None, }); @@ -605,7 +605,7 @@ impl crate::framework::Example for Example { bias: wgpu::DepthBiasState::default(), }), multisample: wgpu::MultisampleState::default(), - multiview: None, + multiview_mask: None, cache: None }); @@ -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..7d1c614bb66 100644 --- a/tests/tests/wgpu-gpu/binding_array/sampled_textures.rs +++ b/tests/tests/wgpu-gpu/binding_array/sampled_textures.rs @@ -207,7 +207,7 @@ async fn binding_array_sampled_textures(ctx: TestingContext, partially_bound: bo depth_stencil: None, multisample: MultisampleState::default(), cache: None, - multiview: None, + multiview_mask: None, }); let mut encoder = ctx @@ -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..50a6b793f31 100644 --- a/tests/tests/wgpu-gpu/clip_distances.rs +++ b/tests/tests/wgpu-gpu/clip_distances.rs @@ -43,7 +43,7 @@ async fn clip_distances(ctx: TestingContext) { write_mask: wgpu::ColorWrites::ALL, })], }), - multiview: None, + multiview_mask: None, cache: None, }); @@ -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..647d3edc330 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()]); @@ -458,7 +459,7 @@ static DEVICE_DESTROY_THEN_MORE: GpuTestConfiguration = GpuTestConfiguration::ne depth_stencil: None, multisample: wgpu::MultisampleState::default(), fragment: None, - multiview: None, + multiview_mask: None, cache: None, }); @@ -616,7 +617,7 @@ static DIFFERENT_BGL_ORDER_BW_SHADER_AND_API: GpuTestConfiguration = GpuTestConf primitive: wgpu::PrimitiveState::default(), depth_stencil: None, multisample: wgpu::MultisampleState::default(), - multiview: None, + multiview_mask: None, cache: None, }); diff --git a/tests/tests/wgpu-gpu/draw_indirect.rs b/tests/tests/wgpu-gpu/draw_indirect.rs index e33ccb83ab7..f9a84b726a7 100644 --- a/tests/tests/wgpu-gpu/draw_indirect.rs +++ b/tests/tests/wgpu-gpu/draw_indirect.rs @@ -207,7 +207,7 @@ async fn run_test(ctx: TestingContext, test_data: TestData, expect_noop: bool) { write_mask: wgpu::ColorWrites::ALL, })], }), - multiview: None, + multiview_mask: None, cache: None, }; let pipeline = ctx.device.create_render_pipeline(&pipeline_desc); @@ -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); @@ -697,7 +698,7 @@ async fn indirect_buffer_offsets(ctx: TestingContext) { write_mask: wgpu::ColorWrites::ALL, })], }), - multiview: None, + multiview_mask: None, cache: None, }; let pipeline = ctx.device.create_render_pipeline(&pipeline_desc); @@ -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/dual_source_blending.rs b/tests/tests/wgpu-gpu/dual_source_blending.rs index eeffa72e524..bbb6c1ff3d4 100644 --- a/tests/tests/wgpu-gpu/dual_source_blending.rs +++ b/tests/tests/wgpu-gpu/dual_source_blending.rs @@ -96,7 +96,7 @@ async fn dual_source_blending_disabled(ctx: TestingContext) { primitive: wgpu::PrimitiveState::default(), depth_stencil: None, multisample: wgpu::MultisampleState::default(), - multiview: None, + multiview_mask: None, cache: None, }); }, @@ -159,7 +159,7 @@ async fn dual_source_blending_enabled(ctx: TestingContext) { primitive: wgpu::PrimitiveState::default(), depth_stencil: None, multisample: wgpu::MultisampleState::default(), - multiview: None, + multiview_mask: None, cache: None, }; 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..11db78a27dd 100644 --- a/tests/tests/wgpu-gpu/mem_leaks.rs +++ b/tests/tests/wgpu-gpu/mem_leaks.rs @@ -126,7 +126,7 @@ async fn draw_test_with_reports( write_mask: wgpu::ColorWrites::ALL, })], }), - multiview: None, + multiview_mask: None, cache: None, }); @@ -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 8a3218970f0..069fa1bf567 100644 --- a/tests/tests/wgpu-gpu/mesh_shader/mod.rs +++ b/tests/tests/wgpu-gpu/mesh_shader/mod.rs @@ -242,6 +242,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); @@ -348,6 +349,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..945e1e2c345 --- /dev/null +++ b/tests/tests/wgpu-gpu/multiview.rs @@ -0,0 +1,228 @@ +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); + vec.push(DRAW_MULTIVIEW_NONCONTIGUOUS); +} + +#[gpu_test] +static DRAW_MULTIVIEW: GpuTestConfiguration = GpuTestConfiguration::new() + .parameters( + TestParameters::default() + .features(Features::MULTIVIEW) + .limits(Limits { + max_multiview_view_count: 2, + ..Limits::defaults() + }), + ) + .run_async(|ctx| run_test(ctx, 2)); + +#[gpu_test] +static DRAW_MULTIVIEW_NONCONTIGUOUS: GpuTestConfiguration = GpuTestConfiguration::new() + .parameters( + TestParameters::default() + .features(Features::MULTIVIEW) + .limits(Limits { + max_multiview_view_count: 4, + ..Limits::defaults() + }), + ) + .run_async(|ctx| run_test(ctx, 4)); + +async fn run_test(ctx: TestingContext, num_layers: u32) { + 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_mask: NonZero::new(1 + (1 << (num_layers - 1))), + 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: num_layers, + }, + 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(num_layers), + }); + let readback_buffer = ctx.device.create_buffer(&wgpu::BufferDescriptor { + label: None, + size: TEXTURE_SIZE as u64 * TEXTURE_SIZE as u64 * num_layers as u64, + usage: wgpu::BufferUsages::COPY_DST | wgpu::BufferUsages::MAP_READ, + mapped_at_creation: false, + }); + + let clear_color = 0.0; + + let mut encoder = ctx + .device + .create_command_encoder(&wgpu::CommandEncoderDescriptor::default()); + { + let multiview_mask = 1 | (1 << (num_layers - 1)); + 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 { + r: clear_color, + g: clear_color, + b: clear_color, + a: clear_color, + }), + store: wgpu::StoreOp::Store, + }, + })], + depth_stencil_attachment: None, + timestamp_writes: None, + occlusion_query_set: None, + multiview_mask: NonZero::new(multiview_mask), + }); + 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: num_layers, + }, + ); + 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 * num_layers as usize); + eprintln!( + "View values: {}, ({}), {}", + data[0], + data[each_texture_size], + data[each_texture_size * (num_layers as usize - 1)] + ); + for view_idx in 0..num_layers as usize { + let target_value = if view_idx == 0 { + 32 + } else if view_idx == num_layers as usize - 1 { + 96 + } else { + (clear_color * 255.0) as u8 + }; + // Some metal devices automatically initialize stuff to 255, so I decided to use 128 instead of that + 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..0f6de327c58 100644 --- a/tests/tests/wgpu-gpu/occlusion_query/mod.rs +++ b/tests/tests/wgpu-gpu/occlusion_query/mod.rs @@ -56,7 +56,7 @@ static OCCLUSION_QUERY: GpuTestConfiguration = GpuTestConfiguration::new() bias: wgpu::DepthBiasState::default(), }), multisample: wgpu::MultisampleState::default(), - multiview: None, + multiview_mask: None, cache: None, }); @@ -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/pipeline.rs b/tests/tests/wgpu-gpu/pipeline.rs index aa62032a5f1..7efaae8dd5b 100644 --- a/tests/tests/wgpu-gpu/pipeline.rs +++ b/tests/tests/wgpu-gpu/pipeline.rs @@ -129,7 +129,7 @@ static RENDER_PIPELINE_DEFAULT_LAYOUT_BAD_MODULE: GpuTestConfiguration = depth_stencil: None, multisample: Default::default(), fragment: None, - multiview: None, + multiview_mask: None, cache: None, }); @@ -182,7 +182,7 @@ static RENDER_PIPELINE_DEFAULT_LAYOUT_BAD_BGL_INDEX: GpuTestConfiguration = write_mask: wgpu::ColorWrites::ALL, })], }), - multiview: None, + multiview_mask: None, cache: None, }); @@ -223,7 +223,7 @@ static NO_TARGETLESS_RENDER: GpuTestConfiguration = GpuTestConfiguration::new() ..Default::default() }, fragment: None, - multiview: None, + multiview_mask: None, cache: None, }); } diff --git a/tests/tests/wgpu-gpu/planar_texture/mod.rs b/tests/tests/wgpu-gpu/planar_texture/mod.rs index 961d4b43f64..7f41c32c3a9 100644 --- a/tests/tests/wgpu-gpu/planar_texture/mod.rs +++ b/tests/tests/wgpu-gpu/planar_texture/mod.rs @@ -46,7 +46,7 @@ fn test_planar_texture_creation_sampling( }, depth_stencil: None, multisample: wgpu::MultisampleState::default(), - multiview: None, + multiview_mask: None, cache: None, }); @@ -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/push_constants.rs b/tests/tests/wgpu-gpu/push_constants.rs index 569fb426dfa..43c3d7c37ee 100644 --- a/tests/tests/wgpu-gpu/push_constants.rs +++ b/tests/tests/wgpu-gpu/push_constants.rs @@ -301,7 +301,7 @@ async fn render_pass_test(ctx: &TestingContext, use_render_bundle: bool) { }, depth_stencil: None, multisample: MultisampleState::default(), - multiview: None, + multiview_mask: None, cache: None, }); diff --git a/tests/tests/wgpu-gpu/regression/issue_3349.rs b/tests/tests/wgpu-gpu/regression/issue_3349.rs index adee4eb6527..ef8e5dac905 100644 --- a/tests/tests/wgpu-gpu/regression/issue_3349.rs +++ b/tests/tests/wgpu-gpu/regression/issue_3349.rs @@ -123,7 +123,7 @@ async fn multi_stage_data_binding_test(ctx: TestingContext) { primitive: wgpu::PrimitiveState::default(), depth_stencil: None, multisample: wgpu::MultisampleState::default(), - multiview: None, + multiview_mask: None, cache: None, }); @@ -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..3aa9202ecd5 100644 --- a/tests/tests/wgpu-gpu/regression/issue_3457.rs +++ b/tests/tests/wgpu-gpu/regression/issue_3457.rs @@ -84,7 +84,7 @@ static PASS_RESET_VERTEX_BUFFER: GpuTestConfiguration = GpuTestConfiguration::ne write_mask: ColorWrites::all(), })], }), - multiview: None, + multiview_mask: None, cache: None, }); @@ -116,7 +116,7 @@ static PASS_RESET_VERTEX_BUFFER: GpuTestConfiguration = GpuTestConfiguration::ne write_mask: ColorWrites::all(), })], }), - multiview: None, + multiview_mask: None, cache: None, }); @@ -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..b7c8284ab52 100644 --- a/tests/tests/wgpu-gpu/regression/issue_4485.rs +++ b/tests/tests/wgpu-gpu/regression/issue_4485.rs @@ -68,7 +68,7 @@ async fn test_impl(ctx: &TestingContext) { write_mask: wgpu::ColorWrites::ALL, })], }), - multiview: None, + multiview_mask: None, cache: None, }); @@ -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..dca272cf470 100644 --- a/tests/tests/wgpu-gpu/regression/issue_4514.rs +++ b/tests/tests/wgpu-gpu/regression/issue_4514.rs @@ -68,7 +68,7 @@ async fn test_impl(ctx: &TestingContext) { write_mask: wgpu::ColorWrites::ALL, })], }), - multiview: None, + multiview_mask: None, cache: None, }); @@ -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_5553.rs b/tests/tests/wgpu-gpu/regression/issue_5553.rs index 7c1884dc3a9..75562cdaa13 100644 --- a/tests/tests/wgpu-gpu/regression/issue_5553.rs +++ b/tests/tests/wgpu-gpu/regression/issue_5553.rs @@ -53,7 +53,7 @@ static ALLOW_INPUT_NOT_CONSUMED: GpuTestConfiguration = GpuTestConfiguration::ne write_mask: ColorWrites::all(), })], }), - multiview: None, + multiview_mask: None, cache: None, }); }); diff --git a/tests/tests/wgpu-gpu/render_pass_ownership.rs b/tests/tests/wgpu-gpu/render_pass_ownership.rs index 8d1f65972e5..6be442ac0ac 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. @@ -548,7 +549,7 @@ fn resource_setup(ctx: &TestingContext) -> ResourceSetup { mask: !0, alpha_to_coverage_enabled: false, }, - multiview: None, + multiview_mask: None, cache: None, }); diff --git a/tests/tests/wgpu-gpu/render_target.rs b/tests/tests/wgpu-gpu/render_target.rs index e45b8630a4e..394fb543420 100644 --- a/tests/tests/wgpu-gpu/render_target.rs +++ b/tests/tests/wgpu-gpu/render_target.rs @@ -106,7 +106,7 @@ async fn run_test( write_mask: wgpu::ColorWrites::ALL, })], }), - multiview: None, + multiview_mask: None, cache: None, }; let pipeline = ctx.device.create_render_pipeline(&pipeline_desc); @@ -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(..)); @@ -318,7 +319,7 @@ async fn run_test_3d(ctx: TestingContext) { write_mask: wgpu::ColorWrites::ALL, })], }), - multiview: None, + multiview_mask: None, cache: None, }; let pipeline = ctx.device.create_render_pipeline(&pipeline_desc); @@ -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..fa602170d03 100644 --- a/tests/tests/wgpu-gpu/scissor_tests/mod.rs +++ b/tests/tests/wgpu-gpu/scissor_tests/mod.rs @@ -69,7 +69,7 @@ async fn scissor_test_impl( write_mask: wgpu::ColorWrites::ALL, })], }), - multiview: None, + multiview_mask: None, cache: None, }); @@ -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..9d19f16cdcc 100644 --- a/tests/tests/wgpu-gpu/shader_primitive_index/mod.rs +++ b/tests/tests/wgpu-gpu/shader_primitive_index/mod.rs @@ -150,7 +150,7 @@ async fn pulling_common( write_mask: wgpu::ColorWrites::ALL, })], }), - multiview: None, + multiview_mask: None, cache: None, }); @@ -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..8677d6493e3 100644 --- a/tests/tests/wgpu-gpu/shader_view_format/mod.rs +++ b/tests/tests/wgpu-gpu/shader_view_format/mod.rs @@ -113,7 +113,7 @@ async fn reinterpret( }, depth_stencil: None, multisample: wgpu::MultisampleState::default(), - multiview: None, + multiview_mask: None, cache: None, }); let bind_group = ctx.device.create_bind_group(&wgpu::BindGroupDescriptor { @@ -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..6621ebc8176 100644 --- a/tests/tests/wgpu-gpu/vertex_formats/mod.rs +++ b/tests/tests/wgpu-gpu/vertex_formats/mod.rs @@ -314,7 +314,7 @@ async fn vertex_formats_common(ctx: TestingContext, tests: &[Test<'_>]) { write_mask: wgpu::ColorWrites::ALL, })], }), - multiview: None, + multiview_mask: None, cache: None, }; @@ -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..42c5b745af0 100644 --- a/tests/tests/wgpu-gpu/vertex_indices/mod.rs +++ b/tests/tests/wgpu-gpu/vertex_indices/mod.rs @@ -259,7 +259,7 @@ async fn vertex_index_common(ctx: TestingContext) { write_mask: wgpu::ColorWrites::ALL, })], }), - multiview: None, + multiview_mask: None, cache: None, }; let builtin_pipeline = ctx.device.create_render_pipeline(&pipeline_desc); @@ -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..be592404393 100644 --- a/tests/tests/wgpu-gpu/vertex_state.rs +++ b/tests/tests/wgpu-gpu/vertex_state.rs @@ -99,7 +99,7 @@ async fn set_array_stride_to_0(ctx: TestingContext) { write_mask: wgpu::ColorWrites::ALL, })], }), - multiview: None, + multiview_mask: None, cache: None, }; let mut first_pipeline_desc = pipeline_desc.clone(); @@ -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..a2369d97927 100644 --- a/tests/tests/wgpu-validation/api/external_texture.rs +++ b/tests/tests/wgpu-validation/api/external_texture.rs @@ -652,7 +652,7 @@ var tex: texture_external; write_mask: ColorWrites::ALL, })], }), - multiview: None, + multiview_mask: None, cache: None, }); @@ -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/tests/tests/wgpu-validation/api/render_pipeline.rs b/tests/tests/wgpu-validation/api/render_pipeline.rs index 654197212e0..1b742b8c957 100644 --- a/tests/tests/wgpu-validation/api/render_pipeline.rs +++ b/tests/tests/wgpu-validation/api/render_pipeline.rs @@ -58,7 +58,7 @@ fn frag() -> @location({}) vec4f {{ primitive: Default::default(), depth_stencil: None, multisample: Default::default(), - multiview: None, + multiview_mask: None, cache: None, }) }, diff --git a/wgpu-core/src/command/bundle.rs b/wgpu-core/src/command/bundle.rs index 8e8bf90b146..b5d141ddee2 100644 --- a/wgpu-core/src/command/bundle.rs +++ b/wgpu-core/src/command/bundle.rs @@ -221,7 +221,7 @@ impl RenderBundleEncoder { } sc }, - multiview: desc.multiview, + multiview_mask: desc.multiview, }, is_depth_read_only, @@ -242,7 +242,7 @@ impl RenderBundleEncoder { depth_stencil: None, }, sample_count: 0, - multiview: None, + multiview_mask: None, }, is_depth_read_only: false, is_stencil_read_only: false, 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..185da58cf46 100644 --- a/wgpu-core/src/command/draw.rs +++ b/wgpu-core/src/command/draw.rs @@ -71,6 +71,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 26317bd4f41..928395c9c54 100644 --- a/wgpu-core/src/command/mod.rs +++ b/wgpu-core/src/command/mod.rs @@ -947,6 +947,7 @@ impl CommandEncoder { depth_stencil_attachment, timestamp_writes, occlusion_query_set, + multiview_mask, } => { api_log!( "Begin encoding render pass with '{}' label", @@ -959,6 +960,7 @@ impl CommandEncoder { depth_stencil_attachment, timestamp_writes, occlusion_query_set, + multiview_mask, ); match res.as_ref() { Err(err) => api_log!("Finished encoding render pass ({err:?})"), diff --git a/wgpu-core/src/command/render.rs b/wgpu-core/src/command/render.rs index 51db9634ea4..e6b4e6210c5 100644 --- a/wgpu-core/src/command/render.rs +++ b/wgpu-core/src/command/render.rs @@ -235,6 +235,8 @@ 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, + /// The multiview array layers that will be used + pub multiview_mask: Option, } /// Describes the attachments of a render pass. @@ -249,6 +251,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 +280,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 +296,7 @@ impl RenderPass { color_attachments, depth_stencil_attachment, occlusion_query_set, + multiview_mask, } = desc; Self { @@ -300,6 +306,7 @@ impl RenderPass { depth_stencil_attachment, timestamp_writes, occlusion_query_set, + multiview_mask, current_bind_groups: BindGroupStateChange::new(), current_pipeline: StateChange::new(), @@ -314,6 +321,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 +345,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 +788,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 +891,7 @@ impl WebGpuError for RenderPassError { | RenderPassErrorInner::PushConstantOutOfMemory | RenderPassErrorInner::MultiViewMismatch | RenderPassErrorInner::MultiViewDimensionMismatch + | RenderPassErrorInner::TooManyMultiviewViews | RenderPassErrorInner::MissingOcclusionQuerySet | RenderPassErrorInner::PassEnded => return ErrorType::Validation, }; @@ -915,7 +927,7 @@ struct RenderPassInfo { extent: wgt::Extent3d, 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); @@ -1356,7 +1372,14 @@ impl RenderPassInfo { } let extent = extent.ok_or(RenderPassErrorInner::MissingAttachments)?; - let multiview = detected_multiview.expect("Multiview was not detected, no attachments"); + + let detected_multiview = + detected_multiview.expect("Multiview was not detected, no attachments"); + if let Some(mask) = multiview_mask { + // 0x01 will have msb 0 + let mask_msb = 31 - mask.leading_zeros(); + assert!(mask_msb < detected_multiview.unwrap().get()); + } let attachment_formats = AttachmentData { colors: color_attachments @@ -1381,7 +1404,7 @@ impl RenderPassInfo { let context = RenderPassContext { attachments: attachment_formats, sample_count, - multiview, + multiview_mask, }; let timestamp_writes_hal = if let Some(tw) = timestamp_writes.as_ref() { @@ -1417,7 +1440,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, }; @@ -1452,7 +1475,7 @@ impl RenderPassInfo { is_stencil_read_only, extent, divergent_discarded_depth_stencil_aspect, - multiview, + multiview_mask, }) } @@ -1519,7 +1542,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 +1691,8 @@ impl Global { None }; + arc_desc.multiview_mask = desc.multiview_mask; + Ok(()) } @@ -1686,6 +1711,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 +1771,7 @@ impl Global { depth_stencil_attachment: Option<&RenderPassDepthStencilAttachment>, timestamp_writes: Option<&PassTimestampWrites>, occlusion_query_set: Option, + multiview_mask: Option, ) { #[cfg(feature = "trace")] { @@ -1766,6 +1793,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 +1815,7 @@ impl Global { depth_stencil_attachment, timestamp_writes, occlusion_query_set, + multiview_mask, }, ); if let Some(err) = encoder_error { @@ -1844,6 +1873,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 +1886,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 +1925,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)?; @@ -2668,6 +2700,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_mask { + 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-core/src/device/global.rs b/wgpu-core/src/device/global.rs index 0542c6ebd51..1d8c795181e 100644 --- a/wgpu-core/src/device/global.rs +++ b/wgpu-core/src/device/global.rs @@ -1524,7 +1524,7 @@ impl Global { depth_stencil: desc.depth_stencil.clone(), multisample: desc.multisample, fragment, - multiview: desc.multiview, + multiview_mask: desc.multiview_mask, cache, }; diff --git a/wgpu-core/src/device/mod.rs b/wgpu-core/src/device/mod.rs index 38e1b1d08f8..baa031ce54a 100644 --- a/wgpu-core/src/device/mod.rs +++ b/wgpu-core/src/device/mod.rs @@ -61,7 +61,7 @@ impl Eq for AttachmentData {} pub(crate) struct RenderPassContext { pub attachments: AttachmentData, pub sample_count: u32, - pub multiview: Option, + pub multiview_mask: Option, } #[derive(Clone, Debug, Error)] #[non_exhaustive] @@ -144,10 +144,10 @@ impl RenderPassContext { res: res.error_ident(), }); } - if self.multiview != other.multiview { + if self.multiview_mask != other.multiview_mask { return Err(RenderPassCompatibilityError::IncompatibleMultiview { - expected: self.multiview, - actual: other.multiview, + expected: self.multiview_mask, + actual: other.multiview_mask, res: res.error_ident(), }); } diff --git a/wgpu-core/src/device/resource.rs b/wgpu-core/src/device/resource.rs index d2233757fab..255ba9263ad 100644 --- a/wgpu-core/src/device/resource.rs +++ b/wgpu-core/src/device/resource.rs @@ -4269,7 +4269,7 @@ impl Device { }; // Multiview is only supported if the feature is enabled - if desc.multiview.is_some() { + if desc.multiview_mask.is_some() { self.require_features(wgt::Features::MULTIVIEW)?; } @@ -4320,7 +4320,7 @@ impl Device { multisample: desc.multisample, fragment_stage, color_targets, - multiview: desc.multiview, + multiview_mask: desc.multiview_mask, cache: cache.as_ref().map(|it| it.raw()), }; unsafe { self.raw().create_render_pipeline(&pipeline_desc) }.map_err( @@ -4354,7 +4354,7 @@ impl Device { depth_stencil: depth_stencil_state.as_ref().map(|state| state.format), }, sample_count: samples, - multiview: desc.multiview, + multiview_mask: desc.multiview_mask, }; let mut flags = pipeline::PipelineFlags::empty(); diff --git a/wgpu-core/src/device/trace.rs b/wgpu-core/src/device/trace.rs index fcb3f589154..fe3f7a9193a 100644 --- a/wgpu-core/src/device/trace.rs +++ b/wgpu-core/src/device/trace.rs @@ -30,7 +30,7 @@ pub(crate) fn new_render_bundle_encoder_descriptor<'a>( } }), sample_count: context.sample_count, - multiview: context.multiview, + multiview: context.multiview_mask, } } diff --git a/wgpu-core/src/pipeline.rs b/wgpu-core/src/pipeline.rs index 753518f67ad..4cf5a2162dc 100644 --- a/wgpu-core/src/pipeline.rs +++ b/wgpu-core/src/pipeline.rs @@ -459,7 +459,7 @@ pub struct RenderPipelineDescriptor< pub fragment: Option>, /// If the pipeline will be used with a multiview render pass, this indicates how many array /// layers the attachments will have. - pub multiview: Option, + pub multiview_mask: Option, /// The pipeline cache to use when creating this pipeline. pub cache: Option, } @@ -524,7 +524,7 @@ pub(crate) struct GeneralRenderPipelineDescriptor< pub fragment: Option>, /// If the pipeline will be used with a multiview render pass, this indicates how many array /// layers the attachments will have. - pub multiview: Option, + pub multiview_mask: Option, /// The pipeline cache to use when creating this pipeline. pub cache: Option, } @@ -540,7 +540,7 @@ impl<'a, PLL, SM, PLC> From> depth_stencil: value.depth_stencil, multisample: value.multisample, fragment: value.fragment, - multiview: value.multiview, + multiview_mask: value.multiview_mask, cache: value.cache, } } @@ -557,7 +557,7 @@ impl<'a, PLL, SM, PLC> From> depth_stencil: value.depth_stencil, multisample: value.multisample, fragment: value.fragment, - multiview: value.multiview, + multiview_mask: value.multiview, cache: value.cache, } } 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 34f20695296..f5391012ca3 100644 --- a/wgpu-hal/examples/halmark/main.rs +++ b/wgpu-hal/examples/halmark/main.rs @@ -280,7 +280,7 @@ impl Example { blend: Some(wgpu_types::BlendState::ALPHA_BLENDING), write_mask: wgpu_types::ColorWrites::default(), })], - multiview: None, + multiview_mask: None, cache: None, }; let pipeline = unsafe { device.create_render_pipeline(&pipeline_desc).unwrap() }; @@ -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..a63c5c5367c 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,13 @@ impl super::Adapter { mesh_shader_supported, ); + features.set(wgt::Features::MULTIVIEW, view_instancing); + + features.set( + wgt::Features::EXPERIMENTAL_MESH_SHADER_MULTIVIEW, + mesh_shader_supported && view_instancing, + ); + // TODO: Determine if IPresentationManager is supported let presentation_timer = auxil::dxgi::time::PresentationTimer::new_dxgi(); @@ -571,6 +585,8 @@ impl super::Adapter { max_srv_count / 2 }; + let max_multiview_view_count = if view_instancing { 4 } else { 0 }; + Some(crate::ExposedAdapter { adapter: super::Adapter { raw: adapter, @@ -670,7 +686,11 @@ 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: if mesh_shader_supported { + max_multiview_view_count + } else { + 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 +714,11 @@ 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, }, 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 7cd7cdac535..8d194c2c319 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/dynamic/device.rs b/wgpu-hal/src/dynamic/device.rs index 20263143808..eea26ac895f 100644 --- a/wgpu-hal/src/dynamic/device.rs +++ b/wgpu-hal/src/dynamic/device.rs @@ -407,7 +407,7 @@ impl DynDevice for D { multisample: desc.multisample, fragment_stage: desc.fragment_stage.clone().map(|f| f.expect_downcast()), color_targets: desc.color_targets, - multiview: desc.multiview, + multiview_mask: desc.multiview_mask, cache: desc.cache.map(|c| c.expect_downcast_ref()), }; diff --git a/wgpu-hal/src/gles/adapter.rs b/wgpu-hal/src/gles/adapter.rs index 806f5567ba2..2260658ad28 100644 --- a/wgpu-hal/src/gles/adapter.rs +++ b/wgpu-hal/src/gles/adapter.rs @@ -803,13 +803,15 @@ 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, }; let mut workarounds = super::Workarounds::empty(); diff --git a/wgpu-hal/src/gles/device.rs b/wgpu-hal/src/gles/device.rs index b45d53cc23e..0df7446ae92 100644 --- a/wgpu-hal/src/gles/device.rs +++ b/wgpu-hal/src/gles/device.rs @@ -22,7 +22,7 @@ struct CompilationContext<'a> { sampler_map: &'a mut super::SamplerBindMap, name_binding_map: &'a mut NameBindingMap, push_constant_items: &'a mut Vec, - multiview: Option, + multiview_mask: Option, clip_distance_count: &'a mut u32, } @@ -218,7 +218,9 @@ impl super::Device { let pipeline_options = glsl::PipelineOptions { shader_stage: naga_stage, entry_point: stage.entry_point.to_owned(), - multiview: context.multiview, + multiview: context + .multiview_mask + .map(|a| NonZeroU32::new(a.get().count_ones()).unwrap()), }; let (module, info) = naga::back::pipeline_constants::process_overrides( @@ -306,7 +308,7 @@ impl super::Device { shaders: ArrayVec, { crate::MAX_CONCURRENT_SHADER_STAGES }>, layout: &super::PipelineLayout, #[cfg_attr(target_arch = "wasm32", allow(unused))] label: Option<&str>, - multiview: Option, + multiview_mask: Option, ) -> Result, crate::PipelineError> { let mut program_stages = ArrayVec::new(); let mut group_to_binding_to_slot = Vec::with_capacity(layout.group_infos.len()); @@ -339,7 +341,7 @@ impl super::Device { shaders, layout, label, - multiview, + multiview_mask, self.shared.shading_language_version, self.shared.private_caps, ) @@ -355,7 +357,7 @@ impl super::Device { shaders: ArrayVec, { crate::MAX_CONCURRENT_SHADER_STAGES }>, layout: &super::PipelineLayout, #[cfg_attr(target_arch = "wasm32", allow(unused))] label: Option<&str>, - multiview: Option, + multiview_mask: Option, glsl_version: naga::back::glsl::Version, private_caps: PrivateCapabilities, ) -> Result, crate::PipelineError> { @@ -390,7 +392,7 @@ impl super::Device { sampler_map: &mut sampler_map, name_binding_map: &mut name_binding_map, push_constant_items: pc_item, - multiview, + multiview_mask, clip_distance_count: &mut clip_distance_count, }; @@ -1375,8 +1377,9 @@ impl crate::Device for super::Device { if let Some(ref fs) = desc.fragment_stage { shaders.push((naga::ShaderStage::Fragment, fs)); } - let inner = - unsafe { self.create_pipeline(gl, shaders, desc.layout, desc.label, desc.multiview) }?; + let inner = unsafe { + self.create_pipeline(gl, shaders, desc.layout, desc.label, desc.multiview_mask) + }?; let (vertex_buffers, vertex_attributes) = { let mut buffers = Vec::new(); diff --git a/wgpu-hal/src/lib.rs b/wgpu-hal/src/lib.rs index 503be802d4e..0350edef345 100644 --- a/wgpu-hal/src/lib.rs +++ b/wgpu-hal/src/lib.rs @@ -2361,7 +2361,7 @@ pub struct RenderPipelineDescriptor< pub color_targets: &'a [Option], /// If the pipeline will be used with a multiview render pass, this indicates how many array /// layers the attachments will have. - pub multiview: Option, + pub multiview_mask: Option, /// The cache which will be used and filled when compiling this pipeline pub cache: Option<&'a Pc>, } @@ -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..a9c16cebfe4 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,12 @@ 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 + }, }, 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..8c30d0ec862 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,33 @@ impl crate::CommandEncoder for super::CommandEncoder { descriptor .set_visibility_result_buffer(Some(occlusion_query_set.raw_buffer.as_ref())) } - + // This strangely isn't mentioned in https://developer.apple.com/documentation/metal/improving-rendering-performance-with-vertex-amplification. + // The docs for [`renderTargetArrayLength`](https://developer.apple.com/documentation/metal/mtlrenderpassdescriptor/rendertargetarraylength) + // also say "The number of active layers that all attachments must have for layered rendering," implying it is only for layered rendering. + // However, when I don't set this, I get undefined behavior in nonzero layers, so I think it is required. + 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 = 32 - 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, + viewportArrayIndexOffset: i, + }); + } + } + 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 c12b552b162..5925cc513cf 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_mask { + descriptor.set_max_vertex_amplification_count(mv.get().count_ones() 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..887b279af25 100644 --- a/wgpu-hal/src/noop/mod.rs +++ b/wgpu-hal/src/noop/mod.rs @@ -192,15 +192,17 @@ 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, }, alignments: crate::Alignments { // All maximally permissive diff --git a/wgpu-hal/src/vulkan/adapter.rs b/wgpu-hal/src/vulkan/adapter.rs index 9c007bf6ef9..86957573fee 100644 --- a/wgpu-hal/src/vulkan/adapter.rs +++ b/wgpu-hal/src/vulkan/adapter.rs @@ -17,7 +17,6 @@ const INDEXING_FEATURES: wgt::Features = wgt::Features::TEXTURE_BINDING_ARRAY .union(wgt::Features::STORAGE_TEXTURE_ARRAY_NON_UNIFORM_INDEXING) .union(wgt::Features::UNIFORM_BUFFER_BINDING_ARRAYS) .union(wgt::Features::PARTIALLY_BOUND_BINDING_ARRAY); - #[expect(rustdoc::private_intra_doc_links)] /// Features supported by a [`vk::PhysicalDevice`] and its extensions. /// @@ -962,6 +961,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 +1194,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 +1256,11 @@ impl PhysicalDeviceProperties { properties.max_per_stage_descriptor_acceleration_structures; } + let max_multiview_view_count = self + .multiview + .map(|a| a.max_multiview_view_count.min(32)) + .unwrap_or(0); + wgt::Limits { max_texture_dimension_1d: limits.max_image_dimension1_d, max_texture_dimension_2d: limits.max_image_dimension2_d, @@ -1313,13 +1321,15 @@ 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, } } @@ -1378,6 +1388,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 +1468,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) }; @@ -1822,6 +1842,10 @@ impl super::Instance { shader_int8: phd_features .shader_float16_int8 .is_some_and(|features| features.shader_int8 != 0), + multiview_instance_index_limit: phd_capabilities + .multiview + .map(|a| a.max_multiview_instance_index) + .unwrap_or(0), }; let capabilities = crate::Capabilities { limits: phd_capabilities.to_wgpu_limits(), diff --git a/wgpu-hal/src/vulkan/command.rs b/wgpu-hal/src/vulkan/command.rs index 651fdcf0cc6..16ba1e320d1 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 { @@ -994,6 +979,7 @@ impl crate::CommandEncoder for super::CommandEncoder { unsafe fn set_render_pipeline(&mut self, pipeline: &super::RenderPipeline) { unsafe { + self.current_pipeline_is_multiview = pipeline.is_multiview; self.device.raw.cmd_bind_pipeline( self.active, vk::PipelineBindPoint::GRAPHICS, @@ -1081,6 +1067,12 @@ impl crate::CommandEncoder for super::CommandEncoder { first_instance: u32, instance_count: u32, ) { + if self.current_pipeline_is_multiview + && (first_instance as u64 + instance_count as u64 - 1) + > self.device.private_caps.multiview_instance_index_limit as u64 + { + panic!("This vulkan device is affected by [#8333](https://github.com/gfx-rs/wgpu/issues/8333)"); + } unsafe { self.device.raw.cmd_draw( self.active, @@ -1099,6 +1091,12 @@ impl crate::CommandEncoder for super::CommandEncoder { first_instance: u32, instance_count: u32, ) { + if self.current_pipeline_is_multiview + && (first_instance as u64 + instance_count as u64 - 1) + > self.device.private_caps.multiview_instance_index_limit as u64 + { + panic!("This vulkan device is affected by [#8333](https://github.com/gfx-rs/wgpu/issues/8333)"); + } unsafe { self.device.raw.cmd_draw_indexed( self.active, diff --git a/wgpu-hal/src/vulkan/device.rs b/wgpu-hal/src/vulkan/device.rs index 1a710cb2d99..53cd55f8853 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, @@ -1474,6 +1470,7 @@ impl crate::Device for super::Device { framebuffers: Default::default(), temp_texture_views: Default::default(), counters: Arc::clone(&self.counters), + current_pipeline_is_multiview: false, }) } @@ -1993,7 +1990,7 @@ impl crate::Device for super::Device { ]; let mut compatible_rp_key = super::RenderPassKey { sample_count: desc.multisample.count, - multiview: desc.multiview, + multiview_mask: desc.multiview_mask, ..Default::default() }; let mut stages = ArrayVec::<_, { crate::MAX_CONCURRENT_SHADER_STAGES }>::new(); @@ -2262,7 +2259,10 @@ impl crate::Device for super::Device { self.counters.render_pipelines.add(1); - Ok(super::RenderPipeline { raw }) + Ok(super::RenderPipeline { + raw, + is_multiview: desc.multiview_mask.is_some(), + }) } unsafe fn destroy_render_pipeline(&self, pipeline: super::RenderPipeline) { diff --git a/wgpu-hal/src/vulkan/mod.rs b/wgpu-hal/src/vulkan/mod.rs index 183950b71f0..1fa4d388b4b 100644 --- a/wgpu-hal/src/vulkan/mod.rs +++ b/wgpu-hal/src/vulkan/mod.rs @@ -636,6 +636,9 @@ struct PrivateCapabilities { /// [`VK_KHR_shader_float16_int8`]: https://registry.khronos.org/vulkan/specs/latest/man/html/VK_KHR_shader_float16_int8.html /// [see spec]: https://registry.khronos.org/vulkan/specs/latest/man/html/VkPhysicalDeviceShaderFloat16Int8Features.html#extension-features-shaderInt8 shader_int8: bool, + + /// This is done to panic before undefined behavior, and is imperfect. + multiview_instance_index_limit: u32, } bitflags::bitflags!( @@ -707,7 +710,7 @@ struct RenderPassKey { colors: ArrayVec, { crate::MAX_COLOR_ATTACHMENTS }>, depth_stencil: Option, sample_count: u32, - multiview: Option, + multiview_mask: Option, } struct DeviceShared { @@ -975,7 +978,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, @@ -1208,6 +1211,8 @@ pub struct CommandEncoder { temp_texture_views: FastHashMap, counters: Arc, + + current_pipeline_is_multiview: bool, } impl Drop for CommandEncoder { @@ -1280,6 +1285,7 @@ impl crate::DynShaderModule for ShaderModule {} #[derive(Debug)] pub struct RenderPipeline { raw: vk::Pipeline, + is_multiview: bool, } impl crate::DynRenderPipeline for RenderPipeline {} diff --git a/wgpu-info/src/human.rs b/wgpu-info/src/human.rs index f0930bd0212..6c1b34c28c4 100644 --- a/wgpu-info/src/human.rs +++ b/wgpu-info/src/human.rs @@ -163,13 +163,15 @@ 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, } = 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 +211,15 @@ 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}")?; // 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 f9f48e1e82b..32a999fff97 100644 --- a/wgpu-types/src/lib.rs +++ b/wgpu-types/src/lib.rs @@ -519,12 +519,14 @@ 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); }; } @@ -694,8 +696,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 +715,9 @@ 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, } impl Default for Limits { @@ -767,12 +772,13 @@ 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, /// }); /// ``` /// @@ -820,13 +826,15 @@ 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, } } @@ -875,13 +883,15 @@ 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, /// }); /// ``` #[must_use] @@ -898,7 +908,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 +960,15 @@ 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, /// }); /// ``` #[must_use] @@ -1051,7 +1063,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/api/render_pipeline.rs b/wgpu/src/api/render_pipeline.rs index e887bb4b97e..c15f9c6ab0d 100644 --- a/wgpu/src/api/render_pipeline.rs +++ b/wgpu/src/api/render_pipeline.rs @@ -229,7 +229,7 @@ pub struct RenderPipelineDescriptor<'a> { pub fragment: Option>, /// If the pipeline will be used with a multiview render pass, this indicates how many array /// layers the attachments will have. - pub multiview: Option, + pub multiview_mask: Option, /// The pipeline cache to use when creating this pipeline. pub cache: Option<&'a PipelineCache>, } diff --git a/wgpu/src/backend/webgpu.rs b/wgpu/src/backend/webgpu.rs index be5418bb1bc..ba5a92c2229 100644 --- a/wgpu/src/backend/webgpu.rs +++ b/wgpu/src/backend/webgpu.rs @@ -828,13 +828,15 @@ 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, } } diff --git a/wgpu/src/backend/wgpu_core.rs b/wgpu/src/backend/wgpu_core.rs index 405e4779b70..d32adc51c37 100644 --- a/wgpu/src/backend/wgpu_core.rs +++ b/wgpu/src/backend/wgpu_core.rs @@ -1367,7 +1367,7 @@ impl dispatch::DeviceInterface for CoreDevice { targets: Borrowed(frag.targets), } }), - multiview: desc.multiview, + multiview_mask: desc.multiview_mask, cache: desc.cache.map(|cache| cache.inner.as_core().id), }; @@ -2554,6 +2554,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..f05ea151252 100644 --- a/wgpu/src/util/texture_blitter.rs +++ b/wgpu/src/util/texture_blitter.rs @@ -130,7 +130,7 @@ impl<'a> TextureBlitterBuilder<'a> { write_mask: ColorWrites::ALL, })], }), - multiview: None, + multiview_mask: None, cache: None, }); @@ -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, &[]);