From 748097a15961e81a7e289a694fda49c76ca4aeef Mon Sep 17 00:00:00 2001 From: Tim Balsfulland Date: Mon, 7 Jun 2021 16:41:13 +0200 Subject: [PATCH] add support for `VK_KHR_multiview` (#1598) * initial support for the `VK_KHR_multiview` extension The `VK_KHR_multiview` extension can be used to efficiently draw to multiple layers of a framebuffer at once with. This is particularly useful for virtual reality applications or other types of stereoscopic rendering where both eyes need to be rendered and they share almost all visible vertices. * allow creation of multi-layer attachment images Using the `AttachmentImage::multisampled_with_usage_with_layers` constructor. More constructors with different combinations could be added for the `AttachmentImage` but `multisampled_with_usage_with_layers` already exposes all possible options. I think all these different constructors should be replaced with a constructor that takes a struct that implements `Default` or an enum with the different possibilities. * compliance with VUID-VkFramebufferCreateInfo-renderPass-02531 * removed changelog entries according to new policy * migrate `VK_KHR_multiview` support to ash * add more comments * add remaining `VK_KHR_multiview` validation * validate instanced drawing using `multiview` limits * add some missing validation relating to `VIEW_LOCAL` dependencies * use the vulkano device feature instead of depending on the `multiview` extension directly --- examples/src/bin/multiview.rs | 350 ++++++++++++++++++ vulkano-shaders/src/codegen.rs | 1 + vulkano-shaders/src/enums.rs | 1 + vulkano/src/command_buffer/auto.rs | 21 ++ .../command_buffer/validity/vertex_buffers.rs | 38 +- vulkano/src/image/attachment.rs | 83 ++++- vulkano/src/instance/physical_device.rs | 40 +- .../src/pipeline/graphics_pipeline/builder.rs | 32 ++ .../graphics_pipeline/creation_error.rs | 12 + vulkano/src/render_pass/desc.rs | 67 ++++ vulkano/src/render_pass/framebuffer.rs | 35 +- vulkano/src/render_pass/mod.rs | 1 + vulkano/src/render_pass/render_pass.rs | 84 +++++ 13 files changed, 738 insertions(+), 27 deletions(-) create mode 100644 examples/src/bin/multiview.rs diff --git a/examples/src/bin/multiview.rs b/examples/src/bin/multiview.rs new file mode 100644 index 00000000..ab2fa137 --- /dev/null +++ b/examples/src/bin/multiview.rs @@ -0,0 +1,350 @@ +// Copyright (c) 2016 The vulkano developers +// Licensed under the Apache License, Version 2.0 +// or the MIT +// license , +// at your option. All files in the project carrying such +// notice may not be copied, modified, or distributed except +// according to those terms. + +//! This example demonstrates using the `VK_KHR_multiview` extension to render to multiple +//! layers of the framebuffer in one render pass. This can significantly improve performance +//! in cases where multiple perspectives or cameras are very similar like in virtual reality +//! or other types of stereoscopic rendering where the left and right eye only differ +//! in a small position offset. + +use std::fs::File; +use std::io::BufWriter; +use std::iter; +use std::path::Path; +use std::sync::Arc; + +use vulkano::buffer::{BufferUsage, CpuAccessibleBuffer}; +use vulkano::command_buffer::{ + AutoCommandBufferBuilder, CommandBufferUsage, DynamicState, SubpassContents, +}; +use vulkano::device::{Device, DeviceExtensions, Features}; +use vulkano::format::Format; +use vulkano::image::view::ImageView; +use vulkano::image::{ + ImageAccess, ImageCreateFlags, ImageDimensions, ImageLayout, ImageUsage, SampleCount, + StorageImage, +}; +use vulkano::instance::PhysicalDevice; +use vulkano::instance::{Instance, InstanceExtensions}; +use vulkano::pipeline::viewport::Viewport; +use vulkano::pipeline::GraphicsPipeline; +use vulkano::render_pass::{ + AttachmentDesc, Framebuffer, LoadOp, MultiviewDesc, RenderPass, RenderPassDesc, StoreOp, + Subpass, SubpassDesc, +}; +use vulkano::sync::GpuFuture; +use vulkano::{sync, Version}; + +fn main() { + let instance = Instance::new( + None, + Version::V1_1, + &InstanceExtensions { + khr_get_physical_device_properties2: true, // required to get multiview limits + + ..InstanceExtensions::none() + }, + None, + ) + .unwrap(); + + let physical = PhysicalDevice::enumerate(&instance).next().unwrap(); + + // This example renders to two layers of the framebuffer using the multiview extension so we + // check that at least two views are supported by the device. + // Not checking this on a device that doesn't support two views + // will lead to a runtime error when creating the `RenderPass`. + // The `max_multiview_view_count` function will return `None` + // when the `VK_KHR_get_physical_device_properties2` instance extension has not been enabled. + if physical + .extended_properties() + .max_multiview_view_count() + .unwrap_or(0) + < 2 + { + println!("The device doesn't support two multiview views or the VK_KHR_get_physical_device_properties2 instance extension has not been loaded"); + + // A real application should probably fall back to rendering the framebuffer layers + // in multiple passes when multiview isn't supported. + return; + } + + let queue_family = physical + .queue_families() + .find(|&q| q.supports_graphics()) + .unwrap(); + + let (device, mut queues) = Device::new( + physical, + &Features { + // enabling the `multiview` feature will use the `VK_KHR_multiview` extension on + // Vulkan 1.0 and the device feature on Vulkan 1.1+ + multiview: true, + ..Features::none() + }, + &DeviceExtensions::none(), + [(queue_family, 0.5)].iter().cloned(), + ) + .unwrap(); + + let queue = queues.next().unwrap(); + + let image = StorageImage::with_usage( + device.clone(), + ImageDimensions::Dim2d { + width: 512, + height: 512, + array_layers: 2, + }, + Format::B8G8R8A8Srgb, + ImageUsage { + transfer_source: true, + color_attachment: true, + ..ImageUsage::none() + }, + ImageCreateFlags::none(), + Some(queue_family), + ) + .unwrap(); + + let image_view = ImageView::new(image.clone()).unwrap(); + + let vertex_buffer = { + #[derive(Default, Debug, Clone)] + struct Vertex { + position: [f32; 2], + } + vulkano::impl_vertex!(Vertex, position); + + CpuAccessibleBuffer::from_iter( + device.clone(), + BufferUsage::all(), + false, + [ + Vertex { + position: [-0.5, -0.25], + }, + Vertex { + position: [0.0, 0.5], + }, + Vertex { + position: [0.25, -0.1], + }, + ] + .iter() + .cloned(), + ) + .unwrap() + }; + + // Note the `#extension GL_EXT_multiview : enable` that enables the multiview extension + // for the shader and the use of `gl_ViewIndex` which contains a value based on which + // view the shader is being invoked for. + // In this example `gl_ViewIndex` is used toggle a hardcoded offset for vertex positions + // but in a VR application you could easily use it as an index to a uniform array + // that contains the transformation matrices for the left and right eye. + mod vs { + vulkano_shaders::shader! { + ty: "vertex", + src: " + #version 450 + #extension GL_EXT_multiview : enable + + layout(location = 0) in vec2 position; + + void main() { + gl_Position = vec4(position, 0.0, 1.0) + gl_ViewIndex * vec4(0.25, 0.25, 0.0, 0.0); + } + " + } + } + + mod fs { + vulkano_shaders::shader! { + ty: "fragment", + src: " + #version 450 + + layout(location = 0) out vec4 f_color; + + void main() { + f_color = vec4(1.0, 0.0, 0.0, 1.0); + } + " + } + } + + let vs = vs::Shader::load(device.clone()).unwrap(); + let fs = fs::Shader::load(device.clone()).unwrap(); + + let render_pass_description = RenderPassDesc::with_multiview( + vec![AttachmentDesc { + format: image.format(), + samples: SampleCount::Sample1, + load: LoadOp::Clear, + store: StoreOp::Store, + stencil_load: LoadOp::Clear, + stencil_store: StoreOp::Store, + initial_layout: ImageLayout::ColorAttachmentOptimal, + final_layout: ImageLayout::ColorAttachmentOptimal, + }], + vec![SubpassDesc { + color_attachments: vec![(0, ImageLayout::ColorAttachmentOptimal)], + depth_stencil: None, + input_attachments: vec![], + resolve_attachments: vec![], + preserve_attachments: vec![], + }], + vec![], + MultiviewDesc { + // the view masks indicate which layers of the framebuffer + // should be rendered for each subpass + view_masks: vec![0b11], + // the correlation masks indicate sets of views that may be more efficient to render concurrently + correlation_masks: vec![0b11], + // for each dependency the view offset controls which views in the source subpass + // the views in the destination subpass depend on + view_offsets: vec![], + }, + ); + + let render_pass = Arc::new(RenderPass::new(device.clone(), render_pass_description).unwrap()); + + let framebuffer = Arc::new( + Framebuffer::start(render_pass.clone()) + .add(image_view) + .unwrap() + .build() + .unwrap(), + ); + + let pipeline = Arc::new( + GraphicsPipeline::start() + .vertex_input_single_buffer() + .vertex_shader(vs.main_entry_point(), ()) + .triangle_list() + .viewports(iter::once(Viewport { + origin: [0.0, 0.0], + dimensions: [ + image.dimensions().width() as f32, + image.dimensions().height() as f32, + ], + depth_range: 0.0..1.0, + })) + .fragment_shader(fs.main_entry_point(), ()) + .render_pass(Subpass::from(render_pass.clone(), 0).unwrap()) + .build(device.clone()) + .unwrap(), + ); + + let dynamic_state = DynamicState::none(); + + let clear_values = vec![[0.0, 0.0, 1.0, 1.0].into()]; + + let create_buffer = || { + CpuAccessibleBuffer::from_iter( + device.clone(), + BufferUsage::all(), + false, + (0..image.dimensions().width() * image.dimensions().height() * 4).map(|_| 0u8), + ) + .unwrap() + }; + + let buffer1 = create_buffer(); + let buffer2 = create_buffer(); + + let mut builder = AutoCommandBufferBuilder::primary( + device.clone(), + queue_family, + CommandBufferUsage::OneTimeSubmit, + ) + .unwrap(); + + // drawing commands are broadcast to each view in the view mask of the active renderpass + // which means only a single draw call is needed to draw to multiple layers of the framebuffer + builder + .begin_render_pass(framebuffer.clone(), SubpassContents::Inline, clear_values) + .unwrap() + .draw( + pipeline.clone(), + &dynamic_state, + vertex_buffer.clone(), + (), + (), + vec![], + ) + .unwrap() + .end_render_pass() + .unwrap(); + + // copy the image layers to different buffers to save them as individual images to disk + builder + .copy_image_to_buffer_dimensions( + image.clone(), + buffer1.clone(), + [0, 0, 0], + image.dimensions().width_height_depth(), + 0, + 1, + 0, + ) + .unwrap() + .copy_image_to_buffer_dimensions( + image.clone(), + buffer2.clone(), + [0, 0, 0], + image.dimensions().width_height_depth(), + 1, + 1, + 0, + ) + .unwrap(); + + let command_buffer = builder.build().unwrap(); + + let future = sync::now(device.clone()) + .then_execute(queue.clone(), command_buffer) + .unwrap() + .then_signal_fence_and_flush() + .unwrap(); + + future.wait(None).unwrap(); + + // write each layer to its own file + write_image_buffer_to_file( + buffer1, + "multiview1.png", + image.dimensions().width(), + image.dimensions().height(), + ); + write_image_buffer_to_file( + buffer2, + "multiview2.png", + image.dimensions().width(), + image.dimensions().height(), + ); +} + +fn write_image_buffer_to_file( + buffer: Arc>, + path: &str, + width: u32, + height: u32, +) { + let buffer_content = buffer.read().unwrap(); + let path = Path::new(path); + let file = File::create(path).unwrap(); + let ref mut w = BufWriter::new(file); + let mut encoder = png::Encoder::new(w, width, height); + encoder.set_color(png::ColorType::RGBA); + encoder.set_depth(png::BitDepth::Eight); + let mut writer = encoder.write_header().unwrap(); + writer.write_image_data(&buffer_content).unwrap(); +} diff --git a/vulkano-shaders/src/codegen.rs b/vulkano-shaders/src/codegen.rs index ffe4871d..78c568cf 100644 --- a/vulkano-shaders/src/codegen.rs +++ b/vulkano-shaders/src/codegen.rs @@ -537,6 +537,7 @@ fn capability_requirement(cap: &Capability) -> DeviceRequirement { Capability::CapabilityStorageInputOutput16 => { DeviceRequirement::Extensions(&["khr_16bit_storage"]) } + Capability::CapabilityMultiView => DeviceRequirement::Features(&["multiview"]), Capability::CapabilityStorageInputOutput8 => { DeviceRequirement::Extensions(&["khr_8bit_storage"]) } diff --git a/vulkano-shaders/src/enums.rs b/vulkano-shaders/src/enums.rs index 940cce76..35fb0579 100644 --- a/vulkano-shaders/src/enums.rs +++ b/vulkano-shaders/src/enums.rs @@ -552,6 +552,7 @@ enumeration! { CapabilityStorageUniform16 = 4434, CapabilityStoragePushConstant16 = 4435, CapabilityStorageInputOutput16 = 4436, + CapabilityMultiView = 4439, CapabilityStorageInputOutput8 = 4448, CapabilityStoragePushConstant8 = 4450, } Capability; diff --git a/vulkano/src/command_buffer/auto.rs b/vulkano/src/command_buffer/auto.rs index 085daad1..0fdd6388 100644 --- a/vulkano/src/command_buffer/auto.rs +++ b/vulkano/src/command_buffer/auto.rs @@ -1828,6 +1828,22 @@ where panic!("Too many clear values") } + if let Some(multiview_desc) = framebuffer.render_pass().desc().multiview() { + // When multiview is enabled, at the beginning of each subpass all non-render pass state is undefined + self.state_cacher.invalidate(); + + // ensure that the framebuffer is compatible with the render pass multiview configuration + if multiview_desc + .view_masks + .iter() + .chain(multiview_desc.correlation_masks.iter()) + .map(|&mask| 32 - mask.leading_zeros()) // calculates the highest used layer index of the mask + .any(|highest_used_layer| highest_used_layer > framebuffer.layers()) + { + panic!("A multiview mask references more layers than exist in the framebuffer"); + } + } + let framebuffer_object = FramebufferAbstract::inner(&framebuffer).internal_object(); self.inner .begin_render_pass(framebuffer.clone(), contents, clear_values)?; @@ -2043,6 +2059,11 @@ where *index += 1; render_pass_state.contents = contents; } + + if let Some(multiview) = rp.desc().multiview() { + // When multiview is enabled, at the beginning of each subpass all non-render pass state is undefined + self.state_cacher.invalidate(); + } } else { return Err(AutoCommandBufferBuilderContextError::ForbiddenOutsideRenderPass); } diff --git a/vulkano/src/command_buffer/validity/vertex_buffers.rs b/vulkano/src/command_buffer/validity/vertex_buffers.rs index 66a19c5a..5369bcfe 100644 --- a/vulkano/src/command_buffer/validity/vertex_buffers.rs +++ b/vulkano/src/command_buffer/validity/vertex_buffers.rs @@ -13,6 +13,7 @@ use std::fmt; use crate::buffer::BufferAccess; use crate::device::DeviceOwned; use crate::pipeline::vertex::VertexSource; +use crate::pipeline::GraphicsPipelineAbstract; use crate::VulkanObject; /// Checks whether vertex buffers can be bound. @@ -21,12 +22,12 @@ use crate::VulkanObject; /// /// - Panics if one of the vertex buffers was not created with the same device as `pipeline`. /// -pub fn check_vertex_buffers( - pipeline: &P, +pub fn check_vertex_buffers( + pipeline: &GP, vertex_buffers: V, ) -> Result where - P: DeviceOwned + VertexSource, + GP: GraphicsPipelineAbstract + DeviceOwned + VertexSource, { let (vertex_buffers, vertex_count, instance_count) = pipeline.decode(vertex_buffers); @@ -41,6 +42,24 @@ where } } + if let Some(multiview) = pipeline.subpass().render_pass().desc().multiview() { + let max_instance_index = pipeline + .device() + .physical_device() + .extended_properties() + .max_multiview_instance_index() + .unwrap_or(0) as usize; + + // vulkano currently always uses `0` as the first instance which means the highest + // used index will just be `instance_count - 1` + if instance_count > max_instance_index + 1 { + return Err(CheckVertexBufferError::TooManyInstances { + instance_count, + max_instance_count: max_instance_index + 1, + }); + } + } + Ok(CheckVertexBuffer { vertex_buffers, vertex_count: vertex_count as u32, @@ -66,6 +85,16 @@ pub enum CheckVertexBufferError { /// Index of the buffer that is missing usage. num_buffer: usize, }, + + /// The vertex buffer has too many instances. + /// When the `multiview` feature is used the maximum amount of instances may be reduced + /// because the implementation may use instancing internally to implement `multiview`. + TooManyInstances { + /// The used amount of instances. + instance_count: usize, + /// The allowed amount of instances. + max_instance_count: usize, + }, } impl error::Error for CheckVertexBufferError {} @@ -80,6 +109,9 @@ impl fmt::Display for CheckVertexBufferError { CheckVertexBufferError::BufferMissingUsage { .. } => { "the vertex buffer usage is missing on a vertex buffer" } + CheckVertexBufferError::TooManyInstances { .. } => { + "the vertex buffer has too many instances" + } } ) } diff --git a/vulkano/src/image/attachment.rs b/vulkano/src/image/attachment.rs index 1c21968b..34997d78 100644 --- a/vulkano/src/image/attachment.rs +++ b/vulkano/src/image/attachment.rs @@ -108,6 +108,7 @@ impl AttachmentImage { AttachmentImage::new_impl( device, dimensions, + 1, format, ImageUsage::none(), SampleCount::Sample1, @@ -128,7 +129,14 @@ impl AttachmentImage { ..ImageUsage::none() }; - AttachmentImage::new_impl(device, dimensions, format, base_usage, SampleCount::Sample1) + AttachmentImage::new_impl( + device, + dimensions, + 1, + format, + base_usage, + SampleCount::Sample1, + ) } /// Same as `new`, but creates a multisampled image. @@ -142,7 +150,7 @@ impl AttachmentImage { samples: SampleCount, format: Format, ) -> Result, ImageCreationError> { - AttachmentImage::new_impl(device, dimensions, format, ImageUsage::none(), samples) + AttachmentImage::new_impl(device, dimensions, 1, format, ImageUsage::none(), samples) } /// Same as `multisampled`, but creates an image that can be used as an input attachment. @@ -160,7 +168,7 @@ impl AttachmentImage { ..ImageUsage::none() }; - AttachmentImage::new_impl(device, dimensions, format, base_usage, samples) + AttachmentImage::new_impl(device, dimensions, 1, format, base_usage, samples) } /// Same as `new`, but lets you specify additional usages. @@ -175,15 +183,13 @@ impl AttachmentImage { format: Format, usage: ImageUsage, ) -> Result, ImageCreationError> { - AttachmentImage::new_impl(device, dimensions, format, usage, SampleCount::Sample1) + AttachmentImage::new_impl(device, dimensions, 1, format, usage, SampleCount::Sample1) } /// Same as `with_usage`, but creates a multisampled image. /// /// > **Note**: You can also use this function and pass `1` for the number of samples if you /// > want a regular image. - /// - /// > **Note**: This function is just a convenient shortcut for `multisampled_with_usage`. #[inline] pub fn multisampled_with_usage( device: Arc, @@ -192,7 +198,23 @@ impl AttachmentImage { format: Format, usage: ImageUsage, ) -> Result, ImageCreationError> { - AttachmentImage::new_impl(device, dimensions, format, usage, samples) + AttachmentImage::new_impl(device, dimensions, 1, format, usage, samples) + } + + /// Same as `multisampled_with_usage`, but creates an image with multiple layers. + /// + /// > **Note**: You can also use this function and pass `1` for the number of layers if you + /// > want a regular image. + #[inline] + pub fn multisampled_with_usage_with_layers( + device: Arc, + dimensions: [u32; 2], + array_layers: u32, + samples: SampleCount, + format: Format, + usage: ImageUsage, + ) -> Result, ImageCreationError> { + AttachmentImage::new_impl(device, dimensions, array_layers, format, usage, samples) } /// Same as `new`, except that the image can later be sampled. @@ -209,7 +231,14 @@ impl AttachmentImage { ..ImageUsage::none() }; - AttachmentImage::new_impl(device, dimensions, format, base_usage, SampleCount::Sample1) + AttachmentImage::new_impl( + device, + dimensions, + 1, + format, + base_usage, + SampleCount::Sample1, + ) } /// Same as `sampled`, except that the image can be used as an input attachment. @@ -227,7 +256,14 @@ impl AttachmentImage { ..ImageUsage::none() }; - AttachmentImage::new_impl(device, dimensions, format, base_usage, SampleCount::Sample1) + AttachmentImage::new_impl( + device, + dimensions, + 1, + format, + base_usage, + SampleCount::Sample1, + ) } /// Same as `sampled`, but creates a multisampled image. @@ -248,7 +284,7 @@ impl AttachmentImage { ..ImageUsage::none() }; - AttachmentImage::new_impl(device, dimensions, format, base_usage, samples) + AttachmentImage::new_impl(device, dimensions, 1, format, base_usage, samples) } /// Same as `sampled_multisampled`, but creates an image that can be used as an input @@ -268,7 +304,7 @@ impl AttachmentImage { ..ImageUsage::none() }; - AttachmentImage::new_impl(device, dimensions, format, base_usage, samples) + AttachmentImage::new_impl(device, dimensions, 1, format, base_usage, samples) } /// Same as `new`, except that the image will be transient. @@ -288,7 +324,14 @@ impl AttachmentImage { ..ImageUsage::none() }; - AttachmentImage::new_impl(device, dimensions, format, base_usage, SampleCount::Sample1) + AttachmentImage::new_impl( + device, + dimensions, + 1, + format, + base_usage, + SampleCount::Sample1, + ) } /// Same as `transient`, except that the image can be used as an input attachment. @@ -306,7 +349,14 @@ impl AttachmentImage { ..ImageUsage::none() }; - AttachmentImage::new_impl(device, dimensions, format, base_usage, SampleCount::Sample1) + AttachmentImage::new_impl( + device, + dimensions, + 1, + format, + base_usage, + SampleCount::Sample1, + ) } /// Same as `transient`, but creates a multisampled image. @@ -327,7 +377,7 @@ impl AttachmentImage { ..ImageUsage::none() }; - AttachmentImage::new_impl(device, dimensions, format, base_usage, samples) + AttachmentImage::new_impl(device, dimensions, 1, format, base_usage, samples) } /// Same as `transient_multisampled`, but creates an image that can be used as an input @@ -347,13 +397,14 @@ impl AttachmentImage { ..ImageUsage::none() }; - AttachmentImage::new_impl(device, dimensions, format, base_usage, samples) + AttachmentImage::new_impl(device, dimensions, 1, format, base_usage, samples) } // All constructors dispatch to this one. fn new_impl( device: Arc, dimensions: [u32; 2], + array_layers: u32, format: Format, base_usage: ImageUsage, samples: SampleCount, @@ -378,7 +429,7 @@ impl AttachmentImage { let dims = ImageDimensions::Dim2d { width: dimensions[0], height: dimensions[1], - array_layers: 1, + array_layers, }; UnsafeImage::new( diff --git a/vulkano/src/instance/physical_device.rs b/vulkano/src/instance/physical_device.rs index 1826f427..e19b4b00 100644 --- a/vulkano/src/instance/physical_device.rs +++ b/vulkano/src/instance/physical_device.rs @@ -129,9 +129,14 @@ fn init_physical_devices_inner2( let properties: ash::vk::PhysicalDeviceProperties = unsafe { let mut subgroup_properties = ash::vk::PhysicalDeviceSubgroupProperties::default(); + let mut multiview_properties = ash::vk::PhysicalDeviceMultiviewProperties { + p_next: &mut subgroup_properties as *mut _ as *mut c_void, + ..Default::default() + }; + let mut output = ash::vk::PhysicalDeviceProperties2 { p_next: if instance.api_version() >= Version::V1_1 { - &mut subgroup_properties as *mut _ as *mut c_void + &mut multiview_properties as *mut _ as *mut c_void } else { ptr::null_mut() }, @@ -147,11 +152,11 @@ fn init_physical_devices_inner2( } extended_properties = PhysicalDeviceExtendedProperties { - subgroup_size: if instance.api_version() >= Version::V1_1 { - Some(subgroup_properties.subgroup_size) - } else { - None - }, + subgroup_size: Some(subgroup_properties.subgroup_size), + max_multiview_view_count: Some(multiview_properties.max_multiview_view_count), + max_multiview_instance_index: Some( + multiview_properties.max_multiview_instance_index, + ), ..extended_properties }; @@ -261,12 +266,16 @@ pub(super) struct PhysicalDeviceInfos { /// TODO: Only a small subset of available properties(https://www.khronos.org/registry/vulkan/specs/1.2-extensions/man/html/VkPhysicalDeviceProperties2.html) is implemented at this moment. pub struct PhysicalDeviceExtendedProperties { subgroup_size: Option, + max_multiview_view_count: Option, + max_multiview_instance_index: Option, } impl PhysicalDeviceExtendedProperties { - pub(super) fn empty() -> Self { + fn empty() -> Self { Self { subgroup_size: None, + max_multiview_view_count: None, + max_multiview_instance_index: None, } } @@ -277,6 +286,23 @@ impl PhysicalDeviceExtendedProperties { pub fn subgroup_size(&self) -> &Option { &self.subgroup_size } + + /// The maximum number of views that can be used in a subpass using the multiview feature. + /// + /// See https://www.khronos.org/registry/vulkan/specs/1.2-extensions/man/html/VkPhysicalDeviceMultiviewProperties.html for details + #[inline] + pub fn max_multiview_view_count(&self) -> &Option { + &self.max_multiview_view_count + } + + /// The maximum number valid value of instance index (for instanced rendering) + /// allowed to be generated by a drawing command using this multiview description. + /// + /// See https://www.khronos.org/registry/vulkan/specs/1.2-extensions/man/html/VkPhysicalDeviceMultiviewProperties.html for details + #[inline] + pub fn max_multiview_instance_index(&self) -> &Option { + &self.max_multiview_instance_index + } } /// Represents one of the available devices on this machine. diff --git a/vulkano/src/pipeline/graphics_pipeline/builder.rs b/vulkano/src/pipeline/graphics_pipeline/builder.rs index 3501a7d6..f1d854cf 100644 --- a/vulkano/src/pipeline/graphics_pipeline/builder.rs +++ b/vulkano/src/pipeline/graphics_pipeline/builder.rs @@ -955,6 +955,38 @@ where None }; + if let Some(multiview) = self + .subpass + .as_ref() + .unwrap() + .render_pass() + .desc() + .multiview() + .as_ref() + { + if multiview.used_layer_count() > 0 { + if self.geometry_shader.is_some() + && !device + .physical_device() + .supported_features() + .multiview_geometry_shader + { + return Err(GraphicsPipelineCreationError::MultiviewGeometryShaderNotSupported); + } + + if self.tessellation.is_some() + && !device + .physical_device() + .supported_features() + .multiview_tessellation_shader + { + return Err( + GraphicsPipelineCreationError::MultiviewTessellationShaderNotSupported, + ); + } + } + } + let pipeline = unsafe { let infos = ash::vk::GraphicsPipelineCreateInfo { flags: ash::vk::PipelineCreateFlags::empty(), // TODO: some flags are available but none are critical diff --git a/vulkano/src/pipeline/graphics_pipeline/creation_error.rs b/vulkano/src/pipeline/graphics_pipeline/creation_error.rs index 3ef37ae4..a25e5b94 100644 --- a/vulkano/src/pipeline/graphics_pipeline/creation_error.rs +++ b/vulkano/src/pipeline/graphics_pipeline/creation_error.rs @@ -164,6 +164,12 @@ pub enum GraphicsPipelineCreationError { /// The `alpha_to_one` feature must be enabled in order to use alpha-to-one. AlphaToOneFeatureNotEnabled, + + /// The device doesn't support using the `multiview´ feature with geometry shaders. + MultiviewGeometryShaderNotSupported, + + /// The device doesn't support using the `multiview´ feature with tessellation shaders. + MultiviewTessellationShaderNotSupported, } impl error::Error for GraphicsPipelineCreationError { @@ -300,6 +306,12 @@ impl fmt::Display for GraphicsPipelineCreationError { GraphicsPipelineCreationError::AlphaToOneFeatureNotEnabled => { "the `alpha_to_one` feature must be enabled in order to use alpha-to-one" } + GraphicsPipelineCreationError::MultiviewGeometryShaderNotSupported => { + "the device doesn't support using the `multiview´ feature with geometry shaders" + } + GraphicsPipelineCreationError::MultiviewTessellationShaderNotSupported => { + "the device doesn't support using the `multiview´ feature with tessellation shaders" + } } ) } diff --git a/vulkano/src/render_pass/desc.rs b/vulkano/src/render_pass/desc.rs index a42b3383..192a042c 100644 --- a/vulkano/src/render_pass/desc.rs +++ b/vulkano/src/render_pass/desc.rs @@ -21,6 +21,7 @@ pub struct RenderPassDesc { attachments: Vec, subpasses: Vec, dependencies: Vec, + multiview: Option, } impl RenderPassDesc { @@ -34,6 +35,23 @@ impl RenderPassDesc { attachments, subpasses, dependencies, + multiview: None, + } + } + + /// Creates a description of a render pass that uses the multiview feature. + /// See [`MultiviewDesc`] for an explanation of possible configuration options. + pub fn with_multiview( + attachments: Vec, + subpasses: Vec, + dependencies: Vec, + multiview: MultiviewDesc, + ) -> RenderPassDesc { + RenderPassDesc { + attachments, + subpasses, + dependencies, + multiview: Some(multiview), } } @@ -49,6 +67,7 @@ impl RenderPassDesc { preserve_attachments: vec![], }], dependencies: vec![], + multiview: None, } } @@ -70,6 +89,12 @@ impl RenderPassDesc { &self.dependencies } + // Returns the multiview configuration of the description. + #[inline] + pub fn multiview(&self) -> &Option { + &self.multiview + } + /// Decodes `I` into a list of clear values where each element corresponds /// to an attachment. The size of the returned iterator must be the same as the number of /// attachments. @@ -332,3 +357,45 @@ impl From for ash::vk::AttachmentLoadOp { Self::from_raw(val as i32) } } + +/// Describes the `multiview` configuration for the render pass which is used to draw +/// to multiple layers of a framebuffer inside of a single render pass. +#[derive(Debug, Clone)] +pub struct MultiviewDesc { + /// The view masks indicate which layers of the framebuffer should be rendered for each subpass. + /// Values are bit masks which means that for example `0b11` will draw to the first two layers + /// and `0b101` will draw to the first and third layer. + pub view_masks: Vec, + + /// The correlation masks indicate sets of views that may be more efficient to render + /// concurrently (usually because they show the same geometry from almost the same perspective). + /// Values are bit masks which means that for example `0b11` means the first two layers are + /// highly correlated and `0b101` means the first and third layer are highly correlated. + pub correlation_masks: Vec, + + /// The view offsets contain additional information for each subpass dependency that indicate + /// which views in the source subpass the views of the destination subpass depend on. + pub view_offsets: Vec, +} + +impl MultiviewDesc { + /// Returns the index of the layer with the biggest index that is + /// referred to by a mask in the multiview description. + pub fn highest_used_layer(&self) -> u32 { + self.view_masks + .iter() + .chain(self.correlation_masks.iter()) + .map(|&mask| 32 - mask.leading_zeros()) // the highest set bit corresponds to the highest used layer + .max() + .unwrap_or(0) + } + + /// Returns the amount of layers that are used in the multiview description. + pub fn used_layer_count(&self) -> u32 { + self.view_masks + .iter() + .chain(self.correlation_masks.iter()) + .fold(0, |acc, &mask| acc | mask) + .count_ones() + } +} diff --git a/vulkano/src/render_pass/framebuffer.rs b/vulkano/src/render_pass/framebuffer.rs index e2e31205..e728f13a 100644 --- a/vulkano/src/render_pass/framebuffer.rs +++ b/vulkano/src/render_pass/framebuffer.rs @@ -291,6 +291,27 @@ where } } + let mut layers = 1; + + if let Some(multiview) = self.render_pass.desc().multiview() { + // There needs to be at least as many layers in the framebuffer + // as the highest layer that gets referenced by the multiview masking. + if multiview.highest_used_layer() > dimensions[2] { + return Err(FramebufferCreationError::InsufficientLayerCount { + minimum: multiview.highest_used_layer(), + current: dimensions[2], + }); + } + + // VUID-VkFramebufferCreateInfo-renderPass-02531 + // The framebuffer has to be created with one layer if multiview is enabled even though + // the underlying images generally have more layers + // but these layers get used by the multiview functionality. + if multiview.view_masks.iter().any(|&mask| mask != 0) { + layers = 1; + } + } + let framebuffer = unsafe { let fns = device.fns(); @@ -301,7 +322,7 @@ where p_attachments: self.raw_ids.as_ptr(), width: dimensions[0], height: dimensions[1], - layers: dimensions[2], + layers, ..Default::default() }; @@ -493,6 +514,15 @@ pub enum FramebufferCreationError { OomError(OomError), /// The requested dimensions exceed the device's limits. DimensionsTooLarge, + /// The number of minimum layers expected by the render pass exceed the framebuffer layers. + /// This can happen when the multiview feature is enabled and the specified view or correlation + /// masks refer to more layers than the framebuffer has. + InsufficientLayerCount { + /// Minimum number of layers. + minimum: u32, + /// Number of framebuffer layers. + current: u32, + }, /// The attachment has a size that isn't compatible with the requested framebuffer dimensions. AttachmentDimensionsIncompatible { /// Expected dimensions. @@ -542,6 +572,9 @@ impl fmt::Display for FramebufferCreationError { FramebufferCreationError::DimensionsTooLarge => { "the dimensions of the framebuffer are too large" } + FramebufferCreationError::InsufficientLayerCount { .. } => { + "the number of minimum layers expected by the render pass exceed the framebuffer layers" + } FramebufferCreationError::AttachmentDimensionsIncompatible { .. } => { "the attachment has a size that isn't compatible with the framebuffer dimensions" } diff --git a/vulkano/src/render_pass/mod.rs b/vulkano/src/render_pass/mod.rs index d97891cf..8f495732 100644 --- a/vulkano/src/render_pass/mod.rs +++ b/vulkano/src/render_pass/mod.rs @@ -34,6 +34,7 @@ pub use self::desc::RenderPassDesc; pub use self::desc::StoreOp; pub use self::desc::SubpassDependencyDesc; pub use self::desc::SubpassDesc; +pub use self::desc::MultiviewDesc; pub use self::framebuffer::Framebuffer; pub use self::framebuffer::FramebufferAbstract; pub use self::framebuffer::FramebufferBuilder; diff --git a/vulkano/src/render_pass/render_pass.rs b/vulkano/src/render_pass/render_pass.rs index 8550f4ea..28b78252 100644 --- a/vulkano/src/render_pass/render_pass.rs +++ b/vulkano/src/render_pass/render_pass.rs @@ -377,8 +377,92 @@ impl RenderPass { }) .collect::>(); + let multiview_create_info = match description.multiview() { + Some(multiview) => { + debug_assert!(device.enabled_features().multiview); + debug_assert!( + device + .physical_device() + .extended_properties() + .max_multiview_view_count() + .unwrap_or(0) + >= multiview.used_layer_count() + ); + + // each subpass must have a corresponding view mask + // or there are no view masks at all (which is probably a bug because + // nothing will get drawn) + debug_assert!( + multiview.view_masks.len() == passes.len() || multiview.view_masks.is_empty() + ); + + // either all subpasses must have a non-zero view mask or all must be zero + // (multiview is considered to be disabled when all view masks are zero) + debug_assert!( + multiview.view_masks.iter().all(|&mask| mask != 0) + || multiview.view_masks.iter().all(|&mask| mask == 0) + ); + + // one view offset for each dependency + // or no view offsets at all + debug_assert!( + dependencies.len() == multiview.view_offsets.len() + || multiview.view_offsets.is_empty() + ); + + // VUID-VkRenderPassCreateInfo-pNext-02512 + debug_assert!(dependencies.iter().zip(&multiview.view_offsets).all( + |(dependency, &view_offset)| dependency + .dependency_flags + .contains(ash::vk::DependencyFlags::VIEW_LOCAL) + || view_offset == 0 + )); + + // VUID-VkRenderPassCreateInfo-pNext-02514 + debug_assert!( + multiview.view_masks.iter().any(|&view_mask| view_mask != 0) + || dependencies.iter().all(|dependency| !dependency + .dependency_flags + .contains(ash::vk::DependencyFlags::VIEW_LOCAL)) + ); + + // VUID-VkRenderPassCreateInfo-pNext-02515 + debug_assert!( + multiview.view_masks.iter().any(|&view_mask| view_mask != 0) + || multiview.correlation_masks.is_empty() + ); + + // VUID-VkRenderPassMultiviewCreateInfo-pCorrelationMasks-00841 + // ensure that each view index is contained in at most one correlation mask + // by checking for any overlap in all pairs of correlation masks + debug_assert!(multiview + .correlation_masks + .iter() + .enumerate() + .all(|(i, &mask)| multiview.correlation_masks[i + 1..] + .iter() + .all(|&other_mask| other_mask & mask == 0))); + + ash::vk::RenderPassMultiviewCreateInfo { + subpass_count: passes.len() as u32, + p_view_masks: multiview.view_masks.as_ptr(), + dependency_count: dependencies.len() as u32, + p_view_offsets: multiview.view_offsets.as_ptr(), + correlation_mask_count: multiview.correlation_masks.len() as u32, + p_correlation_masks: multiview.correlation_masks.as_ptr(), + ..Default::default() + } + } + None => ash::vk::RenderPassMultiviewCreateInfo::default(), + }; + let render_pass = unsafe { let infos = ash::vk::RenderPassCreateInfo { + p_next: if description.multiview().is_none() { + ptr::null() + } else { + &multiview_create_info as *const _ as _ + }, flags: ash::vk::RenderPassCreateFlags::empty(), attachment_count: attachments.len() as u32, p_attachments: if attachments.is_empty() {