From 375cf497e1e9dca2bcbbedba19a8ed7ed1e06cb4 Mon Sep 17 00:00:00 2001 From: SupaMaggie70 Date: Wed, 10 Sep 2025 12:56:37 -0500 Subject: [PATCH 01/44] Exposed new limits for multiview --- wgpu-hal/src/dx12/adapter.rs | 3 +++ wgpu-hal/src/gles/adapter.rs | 5 ++++- wgpu-hal/src/metal/adapter.rs | 6 ++++-- wgpu-hal/src/noop/mod.rs | 11 +++++++---- wgpu-hal/src/vulkan/adapter.rs | 31 +++++++++++++++++++++++++++++-- wgpu-info/src/human.rs | 10 ++++++++-- wgpu-types/src/lib.rs | 25 +++++++++++++++++++------ 7 files changed, 74 insertions(+), 17 deletions(-) diff --git a/wgpu-hal/src/dx12/adapter.rs b/wgpu-hal/src/dx12/adapter.rs index 1645088afb4..a888d109416 100644 --- a/wgpu-hal/src/dx12/adapter.rs +++ b/wgpu-hal/src/dx12/adapter.rs @@ -671,6 +671,9 @@ impl super::Adapter { } else { 0 }, + + max_multiview_view_count: 0, + max_multiview_instance_index: 0, }, alignments: crate::Alignments { buffer_copy_offset: wgt::BufferSize::new( diff --git a/wgpu-hal/src/gles/adapter.rs b/wgpu-hal/src/gles/adapter.rs index 806f5567ba2..e3507b3ad09 100644 --- a/wgpu-hal/src/gles/adapter.rs +++ b/wgpu-hal/src/gles/adapter.rs @@ -803,13 +803,16 @@ impl super::Adapter { max_task_workgroup_total_count: 0, max_task_workgroups_per_dimension: 0, - max_mesh_multiview_count: 0, + max_mesh_multiview_view_count: 0, max_mesh_output_layers: 0, max_blas_primitive_count: 0, max_blas_geometry_count: 0, max_tlas_instance_count: 0, max_acceleration_structures_per_shader_stage: 0, + + max_multiview_view_count: 0, + max_multiview_instance_index: 0, }; let mut workarounds = super::Workarounds::empty(); diff --git a/wgpu-hal/src/metal/adapter.rs b/wgpu-hal/src/metal/adapter.rs index 47ffbd3c6c1..c09a157683a 100644 --- a/wgpu-hal/src/metal/adapter.rs +++ b/wgpu-hal/src/metal/adapter.rs @@ -1031,7 +1031,6 @@ impl super::PrivateCapabilities { downlevel .flags .set(wgt::DownlevelFlags::ANISOTROPIC_FILTERING, true); - let base = wgt::Limits::default(); crate::Capabilities { limits: wgt::Limits { @@ -1079,7 +1078,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 +1091,9 @@ 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: 0, + max_multiview_instance_index: 0, }, alignments: crate::Alignments { buffer_copy_offset: wgt::BufferSize::new(self.buffer_alignment).unwrap(), diff --git a/wgpu-hal/src/noop/mod.rs b/wgpu-hal/src/noop/mod.rs index abd7c628a98..3c5ac2588e8 100644 --- a/wgpu-hal/src/noop/mod.rs +++ b/wgpu-hal/src/noop/mod.rs @@ -192,15 +192,18 @@ pub const CAPABILITIES: crate::Capabilities = { max_push_constant_size: ALLOC_MAX_U32, max_non_sampler_bindings: ALLOC_MAX_U32, - max_task_workgroup_total_count: 0, - max_task_workgroups_per_dimension: 0, - max_mesh_multiview_count: 0, - max_mesh_output_layers: 0, + max_task_workgroup_total_count: ALLOC_MAX_U32, + max_task_workgroups_per_dimension: ALLOC_MAX_U32, + max_mesh_multiview_view_count: ALLOC_MAX_U32, + max_mesh_output_layers: ALLOC_MAX_U32, max_blas_primitive_count: ALLOC_MAX_U32, max_blas_geometry_count: ALLOC_MAX_U32, max_tlas_instance_count: ALLOC_MAX_U32, max_acceleration_structures_per_shader_stage: ALLOC_MAX_U32, + + max_multiview_view_count: ALLOC_MAX_U32, + max_multiview_instance_index: ALLOC_MAX_U32, }, alignments: crate::Alignments { // All maximally permissive diff --git a/wgpu-hal/src/vulkan/adapter.rs b/wgpu-hal/src/vulkan/adapter.rs index 0ebf1fec9a4..4f57103db0a 100644 --- a/wgpu-hal/src/vulkan/adapter.rs +++ b/wgpu-hal/src/vulkan/adapter.rs @@ -958,6 +958,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. @@ -1187,7 +1191,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) => ( @@ -1249,6 +1253,16 @@ impl PhysicalDeviceProperties { properties.max_per_stage_descriptor_acceleration_structures; } + let (max_multiview_view_count, max_multiview_instance_index) = + if let Some(properties) = self.multiview { + ( + properties.max_multiview_view_count, + properties.max_multiview_instance_index, + ) + } else { + (0, 0) + }; + wgt::Limits { max_texture_dimension_1d: limits.max_image_dimension1_d, max_texture_dimension_2d: limits.max_image_dimension2_d, @@ -1309,13 +1323,16 @@ impl PhysicalDeviceProperties { max_task_workgroup_total_count, max_task_workgroups_per_dimension, - max_mesh_multiview_count, + max_mesh_multiview_view_count, max_mesh_output_layers, max_blas_primitive_count, max_blas_geometry_count, max_tlas_instance_count, max_acceleration_structures_per_shader_stage, + + max_multiview_view_count, + max_multiview_instance_index, } } @@ -1374,6 +1391,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 @@ -1451,6 +1471,13 @@ impl super::InstanceShared { properties2 = properties2.push_next(next); } + if supports_multiview { + let next = capabilities + .multiview + .insert(vk::PhysicalDeviceMultiviewProperties::default()); + properties2 = properties2.push_next(next); + } + unsafe { get_device_properties.get_physical_device_properties2(phd, &mut properties2) }; diff --git a/wgpu-info/src/human.rs b/wgpu-info/src/human.rs index f0930bd0212..b1e4e7db1e4 100644 --- a/wgpu-info/src/human.rs +++ b/wgpu-info/src/human.rs @@ -163,13 +163,16 @@ fn print_adapter(output: &mut impl io::Write, report: &AdapterReport, idx: usize max_task_workgroup_total_count, max_task_workgroups_per_dimension, - max_mesh_multiview_count, + max_mesh_multiview_view_count: max_mesh_multiview_count, max_mesh_output_layers, max_blas_primitive_count, max_blas_geometry_count, max_tlas_instance_count, max_acceleration_structures_per_shader_stage, + + max_multiview_view_count, + max_multiview_instance_index } = limits; writeln!(output, "\t\t Max Texture Dimension 1d: {max_texture_dimension_1d}")?; writeln!(output, "\t\t Max Texture Dimension 2d: {max_texture_dimension_2d}")?; @@ -209,13 +212,16 @@ fn print_adapter(output: &mut impl io::Write, report: &AdapterReport, idx: usize writeln!(output, "\t\t Max Task Workgroup Total Count: {max_task_workgroup_total_count}")?; writeln!(output, "\t\t Max Task Workgroups Per Dimension: {max_task_workgroups_per_dimension}")?; - writeln!(output, "\t\t Max Mesh Multiview Count: {max_mesh_multiview_count}")?; + writeln!(output, "\t\t Max Mesh Multiview View Count: {max_mesh_multiview_count}")?; writeln!(output, "\t\t Max Mesh Output Layers: {max_mesh_output_layers}")?; writeln!(output, "\t\t Max BLAS Primitive count: {max_blas_primitive_count}")?; writeln!(output, "\t\t Max BLAS Geometry count: {max_blas_geometry_count}")?; writeln!(output, "\t\t Max TLAS Instance count: {max_tlas_instance_count}")?; writeln!(output, "\t\t Max Acceleration Structures Per Shader Stage: {max_acceleration_structures_per_shader_stage}")?; + + writeln!(output, "\t\t Max Multiview View Count: {max_multiview_view_count}")?; + writeln!(output, "\t\t Max Multiview Instance Index: {max_multiview_instance_index}")?; // This one reflects more of a wgpu implementation limitations than a hardware limit // so don't show it here. let _ = max_non_sampler_bindings; diff --git a/wgpu-types/src/lib.rs b/wgpu-types/src/lib.rs index 7674c0a95d8..3d1aaaf3a1b 100644 --- a/wgpu-types/src/lib.rs +++ b/wgpu-types/src/lib.rs @@ -518,12 +518,15 @@ macro_rules! with_limits { $macro_name!(max_task_workgroup_total_count, Ordering::Less); $macro_name!(max_task_workgroups_per_dimension, Ordering::Less); - $macro_name!(max_mesh_multiview_count, Ordering::Less); + $macro_name!(max_mesh_multiview_view_count, Ordering::Less); $macro_name!(max_mesh_output_layers, Ordering::Less); $macro_name!(max_blas_primitive_count, Ordering::Less); $macro_name!(max_blas_geometry_count, Ordering::Less); $macro_name!(max_tlas_instance_count, Ordering::Less); + + $macro_name!(max_multiview_view_count, Ordering::Less); + $macro_name!(max_multiview_instance_index, Ordering::Less); }; } @@ -693,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`] @@ -712,6 +715,13 @@ pub struct Limits { /// Requesting more than 0 during device creation only makes sense if [`Features::EXPERIMENTAL_RAY_QUERY`] /// is enabled. pub max_acceleration_structures_per_shader_stage: u32, + + /// The maximum number of views that can be used in multiview rendering + pub max_multiview_view_count: u32, + /// For an instanced draw call using multiview, the maximum instance index. For example, + /// if draw is called with instances a..b, then b must be <= this limit + 1. + /// Note that this is NOT just the maximum total number of instances. + pub max_multiview_instance_index: u32, } impl Default for Limits { @@ -819,13 +829,16 @@ impl Limits { max_task_workgroup_total_count: 0, max_task_workgroups_per_dimension: 0, - max_mesh_multiview_count: 0, + max_mesh_multiview_view_count: 0, max_mesh_output_layers: 0, max_blas_primitive_count: 0, max_blas_geometry_count: 0, max_tlas_instance_count: 0, max_acceleration_structures_per_shader_stage: 0, + + max_multiview_view_count: 0, + max_multiview_instance_index: 0, } } @@ -897,7 +910,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() } @@ -1050,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 From 88fcb2f0a6830ad648aa85c6fffbade0df211956 Mon Sep 17 00:00:00 2001 From: SupaMaggie70 Date: Wed, 10 Sep 2025 13:18:20 -0500 Subject: [PATCH 02/44] Added multiview view count checks --- wgpu-core/src/command/draw.rs | 2 ++ wgpu-core/src/command/render.rs | 19 ++++++++++++++++++- 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/wgpu-core/src/command/draw.rs b/wgpu-core/src/command/draw.rs index 7a57077a4fa..527567f72bc 100644 --- a/wgpu-core/src/command/draw.rs +++ b/wgpu-core/src/command/draw.rs @@ -71,6 +71,8 @@ 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 must be <= `Limits::max_mesh_multiview_view_count`")] + MeshPipelineMultiviewLimitsViolated, } impl WebGpuError for DrawError { diff --git a/wgpu-core/src/command/render.rs b/wgpu-core/src/command/render.rs index 344d55bd6d4..71aca699d8e 100644 --- a/wgpu-core/src/command/render.rs +++ b/wgpu-core/src/command/render.rs @@ -775,6 +775,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)] @@ -876,6 +878,7 @@ impl WebGpuError for RenderPassError { | RenderPassErrorInner::PushConstantOutOfMemory | RenderPassErrorInner::MultiViewMismatch | RenderPassErrorInner::MultiViewDimensionMismatch + | RenderPassErrorInner::TooManyMultiviewViews | RenderPassErrorInner::MissingOcclusionQuerySet | RenderPassErrorInner::PassEnded => return ErrorType::Validation, }; @@ -1010,8 +1013,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); @@ -2649,6 +2655,17 @@ fn draw_mesh_tasks( api_log!("RenderPass::draw_mesh_tasks {group_count_x} {group_count_y} {group_count_z}"); state.is_ready(DrawCommandFamily::DrawMeshTasks)?; + if let Some(mv) = state.info.multiview { + if !state + .general + .device + .features + .contains(wgt::Features::EXPERIMENTAL_MESH_SHADER_MULTIVIEW) + || mv.get() > state.general.device.limits.max_mesh_multiview_view_count + { + return Err(DrawError::MeshPipelineMultiviewLimitsViolated); + } + } let groups_size_limit = state .general From 9e20b55081adfd7105f6f755c9076d4851c1045f Mon Sep 17 00:00:00 2001 From: SupaMaggie70 Date: Wed, 10 Sep 2025 13:27:56 -0500 Subject: [PATCH 03/44] Added check for instance limit --- wgpu-core/src/command/draw.rs | 16 ++++++++++++++-- wgpu-core/src/command/render.rs | 14 +++++++++++++- 2 files changed, 27 insertions(+), 3 deletions(-) diff --git a/wgpu-core/src/command/draw.rs b/wgpu-core/src/command/draw.rs index 527567f72bc..77c22f089c8 100644 --- a/wgpu-core/src/command/draw.rs +++ b/wgpu-core/src/command/draw.rs @@ -44,6 +44,13 @@ pub enum DrawError { instance_limit: u64, slot: u32, }, + #[error( + "Instance {last_instance} extends beyond limit {instance_limit} imposed by multiview restrictions on the render pass." + )] + InstanceBeyondMultiviewLimit { + last_instance: u64, + instance_limit: u64, + }, #[error("Index {last_index} extends beyond limit {index_limit}. Did you bind the correct index buffer?")] IndexBeyondLimit { last_index: u64, index_limit: u64 }, #[error( @@ -71,8 +78,13 @@ pub enum DrawError { limit: u32, max_total: u32, }, - #[error("Mesh shader calls in multiview render passes require `EXPERIMENTAL_MESH_SHADER_MULTIVIEW`, and the view count must be <= `Limits::max_mesh_multiview_view_count`")] - MeshPipelineMultiviewLimitsViolated, + #[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/render.rs b/wgpu-core/src/command/render.rs index 71aca699d8e..f364c0d1172 100644 --- a/wgpu-core/src/command/render.rs +++ b/wgpu-core/src/command/render.rs @@ -2631,6 +2631,15 @@ fn draw_indexed( .vertex .limits .validate_instance_limit(first_instance, instance_count)?; + if state.info.multiview.is_some() + && first_instance + instance_count + > state.general.device.limits.max_multiview_instance_index + 1 + { + return Err(DrawError::InstanceBeyondMultiviewLimit { + last_instance: (first_instance + instance_count) as u64, + instance_limit: (state.general.device.limits.max_multiview_instance_index + 1) as u64, + }); + } unsafe { if instance_count > 0 && index_count > 0 { @@ -2663,7 +2672,10 @@ fn draw_mesh_tasks( .contains(wgt::Features::EXPERIMENTAL_MESH_SHADER_MULTIVIEW) || mv.get() > state.general.device.limits.max_mesh_multiview_view_count { - return Err(DrawError::MeshPipelineMultiviewLimitsViolated); + return Err(DrawError::MeshPipelineMultiviewLimitsViolated { + views_given: mv.get(), + max_multiviews: state.general.device.limits.max_mesh_multiview_view_count, + }); } } From f8ab779c8b9a05eaa11b545fa19c505b8581d595 Mon Sep 17 00:00:00 2001 From: SupaMaggie70 Date: Wed, 10 Sep 2025 14:26:30 -0500 Subject: [PATCH 04/44] Added multiview test --- tests/tests/wgpu-gpu/main.rs | 2 + tests/tests/wgpu-gpu/multiview.rs | 199 ++++++++++++++++++++++++++++++ 2 files changed, 201 insertions(+) create mode 100644 tests/tests/wgpu-gpu/multiview.rs diff --git a/tests/tests/wgpu-gpu/main.rs b/tests/tests/wgpu-gpu/main.rs index d461845e92b..07961318260 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; @@ -96,6 +97,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/multiview.rs b/tests/tests/wgpu-gpu/multiview.rs new file mode 100644 index 00000000000..f368e9777e7 --- /dev/null +++ b/tests/tests/wgpu-gpu/multiview.rs @@ -0,0 +1,199 @@ +use std::num::NonZero; + +use wgpu::{ + util::{BufferInitDescriptor, DeviceExt}, + vertex_attr_array, Features, Limits, +}; +use wgpu_test::{ + gpu_test, GpuTestConfiguration, GpuTestInitializer, TestParameters, TestingContext, +}; + +pub fn all_tests(vec: &mut Vec) { + vec.push(DRAW_MULTIVIEW); +} + +#[gpu_test] +static DRAW_MULTIVIEW: GpuTestConfiguration = GpuTestConfiguration::new() + .parameters( + TestParameters::default() + .features(Features::MULTIVIEW) + .limits(Limits { + max_multiview_view_count: 2, + max_multiview_instance_index: 1, + ..Limits::defaults() + }), + ) + .run_async(run_test); + +async fn run_test(ctx: TestingContext) { + let vertex_buffer_content: &[f32; 12] = &[ + // Triangle 1 + -1.0, -1.0, // Bottom left + 1.0, 1.0, // Top right + -1.0, 1.0, // Top left + // Triangle 2 + -1.0, -1.0, // Bottom left + 1.0, -1.0, // Bottom right + 1.0, 1.0, // Top right + ]; + let vertex_buffer = ctx.device.create_buffer_init(&BufferInitDescriptor { + label: None, + contents: bytemuck::cast_slice(vertex_buffer_content), + usage: wgpu::BufferUsages::VERTEX, + }); + + let shader_src = " + @vertex + fn vs_main(@location(0) position: vec2f) -> @builtin(position) vec4f { + return vec4f(position, 0.0, 1.0); + } + + @fragment + fn fs_main() -> @location(0) vec4f { + return vec4f(1.0); + } + "; + + let shader = ctx + .device + .create_shader_module(wgpu::ShaderModuleDescriptor { + label: None, + source: wgpu::ShaderSource::Wgsl(shader_src.into()), + }); + + let pipeline_desc = wgpu::RenderPipelineDescriptor { + label: None, + vertex: wgpu::VertexState { + buffers: &[wgpu::VertexBufferLayout { + array_stride: 8, + step_mode: wgpu::VertexStepMode::Vertex, + attributes: &vertex_attr_array![0 => Float32x2], + }], + module: &shader, + entry_point: Some("vs_main"), + compilation_options: Default::default(), + }, + primitive: wgpu::PrimitiveState::default(), + fragment: Some(wgpu::FragmentState { + module: &shader, + entry_point: Some("fs_main"), + compilation_options: Default::default(), + targets: &[Some(wgpu::ColorTargetState { + format: wgpu::TextureFormat::R8Unorm, + blend: None, + write_mask: wgpu::ColorWrites::ALL, + })], + }), + multiview: NonZero::new(2), + multisample: Default::default(), + layout: None, + depth_stencil: None, + cache: None, + }; + const TEXTURE_SIZE: u32 = 512; + let pipeline = ctx.device.create_render_pipeline(&pipeline_desc); + let create_texture = || { + let texture = ctx.device.create_texture(&wgpu::TextureDescriptor { + label: None, + size: wgpu::Extent3d { + width: TEXTURE_SIZE, + height: TEXTURE_SIZE, + depth_or_array_layers: 1, + }, + 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::D2), + 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: None, + }); + (texture, view) + }; + let (texture1, view1) = create_texture(); + let (texture2, view2) = create_texture(); + let readback_buffer = ctx.device.create_buffer(&wgpu::BufferDescriptor { + label: None, + size: TEXTURE_SIZE as u64 * TEXTURE_SIZE as u64 * 2, + usage: wgpu::BufferUsages::COPY_DST | wgpu::BufferUsages::MAP_READ, + mapped_at_creation: false, + }); + + let mut encoder = ctx + .device + .create_command_encoder(&wgpu::CommandEncoderDescriptor::default()); + let mut rpass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { + label: None, + color_attachments: &[ + Some(wgpu::RenderPassColorAttachment { + view: &view1, + depth_slice: None, + resolve_target: None, + ops: wgpu::Operations { + load: wgpu::LoadOp::Clear(wgpu::Color::BLACK), + store: wgpu::StoreOp::Store, + }, + }), + Some(wgpu::RenderPassColorAttachment { + view: &view2, + depth_slice: None, + resolve_target: None, + ops: wgpu::Operations { + load: wgpu::LoadOp::Clear(wgpu::Color::BLACK), + store: wgpu::StoreOp::Store, + }, + }), + ], + depth_stencil_attachment: None, + timestamp_writes: None, + occlusion_query_set: None, + }); + rpass.set_pipeline(&pipeline); + rpass.set_vertex_buffer(0, vertex_buffer.slice(..)); + rpass.draw(0..6, 0..1); + drop(rpass); + for i in 0..2 { + let texture = [&texture1, &texture2][i]; + encoder.copy_texture_to_buffer( + wgpu::TexelCopyTextureInfo { + texture, + mip_level: 0, + origin: wgpu::Origin3d::ZERO, + aspect: wgpu::TextureAspect::All, + }, + wgpu::TexelCopyBufferInfo { + buffer: &readback_buffer, + layout: wgpu::TexelCopyBufferLayout { + offset: i as u64 * TEXTURE_SIZE as u64 * TEXTURE_SIZE as u64, + bytes_per_row: Some(TEXTURE_SIZE), + rows_per_image: Some(TEXTURE_SIZE), + }, + }, + wgpu::Extent3d { + width: TEXTURE_SIZE, + height: TEXTURE_SIZE, + depth_or_array_layers: 1, + }, + ); + } + ctx.queue.submit([encoder.finish()]); + + let slice = readback_buffer.slice(..); + slice.map_async(wgpu::MapMode::Read, |_| ()); + + ctx.async_poll(wgpu::PollType::wait()).await.unwrap(); + + let data = slice.get_mapped_range(); + let succeeded = data.iter().all(|b| *b == u8::MAX); + assert!(succeeded); +} From 56f8fc15cb91d593372bb5f0c6864626bd01f855 Mon Sep 17 00:00:00 2001 From: SupaMaggie70 Date: Wed, 10 Sep 2025 14:33:36 -0500 Subject: [PATCH 05/44] Fixed various compile issues --- wgpu-hal/src/dx12/adapter.rs | 2 +- wgpu-types/src/lib.rs | 14 +++++++++++--- wgpu/src/backend/webgpu.rs | 5 ++++- 3 files changed, 16 insertions(+), 5 deletions(-) diff --git a/wgpu-hal/src/dx12/adapter.rs b/wgpu-hal/src/dx12/adapter.rs index a888d109416..b33d57d195b 100644 --- a/wgpu-hal/src/dx12/adapter.rs +++ b/wgpu-hal/src/dx12/adapter.rs @@ -648,7 +648,7 @@ 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: if supports_ray_tracing { diff --git a/wgpu-types/src/lib.rs b/wgpu-types/src/lib.rs index 3d1aaaf3a1b..ee91b4feee7 100644 --- a/wgpu-types/src/lib.rs +++ b/wgpu-types/src/lib.rs @@ -776,12 +776,14 @@ impl Limits { /// max_non_sampler_bindings: 1_000_000, /// max_task_workgroup_total_count: 0, /// max_task_workgroups_per_dimension: 0, - /// max_mesh_multiview_count: 0, + /// max_mesh_multiview_view_count: 0, /// max_mesh_output_layers: 0, /// max_blas_primitive_count: 0, /// max_blas_geometry_count: 0, /// max_tlas_instance_count: 0, /// max_acceleration_structures_per_shader_stage: 0, + /// max_multiview_view_count: 0, + /// max_multiview_instance_index: 0, /// }); /// ``` /// @@ -887,13 +889,16 @@ impl Limits { /// /// max_task_workgroup_total_count: 0, /// max_task_workgroups_per_dimension: 0, - /// max_mesh_multiview_count: 0, + /// max_mesh_multiview_view_count: 0, /// max_mesh_output_layers: 0, /// /// max_blas_primitive_count: 0, /// max_blas_geometry_count: 0, /// max_tlas_instance_count: 0, /// max_acceleration_structures_per_shader_stage: 0, + /// + /// max_multiview_view_count: 0, + /// max_multiview_instance_index: 0, /// }); /// ``` #[must_use] @@ -962,13 +967,16 @@ impl Limits { /// /// max_task_workgroup_total_count: 0, /// max_task_workgroups_per_dimension: 0, - /// max_mesh_multiview_count: 0, + /// max_mesh_multiview_view_count: 0, /// max_mesh_output_layers: 0, /// /// max_blas_primitive_count: 0, /// max_blas_geometry_count: 0, /// max_tlas_instance_count: 0, /// max_acceleration_structures_per_shader_stage: 0, + /// + /// max_multiview_view_count: 0, + /// max_multiview_instance_index: 0, /// }); /// ``` #[must_use] diff --git a/wgpu/src/backend/webgpu.rs b/wgpu/src/backend/webgpu.rs index 75049d38ceb..fadac604139 100644 --- a/wgpu/src/backend/webgpu.rs +++ b/wgpu/src/backend/webgpu.rs @@ -827,13 +827,16 @@ fn map_wgt_limits(limits: webgpu_sys::GpuSupportedLimits) -> wgt::Limits { max_task_workgroup_total_count: wgt::Limits::default().max_task_workgroup_total_count, max_task_workgroups_per_dimension: wgt::Limits::default().max_task_workgroups_per_dimension, max_mesh_output_layers: wgt::Limits::default().max_mesh_output_layers, - max_mesh_multiview_count: wgt::Limits::default().max_mesh_multiview_count, + max_mesh_multiview_view_count: wgt::Limits::default().max_mesh_multiview_view_count, max_blas_primitive_count: wgt::Limits::default().max_blas_primitive_count, max_blas_geometry_count: wgt::Limits::default().max_blas_geometry_count, max_tlas_instance_count: wgt::Limits::default().max_tlas_instance_count, max_acceleration_structures_per_shader_stage: wgt::Limits::default() .max_acceleration_structures_per_shader_stage, + + max_multiview_view_count: wgt::Limits::default().max_multiview_view_count, + max_multiview_instance_index: wgt::Limits::default().max_multiview_instance_index, } } From c103f4210f011790a571cecdd4493ef64ec8a4dd Mon Sep 17 00:00:00 2001 From: SupaMaggie70 Date: Wed, 10 Sep 2025 14:45:05 -0500 Subject: [PATCH 06/44] Updated multiview test (idk what I'm doing) --- tests/tests/wgpu-gpu/multiview.rs | 48 ++++++++++++------------------- 1 file changed, 19 insertions(+), 29 deletions(-) diff --git a/tests/tests/wgpu-gpu/multiview.rs b/tests/tests/wgpu-gpu/multiview.rs index f368e9777e7..127a347e83a 100644 --- a/tests/tests/wgpu-gpu/multiview.rs +++ b/tests/tests/wgpu-gpu/multiview.rs @@ -92,13 +92,13 @@ async fn run_test(ctx: TestingContext) { }; const TEXTURE_SIZE: u32 = 512; let pipeline = ctx.device.create_render_pipeline(&pipeline_desc); - let create_texture = || { + let (texture, view) = { let texture = ctx.device.create_texture(&wgpu::TextureDescriptor { label: None, size: wgpu::Extent3d { width: TEXTURE_SIZE, height: TEXTURE_SIZE, - depth_or_array_layers: 1, + depth_or_array_layers: 2, }, mip_level_count: 1, sample_count: 1, @@ -110,18 +110,16 @@ async fn run_test(ctx: TestingContext) { let view = texture.create_view(&wgpu::TextureViewDescriptor { label: None, format: Some(wgpu::TextureFormat::R8Unorm), - dimension: Some(wgpu::TextureViewDimension::D2), + 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: None, + array_layer_count: Some(2), }); (texture, view) }; - let (texture1, view1) = create_texture(); - let (texture2, view2) = create_texture(); let readback_buffer = ctx.device.create_buffer(&wgpu::BufferDescriptor { label: None, size: TEXTURE_SIZE as u64 * TEXTURE_SIZE as u64 * 2, @@ -134,26 +132,15 @@ async fn run_test(ctx: TestingContext) { .create_command_encoder(&wgpu::CommandEncoderDescriptor::default()); let mut rpass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { label: None, - color_attachments: &[ - Some(wgpu::RenderPassColorAttachment { - view: &view1, - depth_slice: None, - resolve_target: None, - ops: wgpu::Operations { - load: wgpu::LoadOp::Clear(wgpu::Color::BLACK), - store: wgpu::StoreOp::Store, - }, - }), - Some(wgpu::RenderPassColorAttachment { - view: &view2, - depth_slice: None, - resolve_target: None, - ops: wgpu::Operations { - load: wgpu::LoadOp::Clear(wgpu::Color::BLACK), - store: wgpu::StoreOp::Store, - }, - }), - ], + color_attachments: &[Some(wgpu::RenderPassColorAttachment { + view: &view, + depth_slice: None, + resolve_target: None, + ops: wgpu::Operations { + load: wgpu::LoadOp::Clear(wgpu::Color::BLACK), + store: wgpu::StoreOp::Store, + }, + })], depth_stencil_attachment: None, timestamp_writes: None, occlusion_query_set: None, @@ -163,12 +150,15 @@ async fn run_test(ctx: TestingContext) { rpass.draw(0..6, 0..1); drop(rpass); for i in 0..2 { - let texture = [&texture1, &texture2][i]; encoder.copy_texture_to_buffer( wgpu::TexelCopyTextureInfo { - texture, + texture: &texture, mip_level: 0, - origin: wgpu::Origin3d::ZERO, + origin: wgpu::Origin3d { + x: 512, + y: 512, + z: i, + }, aspect: wgpu::TextureAspect::All, }, wgpu::TexelCopyBufferInfo { From 06ce95c11c5b41278e1f8e0248a5223b143862c2 Mon Sep 17 00:00:00 2001 From: SupaMaggie70 Date: Wed, 10 Sep 2025 14:53:42 -0500 Subject: [PATCH 07/44] Updated test again --- tests/tests/wgpu-gpu/multiview.rs | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/tests/tests/wgpu-gpu/multiview.rs b/tests/tests/wgpu-gpu/multiview.rs index 127a347e83a..7c64bee2770 100644 --- a/tests/tests/wgpu-gpu/multiview.rs +++ b/tests/tests/wgpu-gpu/multiview.rs @@ -49,8 +49,8 @@ async fn run_test(ctx: TestingContext) { } @fragment - fn fs_main() -> @location(0) vec4f { - return vec4f(1.0); + fn fs_main(@view_index view_index: u32) -> @location(0) vec4f { + return vec4f(f32(view_index)); } "; @@ -154,11 +154,7 @@ async fn run_test(ctx: TestingContext) { wgpu::TexelCopyTextureInfo { texture: &texture, mip_level: 0, - origin: wgpu::Origin3d { - x: 512, - y: 512, - z: i, - }, + origin: wgpu::Origin3d { x: 0, y: 0, z: i }, aspect: wgpu::TextureAspect::All, }, wgpu::TexelCopyBufferInfo { @@ -184,6 +180,11 @@ async fn run_test(ctx: TestingContext) { ctx.async_poll(wgpu::PollType::wait()).await.unwrap(); let data = slice.get_mapped_range(); - let succeeded = data.iter().all(|b| *b == u8::MAX); - assert!(succeeded); + for view_idx in 0..2 { + let texture_bytes = (TEXTURE_SIZE * TEXTURE_SIZE) as usize; + let succeeded = &data[texture_bytes * view_idx..texture_bytes * (view_idx + 1)] + .iter() + .all(|b| *b == if view_idx == 1 { u8::MAX } else { 0 }); + assert!(succeeded); + } } From d4563107b24183397d0c78ab6d3624c29bd2bc77 Mon Sep 17 00:00:00 2001 From: SupaMaggie70 Date: Wed, 10 Sep 2025 15:04:02 -0500 Subject: [PATCH 08/44] Fixed shader hopefully --- tests/tests/wgpu-gpu/multiview.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/tests/wgpu-gpu/multiview.rs b/tests/tests/wgpu-gpu/multiview.rs index 7c64bee2770..03ae5de1c8b 100644 --- a/tests/tests/wgpu-gpu/multiview.rs +++ b/tests/tests/wgpu-gpu/multiview.rs @@ -49,7 +49,7 @@ async fn run_test(ctx: TestingContext) { } @fragment - fn fs_main(@view_index view_index: u32) -> @location(0) vec4f { + fn fs_main(@builtin(view_index) view_index: i32) -> @location(0) vec4f { return vec4f(f32(view_index)); } "; From de5b1be0e2f4d7d8fddd65a3d480f070e66d4407 Mon Sep 17 00:00:00 2001 From: SupaMaggie70Incorporated Date: Wed, 10 Sep 2025 22:43:00 -0500 Subject: [PATCH 09/44] Added some of the functionality to dx12 --- wgpu-hal/src/dx12/adapter.rs | 21 ++++++++++++++++----- wgpu-hal/src/dx12/command.rs | 10 ++++++++++ wgpu-hal/src/vulkan/device.rs | 4 ---- 3 files changed, 26 insertions(+), 9 deletions(-) diff --git a/wgpu-hal/src/dx12/adapter.rs b/wgpu-hal/src/dx12/adapter.rs index b33d57d195b..2c8ecb53982 100644 --- a/wgpu-hal/src/dx12/adapter.rs +++ b/wgpu-hal/src/dx12/adapter.rs @@ -171,9 +171,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), @@ -181,7 +181,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 = { @@ -528,6 +535,8 @@ impl super::Adapter { atomic_int64_on_typed_resource_supported, ); + features.set(wgt::Features::MULTIVIEW, view_instancing); + // TODO: Determine if IPresentationManager is supported let presentation_timer = auxil::dxgi::time::PresentationTimer::new_dxgi(); @@ -672,8 +681,10 @@ impl super::Adapter { 0 }, - max_multiview_view_count: 0, - max_multiview_instance_index: 0, + // See https://microsoft.github.io/DirectX-Specs/d3d/ViewInstancing.html#maximum-viewinstancecount + max_multiview_view_count: if view_instancing { 4 } else { 0 }, + // This limit is specific to vulkan + max_multiview_instance_index: if view_instancing { u32::MAX } else { 0 }, }, alignments: crate::Alignments { buffer_copy_offset: wgt::BufferSize::new( diff --git a/wgpu-hal/src/dx12/command.rs b/wgpu-hal/src/dx12/command.rs index edfea952ed2..a521ccaeba8 100644 --- a/wgpu-hal/src/dx12/command.rs +++ b/wgpu-hal/src/dx12/command.rs @@ -887,6 +887,16 @@ impl crate::CommandEncoder for super::CommandEncoder { } } + if let Some(multiview) = desc.multiview { + // Currently with multiview we render to all views. + let multiview_mask = (1 << multiview.get()) - 1; + unsafe { + list.cast::() + .unwrap() + .SetViewInstanceMask(multiview_mask); + } + } + let raw_vp = Direct3D12::D3D12_VIEWPORT { TopLeftX: 0.0, TopLeftY: 0.0, diff --git a/wgpu-hal/src/vulkan/device.rs b/wgpu-hal/src/vulkan/device.rs index 1297e57f09b..d9037118685 100644 --- a/wgpu-hal/src/vulkan/device.rs +++ b/wgpu-hal/src/vulkan/device.rs @@ -209,10 +209,6 @@ 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); - // 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. From 263aa77b812c1aeef547c7688635fabcae8362ab Mon Sep 17 00:00:00 2001 From: SupaMaggie70Incorporated Date: Wed, 10 Sep 2025 23:30:14 -0500 Subject: [PATCH 10/44] Added multiview mask parameter (didn't update all code to use this parameter, compile is broken) --- .../standalone/02_hello_window/src/main.rs | 1 + player/src/lib.rs | 2 ++ tests/tests/wgpu-gpu/multiview.rs | 1 + wgpu-core/src/command/clear.rs | 2 +- wgpu-core/src/command/render.rs | 20 +++++++++++++++++-- wgpu-core/src/device/trace.rs | 3 ++- wgpu-hal/src/dx12/command.rs | 6 ++---- wgpu-hal/src/dynamic/command.rs | 2 +- wgpu-hal/src/lib.rs | 2 +- wgpu-hal/src/vulkan/command.rs | 17 +--------------- wgpu-hal/src/vulkan/device.rs | 9 ++++----- wgpu-hal/src/vulkan/mod.rs | 4 ++-- wgpu/src/api/render_pass.rs | 4 +++- wgpu/src/backend/wgpu_core.rs | 1 + wgpu/src/util/texture_blitter.rs | 1 + 15 files changed, 41 insertions(+), 34 deletions(-) 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/player/src/lib.rs b/player/src/lib.rs index 8ba7e13ce1b..d01a743be61 100644 --- a/player/src/lib.rs +++ b/player/src/lib.rs @@ -113,6 +113,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, @@ -121,6 +122,7 @@ impl GlobalPlay for wgc::global::Global { target_depth_stencil.as_ref(), timestamp_writes.as_ref(), occlusion_query_set_id, + multiview_mask, ); } trace::Command::BuildAccelerationStructures { blas, tlas } => { diff --git a/tests/tests/wgpu-gpu/multiview.rs b/tests/tests/wgpu-gpu/multiview.rs index 03ae5de1c8b..7d77228e5b9 100644 --- a/tests/tests/wgpu-gpu/multiview.rs +++ b/tests/tests/wgpu-gpu/multiview.rs @@ -144,6 +144,7 @@ async fn run_test(ctx: TestingContext) { depth_stencil_attachment: None, timestamp_writes: None, occlusion_query_set: None, + multiview_mask: 3, }); rpass.set_pipeline(&pipeline); rpass.set_vertex_buffer(0, vertex_buffer.slice(..)); diff --git a/wgpu-core/src/command/clear.rs b/wgpu-core/src/command/clear.rs index 840e9068125..a6e603b7659 100644 --- a/wgpu-core/src/command/clear.rs +++ b/wgpu-core/src/command/clear.rs @@ -527,7 +527,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/render.rs b/wgpu-core/src/command/render.rs index f364c0d1172..2044c0a32ae 100644 --- a/wgpu-core/src/command/render.rs +++ b/wgpu-core/src/command/render.rs @@ -229,6 +229,7 @@ pub struct RenderPassDescriptor<'a> { pub timestamp_writes: Option<&'a PassTimestampWrites>, /// Defines where the occlusion query results will be stored for this pass. pub occlusion_query_set: Option, + pub multiview_mask: Option, } /// Describes the attachments of a render pass. @@ -243,6 +244,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; @@ -270,6 +273,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, @@ -285,6 +289,7 @@ impl RenderPass { color_attachments, depth_stencil_attachment, occlusion_query_set, + multiview_mask, } = desc; Self { @@ -294,6 +299,7 @@ impl RenderPass { depth_stencil_attachment, timestamp_writes, occlusion_query_set, + multiview_mask, current_bind_groups: BindGroupStateChange::new(), current_pipeline: StateChange::new(), @@ -308,6 +314,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(), } @@ -331,6 +338,7 @@ impl fmt::Debug for RenderPass { "push constant u32 count", &self.base.push_constant_data.len(), ) + .field("multiview mask", &self.multiview_mask) .finish() } } @@ -915,6 +923,7 @@ struct RenderPassInfo { divergent_discarded_depth_stencil_aspect: Option<(wgt::TextureAspect, Arc)>, multiview: Option, + multiview_mask: Option, } impl RenderPassInfo { @@ -969,6 +978,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"); @@ -1422,7 +1432,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, }; @@ -1459,6 +1469,7 @@ impl RenderPassInfo { extent, divergent_discarded_depth_stencil_aspect, multiview, + multiview_mask, }) } @@ -1525,7 +1536,7 @@ impl RenderPassInfo { stencil_ops, clear_value: (0.0, 0), }), - multiview: self.multiview, + multiview_mask: self.multiview_mask, timestamp_writes: None, occlusion_query_set: None, }; @@ -1692,6 +1703,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), @@ -1751,6 +1763,7 @@ impl Global { depth_stencil_attachment: Option<&RenderPassDepthStencilAttachment>, timestamp_writes: Option<&PassTimestampWrites>, occlusion_query_set: Option, + multiview_mask: Option, ) { #[cfg(feature = "trace")] { @@ -1772,6 +1785,7 @@ impl Global { target_depth_stencil: depth_stencil_attachment.cloned(), timestamp_writes: timestamp_writes.cloned(), occlusion_query_set_id: occlusion_query_set, + multiview_mask, }); } } @@ -1793,6 +1807,7 @@ impl Global { depth_stencil_attachment, timestamp_writes, occlusion_query_set, + multiview_mask, }, ); if let Some(err) = encoder_error { @@ -1888,6 +1903,7 @@ impl Global { &mut pending_query_resets, &mut pending_discard_init_fixups, snatch_guard, + pass.multiview_mask, ) .map_pass_err(pass_scope)?; diff --git a/wgpu-core/src/device/trace.rs b/wgpu-core/src/device/trace.rs index 80432d5e938..33e27b8cdad 100644 --- a/wgpu-core/src/device/trace.rs +++ b/wgpu-core/src/device/trace.rs @@ -1,5 +1,5 @@ use alloc::{string::String, vec::Vec}; -use core::{convert::Infallible, ops::Range}; +use core::{convert::Infallible, num::NonZeroU32, ops::Range}; #[cfg(feature = "trace")] use {alloc::borrow::Cow, std::io::Write as _}; @@ -217,6 +217,7 @@ pub enum Command { target_depth_stencil: Option, timestamp_writes: Option, occlusion_query_set_id: Option, + multiview_mask: Option, }, BuildAccelerationStructures { blas: Vec, diff --git a/wgpu-hal/src/dx12/command.rs b/wgpu-hal/src/dx12/command.rs index a521ccaeba8..01ad8262fd3 100644 --- a/wgpu-hal/src/dx12/command.rs +++ b/wgpu-hal/src/dx12/command.rs @@ -887,13 +887,11 @@ impl crate::CommandEncoder for super::CommandEncoder { } } - if let Some(multiview) = desc.multiview { - // Currently with multiview we render to all views. - let multiview_mask = (1 << multiview.get()) - 1; + if let Some(multiview_mask) = desc.multiview_mask { unsafe { list.cast::() .unwrap() - .SetViewInstanceMask(multiview_mask); + .SetViewInstanceMask(multiview_mask.get()); } } 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/lib.rs b/wgpu-hal/src/lib.rs index b4255a6c811..cdcc799400f 100644 --- a/wgpu-hal/src/lib.rs +++ b/wgpu-hal/src/lib.rs @@ -2488,7 +2488,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/vulkan/command.rs b/wgpu-hal/src/vulkan/command.rs index 651fdcf0cc6..fea5667f9a8 100644 --- a/wgpu-hal/src/vulkan/command.rs +++ b/wgpu-hal/src/vulkan/command.rs @@ -784,7 +784,7 @@ impl crate::CommandEncoder for super::CommandEncoder { colors: ArrayVec::default(), depth_stencil: None, sample_count: desc.sample_count, - multiview: desc.multiview, + multiview_mask: desc.multiview_mask, }; let mut fb_key = super::FramebufferKey { raw_pass: vk::RenderPass::null(), @@ -825,15 +825,6 @@ impl crate::CommandEncoder for super::CommandEncoder { vk_clear_values.push(unsafe { mem::zeroed() }); fb_key.push_view(at.view.identified_raw_view()); } - - // Assert this attachment is valid for the detected multiview, as a sanity check - // The driver crash for this is really bad on AMD, so the check is worth it - if let Some(multiview) = desc.multiview { - assert_eq!(cat.target.view.layers, multiview); - if let Some(ref resolve_target) = cat.resolve_target { - assert_eq!(resolve_target.view.layers, multiview); - } - } } else { rp_key.colors.push(None); } @@ -850,12 +841,6 @@ impl crate::CommandEncoder for super::CommandEncoder { stencil_ops: ds.stencil_ops, }); fb_key.push_view(ds.target.view.identified_raw_view()); - - // Assert this attachment is valid for the detected multiview, as a sanity check - // The driver crash for this is really bad on AMD, so the check is worth it - if let Some(multiview) = desc.multiview { - assert_eq!(ds.target.view.layers, multiview); - } } let render_area = vk::Rect2D { diff --git a/wgpu-hal/src/vulkan/device.rs b/wgpu-hal/src/vulkan/device.rs index d9037118685..81790da2833 100644 --- a/wgpu-hal/src/vulkan/device.rs +++ b/wgpu-hal/src/vulkan/device.rs @@ -83,7 +83,7 @@ impl super::DeviceShared { ref colors, ref depth_stencil, sample_count, - multiview, + multiview_mask, } = *e.key(); let mut vk_attachments = Vec::new(); @@ -208,11 +208,11 @@ impl super::DeviceShared { let mut multiview_info; let mask; - if let Some(multiview) = multiview { + 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() @@ -1362,7 +1362,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, @@ -1977,7 +1977,6 @@ impl crate::Device for super::Device { ]; let mut compatible_rp_key = super::RenderPassKey { sample_count: desc.multisample.count, - multiview: desc.multiview, ..Default::default() }; let mut stages = ArrayVec::<_, { crate::MAX_CONCURRENT_SHADER_STAGES }>::new(); diff --git a/wgpu-hal/src/vulkan/mod.rs b/wgpu-hal/src/vulkan/mod.rs index 8a0bb03fc3c..504fdeb3fdb 100644 --- a/wgpu-hal/src/vulkan/mod.rs +++ b/wgpu-hal/src/vulkan/mod.rs @@ -704,7 +704,7 @@ struct RenderPassKey { colors: ArrayVec, { crate::MAX_COLOR_ATTACHMENTS }>, depth_stencil: Option, sample_count: u32, - multiview: Option, + multiview_mask: Option, } struct DeviceShared { @@ -965,7 +965,7 @@ impl Texture { pub struct TextureView { raw_texture: vk::Image, raw: vk::ImageView, - layers: NonZeroU32, + _layers: NonZeroU32, format: wgt::TextureFormat, raw_format: vk::Format, base_mip_level: u32, diff --git a/wgpu/src/api/render_pass.rs b/wgpu/src/api/render_pass.rs index c8cc56018eb..5d4f6635278 100644 --- a/wgpu/src/api/render_pass.rs +++ b/wgpu/src/api/render_pass.rs @@ -1,4 +1,4 @@ -use core::ops::Range; +use core::{num::NonZeroU32, ops::Range}; use crate::{ api::{impl_deferred_command_buffer_actions, SharedDeferredCommandBufferActions}, @@ -654,6 +654,8 @@ pub struct RenderPassDescriptor<'a> { pub timestamp_writes: Option>, /// Defines where the occlusion query results will be stored for this pass. pub occlusion_query_set: Option<&'a QuerySet>, + /// The mask of multiview image layers to use for this render pass + pub multiview_mask: Option, } #[cfg(send_sync)] static_assertions::assert_impl_all!(RenderPassDescriptor<'_>: Send, Sync); diff --git a/wgpu/src/backend/wgpu_core.rs b/wgpu/src/backend/wgpu_core.rs index 812cd5276a4..73012ca64c9 100644 --- a/wgpu/src/backend/wgpu_core.rs +++ b/wgpu/src/backend/wgpu_core.rs @@ -2534,6 +2534,7 @@ impl dispatch::CommandEncoderInterface for CoreCommandEncoder { color_attachments: Borrowed(&colors), depth_stencil_attachment: depth_stencil.as_ref(), occlusion_query_set: desc.occlusion_query_set.map(|qs| qs.inner.as_core().id), + multiview_mask: desc.multiview_mask, }, ); diff --git a/wgpu/src/util/texture_blitter.rs b/wgpu/src/util/texture_blitter.rs index e6b46e02859..edabd905be6 100644 --- a/wgpu/src/util/texture_blitter.rs +++ b/wgpu/src/util/texture_blitter.rs @@ -209,6 +209,7 @@ impl TextureBlitter { depth_stencil_attachment: None, timestamp_writes: None, occlusion_query_set: None, + multiview_mask: None, }); pass.set_pipeline(&self.pipeline); pass.set_bind_group(0, &bind_group, &[]); From eca707f1b3bba8a62e6ae17b342061e3386e0c46 Mon Sep 17 00:00:00 2001 From: SupaMaggie70Incorporated Date: Wed, 10 Sep 2025 23:50:44 -0500 Subject: [PATCH 11/44] Added multiview to HLSL writer --- naga/src/back/hlsl/conv.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) 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:?}"))) } }) From 1860e5fae8c2807762de920e06616f75fc5859b9 Mon Sep 17 00:00:00 2001 From: SupaMaggie70 Date: Mon, 15 Sep 2025 11:09:52 -0500 Subject: [PATCH 12/44] Other merge commit --- wgpu-core/src/command/encoder_command.rs | 3 ++- wgpu-core/src/device/trace.rs | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/wgpu-core/src/command/encoder_command.rs b/wgpu-core/src/command/encoder_command.rs index 058caed025c..35c7a41813c 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, vec::Vec}; @@ -62,6 +62,7 @@ pub enum Command { target_depth_stencil: Option, timestamp_writes: Option, occlusion_query_set_id: Option, + multiview_mask: Option>, }, BuildAccelerationStructures { blas: Vec, diff --git a/wgpu-core/src/device/trace.rs b/wgpu-core/src/device/trace.rs index fcb50ce49f4..fcb3f589154 100644 --- a/wgpu-core/src/device/trace.rs +++ b/wgpu-core/src/device/trace.rs @@ -1,5 +1,5 @@ use alloc::{string::String, vec::Vec}; -use core::{convert::Infallible, num::NonZeroU32, ops::Range}; +use core::{convert::Infallible, ops::Range}; #[cfg(feature = "trace")] use {alloc::borrow::Cow, std::io::Write as _}; From ac82457cc66f855461fd5863ebde575d1b891406 Mon Sep 17 00:00:00 2001 From: SupaMaggie70 Date: Mon, 15 Sep 2025 11:18:41 -0500 Subject: [PATCH 13/44] Added changelog entry which assumes the view bitmask is allowed --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8bac70e359f..ce8cdcc5048 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -189,6 +189,7 @@ By @cwfitzgerald in [#8162](https://github.com/gfx-rs/wgpu/pull/8162). - Require new `F16_IN_F32` downlevel flag for `quantizeToF16`, `pack2x16float`, and `unpack2x16float` in WGSL input. By @aleiserson in [#8130](https://github.com/gfx-rs/wgpu/pull/8130). - The error message for non-copyable depth/stencil formats no longer mentions the aspect when it is not relevant. By @reima in [#8156](https://github.com/gfx-rs/wgpu/pull/8156). - Track the initialization status of buffer memory correctly when `copy_texture_to_buffer` skips over padding space between rows or layers, or when the start/end of a texture-buffer transfer is not 4B aligned. By @andyleiserson in [#8099](https://github.com/gfx-rs/wgpu/pull/8099). +- Fix multiview, add tests, add support to DX12, add support for view bitmask. By @SupaMaggie70Incorporated in [#8206](https://github.com/gfx-rs/wgpu/pull/8206). #### naga From 424f31e7c3176ad4227fdd4a2168bd3569302e01 Mon Sep 17 00:00:00 2001 From: SupaMaggie70 Date: Mon, 22 Sep 2025 14:46:20 -0500 Subject: [PATCH 14/44] Updated changelog --- CHANGELOG.md | 24 +++++++++++++++++++++++- examples/features/src/boids/mod.rs | 3 +++ 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a545564fad7..d58ccf5a9b9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -158,6 +158,29 @@ by if the `Feature::MULTI_DRAW_INDIRECT_COUNT` feature is available on the devic By @cwfitzgerald in [#8162](https://github.com/gfx-rs/wgpu/pull/8162). +#### Multiview on DX12 and support for view bitmasks + +Multiview has been reworked, adding support for DX12 and Metal, and adding testing and validation to wgpu itself. +This change also introduces a view bitmask, a new field in `RenderPassDescriptor` that allows a render pass to render multiple to non-adjacent layers. Note that this also influences apps that don't use multiview, as they have to set this field to `None`. +```diff +- wgpu::RenderPassDescriptor { +- label: None, +- color_attachments: &color_attachments, +- depth_stencil_attachment: None, +- timestamp_writes: None, +- occlusion_query_set: None, +- } ++ wgpu::RenderPassDescriptor { ++ label: None, ++ color_attachments: &color_attachments, ++ depth_stencil_attachment: None, ++ timestamp_writes: None, ++ occlusion_query_set: None, ++ multiview_mask: NonZero::new(3), ++ } +``` +By @SupaMaggie70Incorporated in [#8206](https://github.com/gfx-rs/wgpu/pull/8206). + ### New Features #### General @@ -189,7 +212,6 @@ By @cwfitzgerald in [#8162](https://github.com/gfx-rs/wgpu/pull/8162). - Require new `F16_IN_F32` downlevel flag for `quantizeToF16`, `pack2x16float`, and `unpack2x16float` in WGSL input. By @aleiserson in [#8130](https://github.com/gfx-rs/wgpu/pull/8130). - The error message for non-copyable depth/stencil formats no longer mentions the aspect when it is not relevant. By @reima in [#8156](https://github.com/gfx-rs/wgpu/pull/8156). - Track the initialization status of buffer memory correctly when `copy_texture_to_buffer` skips over padding space between rows or layers, or when the start/end of a texture-buffer transfer is not 4B aligned. By @andyleiserson in [#8099](https://github.com/gfx-rs/wgpu/pull/8099). -- Fix multiview, add tests, add support to DX12, add support for view bitmask. By @SupaMaggie70Incorporated in [#8206](https://github.com/gfx-rs/wgpu/pull/8206). #### naga diff --git a/examples/features/src/boids/mod.rs b/examples/features/src/boids/mod.rs index d272442275a..15f3a90bd05 100644 --- a/examples/features/src/boids/mod.rs +++ b/examples/features/src/boids/mod.rs @@ -1,6 +1,8 @@ // Flocking boids example with gpu compute update pass // adapted from https://github.com/austinEng/webgpu-samples/blob/master/src/examples/computeBoids.ts +use std::num::NonZero; + use nanorand::{Rng, WyRand}; use wgpu::util::DeviceExt; @@ -276,6 +278,7 @@ impl crate::framework::Example for Example { depth_stencil_attachment: None, timestamp_writes: None, occlusion_query_set: None, + multiview_mask: NonZero::new(3), }; // get command encoder From f933d2c7846fe27b5de609e14866e5357a188411 Mon Sep 17 00:00:00 2001 From: SupaMaggie70 Date: Mon, 22 Sep 2025 15:05:45 -0500 Subject: [PATCH 15/44] Exposed feature on metal, commented dx12, restricted vulkan to <=64 due to bitmask limitation --- wgpu-hal/src/dx12/adapter.rs | 2 ++ wgpu-hal/src/metal/adapter.rs | 26 ++++++++++++++++++++++++-- wgpu-hal/src/metal/mod.rs | 1 + wgpu-hal/src/vulkan/adapter.rs | 3 ++- 4 files changed, 29 insertions(+), 3 deletions(-) diff --git a/wgpu-hal/src/dx12/adapter.rs b/wgpu-hal/src/dx12/adapter.rs index 15696f238e0..e46fe981df5 100644 --- a/wgpu-hal/src/dx12/adapter.rs +++ b/wgpu-hal/src/dx12/adapter.rs @@ -684,6 +684,8 @@ impl super::Adapter { }, // See https://microsoft.github.io/DirectX-Specs/d3d/ViewInstancing.html#maximum-viewinstancecount + // This is really frickin annoying, 6 (probably 8) for cube mapping would be really nice. But they + // arbitrarily chose 4, eliminating tons of use cases. max_multiview_view_count: if view_instancing { 4 } else { 0 }, // This limit is specific to vulkan max_multiview_instance_index: if view_instancing { u32::MAX } else { 0 }, diff --git a/wgpu-hal/src/metal/adapter.rs b/wgpu-hal/src/metal/adapter.rs index c09a157683a..8bf8ee3e8fb 100644 --- a/wgpu-hal/src/metal/adapter.rs +++ b/wgpu-hal/src/metal/adapter.rs @@ -902,6 +902,16 @@ 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 64 bits, so it can't be higher even if the device for some reason claims to support that. + while device.supports_vertex_amplification_count(factor) && factor <= 64 { + factor *= 2; + } + factor as u32 + }, } } @@ -1001,6 +1011,10 @@ impl super::PrivateCapabilities { features.insert(F::SUBGROUP | F::SUBGROUP_BARRIER); } + if self.supported_vertex_amplification_factor > 1 { + features.insert(F::MULTIVIEW); + } + features } @@ -1092,8 +1106,16 @@ impl super::PrivateCapabilities { // buffer binding points or via argument buffers max_acceleration_structures_per_shader_stage: 0, - max_multiview_view_count: 0, - max_multiview_instance_index: 0, + max_multiview_view_count: if self.supported_vertex_amplification_factor > 1 { + self.supported_vertex_amplification_factor + } else { + 0 + }, + max_multiview_instance_index: if self.supported_vertex_amplification_factor > 1 { + u32::MAX + } else { + 0 + }, }, alignments: crate::Alignments { buffer_copy_offset: wgt::BufferSize::new(self.buffer_alignment).unwrap(), diff --git a/wgpu-hal/src/metal/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/vulkan/adapter.rs b/wgpu-hal/src/vulkan/adapter.rs index 4f57103db0a..c49f99e1563 100644 --- a/wgpu-hal/src/vulkan/adapter.rs +++ b/wgpu-hal/src/vulkan/adapter.rs @@ -1256,7 +1256,8 @@ impl PhysicalDeviceProperties { let (max_multiview_view_count, max_multiview_instance_index) = if let Some(properties) = self.multiview { ( - properties.max_multiview_view_count, + // The bitmask only uses 64 bits, so it can't be higher even if the device for some reason claims to support that. + properties.max_multiview_view_count.min(64), properties.max_multiview_instance_index, ) } else { From 13b012fa277ff41a3ab74aabff5be2000d90e60f Mon Sep 17 00:00:00 2001 From: SupaMaggie70 Date: Mon, 22 Sep 2025 15:32:27 -0500 Subject: [PATCH 16/44] Updated multiview for metal --- wgpu-hal/src/metal/adapter.rs | 10 ++++++---- wgpu-hal/src/metal/command.rs | 15 +++++++++++++++ wgpu-hal/src/metal/device.rs | 4 ++++ wgpu-hal/src/vulkan/adapter.rs | 4 ++-- 4 files changed, 27 insertions(+), 6 deletions(-) diff --git a/wgpu-hal/src/metal/adapter.rs b/wgpu-hal/src/metal/adapter.rs index 8bf8ee3e8fb..30d899672db 100644 --- a/wgpu-hal/src/metal/adapter.rs +++ b/wgpu-hal/src/metal/adapter.rs @@ -906,11 +906,13 @@ impl super::PrivateCapabilities { 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 64 bits, so it can't be higher even if the device for some reason claims to support that. - while device.supports_vertex_amplification_count(factor) && factor <= 64 { - factor *= 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 } - factor as u32 }, } } diff --git a/wgpu-hal/src/metal/command.rs b/wgpu-hal/src/metal/command.rs index 5888eb4e909..758a1e332ba 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; @@ -656,6 +657,20 @@ impl crate::CommandEncoder for super::CommandEncoder { let raw = self.raw_cmd_buf.as_ref().unwrap(); let encoder = raw.new_render_command_encoder(descriptor); + if let Some(mv) = desc.multiview_mask { + let mv = mv.get(); + let mut maps: SmallVec<[metal::VertexAmplificationViewMapping; 32]> = + SmallVec::new(); + for i in 0..32 { + if (mv & (1 << i)) != 0 { + maps.push(metal::VertexAmplificationViewMapping { + renderTargetArrayIndexOffset: i, + viewportArrayIndexOffset: 0, + }); + } + } + encoder.set_vertex_amplification_count(mv.count_ones() as u64, Some(&maps)); + } if let Some(label) = desc.label { encoder.set_label(label); } diff --git a/wgpu-hal/src/metal/device.rs b/wgpu-hal/src/metal/device.rs index dd5d05b6d50..046d06b3b57 100644 --- a/wgpu-hal/src/metal/device.rs +++ b/wgpu-hal/src/metal/device.rs @@ -1337,6 +1337,10 @@ impl crate::Device for super::Device { descriptor.set_label(name); } + if let Some(mv) = desc.multiview { + descriptor.set_max_vertex_amplification_count(mv.get() as u64); + } + let raw = self .shared .device diff --git a/wgpu-hal/src/vulkan/adapter.rs b/wgpu-hal/src/vulkan/adapter.rs index c49f99e1563..d000254ea9b 100644 --- a/wgpu-hal/src/vulkan/adapter.rs +++ b/wgpu-hal/src/vulkan/adapter.rs @@ -1256,8 +1256,8 @@ impl PhysicalDeviceProperties { let (max_multiview_view_count, max_multiview_instance_index) = if let Some(properties) = self.multiview { ( - // The bitmask only uses 64 bits, so it can't be higher even if the device for some reason claims to support that. - properties.max_multiview_view_count.min(64), + // The bitmask only uses 32 bits, so it can't be higher even if the device for some reason claims to support that. + properties.max_multiview_view_count.min(32), properties.max_multiview_instance_index, ) } else { From 0cff8c5cef9adffc064171d14fa172bfe626cb6b Mon Sep 17 00:00:00 2001 From: SupaMaggie70 Date: Mon, 22 Sep 2025 15:35:30 -0500 Subject: [PATCH 17/44] Added view index thing to msl writer --- naga/src/back/msl/mod.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/naga/src/back/msl/mod.rs b/naga/src/back/msl/mod.rs index 7e9180e0c25..49e318737a2 100644 --- a/naga/src/back/msl/mod.rs +++ b/naga/src/back/msl/mod.rs @@ -659,6 +659,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", @@ -685,7 +686,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)) } }; From bf9775aa1c0799587af262a7a10d02c8a675ea33 Mon Sep 17 00:00:00 2001 From: SupaMaggie70 Date: Mon, 22 Sep 2025 15:53:06 -0500 Subject: [PATCH 18/44] Finalized MSL writer changes --- naga/src/back/msl/mod.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/naga/src/back/msl/mod.rs b/naga/src/back/msl/mod.rs index 49e318737a2..ad5276283b6 100644 --- a/naga/src/back/msl/mod.rs +++ b/naga/src/back/msl/mod.rs @@ -521,6 +521,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())); + } _ => {} } From 4ba47594ec6f18e05b3c0eb1f11a01378f509ac4 Mon Sep 17 00:00:00 2001 From: SupaMaggie70 Date: Mon, 22 Sep 2025 15:56:17 -0500 Subject: [PATCH 19/44] Removed nonsense multiview mask from boids example, fixed wgpu-hal examples multiview fields --- examples/features/src/boids/mod.rs | 1 - wgpu-hal/examples/halmark/main.rs | 2 +- wgpu-hal/examples/raw-gles.rs | 2 +- 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/examples/features/src/boids/mod.rs b/examples/features/src/boids/mod.rs index 15f3a90bd05..2fed2da61a8 100644 --- a/examples/features/src/boids/mod.rs +++ b/examples/features/src/boids/mod.rs @@ -278,7 +278,6 @@ impl crate::framework::Example for Example { depth_stencil_attachment: None, timestamp_writes: None, occlusion_query_set: None, - multiview_mask: NonZero::new(3), }; // get command encoder diff --git a/wgpu-hal/examples/halmark/main.rs b/wgpu-hal/examples/halmark/main.rs index 22f211c909b..9bf5976ec74 100644 --- a/wgpu-hal/examples/halmark/main.rs +++ b/wgpu-hal/examples/halmark/main.rs @@ -727,7 +727,7 @@ impl Example { }, })], depth_stencil_attachment: None, - multiview: None, + multiview_mask: None, timestamp_writes: None, occlusion_query_set: None, }; diff --git a/wgpu-hal/examples/raw-gles.rs b/wgpu-hal/examples/raw-gles.rs index e90561efe19..bda0d345875 100644 --- a/wgpu-hal/examples/raw-gles.rs +++ b/wgpu-hal/examples/raw-gles.rs @@ -329,7 +329,7 @@ fn fill_screen(exposed: &hal::ExposedAdapter, width: u32, height clear_value: wgpu_types::Color::BLUE, })], depth_stencil_attachment: None, - multiview: None, + multiview_mask: None, timestamp_writes: None, occlusion_query_set: None, }; From 863283f9070f1fc1a28b8c0521c41a4c6906ac8f Mon Sep 17 00:00:00 2001 From: SupaMaggie70 Date: Mon, 22 Sep 2025 16:04:29 -0500 Subject: [PATCH 20/44] Mopped up miscellaneous crates, going to work on tests & examples now --- benches/benches/wgpu-benchmark/renderpass.rs | 2 ++ deno_webgpu/command_encoder.rs | 2 ++ deno_webgpu/render_pass.rs | 3 +++ wgpu-hal/Cargo.toml | 3 +-- wgpu-hal/src/metal/command.rs | 4 ++++ 5 files changed, 12 insertions(+), 2 deletions(-) diff --git a/benches/benches/wgpu-benchmark/renderpass.rs b/benches/benches/wgpu-benchmark/renderpass.rs index 946c32bfe40..37754116696 100644 --- a/benches/benches/wgpu-benchmark/renderpass.rs +++ b/benches/benches/wgpu-benchmark/renderpass.rs @@ -370,6 +370,7 @@ impl RenderpassState { occlusion_query_set: None, timestamp_writes: None, depth_stencil_attachment: None, + multiview_mask: None, }); let start_idx = pass_number * draws_per_pass; @@ -417,6 +418,7 @@ impl RenderpassState { occlusion_query_set: None, timestamp_writes: None, depth_stencil_attachment: None, + multiview_mask: None, }); render_pass.set_pipeline(self.bindless_pipeline.as_ref().unwrap()); diff --git a/deno_webgpu/command_encoder.rs b/deno_webgpu/command_encoder.rs index 0106b0c0c8d..920e2386c8d 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; @@ -125,6 +126,7 @@ impl GPUCommandEncoder { depth_stencil_attachment: depth_stencil_attachment.as_ref(), timestamp_writes: timestamp_writes.as_ref(), occlusion_query_set: descriptor.occlusion_query_set.map(|query_set| query_set.id), + multiview_mask: NonZero::new(descriptor.multiview_mask), }; let (render_pass, err) = self diff --git a/deno_webgpu/render_pass.rs b/deno_webgpu/render_pass.rs index 97f1bd8e860..8942291d36b 100644 --- a/deno_webgpu/render_pass.rs +++ b/deno_webgpu/render_pass.rs @@ -396,6 +396,9 @@ pub(crate) struct GPURenderPassDescriptor { /*#[webidl(default = 50000000)] #[options(enforce_range = true)] pub max_draw_count: u64,*/ + #[webidl(default = 0xFFFFFFFF)] + #[options(enforce_range = true)] + pub multiview_mask: u32, } #[derive(WebIDL)] 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/src/metal/command.rs b/wgpu-hal/src/metal/command.rs index 758a1e332ba..46b185c045b 100644 --- a/wgpu-hal/src/metal/command.rs +++ b/wgpu-hal/src/metal/command.rs @@ -658,6 +658,9 @@ impl crate::CommandEncoder for super::CommandEncoder { 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 mut maps: SmallVec<[metal::VertexAmplificationViewMapping; 32]> = SmallVec::new(); @@ -665,6 +668,7 @@ impl crate::CommandEncoder for super::CommandEncoder { if (mv & (1 << i)) != 0 { maps.push(metal::VertexAmplificationViewMapping { renderTargetArrayIndexOffset: i, + // WGPU doesn't allow rendering to multiple viewports in a single pass viewportArrayIndexOffset: 0, }); } From ccec13b5113f866678e01274e47e1bdde6192848 Mon Sep 17 00:00:00 2001 From: SupaMaggie70 Date: Mon, 22 Sep 2025 16:07:37 -0500 Subject: [PATCH 21/44] Fixed examples --- examples/features/src/boids/mod.rs | 3 +-- examples/features/src/bunnymark/mod.rs | 1 + examples/features/src/conservative_raster/mod.rs | 2 ++ examples/features/src/cube/mod.rs | 1 + examples/features/src/hello_triangle/mod.rs | 1 + examples/features/src/hello_windows/mod.rs | 1 + examples/features/src/mesh_shader/mod.rs | 1 + examples/features/src/mipmap/mod.rs | 2 ++ examples/features/src/msaa_line/mod.rs | 1 + examples/features/src/multiple_render_targets/mod.rs | 2 ++ examples/features/src/ray_cube_compute/mod.rs | 1 + examples/features/src/ray_cube_fragment/mod.rs | 1 + examples/features/src/ray_cube_normals/mod.rs | 1 + examples/features/src/ray_scene/mod.rs | 1 + examples/features/src/ray_shadows/mod.rs | 1 + examples/features/src/ray_traced_triangle/mod.rs | 1 + examples/features/src/render_to_texture/mod.rs | 1 + examples/features/src/shadow/mod.rs | 2 ++ examples/features/src/skybox/mod.rs | 1 + examples/features/src/srgb_blend/mod.rs | 1 + examples/features/src/stencil_triangles/mod.rs | 1 + examples/features/src/texture_arrays/mod.rs | 1 + examples/features/src/timestamp_queries/mod.rs | 1 + examples/features/src/uniform_values/mod.rs | 1 + examples/features/src/water/mod.rs | 3 +++ 25 files changed, 31 insertions(+), 2 deletions(-) diff --git a/examples/features/src/boids/mod.rs b/examples/features/src/boids/mod.rs index 2fed2da61a8..be79435b70d 100644 --- a/examples/features/src/boids/mod.rs +++ b/examples/features/src/boids/mod.rs @@ -1,8 +1,6 @@ // Flocking boids example with gpu compute update pass // adapted from https://github.com/austinEng/webgpu-samples/blob/master/src/examples/computeBoids.ts -use std::num::NonZero; - use nanorand::{Rng, WyRand}; use wgpu::util::DeviceExt; @@ -278,6 +276,7 @@ impl crate::framework::Example for Example { depth_stencil_attachment: None, timestamp_writes: None, occlusion_query_set: None, + multiview_mask: None, }; // get command encoder diff --git a/examples/features/src/bunnymark/mod.rs b/examples/features/src/bunnymark/mod.rs index 233fe8ba539..512137fd30f 100644 --- a/examples/features/src/bunnymark/mod.rs +++ b/examples/features/src/bunnymark/mod.rs @@ -122,6 +122,7 @@ impl Example { depth_stencil_attachment: None, timestamp_writes: None, occlusion_query_set: None, + multiview_mask: None, }); rpass.set_pipeline(&self.pipeline); rpass.set_bind_group(0, &self.global_group, &[]); diff --git a/examples/features/src/conservative_raster/mod.rs b/examples/features/src/conservative_raster/mod.rs index d2f5c74fdec..2f1f417b161 100644 --- a/examples/features/src/conservative_raster/mod.rs +++ b/examples/features/src/conservative_raster/mod.rs @@ -273,6 +273,7 @@ impl crate::framework::Example for Example { depth_stencil_attachment: None, timestamp_writes: None, occlusion_query_set: None, + multiview_mask: None, }); rpass.set_pipeline(&self.pipeline_triangle_conservative); @@ -295,6 +296,7 @@ impl crate::framework::Example for Example { depth_stencil_attachment: None, timestamp_writes: None, occlusion_query_set: None, + multiview_mask: None, }); rpass.set_pipeline(&self.pipeline_upscale); diff --git a/examples/features/src/cube/mod.rs b/examples/features/src/cube/mod.rs index 686dca3782e..ccdd31af08d 100644 --- a/examples/features/src/cube/mod.rs +++ b/examples/features/src/cube/mod.rs @@ -356,6 +356,7 @@ impl crate::framework::Example for Example { depth_stencil_attachment: None, timestamp_writes: None, occlusion_query_set: None, + multiview_mask: None, }); rpass.push_debug_group("Prepare data for draw."); rpass.set_pipeline(&self.pipeline); diff --git a/examples/features/src/hello_triangle/mod.rs b/examples/features/src/hello_triangle/mod.rs index eca99f6bb37..cf5d40e7659 100644 --- a/examples/features/src/hello_triangle/mod.rs +++ b/examples/features/src/hello_triangle/mod.rs @@ -129,6 +129,7 @@ async fn run(event_loop: EventLoop<()>, window: Window) { depth_stencil_attachment: None, timestamp_writes: None, occlusion_query_set: None, + multiview_mask: None, }); rpass.set_pipeline(&render_pipeline); rpass.draw(0..3, 0..1); diff --git a/examples/features/src/hello_windows/mod.rs b/examples/features/src/hello_windows/mod.rs index fbd69f0bff2..c1234e54998 100644 --- a/examples/features/src/hello_windows/mod.rs +++ b/examples/features/src/hello_windows/mod.rs @@ -133,6 +133,7 @@ async fn run(event_loop: EventLoop<()>, viewports: Vec<(Arc, wgpu::Color depth_stencil_attachment: None, timestamp_writes: None, occlusion_query_set: None, + multiview_mask: None, }); } diff --git a/examples/features/src/mesh_shader/mod.rs b/examples/features/src/mesh_shader/mod.rs index 675150f5106..3b5afe4a37e 100644 --- a/examples/features/src/mesh_shader/mod.rs +++ b/examples/features/src/mesh_shader/mod.rs @@ -106,6 +106,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 5e5cc3d34e0..4e49132594e 100644 --- a/examples/features/src/mipmap/mod.rs +++ b/examples/features/src/mipmap/mod.rs @@ -170,6 +170,7 @@ impl Example { depth_stencil_attachment: None, timestamp_writes: None, occlusion_query_set: None, + multiview_mask: None, }); if let Some(ref query_sets) = query_sets { rpass.write_timestamp(&query_sets.timestamp, timestamp_query_index_base); @@ -492,6 +493,7 @@ impl crate::framework::Example for Example { depth_stencil_attachment: None, timestamp_writes: None, occlusion_query_set: None, + multiview_mask: None, }); rpass.set_pipeline(&self.draw_pipeline); rpass.set_bind_group(0, &self.bind_group, &[]); diff --git a/examples/features/src/msaa_line/mod.rs b/examples/features/src/msaa_line/mod.rs index 71c6b277c4b..7dd4eb2ec06 100644 --- a/examples/features/src/msaa_line/mod.rs +++ b/examples/features/src/msaa_line/mod.rs @@ -307,6 +307,7 @@ impl crate::framework::Example for Example { depth_stencil_attachment: None, timestamp_writes: None, occlusion_query_set: None, + multiview_mask: None, }) .execute_bundles(iter::once(&self.bundle)); } diff --git a/examples/features/src/multiple_render_targets/mod.rs b/examples/features/src/multiple_render_targets/mod.rs index d708f701f0b..31c4481cd74 100644 --- a/examples/features/src/multiple_render_targets/mod.rs +++ b/examples/features/src/multiple_render_targets/mod.rs @@ -182,6 +182,7 @@ impl MultiTargetRenderer { depth_stencil_attachment: None, timestamp_writes: None, occlusion_query_set: None, + multiview_mask: None, }); rpass.set_pipeline(&self.pipeline); rpass.set_bind_group(0, &self.bindgroup, &[]); @@ -339,6 +340,7 @@ impl TargetRenderer { depth_stencil_attachment: None, timestamp_writes: None, occlusion_query_set: None, + multiview_mask: None, }); rpass.set_pipeline(&self.pipeline); rpass.set_bind_group(0, &self.bindgroup_left, &[]); diff --git a/examples/features/src/ray_cube_compute/mod.rs b/examples/features/src/ray_cube_compute/mod.rs index 3d875b1c745..2e60e5fd956 100644 --- a/examples/features/src/ray_cube_compute/mod.rs +++ b/examples/features/src/ray_cube_compute/mod.rs @@ -469,6 +469,7 @@ impl crate::framework::Example for Example { depth_stencil_attachment: None, timestamp_writes: None, occlusion_query_set: None, + multiview_mask: None, }); rpass.set_pipeline(&self.blit_pipeline); diff --git a/examples/features/src/ray_cube_fragment/mod.rs b/examples/features/src/ray_cube_fragment/mod.rs index dde26e02832..6643152a937 100644 --- a/examples/features/src/ray_cube_fragment/mod.rs +++ b/examples/features/src/ray_cube_fragment/mod.rs @@ -355,6 +355,7 @@ impl crate::framework::Example for Example { depth_stencil_attachment: None, timestamp_writes: None, occlusion_query_set: None, + multiview_mask: None, }); rpass.set_pipeline(&self.pipeline); diff --git a/examples/features/src/ray_cube_normals/mod.rs b/examples/features/src/ray_cube_normals/mod.rs index 4620b7551c0..a5553c7fa79 100644 --- a/examples/features/src/ray_cube_normals/mod.rs +++ b/examples/features/src/ray_cube_normals/mod.rs @@ -455,6 +455,7 @@ impl crate::framework::Example for Example { depth_stencil_attachment: None, timestamp_writes: None, occlusion_query_set: None, + multiview_mask: None, }); rpass.set_pipeline(&self.blit_pipeline); diff --git a/examples/features/src/ray_scene/mod.rs b/examples/features/src/ray_scene/mod.rs index 21c2aada802..eb367cc53a2 100644 --- a/examples/features/src/ray_scene/mod.rs +++ b/examples/features/src/ray_scene/mod.rs @@ -535,6 +535,7 @@ impl crate::framework::Example for Example { depth_stencil_attachment: None, timestamp_writes: None, occlusion_query_set: None, + multiview_mask: None, }); rpass.set_pipeline(&self.pipeline); diff --git a/examples/features/src/ray_shadows/mod.rs b/examples/features/src/ray_shadows/mod.rs index 559ac32342a..93a86edf65d 100644 --- a/examples/features/src/ray_shadows/mod.rs +++ b/examples/features/src/ray_shadows/mod.rs @@ -348,6 +348,7 @@ impl crate::framework::Example for Example { depth_stencil_attachment: None, timestamp_writes: None, occlusion_query_set: None, + multiview_mask: None, }); rpass.set_pipeline(&self.pipeline); diff --git a/examples/features/src/ray_traced_triangle/mod.rs b/examples/features/src/ray_traced_triangle/mod.rs index 0df2e829d2b..eefe0ce6d1f 100644 --- a/examples/features/src/ray_traced_triangle/mod.rs +++ b/examples/features/src/ray_traced_triangle/mod.rs @@ -414,6 +414,7 @@ impl crate::framework::Example for Example { depth_stencil_attachment: None, timestamp_writes: None, occlusion_query_set: None, + multiview_mask: None, }); rpass.set_pipeline(&self.blit_pipeline); diff --git a/examples/features/src/render_to_texture/mod.rs b/examples/features/src/render_to_texture/mod.rs index 7981f665506..e7aa5de14de 100644 --- a/examples/features/src/render_to_texture/mod.rs +++ b/examples/features/src/render_to_texture/mod.rs @@ -95,6 +95,7 @@ async fn run(_path: Option) { depth_stencil_attachment: None, occlusion_query_set: None, timestamp_writes: None, + multiview_mask: None, }); render_pass.set_pipeline(&pipeline); render_pass.draw(0..3, 0..1); diff --git a/examples/features/src/shadow/mod.rs b/examples/features/src/shadow/mod.rs index b89d2c902dc..477f6a7429e 100644 --- a/examples/features/src/shadow/mod.rs +++ b/examples/features/src/shadow/mod.rs @@ -771,6 +771,7 @@ impl crate::framework::Example for Example { }), timestamp_writes: None, occlusion_query_set: None, + multiview_mask: None, }); pass.set_pipeline(&self.shadow_pass.pipeline); pass.set_bind_group(0, &self.shadow_pass.bind_group, &[]); @@ -816,6 +817,7 @@ impl crate::framework::Example for Example { }), timestamp_writes: None, occlusion_query_set: None, + multiview_mask: None, }); pass.set_pipeline(&self.forward_pass.pipeline); pass.set_bind_group(0, &self.forward_pass.bind_group, &[]); diff --git a/examples/features/src/skybox/mod.rs b/examples/features/src/skybox/mod.rs index 699f6615ba9..3847a18824d 100644 --- a/examples/features/src/skybox/mod.rs +++ b/examples/features/src/skybox/mod.rs @@ -444,6 +444,7 @@ impl crate::framework::Example for Example { }), timestamp_writes: None, occlusion_query_set: None, + multiview_mask: None, }); rpass.set_bind_group(0, &self.bind_group, &[]); diff --git a/examples/features/src/srgb_blend/mod.rs b/examples/features/src/srgb_blend/mod.rs index fcd59265b76..dbc1455dfce 100644 --- a/examples/features/src/srgb_blend/mod.rs +++ b/examples/features/src/srgb_blend/mod.rs @@ -196,6 +196,7 @@ impl crate::framework::Example for Example { depth_stencil_attachment: None, timestamp_writes: None, occlusion_query_set: None, + multiview_mask: None, }); rpass.push_debug_group("Prepare data for draw."); rpass.set_pipeline(&self.pipeline); diff --git a/examples/features/src/stencil_triangles/mod.rs b/examples/features/src/stencil_triangles/mod.rs index e1e44acf40f..c39deb1c2f6 100644 --- a/examples/features/src/stencil_triangles/mod.rs +++ b/examples/features/src/stencil_triangles/mod.rs @@ -222,6 +222,7 @@ impl crate::framework::Example for Example { }), timestamp_writes: None, occlusion_query_set: None, + multiview_mask: None, }); rpass.set_stencil_reference(1); diff --git a/examples/features/src/texture_arrays/mod.rs b/examples/features/src/texture_arrays/mod.rs index e7615444200..151bd0b2ca8 100644 --- a/examples/features/src/texture_arrays/mod.rs +++ b/examples/features/src/texture_arrays/mod.rs @@ -402,6 +402,7 @@ impl crate::framework::Example for Example { depth_stencil_attachment: None, timestamp_writes: None, occlusion_query_set: None, + multiview_mask: None, }); rpass.set_pipeline(&self.pipeline); diff --git a/examples/features/src/timestamp_queries/mod.rs b/examples/features/src/timestamp_queries/mod.rs index 2531cc4e692..7deaa2b7f44 100644 --- a/examples/features/src/timestamp_queries/mod.rs +++ b/examples/features/src/timestamp_queries/mod.rs @@ -395,6 +395,7 @@ fn render_pass( end_of_pass_write_index: Some(*next_unused_query + 1), }), occlusion_query_set: None, + multiview_mask: None, }); *next_unused_query += 2; diff --git a/examples/features/src/uniform_values/mod.rs b/examples/features/src/uniform_values/mod.rs index 6a42f2635e1..344b3469471 100644 --- a/examples/features/src/uniform_values/mod.rs +++ b/examples/features/src/uniform_values/mod.rs @@ -319,6 +319,7 @@ async fn run(event_loop: EventLoop<()>, window: Arc) { depth_stencil_attachment: None, occlusion_query_set: None, timestamp_writes: None, + multiview_mask: None, }); render_pass.set_pipeline(&wgpu_context_ref.pipeline); // (9) diff --git a/examples/features/src/water/mod.rs b/examples/features/src/water/mod.rs index 5d1241fce39..2f29bd27bba 100644 --- a/examples/features/src/water/mod.rs +++ b/examples/features/src/water/mod.rs @@ -750,6 +750,7 @@ impl crate::framework::Example for Example { }), timestamp_writes: None, occlusion_query_set: None, + multiview_mask: None, }); rpass.execute_bundles([&self.terrain_bundle]); @@ -778,6 +779,7 @@ impl crate::framework::Example for Example { }), timestamp_writes: None, occlusion_query_set: None, + multiview_mask: None, }); rpass.set_pipeline(&self.terrain_pipeline); rpass.set_bind_group(0, &self.terrain_normal_bind_group, &[]); @@ -805,6 +807,7 @@ impl crate::framework::Example for Example { }), timestamp_writes: None, occlusion_query_set: None, + multiview_mask: None, }); rpass.set_pipeline(&self.water_pipeline); From c6da54a146962ff7dffe57945974c73583b78601 Mon Sep 17 00:00:00 2001 From: SupaMaggie70 Date: Mon, 22 Sep 2025 16:11:10 -0500 Subject: [PATCH 22/44] Fixed every test --- tests/tests/wgpu-gpu/binding_array/sampled_textures.rs | 1 + tests/tests/wgpu-gpu/clip_distances.rs | 1 + tests/tests/wgpu-gpu/device.rs | 1 + tests/tests/wgpu-gpu/draw_indirect.rs | 2 ++ tests/tests/wgpu-gpu/encoder.rs | 1 + tests/tests/wgpu-gpu/mem_leaks.rs | 1 + tests/tests/wgpu-gpu/mesh_shader/mod.rs | 2 ++ tests/tests/wgpu-gpu/multiview.rs | 2 +- tests/tests/wgpu-gpu/occlusion_query/mod.rs | 1 + tests/tests/wgpu-gpu/planar_texture/mod.rs | 1 + tests/tests/wgpu-gpu/regression/issue_3349.rs | 1 + tests/tests/wgpu-gpu/regression/issue_3457.rs | 2 ++ tests/tests/wgpu-gpu/regression/issue_4485.rs | 1 + tests/tests/wgpu-gpu/regression/issue_4514.rs | 1 + tests/tests/wgpu-gpu/render_pass_ownership.rs | 1 + tests/tests/wgpu-gpu/render_target.rs | 2 ++ tests/tests/wgpu-gpu/scissor_tests/mod.rs | 1 + tests/tests/wgpu-gpu/shader_primitive_index/mod.rs | 1 + tests/tests/wgpu-gpu/shader_view_format/mod.rs | 1 + tests/tests/wgpu-gpu/vertex_formats/mod.rs | 1 + tests/tests/wgpu-gpu/vertex_indices/mod.rs | 1 + tests/tests/wgpu-gpu/zero_init_texture_after_discard.rs | 4 ++++ tests/tests/wgpu-validation/api/external_texture.rs | 1 + 23 files changed, 30 insertions(+), 1 deletion(-) diff --git a/tests/tests/wgpu-gpu/binding_array/sampled_textures.rs b/tests/tests/wgpu-gpu/binding_array/sampled_textures.rs index c445857a92f..5c6bcd79019 100644 --- a/tests/tests/wgpu-gpu/binding_array/sampled_textures.rs +++ b/tests/tests/wgpu-gpu/binding_array/sampled_textures.rs @@ -228,6 +228,7 @@ async fn binding_array_sampled_textures(ctx: TestingContext, partially_bound: bo depth_stencil_attachment: None, timestamp_writes: None, occlusion_query_set: None, + multiview_mask: None, }); render_pass.set_pipeline(&pipeline); render_pass.set_bind_group(0, &bind_group, &[]); diff --git a/tests/tests/wgpu-gpu/clip_distances.rs b/tests/tests/wgpu-gpu/clip_distances.rs index 3152333e8dd..af1e3c3d0c4 100644 --- a/tests/tests/wgpu-gpu/clip_distances.rs +++ b/tests/tests/wgpu-gpu/clip_distances.rs @@ -87,6 +87,7 @@ async fn clip_distances(ctx: TestingContext) { depth_stencil_attachment: None, timestamp_writes: None, occlusion_query_set: None, + multiview_mask: None, }); rpass.set_pipeline(&pipeline); rpass.draw(0..3, 0..1); diff --git a/tests/tests/wgpu-gpu/device.rs b/tests/tests/wgpu-gpu/device.rs index 5accb3d33b8..7df1949bab4 100644 --- a/tests/tests/wgpu-gpu/device.rs +++ b/tests/tests/wgpu-gpu/device.rs @@ -346,6 +346,7 @@ static DEVICE_DESTROY_THEN_MORE: GpuTestConfiguration = GpuTestConfiguration::ne depth_stencil_attachment: None, timestamp_writes: None, occlusion_query_set: None, + multiview_mask: None, }); drop(pass); ctx.queue.submit([encoder_for_render_pass.finish()]); diff --git a/tests/tests/wgpu-gpu/draw_indirect.rs b/tests/tests/wgpu-gpu/draw_indirect.rs index a0c8253be0b..a3be9a81d32 100644 --- a/tests/tests/wgpu-gpu/draw_indirect.rs +++ b/tests/tests/wgpu-gpu/draw_indirect.rs @@ -272,6 +272,7 @@ async fn run_test(ctx: TestingContext, test_data: TestData, expect_noop: bool) { depth_stencil_attachment: None, timestamp_writes: None, occlusion_query_set: None, + multiview_mask: None, }); rpass.set_pipeline(&pipeline); @@ -745,6 +746,7 @@ async fn indirect_buffer_offsets(ctx: TestingContext) { depth_stencil_attachment: None, timestamp_writes: None, occlusion_query_set: None, + multiview_mask: None, }); rpass.set_pipeline(&pipeline); diff --git a/tests/tests/wgpu-gpu/encoder.rs b/tests/tests/wgpu-gpu/encoder.rs index 426ef3c808e..ab1c10594ca 100644 --- a/tests/tests/wgpu-gpu/encoder.rs +++ b/tests/tests/wgpu-gpu/encoder.rs @@ -77,6 +77,7 @@ static DROP_ENCODER_AFTER_ERROR: GpuTestConfiguration = GpuTestConfiguration::ne depth_stencil_attachment: None, timestamp_writes: None, occlusion_query_set: None, + multiview_mask: None, }); // This viewport is invalid because it has negative size. diff --git a/tests/tests/wgpu-gpu/mem_leaks.rs b/tests/tests/wgpu-gpu/mem_leaks.rs index c58981d5b1a..4078423f445 100644 --- a/tests/tests/wgpu-gpu/mem_leaks.rs +++ b/tests/tests/wgpu-gpu/mem_leaks.rs @@ -207,6 +207,7 @@ async fn draw_test_with_reports( depth_stencil_attachment: None, timestamp_writes: None, occlusion_query_set: None, + multiview_mask: None, }); rpass.set_pipeline(&pipeline); diff --git a/tests/tests/wgpu-gpu/mesh_shader/mod.rs b/tests/tests/wgpu-gpu/mesh_shader/mod.rs index 23e2c6ccda5..ccda3732848 100644 --- a/tests/tests/wgpu-gpu/mesh_shader/mod.rs +++ b/tests/tests/wgpu-gpu/mesh_shader/mod.rs @@ -141,6 +141,7 @@ fn mesh_pipeline_build( }), timestamp_writes: None, occlusion_query_set: None, + multiview_mask: None, }); pass.set_pipeline(&pipeline); pass.draw_mesh_tasks(1, 1, 1); @@ -234,6 +235,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 index 7d77228e5b9..3164511b044 100644 --- a/tests/tests/wgpu-gpu/multiview.rs +++ b/tests/tests/wgpu-gpu/multiview.rs @@ -144,7 +144,7 @@ async fn run_test(ctx: TestingContext) { depth_stencil_attachment: None, timestamp_writes: None, occlusion_query_set: None, - multiview_mask: 3, + multiview_mask: NonZero::new(3), }); rpass.set_pipeline(&pipeline); rpass.set_vertex_buffer(0, vertex_buffer.slice(..)); diff --git a/tests/tests/wgpu-gpu/occlusion_query/mod.rs b/tests/tests/wgpu-gpu/occlusion_query/mod.rs index a30b0dc5893..2b1b2131794 100644 --- a/tests/tests/wgpu-gpu/occlusion_query/mod.rs +++ b/tests/tests/wgpu-gpu/occlusion_query/mod.rs @@ -84,6 +84,7 @@ static OCCLUSION_QUERY: GpuTestConfiguration = GpuTestConfiguration::new() }), timestamp_writes: None, occlusion_query_set: Some(&query_set), + multiview_mask: None, }); render_pass.set_pipeline(&pipeline); diff --git a/tests/tests/wgpu-gpu/planar_texture/mod.rs b/tests/tests/wgpu-gpu/planar_texture/mod.rs index 961d4b43f64..b491f9cc2da 100644 --- a/tests/tests/wgpu-gpu/planar_texture/mod.rs +++ b/tests/tests/wgpu-gpu/planar_texture/mod.rs @@ -100,6 +100,7 @@ fn test_planar_texture_creation_sampling( depth_stencil_attachment: None, timestamp_writes: None, occlusion_query_set: None, + multiview_mask: None, }); rpass.set_pipeline(&pipeline); rpass.set_bind_group(0, &bind_group, &[]); diff --git a/tests/tests/wgpu-gpu/regression/issue_3349.rs b/tests/tests/wgpu-gpu/regression/issue_3349.rs index adee4eb6527..8637f6c3217 100644 --- a/tests/tests/wgpu-gpu/regression/issue_3349.rs +++ b/tests/tests/wgpu-gpu/regression/issue_3349.rs @@ -166,6 +166,7 @@ async fn multi_stage_data_binding_test(ctx: TestingContext) { depth_stencil_attachment: None, timestamp_writes: None, occlusion_query_set: None, + multiview_mask: None, }); rpass.set_pipeline(&pipeline); diff --git a/tests/tests/wgpu-gpu/regression/issue_3457.rs b/tests/tests/wgpu-gpu/regression/issue_3457.rs index 4b7503776ea..ccc6da4f39f 100644 --- a/tests/tests/wgpu-gpu/regression/issue_3457.rs +++ b/tests/tests/wgpu-gpu/regression/issue_3457.rs @@ -156,6 +156,7 @@ static PASS_RESET_VERTEX_BUFFER: GpuTestConfiguration = GpuTestConfiguration::ne depth_stencil_attachment: None, timestamp_writes: None, occlusion_query_set: None, + multiview_mask: None, }); double_rpass.set_pipeline(&double_pipeline); @@ -192,6 +193,7 @@ static PASS_RESET_VERTEX_BUFFER: GpuTestConfiguration = GpuTestConfiguration::ne depth_stencil_attachment: None, timestamp_writes: None, occlusion_query_set: None, + multiview_mask: None, }); single_rpass.set_pipeline(&single_pipeline); diff --git a/tests/tests/wgpu-gpu/regression/issue_4485.rs b/tests/tests/wgpu-gpu/regression/issue_4485.rs index 5c814377822..006b2fcba21 100644 --- a/tests/tests/wgpu-gpu/regression/issue_4485.rs +++ b/tests/tests/wgpu-gpu/regression/issue_4485.rs @@ -98,6 +98,7 @@ async fn test_impl(ctx: &TestingContext) { depth_stencil_attachment: None, timestamp_writes: None, occlusion_query_set: None, + multiview_mask: None, }); render_pass.set_pipeline(&pipeline); render_pass.draw(0..3, 0..1); diff --git a/tests/tests/wgpu-gpu/regression/issue_4514.rs b/tests/tests/wgpu-gpu/regression/issue_4514.rs index f3ad2051a90..90d126dc3e8 100644 --- a/tests/tests/wgpu-gpu/regression/issue_4514.rs +++ b/tests/tests/wgpu-gpu/regression/issue_4514.rs @@ -98,6 +98,7 @@ async fn test_impl(ctx: &TestingContext) { depth_stencil_attachment: None, timestamp_writes: None, occlusion_query_set: None, + multiview_mask: None, }); render_pass.set_pipeline(&pipeline); render_pass.draw(0..3, 0..1); diff --git a/tests/tests/wgpu-gpu/render_pass_ownership.rs b/tests/tests/wgpu-gpu/render_pass_ownership.rs index 3e2b1a9831d..eea5b8f5bc4 100644 --- a/tests/tests/wgpu-gpu/render_pass_ownership.rs +++ b/tests/tests/wgpu-gpu/render_pass_ownership.rs @@ -91,6 +91,7 @@ async fn render_pass_resource_ownership(ctx: TestingContext) { }), timestamp_writes: None, occlusion_query_set: Some(&occlusion_query_set), + multiview_mask: None, }); // Drop render pass attachments right away. diff --git a/tests/tests/wgpu-gpu/render_target.rs b/tests/tests/wgpu-gpu/render_target.rs index 9e69e805d8e..bf34bd658e9 100644 --- a/tests/tests/wgpu-gpu/render_target.rs +++ b/tests/tests/wgpu-gpu/render_target.rs @@ -203,6 +203,7 @@ async fn run_test( depth_stencil_attachment: None, timestamp_writes: None, occlusion_query_set: None, + multiview_mask: None, }); rpass.set_pipeline(&pipeline); rpass.set_vertex_buffer(0, vertex_buffer.slice(..)); @@ -384,6 +385,7 @@ async fn run_test_3d(ctx: TestingContext) { depth_stencil_attachment: None, timestamp_writes: None, occlusion_query_set: None, + multiview_mask: None, }); rpass.set_pipeline(&pipeline); rpass.set_vertex_buffer(0, vertex_buffer.slice(..)); diff --git a/tests/tests/wgpu-gpu/scissor_tests/mod.rs b/tests/tests/wgpu-gpu/scissor_tests/mod.rs index 14b81eb4337..fdb8b4fcb5f 100644 --- a/tests/tests/wgpu-gpu/scissor_tests/mod.rs +++ b/tests/tests/wgpu-gpu/scissor_tests/mod.rs @@ -98,6 +98,7 @@ async fn scissor_test_impl( depth_stencil_attachment: None, timestamp_writes: None, occlusion_query_set: None, + multiview_mask: None, }); render_pass.set_pipeline(&pipeline); render_pass.set_scissor_rect( diff --git a/tests/tests/wgpu-gpu/shader_primitive_index/mod.rs b/tests/tests/wgpu-gpu/shader_primitive_index/mod.rs index eb6e6fa1d23..2fce464fdb5 100644 --- a/tests/tests/wgpu-gpu/shader_primitive_index/mod.rs +++ b/tests/tests/wgpu-gpu/shader_primitive_index/mod.rs @@ -193,6 +193,7 @@ async fn pulling_common( depth_stencil_attachment: None, timestamp_writes: None, occlusion_query_set: None, + multiview_mask: None, }); rpass.set_pipeline(&pipeline); diff --git a/tests/tests/wgpu-gpu/shader_view_format/mod.rs b/tests/tests/wgpu-gpu/shader_view_format/mod.rs index d17f0e67572..c576445d042 100644 --- a/tests/tests/wgpu-gpu/shader_view_format/mod.rs +++ b/tests/tests/wgpu-gpu/shader_view_format/mod.rs @@ -151,6 +151,7 @@ async fn reinterpret( depth_stencil_attachment: None, timestamp_writes: None, occlusion_query_set: None, + multiview_mask: None, }); rpass.set_pipeline(&pipeline); rpass.set_bind_group(0, &bind_group, &[]); diff --git a/tests/tests/wgpu-gpu/vertex_formats/mod.rs b/tests/tests/wgpu-gpu/vertex_formats/mod.rs index 88f7659307e..b27fa930114 100644 --- a/tests/tests/wgpu-gpu/vertex_formats/mod.rs +++ b/tests/tests/wgpu-gpu/vertex_formats/mod.rs @@ -360,6 +360,7 @@ async fn vertex_formats_common(ctx: TestingContext, tests: &[Test<'_>]) { depth_stencil_attachment: None, timestamp_writes: None, occlusion_query_set: None, + multiview_mask: None, }); rpass.set_vertex_buffer(0, buffer_input.slice(..)); diff --git a/tests/tests/wgpu-gpu/vertex_indices/mod.rs b/tests/tests/wgpu-gpu/vertex_indices/mod.rs index 6d53e960829..849085338b0 100644 --- a/tests/tests/wgpu-gpu/vertex_indices/mod.rs +++ b/tests/tests/wgpu-gpu/vertex_indices/mod.rs @@ -366,6 +366,7 @@ async fn vertex_index_common(ctx: TestingContext) { depth_stencil_attachment: None, timestamp_writes: None, occlusion_query_set: None, + multiview_mask: None, }); { diff --git a/tests/tests/wgpu-gpu/zero_init_texture_after_discard.rs b/tests/tests/wgpu-gpu/zero_init_texture_after_discard.rs index 5d52b6e982a..193971f61c4 100644 --- a/tests/tests/wgpu-gpu/zero_init_texture_after_discard.rs +++ b/tests/tests/wgpu-gpu/zero_init_texture_after_discard.rs @@ -173,6 +173,7 @@ impl<'ctx> TestCase<'ctx> { }), timestamp_writes: None, occlusion_query_set: None, + multiview_mask: None, }); ctx.queue.submit([encoder.finish()]); } else { @@ -259,6 +260,7 @@ impl<'ctx> TestCase<'ctx> { ), timestamp_writes: None, occlusion_query_set: None, + multiview_mask: None, }); } @@ -284,6 +286,7 @@ impl<'ctx> TestCase<'ctx> { ), timestamp_writes: None, occlusion_query_set: None, + multiview_mask: None, }); } @@ -309,6 +312,7 @@ impl<'ctx> TestCase<'ctx> { ), timestamp_writes: None, occlusion_query_set: None, + multiview_mask: None, }); } diff --git a/tests/tests/wgpu-validation/api/external_texture.rs b/tests/tests/wgpu-validation/api/external_texture.rs index 989fe5ac8ae..0750cdf22e5 100644 --- a/tests/tests/wgpu-validation/api/external_texture.rs +++ b/tests/tests/wgpu-validation/api/external_texture.rs @@ -685,6 +685,7 @@ var tex: texture_external; depth_stencil_attachment: None, timestamp_writes: None, occlusion_query_set: None, + multiview_mask: None, }); pass.set_pipeline(&pipeline); From e4653890fb2b32fae40cd3f49946cc843f18f9a8 Mon Sep 17 00:00:00 2001 From: SupaMaggie70 Date: Mon, 22 Sep 2025 16:12:56 -0500 Subject: [PATCH 23/44] Updated changelog entry --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d58ccf5a9b9..91ec3da67dd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -158,7 +158,7 @@ by if the `Feature::MULTI_DRAW_INDIRECT_COUNT` feature is available on the devic By @cwfitzgerald in [#8162](https://github.com/gfx-rs/wgpu/pull/8162). -#### Multiview on DX12 and support for view bitmasks +#### Multiview on all major platforms and support for multiview bitmasks Multiview has been reworked, adding support for DX12 and Metal, and adding testing and validation to wgpu itself. This change also introduces a view bitmask, a new field in `RenderPassDescriptor` that allows a render pass to render multiple to non-adjacent layers. Note that this also influences apps that don't use multiview, as they have to set this field to `None`. From 30438b3b49b374adfc58adc218b7290c3630226b Mon Sep 17 00:00:00 2001 From: SupaMaggie70 Date: Mon, 22 Sep 2025 16:19:27 -0500 Subject: [PATCH 24/44] Made multiview use 32bit integer --- naga/src/valid/interface.rs | 2 +- tests/tests/wgpu-gpu/multiview.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) 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/tests/tests/wgpu-gpu/multiview.rs b/tests/tests/wgpu-gpu/multiview.rs index 3164511b044..7ecdce8c514 100644 --- a/tests/tests/wgpu-gpu/multiview.rs +++ b/tests/tests/wgpu-gpu/multiview.rs @@ -49,7 +49,7 @@ async fn run_test(ctx: TestingContext) { } @fragment - fn fs_main(@builtin(view_index) view_index: i32) -> @location(0) vec4f { + fn fs_main(@builtin(view_index) view_index: u32) -> @location(0) vec4f { return vec4f(f32(view_index)); } "; From f20eaac395b5c9a77d4c3771641083fb1395a7ac Mon Sep 17 00:00:00 2001 From: SupaMaggie70 Date: Mon, 22 Sep 2025 16:21:39 -0500 Subject: [PATCH 25/44] Made the default multiview mask 0 --- deno_webgpu/render_pass.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deno_webgpu/render_pass.rs b/deno_webgpu/render_pass.rs index 8942291d36b..a6223d63149 100644 --- a/deno_webgpu/render_pass.rs +++ b/deno_webgpu/render_pass.rs @@ -396,7 +396,7 @@ pub(crate) struct GPURenderPassDescriptor { /*#[webidl(default = 50000000)] #[options(enforce_range = true)] pub max_draw_count: u64,*/ - #[webidl(default = 0xFFFFFFFF)] + #[webidl(default = 0)] #[options(enforce_range = true)] pub multiview_mask: u32, } From 1528421966012e081f2a7b3d80872068103a2bad Mon Sep 17 00:00:00 2001 From: SupaMaggie70 Date: Mon, 22 Sep 2025 16:33:27 -0500 Subject: [PATCH 26/44] Fixed snapshots, final changelog update hopefully --- CHANGELOG.md | 2 ++ naga/tests/in/wgsl/multiview.toml | 4 +++- naga/tests/in/wgsl/multiview.wgsl | 2 +- naga/tests/in/wgsl/multiview_webgl.wgsl | 2 +- .../out/glsl/wgsl-multiview.main.Fragment.glsl | 2 +- .../glsl/wgsl-multiview_webgl.main.Fragment.glsl | 2 +- naga/tests/out/hlsl/wgsl-multiview.hlsl | 9 +++++++++ naga/tests/out/hlsl/wgsl-multiview.ron | 12 ++++++++++++ naga/tests/out/msl/wgsl-multiview.msl | 14 ++++++++++++++ naga/tests/out/spv/wgsl-multiview.spvasm | 2 +- naga/tests/out/wgsl/wgsl-multiview.wgsl | 2 +- 11 files changed, 46 insertions(+), 7 deletions(-) create mode 100644 naga/tests/out/hlsl/wgsl-multiview.hlsl create mode 100644 naga/tests/out/hlsl/wgsl-multiview.ron create mode 100644 naga/tests/out/msl/wgsl-multiview.msl diff --git a/CHANGELOG.md b/CHANGELOG.md index 1a80be45463..43667785663 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -179,6 +179,8 @@ This change also introduces a view bitmask, a new field in `RenderPassDescriptor + 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). ### New Features diff --git a/naga/tests/in/wgsl/multiview.toml b/naga/tests/in/wgsl/multiview.toml index 690600e0957..2db377a7472 100644 --- a/naga/tests/in/wgsl/multiview.toml +++ b/naga/tests/in/wgsl/multiview.toml @@ -1,3 +1,5 @@ glsl_multiview = 2 god_mode = true -targets = "SPIRV | GLSL | WGSL" + +[msl] +lang_version = [2,3] \ No newline at end of file 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..93adf124266 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 = 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..f47c1a34cb4 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 = int(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..341a4c528e3 --- /dev/null +++ b/naga/tests/out/hlsl/wgsl-multiview.ron @@ -0,0 +1,12 @@ +( + vertex:[ + ], + fragment:[ + ( + entry_point:"main", + target_profile:"ps_5_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; } From 81b681f37c7fbbf87ed94ef769b9b445a31a24b3 Mon Sep 17 00:00:00 2001 From: SupaMaggie70 Date: Mon, 22 Sep 2025 16:34:34 -0500 Subject: [PATCH 27/44] Reformatted multiview.toml --- naga/tests/in/wgsl/multiview.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/naga/tests/in/wgsl/multiview.toml b/naga/tests/in/wgsl/multiview.toml index 2db377a7472..adec5dab215 100644 --- a/naga/tests/in/wgsl/multiview.toml +++ b/naga/tests/in/wgsl/multiview.toml @@ -2,4 +2,4 @@ glsl_multiview = 2 god_mode = true [msl] -lang_version = [2,3] \ No newline at end of file +lang_version = [2, 3] From b04c75bc64fee2ab7a875ca572a74f2b1fbe16d6 Mon Sep 17 00:00:00 2001 From: SupaMaggie70 Date: Mon, 22 Sep 2025 16:46:13 -0500 Subject: [PATCH 28/44] Fixed GLSL writer --- naga/src/back/glsl/mod.rs | 1 - naga/tests/out/glsl/wgsl-multiview_webgl.main.Fragment.glsl | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/naga/src/back/glsl/mod.rs b/naga/src/back/glsl/mod.rs index 4c5a9d8cbcb..9ec36afd8bb 100644 --- a/naga/src/back/glsl/mod.rs +++ b/naga/src/back/glsl/mod.rs @@ -5204,7 +5204,6 @@ 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", // vertex Bi::BaseInstance => "uint(gl_BaseInstance)", 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 f47c1a34cb4..eeccb09c1e3 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() { - uint view_index = int(gl_ViewID_OVR); + uint view_index = gl_ViewIndex; return; } From d0ab6d4f7f88cc3bc8e9f6b58e4a7cd1fc6d99dc Mon Sep 17 00:00:00 2001 From: SupaMaggie70 Date: Mon, 22 Sep 2025 16:51:40 -0500 Subject: [PATCH 29/44] Attempted to fix temporarily the HLSL stuff --- naga/src/back/glsl/mod.rs | 2 -- naga/tests/in/wgsl/multiview.toml | 3 +++ naga/tests/out/hlsl/wgsl-multiview.ron | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/naga/src/back/glsl/mod.rs b/naga/src/back/glsl/mod.rs index 9ec36afd8bb..802bd973bbb 100644 --- a/naga/src/back/glsl/mod.rs +++ b/naga/src/back/glsl/mod.rs @@ -460,7 +460,6 @@ impl IdGenerator { #[derive(Clone, Copy)] struct VaryingOptions { output: bool, - targeting_webgl: bool, draw_parameters: bool, } @@ -468,7 +467,6 @@ impl VaryingOptions { const fn from_writer_options(options: &Options, output: bool) -> Self { Self { output, - targeting_webgl: options.version.is_webgl(), draw_parameters: options.writer_flags.contains(WriterFlags::DRAW_PARAMETERS), } } diff --git a/naga/tests/in/wgsl/multiview.toml b/naga/tests/in/wgsl/multiview.toml index adec5dab215..04c49286f6c 100644 --- a/naga/tests/in/wgsl/multiview.toml +++ b/naga/tests/in/wgsl/multiview.toml @@ -3,3 +3,6 @@ god_mode = true [msl] lang_version = [2, 3] + +[hlsl] +shader_model = "V6_1" diff --git a/naga/tests/out/hlsl/wgsl-multiview.ron b/naga/tests/out/hlsl/wgsl-multiview.ron index 341a4c528e3..ff856788943 100644 --- a/naga/tests/out/hlsl/wgsl-multiview.ron +++ b/naga/tests/out/hlsl/wgsl-multiview.ron @@ -4,7 +4,7 @@ fragment:[ ( entry_point:"main", - target_profile:"ps_5_1", + target_profile:"ps_6_1", ), ], compute:[ From 63ce399b57c211c57f884d003f8e02dfa238b729 Mon Sep 17 00:00:00 2001 From: SupaMaggie70 Date: Mon, 22 Sep 2025 17:00:55 -0500 Subject: [PATCH 30/44] Made HLSL writer throw error if shader model is unsupported --- naga/src/back/hlsl/mod.rs | 2 ++ naga/src/back/hlsl/writer.rs | 8 ++++++++ 2 files changed, 10 insertions(+) diff --git a/naga/src/back/hlsl/mod.rs b/naga/src/back/hlsl/mod.rs index 8df06cf1323..bbd079dbd90 100644 --- a/naga/src/back/hlsl/mod.rs +++ b/naga/src/back/hlsl/mod.rs @@ -649,6 +649,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}")?; } From 11203bbfd749a6cea3eb2f352a03a430a33f5e85 Mon Sep 17 00:00:00 2001 From: SupaMaggie70 Date: Mon, 22 Sep 2025 17:07:40 -0500 Subject: [PATCH 31/44] Fixed webgl writing --- naga/src/back/glsl/mod.rs | 10 +++++++++- .../out/glsl/wgsl-multiview_webgl.main.Fragment.glsl | 2 +- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/naga/src/back/glsl/mod.rs b/naga/src/back/glsl/mod.rs index 802bd973bbb..cd7cf209048 100644 --- a/naga/src/back/glsl/mod.rs +++ b/naga/src/back/glsl/mod.rs @@ -460,6 +460,7 @@ impl IdGenerator { #[derive(Clone, Copy)] struct VaryingOptions { output: bool, + targetting_webgl: bool, draw_parameters: bool, } @@ -467,6 +468,7 @@ impl VaryingOptions { const fn from_writer_options(options: &Options, output: bool) -> Self { Self { output, + targetting_webgl: options.version.is_webgl(), draw_parameters: options.writer_flags.contains(WriterFlags::DRAW_PARAMETERS), } } @@ -5202,7 +5204,13 @@ const fn glsl_built_in(built_in: crate::BuiltIn, options: VaryingOptions) -> &'s "gl_FragCoord" } } - Bi::ViewIndex => "gl_ViewIndex", + Bi::ViewIndex => { + if options.targetting_webgl { + "gl_ViewID_OVR" + } else { + "gl_ViewIndex" + } + } // vertex Bi::BaseInstance => "uint(gl_BaseInstance)", Bi::BaseVertex => "uint(gl_BaseVertex)", 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 eeccb09c1e3..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() { - uint view_index = gl_ViewIndex; + uint view_index = gl_ViewID_OVR; return; } From dbe14005c62f9c3793ccc88c5cb9f1c23c4fc4f6 Mon Sep 17 00:00:00 2001 From: SupaMaggie70 Date: Mon, 22 Sep 2025 17:11:20 -0500 Subject: [PATCH 32/44] Fixed typos and hopefully also glsl (non-webgl) validation --- naga/src/back/glsl/mod.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/naga/src/back/glsl/mod.rs b/naga/src/back/glsl/mod.rs index cd7cf209048..c764430a568 100644 --- a/naga/src/back/glsl/mod.rs +++ b/naga/src/back/glsl/mod.rs @@ -460,7 +460,7 @@ impl IdGenerator { #[derive(Clone, Copy)] struct VaryingOptions { output: bool, - targetting_webgl: bool, + targeting_webgl: bool, draw_parameters: bool, } @@ -468,7 +468,7 @@ impl VaryingOptions { const fn from_writer_options(options: &Options, output: bool) -> Self { Self { output, - targetting_webgl: options.version.is_webgl(), + targeting_webgl: options.version.is_webgl(), draw_parameters: options.writer_flags.contains(WriterFlags::DRAW_PARAMETERS), } } @@ -5205,10 +5205,10 @@ const fn glsl_built_in(built_in: crate::BuiltIn, options: VaryingOptions) -> &'s } } Bi::ViewIndex => { - if options.targetting_webgl { + if options.targeting_webgl { "gl_ViewID_OVR" } else { - "gl_ViewIndex" + "uint(gl_ViewIndex)" } } // vertex From 7f8542d83bbaebc099359125457e3a9e42a8f0c3 Mon Sep 17 00:00:00 2001 From: SupaMaggie70 Date: Mon, 22 Sep 2025 17:15:12 -0500 Subject: [PATCH 33/44] Actually updated fixed snapshots --- naga/tests/out/glsl/wgsl-multiview.main.Fragment.glsl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/naga/tests/out/glsl/wgsl-multiview.main.Fragment.glsl b/naga/tests/out/glsl/wgsl-multiview.main.Fragment.glsl index 93adf124266..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() { - uint view_index = gl_ViewIndex; + uint view_index = uint(gl_ViewIndex); return; } From 7d66fec6574bb31de76ba23e003e5310faf706c2 Mon Sep 17 00:00:00 2001 From: SupaMaggie70 Date: Mon, 22 Sep 2025 19:02:58 -0500 Subject: [PATCH 34/44] I think we're doing illegal shit in metal and somehow the second texture gets 255 no matter what. --- tests/tests/wgpu-gpu/multiview.rs | 146 +++++++++++++++--------------- 1 file changed, 72 insertions(+), 74 deletions(-) diff --git a/tests/tests/wgpu-gpu/multiview.rs b/tests/tests/wgpu-gpu/multiview.rs index 7ecdce8c514..d47690e18b4 100644 --- a/tests/tests/wgpu-gpu/multiview.rs +++ b/tests/tests/wgpu-gpu/multiview.rs @@ -50,7 +50,7 @@ async fn run_test(ctx: TestingContext) { @fragment fn fs_main(@builtin(view_index) view_index: u32) -> @location(0) vec4f { - return vec4f(f32(view_index)); + return vec4f(view_index); } "; @@ -92,34 +92,31 @@ async fn run_test(ctx: TestingContext) { }; const TEXTURE_SIZE: u32 = 512; let pipeline = ctx.device.create_render_pipeline(&pipeline_desc); - let (texture, view) = { - let texture = ctx.device.create_texture(&wgpu::TextureDescriptor { - label: None, - size: wgpu::Extent3d { - width: TEXTURE_SIZE, - height: TEXTURE_SIZE, - depth_or_array_layers: 2, - }, - mip_level_count: 1, - sample_count: 1, - dimension: wgpu::TextureDimension::D2, - format: wgpu::TextureFormat::R8Unorm, - usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::COPY_SRC, - view_formats: &[], - }); - let view = texture.create_view(&wgpu::TextureViewDescriptor { - label: None, - format: Some(wgpu::TextureFormat::R8Unorm), - dimension: Some(wgpu::TextureViewDimension::D2Array), - usage: Some(wgpu::TextureUsages::RENDER_ATTACHMENT), - aspect: wgpu::TextureAspect::All, - base_mip_level: 0, - mip_level_count: None, - base_array_layer: 0, - array_layer_count: Some(2), - }); - (texture, view) - }; + let texture = ctx.device.create_texture(&wgpu::TextureDescriptor { + label: None, + size: wgpu::Extent3d { + width: TEXTURE_SIZE, + height: TEXTURE_SIZE, + depth_or_array_layers: 2, + }, + mip_level_count: 1, + sample_count: 1, + dimension: wgpu::TextureDimension::D2, + format: wgpu::TextureFormat::R8Unorm, + usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::COPY_SRC, + view_formats: &[], + }); + let view = texture.create_view(&wgpu::TextureViewDescriptor { + label: None, + format: Some(wgpu::TextureFormat::R8Unorm), + dimension: Some(wgpu::TextureViewDimension::D2Array), + usage: Some(wgpu::TextureUsages::RENDER_ATTACHMENT), + aspect: wgpu::TextureAspect::All, + base_mip_level: 0, + mip_level_count: None, + base_array_layer: 0, + array_layer_count: Some(2), + }); let readback_buffer = ctx.device.create_buffer(&wgpu::BufferDescriptor { label: None, size: TEXTURE_SIZE as u64 * TEXTURE_SIZE as u64 * 2, @@ -130,49 +127,48 @@ async fn run_test(ctx: TestingContext) { let mut encoder = ctx .device .create_command_encoder(&wgpu::CommandEncoderDescriptor::default()); - let mut rpass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { - label: None, - color_attachments: &[Some(wgpu::RenderPassColorAttachment { - view: &view, - depth_slice: None, - resolve_target: None, - ops: wgpu::Operations { - load: wgpu::LoadOp::Clear(wgpu::Color::BLACK), - store: wgpu::StoreOp::Store, - }, - })], - depth_stencil_attachment: None, - timestamp_writes: None, - occlusion_query_set: None, - multiview_mask: NonZero::new(3), - }); - rpass.set_pipeline(&pipeline); - rpass.set_vertex_buffer(0, vertex_buffer.slice(..)); - rpass.draw(0..6, 0..1); - drop(rpass); - for i in 0..2 { - encoder.copy_texture_to_buffer( - wgpu::TexelCopyTextureInfo { - texture: &texture, - mip_level: 0, - origin: wgpu::Origin3d { x: 0, y: 0, z: i }, - aspect: wgpu::TextureAspect::All, - }, - wgpu::TexelCopyBufferInfo { - buffer: &readback_buffer, - layout: wgpu::TexelCopyBufferLayout { - offset: i as u64 * TEXTURE_SIZE as u64 * TEXTURE_SIZE as u64, - bytes_per_row: Some(TEXTURE_SIZE), - rows_per_image: Some(TEXTURE_SIZE), + { + let mut rpass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { + label: None, + color_attachments: &[Some(wgpu::RenderPassColorAttachment { + view: &view, + depth_slice: None, + resolve_target: None, + ops: wgpu::Operations { + load: wgpu::LoadOp::Clear(wgpu::Color::TRANSPARENT), + store: wgpu::StoreOp::Store, }, - }, - wgpu::Extent3d { - width: TEXTURE_SIZE, - height: TEXTURE_SIZE, - depth_or_array_layers: 1, - }, - ); + })], + depth_stencil_attachment: None, + timestamp_writes: None, + occlusion_query_set: None, + multiview_mask: NonZero::new(3), + }); + rpass.set_pipeline(&pipeline); + rpass.set_vertex_buffer(0, vertex_buffer.slice(..)); + rpass.draw(0..6, 0..1); } + encoder.copy_texture_to_buffer( + wgpu::TexelCopyTextureInfo { + texture: &texture, + mip_level: 0, + origin: wgpu::Origin3d { x: 0, y: 0, z: 0 }, + aspect: wgpu::TextureAspect::All, + }, + wgpu::TexelCopyBufferInfo { + buffer: &readback_buffer, + layout: wgpu::TexelCopyBufferLayout { + offset: 0, + bytes_per_row: Some(TEXTURE_SIZE), + rows_per_image: Some(TEXTURE_SIZE), + }, + }, + wgpu::Extent3d { + width: TEXTURE_SIZE, + height: TEXTURE_SIZE, + depth_or_array_layers: 2, + }, + ); ctx.queue.submit([encoder.finish()]); let slice = readback_buffer.slice(..); @@ -182,10 +178,12 @@ async fn run_test(ctx: TestingContext) { let data = slice.get_mapped_range(); for view_idx in 0..2 { - let texture_bytes = (TEXTURE_SIZE * TEXTURE_SIZE) as usize; - let succeeded = &data[texture_bytes * view_idx..texture_bytes * (view_idx + 1)] + let each_texture_size = (TEXTURE_SIZE * TEXTURE_SIZE) as usize; + let target_value = view_idx as u8 * u8::MAX; + let succeeded = data[each_texture_size * view_idx..each_texture_size * (view_idx + 1)] .iter() - .all(|b| *b == if view_idx == 1 { u8::MAX } else { 0 }); - assert!(succeeded); + .all(|b| *b == target_value); + assert!(data.len() == each_texture_size * 2); + assert!(succeeded, "Failed with view {view_idx}"); } } From 5e3e662ecc13ba233a0672f5eebc6ab819b036b0 Mon Sep 17 00:00:00 2001 From: SupaMaggie70Incorporated Date: Mon, 22 Sep 2025 20:03:31 -0500 Subject: [PATCH 35/44] Fixed illegal behavior in shader that naga currently allows --- tests/tests/wgpu-gpu/multiview.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/tests/wgpu-gpu/multiview.rs b/tests/tests/wgpu-gpu/multiview.rs index d47690e18b4..fe72690f747 100644 --- a/tests/tests/wgpu-gpu/multiview.rs +++ b/tests/tests/wgpu-gpu/multiview.rs @@ -50,7 +50,7 @@ async fn run_test(ctx: TestingContext) { @fragment fn fs_main(@builtin(view_index) view_index: u32) -> @location(0) vec4f { - return vec4f(view_index); + return vec4f(f32(view_index)); } "; @@ -179,6 +179,7 @@ async fn run_test(ctx: TestingContext) { let data = slice.get_mapped_range(); for view_idx in 0..2 { let each_texture_size = (TEXTURE_SIZE * TEXTURE_SIZE) as usize; + panic!("{}, {}", data[0], data[each_texture_size]); let target_value = view_idx as u8 * u8::MAX; let succeeded = data[each_texture_size * view_idx..each_texture_size * (view_idx + 1)] .iter() From f612d0ff37e621aff4ce34ff4bc9f083a282f154 Mon Sep 17 00:00:00 2001 From: SupaMaggie70 Date: Wed, 1 Oct 2025 13:00:05 -0500 Subject: [PATCH 36/44] Fixed compiles --- deno_webgpu/command_encoder.rs | 2 +- tests/tests/wgpu-gpu/multiview.rs | 1 - tests/tests/wgpu-gpu/vertex_state.rs | 1 + wgpu-core/src/command/encoder_command.rs | 1 + wgpu-core/src/command/mod.rs | 2 ++ wgpu-core/src/command/render.rs | 1 + 6 files changed, 6 insertions(+), 2 deletions(-) diff --git a/deno_webgpu/command_encoder.rs b/deno_webgpu/command_encoder.rs index 66db31a1329..f0cdb99b523 100644 --- a/deno_webgpu/command_encoder.rs +++ b/deno_webgpu/command_encoder.rs @@ -143,7 +143,7 @@ impl GPUCommandEncoder { occlusion_query_set: descriptor .occlusion_query_set .map(|query_set| query_set.id), - multiview_mask: NonZer::new(descriptor.multiview_mask), + multiview_mask: NonZero::new(descriptor.multiview_mask), }; let (render_pass, err) = self diff --git a/tests/tests/wgpu-gpu/multiview.rs b/tests/tests/wgpu-gpu/multiview.rs index fe72690f747..ab65966e414 100644 --- a/tests/tests/wgpu-gpu/multiview.rs +++ b/tests/tests/wgpu-gpu/multiview.rs @@ -179,7 +179,6 @@ async fn run_test(ctx: TestingContext) { let data = slice.get_mapped_range(); for view_idx in 0..2 { let each_texture_size = (TEXTURE_SIZE * TEXTURE_SIZE) as usize; - panic!("{}, {}", data[0], data[each_texture_size]); let target_value = view_idx as u8 * u8::MAX; let succeeded = data[each_texture_size * view_idx..each_texture_size * (view_idx + 1)] .iter() diff --git a/tests/tests/wgpu-gpu/vertex_state.rs b/tests/tests/wgpu-gpu/vertex_state.rs index fad6546d39f..8aa1b329a67 100644 --- a/tests/tests/wgpu-gpu/vertex_state.rs +++ b/tests/tests/wgpu-gpu/vertex_state.rs @@ -148,6 +148,7 @@ async fn set_array_stride_to_0(ctx: TestingContext) { depth_stencil_attachment: None, timestamp_writes: None, occlusion_query_set: None, + multiview_mask: None, }); // The D3D12 backend used to not set the stride of vertex buffers if it was 0. diff --git a/wgpu-core/src/command/encoder_command.rs b/wgpu-core/src/command/encoder_command.rs index 4a240d88ad8..0713cc55971 100644 --- a/wgpu-core/src/command/encoder_command.rs +++ b/wgpu-core/src/command/encoder_command.rs @@ -130,6 +130,7 @@ pub enum ArcCommand { depth_stencil_attachment: Option, timestamp_writes: Option, occlusion_query_set: Option>, + multiview_mask: Option>, }, BuildAccelerationStructures { blas: Vec, diff --git a/wgpu-core/src/command/mod.rs b/wgpu-core/src/command/mod.rs index 887c19fb357..cd5adac4691 100644 --- a/wgpu-core/src/command/mod.rs +++ b/wgpu-core/src/command/mod.rs @@ -1329,6 +1329,7 @@ impl Global { depth_stencil_attachment, timestamp_writes, occlusion_query_set, + multiview_mask, } => { encode_render_pass( &mut state, @@ -1337,6 +1338,7 @@ impl Global { depth_stencil_attachment, timestamp_writes, occlusion_query_set, + multiview_mask, )?; } ArcCommand::RunComputePass { diff --git a/wgpu-core/src/command/render.rs b/wgpu-core/src/command/render.rs index 33e306ab82b..54572e4221b 100644 --- a/wgpu-core/src/command/render.rs +++ b/wgpu-core/src/command/render.rs @@ -1865,6 +1865,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, }) }) } From 988bc37f6d1f68f9339b0aa9f31e49fba9bbca7c Mon Sep 17 00:00:00 2001 From: SupaMaggie70 Date: Wed, 1 Oct 2025 13:02:43 -0500 Subject: [PATCH 37/44] Tried to fix windows compiles --- wgpu-hal/src/dx12/adapter.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wgpu-hal/src/dx12/adapter.rs b/wgpu-hal/src/dx12/adapter.rs index dacbf668967..bd4405009b2 100644 --- a/wgpu-hal/src/dx12/adapter.rs +++ b/wgpu-hal/src/dx12/adapter.rs @@ -679,7 +679,7 @@ impl super::Adapter { max_task_workgroups_per_dimension: Direct3D12::D3D12_CS_DISPATCH_MAX_THREAD_GROUPS_PER_DIMENSION, // Multiview not supported by WGPU yet - max_mesh_multiview_count: 0, + max_mesh_multiview_view_count: 0, // This seems to be right, and I can't find anything to suggest it would be less than the 2048 provided here max_mesh_output_layers: Direct3D12::D3D12_REQ_TEXTURE2D_ARRAY_AXIS_DIMENSION, From bd696d43a179b2c28364df89760d0d9e0624dff0 Mon Sep 17 00:00:00 2001 From: SupaMaggie70 Date: Wed, 1 Oct 2025 13:27:11 -0500 Subject: [PATCH 38/44] Updated multiview test to provide more information --- tests/tests/wgpu-gpu/multiview.rs | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/tests/tests/wgpu-gpu/multiview.rs b/tests/tests/wgpu-gpu/multiview.rs index ab65966e414..e67e1a78a92 100644 --- a/tests/tests/wgpu-gpu/multiview.rs +++ b/tests/tests/wgpu-gpu/multiview.rs @@ -50,7 +50,7 @@ async fn run_test(ctx: TestingContext) { @fragment fn fs_main(@builtin(view_index) view_index: u32) -> @location(0) vec4f { - return vec4f(f32(view_index)); + return vec4f(f32(view_index) * 0.5); } "; @@ -177,13 +177,15 @@ async fn run_test(ctx: TestingContext) { ctx.async_poll(wgpu::PollType::wait()).await.unwrap(); let data = slice.get_mapped_range(); + let each_texture_size = (TEXTURE_SIZE * TEXTURE_SIZE) as usize; + assert!(data.len() == each_texture_size * 2); for view_idx in 0..2 { - let each_texture_size = (TEXTURE_SIZE * TEXTURE_SIZE) as usize; - let target_value = view_idx as u8 * u8::MAX; - let succeeded = data[each_texture_size * view_idx..each_texture_size * (view_idx + 1)] + // Some metal devices automatically initialize stuff to 255, so I decided to use 128 instead of that + let target_value = view_idx as u8 * 128; + let failed_value = data[each_texture_size * view_idx..each_texture_size * (view_idx + 1)] .iter() - .all(|b| *b == target_value); - assert!(data.len() == each_texture_size * 2); - assert!(succeeded, "Failed with view {view_idx}"); + .copied() + .find(|b| *b != target_value); + assert_eq!(failed_value, None); } } From 15da6201884b39dd727e8fca2f35f0f6e7df726b Mon Sep 17 00:00:00 2001 From: SupaMaggie70 Date: Wed, 1 Oct 2025 13:37:29 -0500 Subject: [PATCH 39/44] ChatGPT thinks it fixed the vulkan backend (lets see!) --- wgpu-hal/src/vulkan/device.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/wgpu-hal/src/vulkan/device.rs b/wgpu-hal/src/vulkan/device.rs index a91df0c77ba..7049261308a 100644 --- a/wgpu-hal/src/vulkan/device.rs +++ b/wgpu-hal/src/vulkan/device.rs @@ -1987,8 +1987,12 @@ impl crate::Device for super::Device { vk::DynamicState::BLEND_CONSTANTS, vk::DynamicState::STENCIL_REFERENCE, ]; + let multiview_mask = desc + .multiview + .map(|v| NonZeroU32::new((1u32 << v.get()) - 1).unwrap()); let mut compatible_rp_key = super::RenderPassKey { sample_count: desc.multisample.count, + multiview_mask, ..Default::default() }; let mut stages = ArrayVec::<_, { crate::MAX_CONCURRENT_SHADER_STAGES }>::new(); From 27035ddb6a4023308971a4ea6ff973b2fb75ca64 Mon Sep 17 00:00:00 2001 From: SupaMaggie70 Date: Wed, 1 Oct 2025 13:51:48 -0500 Subject: [PATCH 40/44] ChatGPT also failed to fix metal backend lmao --- wgpu-hal/src/metal/command.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/wgpu-hal/src/metal/command.rs b/wgpu-hal/src/metal/command.rs index 46b185c045b..3ed552e1353 100644 --- a/wgpu-hal/src/metal/command.rs +++ b/wgpu-hal/src/metal/command.rs @@ -674,6 +674,7 @@ impl crate::CommandEncoder for super::CommandEncoder { } } encoder.set_vertex_amplification_count(mv.count_ones() as u64, Some(&maps)); + descriptor.set_render_target_array_length(32 - mv.leading_zeros() as u64); } if let Some(label) = desc.label { encoder.set_label(label); From e8b74528a979990382a9855ce82315106df87c90 Mon Sep 17 00:00:00 2001 From: SupaMaggie70 Date: Wed, 1 Oct 2025 14:05:25 -0500 Subject: [PATCH 41/44] Undid fake fix for metal --- wgpu-hal/src/metal/command.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/wgpu-hal/src/metal/command.rs b/wgpu-hal/src/metal/command.rs index 3ed552e1353..79fb6e281ed 100644 --- a/wgpu-hal/src/metal/command.rs +++ b/wgpu-hal/src/metal/command.rs @@ -654,7 +654,6 @@ impl crate::CommandEncoder for super::CommandEncoder { descriptor .set_visibility_result_buffer(Some(occlusion_query_set.raw_buffer.as_ref())) } - let raw = self.raw_cmd_buf.as_ref().unwrap(); let encoder = raw.new_render_command_encoder(descriptor); if let Some(mv) = desc.multiview_mask { @@ -674,7 +673,6 @@ impl crate::CommandEncoder for super::CommandEncoder { } } encoder.set_vertex_amplification_count(mv.count_ones() as u64, Some(&maps)); - descriptor.set_render_target_array_length(32 - mv.leading_zeros() as u64); } if let Some(label) = desc.label { encoder.set_label(label); From b4ce29bd84eca63a2020a18e0ea364133cc83b30 Mon Sep 17 00:00:00 2001 From: SupaMaggie70 Date: Wed, 1 Oct 2025 14:13:46 -0500 Subject: [PATCH 42/44] Tried to fix again (still broken on metal but maybe this'll fix vulkan/dx12) --- tests/tests/wgpu-gpu/multiview.rs | 2 +- wgpu-core/src/command/render.rs | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/tests/wgpu-gpu/multiview.rs b/tests/tests/wgpu-gpu/multiview.rs index e67e1a78a92..0bc371b347a 100644 --- a/tests/tests/wgpu-gpu/multiview.rs +++ b/tests/tests/wgpu-gpu/multiview.rs @@ -186,6 +186,6 @@ async fn run_test(ctx: TestingContext) { .iter() .copied() .find(|b| *b != target_value); - assert_eq!(failed_value, None); + assert_eq!(failed_value, None, "Expected {target_value}"); } } diff --git a/wgpu-core/src/command/render.rs b/wgpu-core/src/command/render.rs index 54572e4221b..b1376c18fff 100644 --- a/wgpu-core/src/command/render.rs +++ b/wgpu-core/src/command/render.rs @@ -1685,6 +1685,8 @@ impl Global { None }; + arc_desc.multiview_mask = desc.multiview_mask; + Ok(()) } From a799a24c33a687dc1db15663981efb1ead7883e9 Mon Sep 17 00:00:00 2001 From: SupaMaggie70 Date: Wed, 1 Oct 2025 15:06:14 -0500 Subject: [PATCH 43/44] I'm not having fun :( --- tests/tests/wgpu-gpu/multiview.rs | 5 +++-- wgpu-hal/src/metal/command.rs | 6 +++++- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/tests/tests/wgpu-gpu/multiview.rs b/tests/tests/wgpu-gpu/multiview.rs index 0bc371b347a..c61fc3b3952 100644 --- a/tests/tests/wgpu-gpu/multiview.rs +++ b/tests/tests/wgpu-gpu/multiview.rs @@ -50,7 +50,7 @@ async fn run_test(ctx: TestingContext) { @fragment fn fs_main(@builtin(view_index) view_index: u32) -> @location(0) vec4f { - return vec4f(f32(view_index) * 0.5); + return vec4f(f32(view_index) * 0.25 + 0.125); } "; @@ -179,9 +179,10 @@ async fn run_test(ctx: TestingContext) { let data = slice.get_mapped_range(); let each_texture_size = (TEXTURE_SIZE * TEXTURE_SIZE) as usize; assert!(data.len() == each_texture_size * 2); + eprintln!("View values: {}, {}", data[0], data[each_texture_size]); for view_idx in 0..2 { // Some metal devices automatically initialize stuff to 255, so I decided to use 128 instead of that - let target_value = view_idx as u8 * 128; + let target_value = 32 + view_idx as u8 * 64; let failed_value = data[each_texture_size * view_idx..each_texture_size * (view_idx + 1)] .iter() .copied() diff --git a/wgpu-hal/src/metal/command.rs b/wgpu-hal/src/metal/command.rs index 79fb6e281ed..c79af76ae67 100644 --- a/wgpu-hal/src/metal/command.rs +++ b/wgpu-hal/src/metal/command.rs @@ -654,6 +654,9 @@ impl crate::CommandEncoder for super::CommandEncoder { descriptor .set_visibility_result_buffer(Some(occlusion_query_set.raw_buffer.as_ref())) } + if let Some(mv) = desc.multiview_mask { + descriptor.set_render_target_array_length(32 - mv.leading_zeros() as u64); + } let raw = self.raw_cmd_buf.as_ref().unwrap(); let encoder = raw.new_render_command_encoder(descriptor); if let Some(mv) = desc.multiview_mask { @@ -661,9 +664,10 @@ impl crate::CommandEncoder for super::CommandEncoder { // Most likely the API just wasn't thought about enough. It's not like they ever allow you // to use enough views to overflow a 32-bit bitmask. let mv = mv.get(); + let msb = 31 - mv.leading_zeros(); let mut maps: SmallVec<[metal::VertexAmplificationViewMapping; 32]> = SmallVec::new(); - for i in 0..32 { + for i in 0..=msb { if (mv & (1 << i)) != 0 { maps.push(metal::VertexAmplificationViewMapping { renderTargetArrayIndexOffset: i, From 9093910896c63644a3f164a7c3ab66f8241bc884 Mon Sep 17 00:00:00 2001 From: SupaMaggie70 Date: Thu, 2 Oct 2025 01:45:12 -0500 Subject: [PATCH 44/44] Updated test to rely on new poll syntax --- tests/tests/wgpu-gpu/multiview.rs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/tests/tests/wgpu-gpu/multiview.rs b/tests/tests/wgpu-gpu/multiview.rs index c61fc3b3952..59093067d15 100644 --- a/tests/tests/wgpu-gpu/multiview.rs +++ b/tests/tests/wgpu-gpu/multiview.rs @@ -174,7 +174,12 @@ async fn run_test(ctx: TestingContext) { let slice = readback_buffer.slice(..); slice.map_async(wgpu::MapMode::Read, |_| ()); - ctx.async_poll(wgpu::PollType::wait()).await.unwrap(); + 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;