Add multiview support (#2187)

Co-authored-by: Layl Bongers <2385329-layl@users.noreply.gitlab.com>
This commit is contained in:
Layl 2021-11-19 16:56:48 +01:00 committed by GitHub
parent fca82ddd7e
commit 2ef72b9313
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
32 changed files with 256 additions and 34 deletions

10
Cargo.lock generated
View File

@ -984,18 +984,18 @@ dependencies = [
[[package]] [[package]]
name = "naga" name = "naga"
version = "0.7.1" version = "0.7.1"
source = "git+https://github.com/gfx-rs/naga?rev=eda078d#eda078d736a906b4267e4803db6b32e3162aac30" source = "git+https://github.com/gfx-rs/naga?rev=29571cc#29571cc4cfbb28558948b1b31ad764f55b69f37b"
dependencies = [ dependencies = [
"bit-set", "bit-set",
"bitflags", "bitflags",
"codespan-reporting", "codespan-reporting",
"fxhash",
"hexf-parse", "hexf-parse",
"indexmap", "indexmap",
"log", "log",
"num-traits 0.2.14", "num-traits 0.2.14",
"petgraph", "petgraph",
"pp-rs", "pp-rs",
"rustc-hash",
"serde", "serde",
"spirv", "spirv",
"thiserror", "thiserror",
@ -1437,6 +1437,12 @@ dependencies = [
"serde", "serde",
] ]
[[package]]
name = "rustc-hash"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2"
[[package]] [[package]]
name = "rusttype" name = "rusttype"
version = "0.9.2" version = "0.9.2"

View File

@ -68,6 +68,7 @@ pub fn op_webgpu_create_render_bundle_encoder(
color_formats: Cow::from(color_formats), color_formats: Cow::from(color_formats),
sample_count: args.sample_count, sample_count: args.sample_count,
depth_stencil, depth_stencil,
multiview: None,
}; };
let res = wgpu_core::command::RenderBundleEncoder::new(&descriptor, device, None); let res = wgpu_core::command::RenderBundleEncoder::new(&descriptor, device, None);

View File

@ -358,6 +358,7 @@ pub fn op_webgpu_create_render_pipeline(
depth_stencil: args.depth_stencil.map(TryInto::try_into).transpose()?, depth_stencil: args.depth_stencil.map(TryInto::try_into).transpose()?,
multisample: args.multisample.into(), multisample: args.multisample.into(),
fragment, fragment,
multiview: None,
}; };
let implicit_pipelines = match args.layout { let implicit_pipelines = match args.layout {

View File

@ -37,7 +37,7 @@ thiserror = "1"
[dependencies.naga] [dependencies.naga]
git = "https://github.com/gfx-rs/naga" git = "https://github.com/gfx-rs/naga"
rev = "eda078d" rev = "29571cc"
#version = "0.7" #version = "0.7"
features = ["span", "validate", "wgsl-in"] features = ["span", "validate", "wgsl-in"]

View File

@ -54,7 +54,7 @@ use crate::{
Label, LabelHelpers, LifeGuard, Stored, Label, LabelHelpers, LifeGuard, Stored,
}; };
use arrayvec::ArrayVec; use arrayvec::ArrayVec;
use std::{borrow::Cow, mem, ops::Range}; use std::{borrow::Cow, mem, num::NonZeroU32, ops::Range};
use thiserror::Error; use thiserror::Error;
use hal::CommandEncoder as _; use hal::CommandEncoder as _;
@ -75,6 +75,8 @@ pub struct RenderBundleEncoderDescriptor<'a> {
/// Sample count this render bundle is capable of rendering to. This must match the pipelines and /// Sample count this render bundle is capable of rendering to. This must match the pipelines and
/// the renderpasses it is used in. /// the renderpasses it is used in.
pub sample_count: u32, pub sample_count: u32,
/// If this render bundle will rendering to multiple array layers in the attachments at the same time.
pub multiview: Option<NonZeroU32>,
} }
#[derive(Debug)] #[derive(Debug)]
@ -112,6 +114,7 @@ impl RenderBundleEncoder {
} }
sc sc
}, },
multiview: desc.multiview,
}, },
is_ds_read_only: match desc.depth_stencil { is_ds_read_only: match desc.depth_stencil {
Some(ds) => { Some(ds) => {
@ -135,6 +138,7 @@ impl RenderBundleEncoder {
depth_stencil: None, depth_stencil: None,
}, },
sample_count: 0, sample_count: 0,
multiview: None,
}, },
is_ds_read_only: false, is_ds_read_only: false,
} }

View File

@ -9,8 +9,8 @@ use crate::{
RenderCommandError, StateChange, RenderCommandError, StateChange,
}, },
device::{ device::{
AttachmentData, MissingDownlevelFlags, MissingFeatures, RenderPassCompatibilityError, AttachmentData, Device, MissingDownlevelFlags, MissingFeatures,
RenderPassContext, RenderPassCompatibilityError, RenderPassContext,
}, },
error::{ErrorFormatter, PrettyError}, error::{ErrorFormatter, PrettyError},
hub::{Global, GlobalIdentityHandlerFactory, HalApi, Storage, Token}, hub::{Global, GlobalIdentityHandlerFactory, HalApi, Storage, Token},
@ -29,7 +29,8 @@ use arrayvec::ArrayVec;
use hal::CommandEncoder as _; use hal::CommandEncoder as _;
use thiserror::Error; use thiserror::Error;
use wgt::{ use wgt::{
BufferAddress, BufferSize, BufferUsages, Color, IndexFormat, TextureUsages, VertexStepMode, BufferAddress, BufferSize, BufferUsages, Color, IndexFormat, TextureUsages,
TextureViewDimension, VertexStepMode,
}; };
#[cfg(any(feature = "serial-pass", feature = "replay"))] #[cfg(any(feature = "serial-pass", feature = "replay"))]
@ -461,6 +462,12 @@ pub enum RenderPassErrorInner {
Bind(#[from] BindError), Bind(#[from] BindError),
#[error(transparent)] #[error(transparent)]
QueryUse(#[from] QueryUseError), QueryUse(#[from] QueryUseError),
#[error("multiview layer count must match")]
MultiViewMismatch,
#[error(
"multiview pass texture views with more than one array layer must have D2Array dimension"
)]
MultiViewDimensionMismatch,
} }
impl PrettyError for RenderPassErrorInner { impl PrettyError for RenderPassErrorInner {
@ -542,6 +549,7 @@ struct RenderPassInfo<'a, A: hal::Api> {
pending_discard_init_fixups: SurfacesInDiscardState, pending_discard_init_fixups: SurfacesInDiscardState,
divergent_discarded_depth_stencil_aspect: Option<(wgt::TextureAspect, &'a TextureView<A>)>, divergent_discarded_depth_stencil_aspect: Option<(wgt::TextureAspect, &'a TextureView<A>)>,
multiview: Option<NonZeroU32>,
} }
impl<'a, A: HalApi> RenderPassInfo<'a, A> { impl<'a, A: HalApi> RenderPassInfo<'a, A> {
@ -582,6 +590,7 @@ impl<'a, A: HalApi> RenderPassInfo<'a, A> {
} }
fn start( fn start(
device: &Device<A>,
label: Option<&str>, label: Option<&str>,
color_attachments: &[RenderPassColorAttachment], color_attachments: &[RenderPassColorAttachment],
depth_stencil_attachment: Option<&RenderPassDepthStencilAttachment>, depth_stencil_attachment: Option<&RenderPassDepthStencilAttachment>,
@ -605,6 +614,39 @@ impl<'a, A: HalApi> RenderPassInfo<'a, A> {
let mut extent = None; let mut extent = None;
let mut sample_count = 0; let mut sample_count = 0;
let mut detected_multiview: Option<Option<NonZeroU32>> = None;
let mut check_multiview = |view: &TextureView<A>| {
// Get the multiview configuration for this texture view
let layers = view.selector.layers.end - view.selector.layers.start;
let this_multiview = if layers >= 2 {
// Trivially proven by the if above
Some(unsafe { NonZeroU32::new_unchecked(layers) })
} else {
None
};
// Make sure that if this view is a multiview, it is set to be an array
if this_multiview.is_some() && view.desc.dimension != TextureViewDimension::D2Array {
return Err(RenderPassErrorInner::MultiViewDimensionMismatch);
}
// Validate matching first, or store the first one
if let Some(multiview) = detected_multiview {
if multiview != this_multiview {
return Err(RenderPassErrorInner::MultiViewMismatch);
}
} else {
// Multiview is only supported if the feature is enabled
if this_multiview.is_some() {
device.require_features(wgt::Features::MULTIVIEW)?;
}
detected_multiview = Some(this_multiview);
}
Ok(())
};
let mut add_view = |view: &TextureView<A>, type_name| { let mut add_view = |view: &TextureView<A>, type_name| {
if let Some(ex) = extent { if let Some(ex) = extent {
if ex != view.extent { if ex != view.extent {
@ -637,6 +679,7 @@ impl<'a, A: HalApi> RenderPassInfo<'a, A> {
.views .views
.use_extend(&*view_guard, at.view, (), ()) .use_extend(&*view_guard, at.view, (), ())
.map_err(|_| RenderPassErrorInner::InvalidAttachment(at.view))?; .map_err(|_| RenderPassErrorInner::InvalidAttachment(at.view))?;
check_multiview(view)?;
add_view(view, "depth")?; add_view(view, "depth")?;
let ds_aspects = view.desc.aspects(); let ds_aspects = view.desc.aspects();
@ -745,6 +788,7 @@ impl<'a, A: HalApi> RenderPassInfo<'a, A> {
.views .views
.use_extend(&*view_guard, at.view, (), ()) .use_extend(&*view_guard, at.view, (), ())
.map_err(|_| RenderPassErrorInner::InvalidAttachment(at.view))?; .map_err(|_| RenderPassErrorInner::InvalidAttachment(at.view))?;
check_multiview(color_view)?;
add_view(color_view, "color")?; add_view(color_view, "color")?;
if !color_view if !color_view
@ -774,6 +818,7 @@ impl<'a, A: HalApi> RenderPassInfo<'a, A> {
.views .views
.use_extend(&*view_guard, resolve_target, (), ()) .use_extend(&*view_guard, resolve_target, (), ())
.map_err(|_| RenderPassErrorInner::InvalidAttachment(resolve_target))?; .map_err(|_| RenderPassErrorInner::InvalidAttachment(resolve_target))?;
check_multiview(resolve_view)?;
if color_view.extent != resolve_view.extent { if color_view.extent != resolve_view.extent {
return Err(RenderPassErrorInner::AttachmentsDimensionMismatch { return Err(RenderPassErrorInner::AttachmentsDimensionMismatch {
previous: (attachment_type_name, extent.unwrap_or_default()), previous: (attachment_type_name, extent.unwrap_or_default()),
@ -829,9 +874,12 @@ impl<'a, A: HalApi> RenderPassInfo<'a, A> {
depth_stencil: depth_stencil_attachment.map(|at| view_guard.get(at.view).unwrap()), depth_stencil: depth_stencil_attachment.map(|at| view_guard.get(at.view).unwrap()),
}; };
let extent = extent.ok_or(RenderPassErrorInner::MissingAttachments)?; let extent = extent.ok_or(RenderPassErrorInner::MissingAttachments)?;
let multiview = detected_multiview.expect("Multiview was not detected, no attachments");
let context = RenderPassContext { let context = RenderPassContext {
attachments: view_data.map(|view| view.desc.format), attachments: view_data.map(|view| view.desc.format),
sample_count, sample_count,
multiview,
}; };
let hal_desc = hal::RenderPassDescriptor { let hal_desc = hal::RenderPassDescriptor {
@ -840,6 +888,7 @@ impl<'a, A: HalApi> RenderPassInfo<'a, A> {
sample_count, sample_count,
color_attachments: &colors, color_attachments: &colors,
depth_stencil_attachment: depth_stencil, depth_stencil_attachment: depth_stencil,
multiview,
}; };
unsafe { unsafe {
cmd_buf.encoder.raw.begin_render_pass(&hal_desc); cmd_buf.encoder.raw.begin_render_pass(&hal_desc);
@ -854,6 +903,7 @@ impl<'a, A: HalApi> RenderPassInfo<'a, A> {
_phantom: PhantomData, _phantom: PhantomData,
pending_discard_init_fixups, pending_discard_init_fixups,
divergent_discarded_depth_stencil_aspect, divergent_discarded_depth_stencil_aspect,
multiview,
}) })
} }
@ -916,6 +966,7 @@ impl<'a, A: HalApi> RenderPassInfo<'a, A> {
stencil_ops, stencil_ops,
clear_value: (0.0, 0), clear_value: (0.0, 0),
}), }),
multiview: self.multiview,
}; };
unsafe { unsafe {
raw.begin_render_pass(&desc); raw.begin_render_pass(&desc);
@ -997,6 +1048,7 @@ impl<G: GlobalIdentityHandlerFactory> Global<G> {
); );
let mut info = RenderPassInfo::start( let mut info = RenderPassInfo::start(
device,
base.label, base.label,
color_attachments, color_attachments,
depth_stencil_attachment, depth_stencil_attachment,

View File

@ -72,6 +72,7 @@ impl<T> AttachmentData<T> {
pub(crate) struct RenderPassContext { pub(crate) struct RenderPassContext {
pub attachments: AttachmentData<TextureFormat>, pub attachments: AttachmentData<TextureFormat>,
pub sample_count: u32, pub sample_count: u32,
pub multiview: Option<NonZeroU32>,
} }
#[derive(Clone, Debug, Error)] #[derive(Clone, Debug, Error)]
pub enum RenderPassCompatibilityError { pub enum RenderPassCompatibilityError {
@ -84,6 +85,8 @@ pub enum RenderPassCompatibilityError {
IncompatibleDepthStencilAttachment(Option<TextureFormat>, Option<TextureFormat>), IncompatibleDepthStencilAttachment(Option<TextureFormat>, Option<TextureFormat>),
#[error("Incompatible sample count: {0:?} != {1:?}")] #[error("Incompatible sample count: {0:?} != {1:?}")]
IncompatibleSampleCount(u32, u32), IncompatibleSampleCount(u32, u32),
#[error("Incompatible multiview: {0:?} != {1:?}")]
IncompatibleMultiview(Option<NonZeroU32>, Option<NonZeroU32>),
} }
impl RenderPassContext { impl RenderPassContext {
@ -112,6 +115,12 @@ impl RenderPassContext {
other.sample_count, other.sample_count,
)); ));
} }
if self.multiview != other.multiview {
return Err(RenderPassCompatibilityError::IncompatibleMultiview(
self.multiview,
other.multiview,
));
}
Ok(()) Ok(())
} }
} }
@ -2454,6 +2463,11 @@ impl<A: HalApi> Device<A> {
.get(pipeline_layout_id) .get(pipeline_layout_id)
.map_err(|_| pipeline::CreateRenderPipelineError::InvalidLayout)?; .map_err(|_| pipeline::CreateRenderPipelineError::InvalidLayout)?;
// Multiview is only supported if the feature is enabled
if desc.multiview.is_some() {
self.require_features(wgt::Features::MULTIVIEW)?;
}
let pipeline_desc = hal::RenderPipelineDescriptor { let pipeline_desc = hal::RenderPipelineDescriptor {
label: desc.label.borrow_option(), label: desc.label.borrow_option(),
layout: &layout.raw, layout: &layout.raw,
@ -2464,6 +2478,7 @@ impl<A: HalApi> Device<A> {
multisample: desc.multisample, multisample: desc.multisample,
fragment_stage, fragment_stage,
color_targets, color_targets,
multiview: desc.multiview,
}; };
let raw = let raw =
unsafe { self.raw.create_render_pipeline(&pipeline_desc) }.map_err( unsafe { self.raw.create_render_pipeline(&pipeline_desc) }.map_err(
@ -2490,6 +2505,7 @@ impl<A: HalApi> Device<A> {
depth_stencil: depth_stencil_state.as_ref().map(|state| state.format), depth_stencil: depth_stencil_state.as_ref().map(|state| state.format),
}, },
sample_count: samples, sample_count: samples,
multiview: desc.multiview,
}; };
let mut flags = pipeline::PipelineFlags::empty(); let mut flags = pipeline::PipelineFlags::empty();

View File

@ -27,6 +27,7 @@ pub(crate) fn new_render_bundle_encoder_descriptor<'a>(
} }
}), }),
sample_count: context.sample_count, sample_count: context.sample_count,
multiview: context.multiview,
} }
} }

View File

@ -5,7 +5,7 @@ use crate::{
id::{DeviceId, PipelineLayoutId, ShaderModuleId}, id::{DeviceId, PipelineLayoutId, ShaderModuleId},
validation, Label, LifeGuard, Stored, validation, Label, LifeGuard, Stored,
}; };
use std::{borrow::Cow, error::Error, fmt}; use std::{borrow::Cow, error::Error, fmt, num::NonZeroU32};
use thiserror::Error; use thiserror::Error;
#[allow(clippy::large_enum_variant)] #[allow(clippy::large_enum_variant)]
@ -243,6 +243,9 @@ pub struct RenderPipelineDescriptor<'a> {
pub multisample: wgt::MultisampleState, pub multisample: wgt::MultisampleState,
/// The fragment processing state for this pipeline. /// The fragment processing state for this pipeline.
pub fragment: Option<FragmentState<'a>>, pub fragment: Option<FragmentState<'a>>,
/// If the pipeline will be used with a multiview render pass, this indicates how many array
/// layers the attachments will have.
pub multiview: Option<NonZeroU32>,
} }
#[derive(Clone, Debug, Error)] #[derive(Clone, Debug, Error)]

View File

@ -75,12 +75,12 @@ js-sys = { version = "0.3" }
[dependencies.naga] [dependencies.naga]
git = "https://github.com/gfx-rs/naga" git = "https://github.com/gfx-rs/naga"
rev = "eda078d" rev = "29571cc"
#version = "0.7" #version = "0.7"
[dev-dependencies.naga] [dev-dependencies.naga]
git = "https://github.com/gfx-rs/naga" git = "https://github.com/gfx-rs/naga"
rev = "eda078d" rev = "29571cc"
#version = "0.7" #version = "0.7"
features = ["wgsl-in"] features = ["wgsl-in"]

View File

@ -238,6 +238,7 @@ impl<A: hal::Api> Example<A> {
blend: Some(wgt::BlendState::ALPHA_BLENDING), blend: Some(wgt::BlendState::ALPHA_BLENDING),
write_mask: wgt::ColorWrites::default(), write_mask: wgt::ColorWrites::default(),
}], }],
multiview: None,
}; };
let pipeline = unsafe { device.create_render_pipeline(&pipeline_desc).unwrap() }; let pipeline = unsafe { device.create_render_pipeline(&pipeline_desc).unwrap() };
@ -664,6 +665,7 @@ impl<A: hal::Api> Example<A> {
}, },
}], }],
depth_stencil_attachment: None, depth_stencil_attachment: None,
multiview: None,
}; };
unsafe { unsafe {
ctx.encoder.begin_render_pass(&pass_desc); ctx.encoder.begin_render_pass(&pass_desc);

View File

@ -93,7 +93,7 @@ pub use vulkan::UpdateAfterBindTypes;
use std::{ use std::{
borrow::Borrow, borrow::Borrow,
fmt, fmt,
num::NonZeroU8, num::{NonZeroU32, NonZeroU8},
ops::{Range, RangeInclusive}, ops::{Range, RangeInclusive},
ptr::NonNull, ptr::NonNull,
}; };
@ -991,6 +991,9 @@ pub struct RenderPipelineDescriptor<'a, A: Api> {
pub fragment_stage: Option<ProgrammableStage<'a, A>>, pub fragment_stage: Option<ProgrammableStage<'a, A>>,
/// The effect of draw calls on the color aspect of the output target. /// The effect of draw calls on the color aspect of the output target.
pub color_targets: &'a [wgt::ColorTargetState], pub color_targets: &'a [wgt::ColorTargetState],
/// If the pipeline will be used with a multiview render pass, this indicates how many array
/// layers the attachments will have.
pub multiview: Option<NonZeroU32>,
} }
/// Specifies how the alpha channel of the textures should be handled during (martin mouv i step) /// Specifies how the alpha channel of the textures should be handled during (martin mouv i step)
@ -1144,6 +1147,7 @@ pub struct RenderPassDescriptor<'a, A: Api> {
pub sample_count: u32, pub sample_count: u32,
pub color_attachments: &'a [ColorAttachment<'a, A>], pub color_attachments: &'a [ColorAttachment<'a, A>],
pub depth_stencil_attachment: Option<DepthStencilAttachment<'a, A>>, pub depth_stencil_attachment: Option<DepthStencilAttachment<'a, A>>,
pub multiview: Option<NonZeroU32>,
} }
#[derive(Clone, Debug)] #[derive(Clone, Debug)]

View File

@ -16,6 +16,7 @@ fn indexing_features() -> wgt::Features {
#[derive(Debug, Default)] #[derive(Debug, Default)]
pub struct PhysicalDeviceFeatures { pub struct PhysicalDeviceFeatures {
core: vk::PhysicalDeviceFeatures, core: vk::PhysicalDeviceFeatures,
vulkan_1_1: Option<vk::PhysicalDeviceVulkan11Features>,
pub(super) vulkan_1_2: Option<vk::PhysicalDeviceVulkan12Features>, pub(super) vulkan_1_2: Option<vk::PhysicalDeviceVulkan12Features>,
pub(super) descriptor_indexing: Option<vk::PhysicalDeviceDescriptorIndexingFeaturesEXT>, pub(super) descriptor_indexing: Option<vk::PhysicalDeviceDescriptorIndexingFeaturesEXT>,
imageless_framebuffer: Option<vk::PhysicalDeviceImagelessFramebufferFeaturesKHR>, imageless_framebuffer: Option<vk::PhysicalDeviceImagelessFramebufferFeaturesKHR>,
@ -23,6 +24,7 @@ pub struct PhysicalDeviceFeatures {
image_robustness: Option<vk::PhysicalDeviceImageRobustnessFeaturesEXT>, image_robustness: Option<vk::PhysicalDeviceImageRobustnessFeaturesEXT>,
robustness2: Option<vk::PhysicalDeviceRobustness2FeaturesEXT>, robustness2: Option<vk::PhysicalDeviceRobustness2FeaturesEXT>,
depth_clip_enable: Option<vk::PhysicalDeviceDepthClipEnableFeaturesEXT>, depth_clip_enable: Option<vk::PhysicalDeviceDepthClipEnableFeaturesEXT>,
multiview: Option<vk::PhysicalDeviceMultiviewFeaturesKHR>,
} }
// This is safe because the structs have `p_next: *mut c_void`, which we null out/never read. // This is safe because the structs have `p_next: *mut c_void`, which we null out/never read.
@ -159,6 +161,15 @@ impl PhysicalDeviceFeatures {
//.shader_resource_residency(requested_features.contains(wgt::Features::SHADER_RESOURCE_RESIDENCY)) //.shader_resource_residency(requested_features.contains(wgt::Features::SHADER_RESOURCE_RESIDENCY))
.geometry_shader(requested_features.contains(wgt::Features::SHADER_PRIMITIVE_INDEX)) .geometry_shader(requested_features.contains(wgt::Features::SHADER_PRIMITIVE_INDEX))
.build(), .build(),
vulkan_1_1: if api_version >= vk::API_VERSION_1_1 {
Some(
vk::PhysicalDeviceVulkan11Features::builder()
.multiview(requested_features.contains(wgt::Features::MULTIVIEW))
.build(),
)
} else {
None
},
vulkan_1_2: if api_version >= vk::API_VERSION_1_2 { vulkan_1_2: if api_version >= vk::API_VERSION_1_2 {
Some( Some(
vk::PhysicalDeviceVulkan12Features::builder() vk::PhysicalDeviceVulkan12Features::builder()
@ -295,6 +306,15 @@ impl PhysicalDeviceFeatures {
} else { } else {
None None
}, },
multiview: if enabled_extensions.contains(&vk::KhrMultiviewFn::name()) {
Some(
vk::PhysicalDeviceMultiviewFeatures::builder()
.multiview(requested_features.contains(wgt::Features::MULTIVIEW))
.build(),
)
} else {
None
},
} }
} }
@ -390,6 +410,10 @@ impl PhysicalDeviceFeatures {
let intel_windows = caps.properties.vendor_id == db::intel::VENDOR && cfg!(windows); let intel_windows = caps.properties.vendor_id == db::intel::VENDOR && cfg!(windows);
if let Some(ref vulkan_1_1) = self.vulkan_1_1 {
features.set(F::MULTIVIEW, vulkan_1_1.multiview != 0);
}
if let Some(ref vulkan_1_2) = self.vulkan_1_2 { if let Some(ref vulkan_1_2) = self.vulkan_1_2 {
const STORAGE: F = F::STORAGE_RESOURCE_BINDING_ARRAY; const STORAGE: F = F::STORAGE_RESOURCE_BINDING_ARRAY;
if Self::all_features_supported( if Self::all_features_supported(
@ -479,6 +503,10 @@ impl PhysicalDeviceFeatures {
features.set(F::DEPTH_CLIP_CONTROL, feature.depth_clip_enable != 0); features.set(F::DEPTH_CLIP_CONTROL, feature.depth_clip_enable != 0);
} }
if let Some(ref multiview) = self.multiview {
features.set(F::MULTIVIEW, multiview.multiview != 0);
}
(features, dl_flags) (features, dl_flags)
} }
@ -522,6 +550,11 @@ impl PhysicalDeviceCapabilities {
extensions.push(vk::KhrMaintenance1Fn::name()); extensions.push(vk::KhrMaintenance1Fn::name());
extensions.push(vk::KhrMaintenance2Fn::name()); extensions.push(vk::KhrMaintenance2Fn::name());
// Below Vulkan 1.1 we can get multiview from an extension
if requested_features.contains(wgt::Features::MULTIVIEW) {
extensions.push(vk::KhrMultiviewFn::name());
}
// `VK_AMD_negative_viewport_height` is obsoleted by `VK_KHR_maintenance1` and must not be enabled alongside `VK_KHR_maintenance1` or a 1.1+ device. // `VK_AMD_negative_viewport_height` is obsoleted by `VK_KHR_maintenance1` and must not be enabled alongside `VK_KHR_maintenance1` or a 1.1+ device.
if !self.supports_extension(vk::KhrMaintenance1Fn::name()) { if !self.supports_extension(vk::KhrMaintenance1Fn::name()) {
extensions.push(vk::AmdNegativeViewportHeightFn::name()); extensions.push(vk::AmdNegativeViewportHeightFn::name());
@ -727,6 +760,13 @@ impl super::InstanceShared {
.features(core) .features(core)
.build(); .build();
if capabilities.properties.api_version >= vk::API_VERSION_1_1 {
features.vulkan_1_1 = Some(vk::PhysicalDeviceVulkan11Features::builder().build());
let mut_ref = features.vulkan_1_1.as_mut().unwrap();
mut_ref.p_next = mem::replace(&mut features2.p_next, mut_ref as *mut _ as *mut _);
}
if capabilities.properties.api_version >= vk::API_VERSION_1_2 { if capabilities.properties.api_version >= vk::API_VERSION_1_2 {
features.vulkan_1_2 = Some(vk::PhysicalDeviceVulkan12Features::builder().build()); features.vulkan_1_2 = Some(vk::PhysicalDeviceVulkan12Features::builder().build());
@ -793,6 +833,7 @@ impl super::InstanceShared {
} }
unsafe { unsafe {
null_p_next(&mut features.vulkan_1_1);
null_p_next(&mut features.vulkan_1_2); null_p_next(&mut features.vulkan_1_2);
null_p_next(&mut features.descriptor_indexing); null_p_next(&mut features.descriptor_indexing);
null_p_next(&mut features.imageless_framebuffer); null_p_next(&mut features.imageless_framebuffer);
@ -998,6 +1039,7 @@ impl super::Adapter {
raw_device: ash::Device, raw_device: ash::Device,
handle_is_owned: bool, handle_is_owned: bool,
enabled_extensions: &[&'static CStr], enabled_extensions: &[&'static CStr],
features: wgt::Features,
uab_types: super::UpdateAfterBindTypes, uab_types: super::UpdateAfterBindTypes,
family_index: u32, family_index: u32,
queue_index: u32, queue_index: u32,
@ -1044,7 +1086,8 @@ impl super::Adapter {
let naga_options = { let naga_options = {
use naga::back::spv; use naga::back::spv;
let capabilities = [
let mut capabilities = vec![
spv::Capability::Shader, spv::Capability::Shader,
spv::Capability::Matrix, spv::Capability::Matrix,
spv::Capability::Sampled1D, spv::Capability::Sampled1D,
@ -1058,6 +1101,11 @@ impl super::Adapter {
spv::Capability::StorageImageExtendedFormats, spv::Capability::StorageImageExtendedFormats,
//TODO: fill out the rest //TODO: fill out the rest
]; ];
if features.contains(wgt::Features::MULTIVIEW) {
capabilities.push(spv::Capability::MultiView);
}
let mut flags = spv::WriterFlags::empty(); let mut flags = spv::WriterFlags::empty();
flags.set( flags.set(
spv::WriterFlags::DEBUG, spv::WriterFlags::DEBUG,
@ -1078,17 +1126,17 @@ impl super::Adapter {
lang_version: (1, 0), lang_version: (1, 0),
flags, flags,
capabilities: Some(capabilities.iter().cloned().collect()), capabilities: Some(capabilities.iter().cloned().collect()),
bounds_check_policies: naga::back::BoundsCheckPolicies { bounds_check_policies: naga::proc::BoundsCheckPolicies {
index: naga::back::BoundsCheckPolicy::Restrict, index: naga::proc::BoundsCheckPolicy::Restrict,
buffer: if self.private_caps.robust_buffer_access { buffer: if self.private_caps.robust_buffer_access {
naga::back::BoundsCheckPolicy::Unchecked naga::proc::BoundsCheckPolicy::Unchecked
} else { } else {
naga::back::BoundsCheckPolicy::Restrict naga::proc::BoundsCheckPolicy::Restrict
}, },
image: if self.private_caps.robust_image_access { image: if self.private_caps.robust_image_access {
naga::back::BoundsCheckPolicy::Unchecked naga::proc::BoundsCheckPolicy::Unchecked
} else { } else {
naga::back::BoundsCheckPolicy::Restrict naga::proc::BoundsCheckPolicy::Restrict
}, },
}, },
} }
@ -1222,6 +1270,7 @@ impl crate::Adapter<super::Api> for super::Adapter {
raw_device, raw_device,
true, true,
&enabled_extensions, &enabled_extensions,
features,
uab_types, uab_types,
family_info.queue_family_index, family_info.queue_family_index,
0, 0,

View File

@ -379,6 +379,15 @@ impl crate::CommandEncoder<super::Api> for super::CommandEncoder {
vk_image_views.push(at.view.raw); vk_image_views.push(at.view.raw);
fb_key.attachments.push(at.view.attachment.clone()); fb_key.attachments.push(at.view.attachment.clone());
} }
// 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);
}
}
} }
if let Some(ref ds) = desc.depth_stencil_attachment { if let Some(ref ds) = desc.depth_stencil_attachment {
vk_clear_values.push(vk::ClearValue { vk_clear_values.push(vk::ClearValue {
@ -393,8 +402,15 @@ impl crate::CommandEncoder<super::Api> for super::CommandEncoder {
stencil_ops: ds.stencil_ops, stencil_ops: ds.stencil_ops,
}); });
fb_key.attachments.push(ds.target.view.attachment.clone()); fb_key.attachments.push(ds.target.view.attachment.clone());
// 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);
}
} }
rp_key.sample_count = fb_key.sample_count; rp_key.sample_count = fb_key.sample_count;
rp_key.multiview = desc.multiview;
let render_area = vk::Rect2D { let render_area = vk::Rect2D {
offset: vk::Offset2D { x: 0, y: 0 }, offset: vk::Offset2D { x: 0, y: 0 },

View File

@ -5,7 +5,9 @@ use ash::{extensions::khr, vk};
use inplace_it::inplace_or_alloc_from_iter; use inplace_it::inplace_or_alloc_from_iter;
use parking_lot::Mutex; use parking_lot::Mutex;
use std::{borrow::Cow, collections::hash_map::Entry, ffi::CString, ptr, sync::Arc}; use std::{
borrow::Cow, collections::hash_map::Entry, ffi::CString, num::NonZeroU32, ptr, sync::Arc,
};
impl super::DeviceShared { impl super::DeviceShared {
pub(super) unsafe fn set_object_name( pub(super) unsafe fn set_object_name(
@ -140,10 +142,30 @@ impl super::DeviceShared {
vk_subpass.build() vk_subpass.build()
}]; }];
let vk_info = vk::RenderPassCreateInfo::builder() let mut vk_info = vk::RenderPassCreateInfo::builder()
.attachments(&vk_attachments) .attachments(&vk_attachments)
.subpasses(&vk_subpasses); .subpasses(&vk_subpasses);
let mut multiview_info;
let mask;
if let Some(multiview) = e.key().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.
mask = [(1 << multiview.get()) - 1];
// On Vulkan 1.1 or later, this is an alias for core functionality
multiview_info = vk::RenderPassMultiviewCreateInfoKHR::builder()
.view_masks(&mask)
.correlation_masks(&mask)
.build();
vk_info = vk_info.push_next(&mut multiview_info);
}
let raw = unsafe { self.raw.create_render_pass(&vk_info, None)? }; let raw = unsafe { self.raw.create_render_pass(&vk_info, None)? };
*e.insert(raw) *e.insert(raw)
@ -605,10 +627,10 @@ impl super::Device {
let temp_options; let temp_options;
let options = if !runtime_checks { let options = if !runtime_checks {
temp_options = naga::back::spv::Options { temp_options = naga::back::spv::Options {
bounds_check_policies: naga::back::BoundsCheckPolicies { bounds_check_policies: naga::proc::BoundsCheckPolicies {
index: naga::back::BoundsCheckPolicy::Unchecked, index: naga::proc::BoundsCheckPolicy::Unchecked,
buffer: naga::back::BoundsCheckPolicy::Unchecked, buffer: naga::proc::BoundsCheckPolicy::Unchecked,
image: naga::back::BoundsCheckPolicy::Unchecked, image: naga::proc::BoundsCheckPolicy::Unchecked,
}, },
..self.naga_options.clone() ..self.naga_options.clone()
}; };
@ -843,12 +865,15 @@ impl crate::Device<super::Api> for super::Device {
texture: &super::Texture, texture: &super::Texture,
desc: &crate::TextureViewDescriptor, desc: &crate::TextureViewDescriptor,
) -> Result<super::TextureView, crate::DeviceError> { ) -> Result<super::TextureView, crate::DeviceError> {
let subresource_range = conv::map_subresource_range(&desc.range, texture.aspects);
let mut vk_info = vk::ImageViewCreateInfo::builder() let mut vk_info = vk::ImageViewCreateInfo::builder()
.flags(vk::ImageViewCreateFlags::empty()) .flags(vk::ImageViewCreateFlags::empty())
.image(texture.raw) .image(texture.raw)
.view_type(conv::map_view_dimension(desc.dimension)) .view_type(conv::map_view_dimension(desc.dimension))
.format(self.shared.private_caps.map_texture_format(desc.format)) .format(self.shared.private_caps.map_texture_format(desc.format))
.subresource_range(conv::map_subresource_range(&desc.range, texture.aspects)); .subresource_range(subresource_range);
let layers =
NonZeroU32::new(subresource_range.layer_count).expect("Unexpected zero layer count");
let mut image_view_info; let mut image_view_info;
let view_usage = if self.shared.private_caps.image_view_usage && !desc.usage.is_empty() { let view_usage = if self.shared.private_caps.image_view_usage && !desc.usage.is_empty() {
@ -879,7 +904,11 @@ impl crate::Device<super::Api> for super::Device {
view_format: desc.format, view_format: desc.format,
}; };
Ok(super::TextureView { raw, attachment }) Ok(super::TextureView {
raw,
layers,
attachment,
})
} }
unsafe fn destroy_texture_view(&self, view: super::TextureView) { unsafe fn destroy_texture_view(&self, view: super::TextureView) {
if !self.shared.private_caps.imageless_framebuffers { if !self.shared.private_caps.imageless_framebuffers {
@ -1286,10 +1315,10 @@ impl crate::Device<super::Api> for super::Device {
} }
let mut naga_options = self.naga_options.clone(); let mut naga_options = self.naga_options.clone();
if !desc.runtime_checks { if !desc.runtime_checks {
naga_options.bounds_check_policies = naga::back::BoundsCheckPolicies { naga_options.bounds_check_policies = naga::proc::BoundsCheckPolicies {
index: naga::back::BoundsCheckPolicy::Unchecked, index: naga::proc::BoundsCheckPolicy::Unchecked,
buffer: naga::back::BoundsCheckPolicy::Unchecked, buffer: naga::proc::BoundsCheckPolicy::Unchecked,
image: naga::back::BoundsCheckPolicy::Unchecked, image: naga::proc::BoundsCheckPolicy::Unchecked,
}; };
} }
Cow::Owned( Cow::Owned(
@ -1335,6 +1364,7 @@ impl crate::Device<super::Api> for super::Device {
]; ];
let mut compatible_rp_key = super::RenderPassKey { let mut compatible_rp_key = super::RenderPassKey {
sample_count: desc.multisample.count, sample_count: desc.multisample.count,
multiview: desc.multiview,
..Default::default() ..Default::default()
}; };
let mut stages = ArrayVec::<_, 2>::new(); let mut stages = ArrayVec::<_, 2>::new();

View File

@ -31,7 +31,7 @@ mod conv;
mod device; mod device;
mod instance; mod instance;
use std::{borrow::Borrow, ffi::CStr, sync::Arc}; use std::{borrow::Borrow, ffi::CStr, num::NonZeroU32, sync::Arc};
use arrayvec::ArrayVec; use arrayvec::ArrayVec;
use ash::{ use ash::{
@ -210,6 +210,7 @@ struct RenderPassKey {
colors: ArrayVec<ColorAttachmentKey, { crate::MAX_COLOR_TARGETS }>, colors: ArrayVec<ColorAttachmentKey, { crate::MAX_COLOR_TARGETS }>,
depth_stencil: Option<DepthStencilAttachmentKey>, depth_stencil: Option<DepthStencilAttachmentKey>,
sample_count: u32, sample_count: u32,
multiview: Option<NonZeroU32>,
} }
#[derive(Clone, Debug, Eq, Hash, PartialEq)] #[derive(Clone, Debug, Eq, Hash, PartialEq)]
@ -373,6 +374,7 @@ impl Texture {
#[derive(Debug)] #[derive(Debug)]
pub struct TextureView { pub struct TextureView {
raw: vk::ImageView, raw: vk::ImageView,
layers: NonZeroU32,
attachment: FramebufferAttachment, attachment: FramebufferAttachment,
} }

View File

@ -526,6 +526,13 @@ bitflags::bitflags! {
/// ///
/// This is a native only feature. /// This is a native only feature.
const SHADER_PRIMITIVE_INDEX = 1 << 39; const SHADER_PRIMITIVE_INDEX = 1 << 39;
/// Enables multiview render passes and `builtin(view_index)` in vertex shaders.
///
/// Supported platforms:
/// - Vulkan
///
/// This is a native only feature.
const MULTIVIEW = 1 << 40;
} }
} }

View File

@ -135,20 +135,20 @@ env_logger = "0.8"
[dependencies.naga] [dependencies.naga]
git = "https://github.com/gfx-rs/naga" git = "https://github.com/gfx-rs/naga"
rev = "eda078d" rev = "29571cc"
#version = "0.7" #version = "0.7"
optional = true optional = true
# used to test all the example shaders # used to test all the example shaders
[dev-dependencies.naga] [dev-dependencies.naga]
git = "https://github.com/gfx-rs/naga" git = "https://github.com/gfx-rs/naga"
rev = "eda078d" rev = "29571cc"
#version = "0.7" #version = "0.7"
features = ["wgsl-in"] features = ["wgsl-in"]
[target.'cfg(target_arch = "wasm32")'.dependencies.naga] [target.'cfg(target_arch = "wasm32")'.dependencies.naga]
git = "https://github.com/gfx-rs/naga" git = "https://github.com/gfx-rs/naga"
rev = "eda078d" rev = "29571cc"
#version = "0.7" #version = "0.7"
features = ["wgsl-out"] features = ["wgsl-out"]

View File

@ -159,6 +159,7 @@ impl framework::Example for Example {
primitive: wgpu::PrimitiveState::default(), primitive: wgpu::PrimitiveState::default(),
depth_stencil: None, depth_stencil: None,
multisample: wgpu::MultisampleState::default(), multisample: wgpu::MultisampleState::default(),
multiview: None,
}); });
// create compute pipeline // create compute pipeline

View File

@ -127,6 +127,7 @@ impl framework::Example for Example {
}, },
depth_stencil: None, depth_stencil: None,
multisample: wgpu::MultisampleState::default(), multisample: wgpu::MultisampleState::default(),
multiview: None,
}); });
let texture = { let texture = {

View File

@ -113,6 +113,7 @@ impl framework::Example for Example {
}, },
depth_stencil: None, depth_stencil: None,
multisample: wgpu::MultisampleState::default(), multisample: wgpu::MultisampleState::default(),
multiview: None,
}); });
let pipeline_triangle_regular = let pipeline_triangle_regular =
@ -132,6 +133,7 @@ impl framework::Example for Example {
primitive: wgpu::PrimitiveState::default(), primitive: wgpu::PrimitiveState::default(),
depth_stencil: None, depth_stencil: None,
multisample: wgpu::MultisampleState::default(), multisample: wgpu::MultisampleState::default(),
multiview: None,
}); });
let pipeline_lines = if device let pipeline_lines = if device
@ -159,6 +161,7 @@ impl framework::Example for Example {
}, },
depth_stencil: None, depth_stencil: None,
multisample: wgpu::MultisampleState::default(), multisample: wgpu::MultisampleState::default(),
multiview: None,
}), }),
) )
} else { } else {
@ -215,6 +218,7 @@ impl framework::Example for Example {
primitive: wgpu::PrimitiveState::default(), primitive: wgpu::PrimitiveState::default(),
depth_stencil: None, depth_stencil: None,
multisample: wgpu::MultisampleState::default(), multisample: wgpu::MultisampleState::default(),
multiview: None,
}), }),
bind_group_layout, bind_group_layout,
) )

View File

@ -260,6 +260,7 @@ impl framework::Example for Example {
}, },
depth_stencil: None, depth_stencil: None,
multisample: wgpu::MultisampleState::default(), multisample: wgpu::MultisampleState::default(),
multiview: None,
}); });
let pipeline_wire = if device.features().contains(wgt::Features::POLYGON_MODE_LINE) { let pipeline_wire = if device.features().contains(wgt::Features::POLYGON_MODE_LINE) {
@ -295,6 +296,7 @@ impl framework::Example for Example {
}, },
depth_stencil: None, depth_stencil: None,
multisample: wgpu::MultisampleState::default(), multisample: wgpu::MultisampleState::default(),
multiview: None,
}); });
Some(pipeline_wire) Some(pipeline_wire)
} else { } else {

View File

@ -64,6 +64,7 @@ async fn run(event_loop: EventLoop<()>, window: Window) {
primitive: wgpu::PrimitiveState::default(), primitive: wgpu::PrimitiveState::default(),
depth_stencil: None, depth_stencil: None,
multisample: wgpu::MultisampleState::default(), multisample: wgpu::MultisampleState::default(),
multiview: None,
}); });
let mut config = wgpu::SurfaceConfiguration { let mut config = wgpu::SurfaceConfiguration {

View File

@ -104,6 +104,7 @@ impl Example {
}, },
depth_stencil: None, depth_stencil: None,
multisample: wgpu::MultisampleState::default(), multisample: wgpu::MultisampleState::default(),
multiview: None,
}); });
let bind_group_layout = pipeline.get_bind_group_layout(0); let bind_group_layout = pipeline.get_bind_group_layout(0);
@ -300,6 +301,7 @@ impl framework::Example for Example {
}, },
depth_stencil: None, depth_stencil: None,
multisample: wgpu::MultisampleState::default(), multisample: wgpu::MultisampleState::default(),
multiview: None,
}); });
// Create bind group // Create bind group

View File

@ -72,6 +72,7 @@ impl Example {
count: sample_count, count: sample_count,
..Default::default() ..Default::default()
}, },
multiview: None,
}); });
let mut encoder = let mut encoder =
device.create_render_bundle_encoder(&wgpu::RenderBundleEncoderDescriptor { device.create_render_bundle_encoder(&wgpu::RenderBundleEncoderDescriptor {
@ -79,6 +80,7 @@ impl Example {
color_formats: &[config.format], color_formats: &[config.format],
depth_stencil: None, depth_stencil: None,
sample_count, sample_count,
multiview: None,
}); });
encoder.set_pipeline(&pipeline); encoder.set_pipeline(&pipeline);
encoder.set_vertex_buffer(0, vertex_buffer.slice(..)); encoder.set_vertex_buffer(0, vertex_buffer.slice(..));

View File

@ -527,6 +527,7 @@ impl framework::Example for Example {
}, },
}), }),
multisample: wgpu::MultisampleState::default(), multisample: wgpu::MultisampleState::default(),
multiview: None,
}); });
Pass { Pass {
@ -658,6 +659,7 @@ impl framework::Example for Example {
bias: wgpu::DepthBiasState::default(), bias: wgpu::DepthBiasState::default(),
}), }),
multisample: wgpu::MultisampleState::default(), multisample: wgpu::MultisampleState::default(),
multiview: None,
}); });
Pass { Pass {

View File

@ -223,6 +223,7 @@ impl framework::Example for Skybox {
bias: wgpu::DepthBiasState::default(), bias: wgpu::DepthBiasState::default(),
}), }),
multisample: wgpu::MultisampleState::default(), multisample: wgpu::MultisampleState::default(),
multiview: None,
}); });
let entity_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { let entity_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
label: Some("Entity"), label: Some("Entity"),
@ -253,6 +254,7 @@ impl framework::Example for Skybox {
bias: wgpu::DepthBiasState::default(), bias: wgpu::DepthBiasState::default(),
}), }),
multisample: wgpu::MultisampleState::default(), multisample: wgpu::MultisampleState::default(),
multiview: None,
}); });
let sampler = device.create_sampler(&wgpu::SamplerDescriptor { let sampler = device.create_sampler(&wgpu::SamplerDescriptor {

View File

@ -250,6 +250,7 @@ impl framework::Example for Example {
}, },
depth_stencil: None, depth_stencil: None,
multisample: wgpu::MultisampleState::default(), multisample: wgpu::MultisampleState::default(),
multiview: None,
}); });
Self { Self {

View File

@ -562,6 +562,7 @@ impl framework::Example for Example {
}), }),
// No multisampling is used. // No multisampling is used.
multisample: wgpu::MultisampleState::default(), multisample: wgpu::MultisampleState::default(),
multiview: None,
}); });
// Same idea as the water pipeline. // Same idea as the water pipeline.
@ -595,6 +596,7 @@ impl framework::Example for Example {
bias: wgpu::DepthBiasState::default(), bias: wgpu::DepthBiasState::default(),
}), }),
multisample: wgpu::MultisampleState::default(), multisample: wgpu::MultisampleState::default(),
multiview: None,
}); });
// Done // Done

View File

@ -1285,6 +1285,7 @@ impl crate::Context for Context {
}, },
targets: Borrowed(frag.targets), targets: Borrowed(frag.targets),
}), }),
multiview: desc.multiview,
}; };
let global = &self.0; let global = &self.0;
@ -1506,6 +1507,7 @@ impl crate::Context for Context {
color_formats: Borrowed(desc.color_formats), color_formats: Borrowed(desc.color_formats),
depth_stencil: desc.depth_stencil, depth_stencil: desc.depth_stencil,
sample_count: desc.sample_count, sample_count: desc.sample_count,
multiview: desc.multiview,
}; };
match wgc::command::RenderBundleEncoder::new(&descriptor, device.id, None) { match wgc::command::RenderBundleEncoder::new(&descriptor, device.id, None) {
Ok(id) => id, Ok(id) => id,

View File

@ -1294,6 +1294,9 @@ pub struct RenderPipelineDescriptor<'a> {
pub multisample: MultisampleState, pub multisample: MultisampleState,
/// The compiled fragment stage, its entry point, and the color targets. /// The compiled fragment stage, its entry point, and the color targets.
pub fragment: Option<FragmentState<'a>>, pub fragment: Option<FragmentState<'a>>,
/// If the pipeline will be used with a multiview render pass, this indicates how many array
/// layers the attachments will have.
pub multiview: Option<NonZeroU32>,
} }
/// Describes the attachments of a compute pass. /// Describes the attachments of a compute pass.
@ -1349,6 +1352,8 @@ pub struct RenderBundleEncoderDescriptor<'a> {
/// Sample count this render bundle is capable of rendering to. This must match the pipelines and /// Sample count this render bundle is capable of rendering to. This must match the pipelines and
/// the renderpasses it is used in. /// the renderpasses it is used in.
pub sample_count: u32, pub sample_count: u32,
/// If this render bundle will rendering to multiple array layers in the attachments at the same time.
pub multiview: Option<NonZeroU32>,
} }
/// Surface texture that can be rendered to. /// Surface texture that can be rendered to.

View File

@ -77,6 +77,7 @@ fn pulling_common(
write_mask: wgpu::ColorWrites::ALL, write_mask: wgpu::ColorWrites::ALL,
}], }],
}), }),
multiview: None,
}); });
let dummy = ctx let dummy = ctx