From d6df316629c905737959cb80604a605c7652b31f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Hlusi=C4=8Dka?= Date: Sun, 2 Aug 2020 17:24:58 +0200 Subject: [PATCH 1/5] Multiview --- examples/colour-uniform/main.rs | 3 +- examples/mesh-shading/main.rs | 3 +- examples/quad/main.rs | 3 +- src/backend/empty/src/lib.rs | 1 + src/backend/gl/src/device.rs | 1 + src/backend/vulkan/src/device.rs | 72 ++++++++++++++----- src/backend/vulkan/src/lib.rs | 117 ++++++++++++++++++++++++++----- src/hal/src/device.rs | 1 + src/hal/src/lib.rs | 25 +++++-- src/hal/src/pass.rs | 4 ++ src/warden/src/gpu.rs | 4 +- 11 files changed, 187 insertions(+), 47 deletions(-) diff --git a/examples/colour-uniform/main.rs b/examples/colour-uniform/main.rs index e092f1d8891..a2555b39b92 100644 --- a/examples/colour-uniform/main.rs +++ b/examples/colour-uniform/main.rs @@ -686,12 +686,13 @@ impl RenderPassState { inputs: &[], resolves: &[], preserves: &[], + view_mask: 0, }; device .borrow() .device - .create_render_pass(&[attachment], &[subpass], &[]) + .create_render_pass(&[attachment], &[subpass], &[], None) .ok() }; diff --git a/examples/mesh-shading/main.rs b/examples/mesh-shading/main.rs index 8f5d63f5dbb..3f10176bd7b 100644 --- a/examples/mesh-shading/main.rs +++ b/examples/mesh-shading/main.rs @@ -379,10 +379,11 @@ where inputs: &[], resolves: &[], preserves: &[], + view_mask: 0, }; ManuallyDrop::new( - unsafe { device.create_render_pass(&[attachment], &[subpass], &[]) } + unsafe { device.create_render_pass(&[attachment], &[subpass], &[], None) } .expect("Can't create render pass"), ) }; diff --git a/examples/quad/main.rs b/examples/quad/main.rs index 74eca6893bd..79fd4f582e2 100644 --- a/examples/quad/main.rs +++ b/examples/quad/main.rs @@ -584,10 +584,11 @@ where inputs: &[], resolves: &[], preserves: &[], + view_mask: 0, }; ManuallyDrop::new( - unsafe { device.create_render_pass(&[attachment], &[subpass], &[]) } + unsafe { device.create_render_pass(&[attachment], &[subpass], &[], None) } .expect("Can't create render pass"), ) }; diff --git a/src/backend/empty/src/lib.rs b/src/backend/empty/src/lib.rs index 36e3bfd7de5..b3a910e5a9d 100644 --- a/src/backend/empty/src/lib.rs +++ b/src/backend/empty/src/lib.rs @@ -230,6 +230,7 @@ impl device::Device for Device { _: IA, _: IS, _: ID, + _: Option<&[u32]>, ) -> Result<(), device::OutOfMemory> where IA: IntoIterator, diff --git a/src/backend/gl/src/device.rs b/src/backend/gl/src/device.rs index d0e0244dace..7f48b4716ed 100644 --- a/src/backend/gl/src/device.rs +++ b/src/backend/gl/src/device.rs @@ -605,6 +605,7 @@ impl d::Device for Device { attachments: IA, subpasses: IS, _dependencies: ID, + correlation_masks: Option<&[u32]>, ) -> Result where IA: IntoIterator, diff --git a/src/backend/vulkan/src/device.rs b/src/backend/vulkan/src/device.rs index dc514faf68f..5ee299822d8 100644 --- a/src/backend/vulkan/src/device.rs +++ b/src/backend/vulkan/src/device.rs @@ -555,6 +555,7 @@ impl d::Device for Device { attachments: IA, subpasses: IS, dependencies: ID, + correlation_masks: Option<&[u32]>, ) -> Result where IA: IntoIterator, @@ -590,6 +591,10 @@ impl d::Device for Device { let dependencies = dependencies .into_iter() + .collect::>(); + + let vk_dependencies = dependencies + .iter() .map(|subpass_dep| { let sdep = subpass_dep.borrow(); // TODO: checks @@ -644,13 +649,13 @@ impl d::Device for Device { .collect::>(); let resolves = subpass.resolves.iter().map(make_ref).collect::>(); - (colors, depth_stencil, inputs, preserves, resolves) + (colors, depth_stencil, inputs, preserves, resolves, subpass.view_mask) }) .collect::>(); let subpasses = attachment_refs .iter() - .map(|(colors, depth_stencil, inputs, preserves, resolves)| { + .map(|(colors, depth_stencil, inputs, preserves, resolves, _view_mask)| { vk::SubpassDescription { flags: vk::SubpassDescriptionFlags::empty(), pipeline_bind_point: vk::PipelineBindPoint::GRAPHICS, @@ -673,23 +678,56 @@ impl d::Device for Device { }) .collect::>(); - let result = inplace_it::inplace_or_alloc_array(dependencies.len(), |uninit_guard| { - let dependencies = - uninit_guard.init_with_iter(dependencies); + let multiview_enabled = self.shared.features.contains(Features::MULTIVIEW); - let info = vk::RenderPassCreateInfo { - s_type: vk::StructureType::RENDER_PASS_CREATE_INFO, - p_next: ptr::null(), - flags: vk::RenderPassCreateFlags::empty(), - attachment_count: attachments.len() as u32, - p_attachments: attachments.as_ptr(), - subpass_count: subpasses.len() as u32, - p_subpasses: subpasses.as_ptr(), - dependency_count: dependencies.len() as u32, - p_dependencies: dependencies.as_ptr(), - }; + let view_masks = if multiview_enabled { + Some( + attachment_refs + .iter() + .map(|(_colors, _depth_stencil, _inputs, _preserves, _resolves, view_mask)| { + *view_mask + }) + .collect::>() + ) + } else { + None + }; + + let view_offsets = if multiview_enabled { + Some( + dependencies + .iter() + .map(|dependency| { + dependency.borrow().view_offset + }) + .collect::>() + ) + } else { + None + }; + + let result = inplace_it::inplace_or_alloc_array(vk_dependencies.len(), |uninit_guard| { + let vk_dependencies = + uninit_guard.init_with_iter(vk_dependencies); + + let mut info_builder = vk::RenderPassCreateInfo::builder() + .flags(vk::RenderPassCreateFlags::empty()) + .attachments(&attachments) + .subpasses(&subpasses) + .dependencies(&vk_dependencies); + let mut multiview; + + if multiview_enabled { + multiview = vk::RenderPassMultiviewCreateInfo::builder() + .view_masks(&view_masks.unwrap()) + .view_offsets(&view_offsets.unwrap()) + .correlation_masks(correlation_masks.unwrap()) + .build(); + + info_builder = info_builder.push_next(&mut multiview); + } - self.shared.raw.create_render_pass(&info, None) + self.shared.raw.create_render_pass(&info_builder.build(), None) }); (clear_attachments_mask, result) diff --git a/src/backend/vulkan/src/lib.rs b/src/backend/vulkan/src/lib.rs index 125379496b2..fb1e5f77f32 100644 --- a/src/backend/vulkan/src/lib.rs +++ b/src/backend/vulkan/src/lib.rs @@ -92,6 +92,8 @@ lazy_static! { CStr::from_bytes_with_nul(b"VK_KHR_sampler_mirror_clamp_to_edge\0").unwrap(); static ref KHR_GET_PHYSICAL_DEVICE_PROPERTIES2: &'static CStr = CStr::from_bytes_with_nul(b"VK_KHR_get_physical_device_properties2\0").unwrap(); + static ref KHR_MULTIVIEW: &'static CStr = + CStr::from_bytes_with_nul(b"VK_KHR_multiview\0").unwrap(); static ref EXT_DESCRIPTOR_INDEXING: &'static CStr = CStr::from_bytes_with_nul(b"VK_EXT_descriptor_indexing\0").unwrap(); static ref MESH_SHADER: &'static CStr = MeshShader::name(); @@ -500,17 +502,17 @@ impl hal::Instance for Instance { .map(|device| { let extensions = unsafe { self.raw.inner.enumerate_device_extension_properties(device) }.unwrap(); - let properties = unsafe { self.raw.inner.get_physical_device_properties(device) }; + let properties = PhysicalDeviceProperties::load(&self, &device, &extensions); let info = adapter::AdapterInfo { name: unsafe { - CStr::from_ptr(properties.device_name.as_ptr()) + CStr::from_ptr(properties.properties.device_name.as_ptr()) .to_str() .unwrap_or("Unknown") .to_owned() }, - vendor: properties.vendor_id as usize, - device: properties.device_id as usize, - device_type: match properties.device_type { + vendor: properties.properties.vendor_id as usize, + device: properties.properties.device_id as usize, + device_type: match properties.properties.device_type { ash::vk::PhysicalDeviceType::OTHER => adapter::DeviceType::Other, ash::vk::PhysicalDeviceType::INTEGRATED_GPU => { adapter::DeviceType::IntegratedGpu @@ -650,11 +652,65 @@ impl queue::QueueFamily for QueueFamily { } } +pub struct PhysicalDeviceMultiviewProperties { + pub max_multiview_view_count: u32, + pub max_multiview_instance_index: u32, +} + +impl From for PhysicalDeviceMultiviewProperties { + fn from(other: vk::PhysicalDeviceMultiviewProperties) -> Self { + PhysicalDeviceMultiviewProperties { + max_multiview_view_count: other.max_multiview_view_count, + max_multiview_instance_index: other.max_multiview_instance_index, + } + } +} + +pub struct PhysicalDeviceProperties { + properties: vk::PhysicalDeviceProperties, + multiview: Option, +} + +impl PhysicalDeviceProperties { + pub fn load(instance: &Instance, device: &vk::PhysicalDevice, extensions: &[vk::ExtensionProperties]) -> Self { + fn supports_extension(extensions: &[vk::ExtensionProperties], extension: &CStr) -> bool { + extensions.iter() + .any(|ep| unsafe { CStr::from_ptr(ep.extension_name.as_ptr()) } == extension) + } + + if let Some(ref get_device_properties) = instance.raw.get_physical_device_properties { + let mut properties2_builder = vk::PhysicalDeviceProperties2KHR::builder() + .properties(vk::PhysicalDeviceProperties::builder().build()); + let mut multiview = None; + + // Add extension infos to the p_next chain + if supports_extension(extensions, *KHR_MULTIVIEW) { + multiview = Some(vk::PhysicalDeviceMultiviewProperties::builder().build()); + + properties2_builder = properties2_builder.push_next(multiview.as_mut().unwrap()); + } + + let mut properties2 = properties2_builder.build(); + unsafe { get_device_properties.get_physical_device_properties2_khr(*device, &mut properties2 as *mut _); } + + Self { + properties: properties2.properties, + multiview: multiview.map(|multiview| multiview.into()), + } + } else { + Self { + properties: unsafe { instance.raw.inner.get_physical_device_properties(*device) }, + multiview: None, + } + } + } +} + pub struct PhysicalDevice { instance: Arc, handle: vk::PhysicalDevice, extensions: Vec, - properties: vk::PhysicalDeviceProperties, + properties: PhysicalDeviceProperties, known_memory_flags: vk::MemoryPropertyFlags, } @@ -805,7 +861,7 @@ impl adapter::PhysicalDevice for PhysicalDevice { mesh_fn, maintenance_level, }), - vendor_id: self.properties.vendor_id, + vendor_id: self.properties.properties.vendor_id, valid_ash_memory_types, }; @@ -948,16 +1004,18 @@ impl adapter::PhysicalDevice for PhysicalDevice { fn features(&self) -> Features { // see https://github.com/gfx-rs/gfx/issues/1930 let is_windows_intel_dual_src_bug = cfg!(windows) - && self.properties.vendor_id == info::intel::VENDOR - && (self.properties.device_id & info::intel::DEVICE_KABY_LAKE_MASK + && self.properties.properties.vendor_id == info::intel::VENDOR + && (self.properties.properties.device_id & info::intel::DEVICE_KABY_LAKE_MASK == info::intel::DEVICE_KABY_LAKE_MASK - || self.properties.device_id & info::intel::DEVICE_SKY_LAKE_MASK + || self.properties.properties.device_id & info::intel::DEVICE_SKY_LAKE_MASK == info::intel::DEVICE_SKY_LAKE_MASK); let mut descriptor_indexing_features = None; + let mut multiview = None; let features = if let Some(ref get_device_properties) = self.instance.get_physical_device_properties { - let features = vk::PhysicalDeviceFeatures::builder().build(); - let mut features2 = vk::PhysicalDeviceFeatures2KHR::builder().features(features).build(); + let mut features2 = vk::PhysicalDeviceFeatures2KHR::builder() + .features(vk::PhysicalDeviceFeatures::builder().build()) + .build(); // Add extension infos to the p_next chain if self.supports_extension(*EXT_DESCRIPTOR_INDEXING) { @@ -967,6 +1025,13 @@ impl adapter::PhysicalDevice for PhysicalDevice { mut_ref.p_next = mem::replace(&mut features2.p_next, mut_ref as *mut _ as *mut _); } + if self.supports_extension(*KHR_MULTIVIEW) { + multiview = Some(vk::PhysicalDeviceMultiviewFeatures::builder().build()); + + let mut_ref = multiview.as_mut().unwrap(); + mut_ref.p_next = mem::replace(&mut features2.p_next, mut_ref as *mut _ as *mut _); + } + unsafe { get_device_properties.get_physical_device_features2_khr(self.handle, &mut features2 as *mut _); } features2.features } else { @@ -1000,6 +1065,18 @@ impl adapter::PhysicalDevice for PhysicalDevice { } } + if let Some(ref multiview) = multiview { + if multiview.multiview != 0 { + bits |= Features::MULTIVIEW; + } + if multiview.multiview_geometry_shader != 0 { + bits |= Features::MULTIVIEW_GEOMETRY_SHADER; + } + if multiview.multiview_tessellation_shader != 0 { + bits |= Features::MULTIVIEW_TESSELLATION_SHADER; + } + } + if features.robust_buffer_access != 0 { bits |= Features::ROBUST_BUFFER_ACCESS; } @@ -1178,7 +1255,7 @@ impl adapter::PhysicalDevice for PhysicalDevice { } fn limits(&self) -> Limits { - let limits = &self.properties.limits; + let limits = &self.properties.properties.limits; let max_group_count = limits.max_compute_work_group_count; let max_group_size = limits.max_compute_work_group_size; @@ -1294,6 +1371,8 @@ impl adapter::PhysicalDevice for PhysicalDevice { max_mesh_multiview_view_count: 0, mesh_output_per_vertex_granularity: 0, mesh_output_per_primitive_granularity: 0, + max_multiview_view_count: self.properties.multiview.as_ref().map(|multiview| multiview.max_multiview_view_count).unwrap_or(0), + max_multiview_instance_index: self.properties.multiview.as_ref().map(|multiview| multiview.max_multiview_instance_index).unwrap_or(0), } } @@ -1323,27 +1402,27 @@ impl adapter::PhysicalDevice for PhysicalDevice { } // vendor id - if vendor_id != self.properties.vendor_id { + if vendor_id != self.properties.properties.vendor_id { warn!( "Vendor ID mismatch. Device: {:?}, cache: {:?}.", - self.properties.vendor_id, vendor_id, + self.properties.properties.vendor_id, vendor_id, ); return false; } // device id - if device_id != self.properties.device_id { + if device_id != self.properties.properties.device_id { warn!( "Device ID mismatch. Device: {:?}, cache: {:?}.", - self.properties.device_id, device_id, + self.properties.properties.device_id, device_id, ); return false; } - if self.properties.pipeline_cache_uuid != cache[16 .. 16 + vk::UUID_SIZE] { + if self.properties.properties.pipeline_cache_uuid != cache[16 .. 16 + vk::UUID_SIZE] { warn!( "Pipeline cache UUID mismatch. Device: {:?}, cache: {:?}.", - self.properties.pipeline_cache_uuid, + self.properties.properties.pipeline_cache_uuid, &cache[16 .. 16 + vk::UUID_SIZE], ); return false; diff --git a/src/hal/src/device.rs b/src/hal/src/device.rs index 07e942cdcbc..33103541217 100644 --- a/src/hal/src/device.rs +++ b/src/hal/src/device.rs @@ -440,6 +440,7 @@ pub trait Device: fmt::Debug + Any + Send + Sync { attachments: IA, subpasses: IS, dependencies: ID, + correlation_masks: Option<&[u32]>, ) -> Result where IA: IntoIterator, diff --git a/src/hal/src/lib.rs b/src/hal/src/lib.rs index 8bcf5478340..cea6740ec5e 100644 --- a/src/hal/src/lib.rs +++ b/src/hal/src/lib.rs @@ -242,21 +242,28 @@ bitflags! { const UNSIZED_DESCRIPTOR_ARRAY = 0x0800_0000_0000_0000; /// Support triangle fan primitive topology. - const TRIANGLE_FAN = 0x0001 << 64; + const TRIANGLE_FAN = 0x0000_0000_0000_0001 << 64; /// Support separate stencil reference values for front and back sides. - const SEPARATE_STENCIL_REF_VALUES = 0x0002 << 64; + const SEPARATE_STENCIL_REF_VALUES = 0x0000_0000_0000_0002 << 64; /// Support manually specified vertex attribute rates (divisors). - const INSTANCE_RATE = 0x0004 << 64; + const INSTANCE_RATE = 0x0000_0000_0000_0004 << 64; /// Support non-zero mipmap bias on samplers. - const SAMPLER_MIP_LOD_BIAS = 0x0008 << 64; + const SAMPLER_MIP_LOD_BIAS = 0x0000_0000_0000_0008 << 64; /// Make the NDC coordinate system pointing Y up, to match D3D and Metal. - const NDC_Y_UP = 0x0001 << 80; + const NDC_Y_UP = 0x0000_0000_0001_0000 << 64; /// Supports task shader stage. - const TASK_SHADER = 0x0001 << 96; + const TASK_SHADER = 0x0000_0001_0000_0000 << 64; /// Supports mesh shader stage. - const MESH_SHADER = 0x0002 << 96; + const MESH_SHADER = 0x0000_0002_0000_0000 << 64; + + /// Supports multiview + const MULTIVIEW = 0x0001_0000_0000_0000 << 64; + /// Supports multiview geometry shader + const MULTIVIEW_GEOMETRY_SHADER = 0x0002_0000_0000_0000 << 64; + /// Supports multiview tessellation shader + const MULTIVIEW_TESSELLATION_SHADER = 0x0004_0000_0000_0000 << 64; } } @@ -458,6 +465,10 @@ pub struct Limits { /// The granularity with which mesh outputs qualified as per-primitive are allocated. The value can be used to /// compute the memory size used by the mesh shader, which must be less than or equal to pub mesh_output_per_primitive_granularity: u32, + /// One greater than the maximum view index that can be used in a subpass. + pub max_multiview_view_count: u32, + /// The maximum valid value of instance index allowed to be generated by a drawing command recorded within a subpass of a multiview render pass instance. + pub max_multiview_instance_index: u32, } /// An enum describing the type of an index value in a slice's index buffer diff --git a/src/hal/src/pass.rs b/src/hal/src/pass.rs index c6f2d9045b6..8da15f8744d 100644 --- a/src/hal/src/pass.rs +++ b/src/hal/src/pass.rs @@ -144,6 +144,8 @@ pub struct SubpassDependency { pub accesses: Range, /// Dependency flags. pub flags: Dependencies, + /// Controls which views in the source subpass the views in the destination subpass depend on. + pub view_offset: i32, } /// Description of a subpass for render pass creation. @@ -167,6 +169,8 @@ pub struct SubpassDesc<'a> { /// Attachments that are not used by the subpass but must be preserved to be /// passed on to subsequent passes. pub preserves: &'a [AttachmentId], + /// A bitfield of view indices describing which views rendering is broadcast to in each subpass, when multiview is enabled. If subpassCount is zero, each view mask is treated as zero. + pub view_mask: u32, } /// A sub-pass borrow of a pass. diff --git a/src/warden/src/gpu.rs b/src/warden/src/gpu.rs index e07b1cf5e69..28af7933fde 100644 --- a/src/warden/src/gpu.rs +++ b/src/warden/src/gpu.rs @@ -655,6 +655,7 @@ impl Scene { inputs: &t.2, preserves: &t.3, resolves: &t.4, + view_mask: 0, }) .collect::>(); let raw_deps = dependencies.iter().map(|dep| hal::pass::SubpassDependency { @@ -662,10 +663,11 @@ impl Scene { stages: dep.stages.clone(), accesses: dep.accesses.clone(), flags: memory::Dependencies::empty(), + view_offset: 0, }); let rp = RenderPass { - handle: unsafe { device.create_render_pass(raw_atts, raw_subs, raw_deps) } + handle: unsafe { device.create_render_pass(raw_atts, raw_subs, raw_deps, None) } .expect("Render pass creation failure"), attachments: attachments .iter() From 99e6df7c52cda40cea3148dccc0bea319ab2b2a9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Hlusi=C4=8Dka?= Date: Sun, 2 Aug 2020 17:39:56 +0200 Subject: [PATCH 2/5] Ensure all backends compile --- src/backend/dx11/src/device.rs | 1 + src/backend/dx12/src/device.rs | 1 + src/backend/metal/src/device.rs | 1 + src/backend/webgpu/src/device.rs | 1 + 4 files changed, 4 insertions(+) diff --git a/src/backend/dx11/src/device.rs b/src/backend/dx11/src/device.rs index 5e90dd4eefb..0e9ab085732 100644 --- a/src/backend/dx11/src/device.rs +++ b/src/backend/dx11/src/device.rs @@ -812,6 +812,7 @@ impl device::Device for Device { attachments: IA, subpasses: IS, _dependencies: ID, + _correlation_masks: Option<&[u32]>, ) -> Result where IA: IntoIterator, diff --git a/src/backend/dx12/src/device.rs b/src/backend/dx12/src/device.rs index d9720d19576..e83e8463fc4 100644 --- a/src/backend/dx12/src/device.rs +++ b/src/backend/dx12/src/device.rs @@ -1249,6 +1249,7 @@ impl d::Device for Device { attachments: IA, subpasses: IS, dependencies: ID, + _correlation_masks: Option<&[u32]>, ) -> Result where IA: IntoIterator, diff --git a/src/backend/metal/src/device.rs b/src/backend/metal/src/device.rs index 84d67a09d8a..dd477e75e6d 100644 --- a/src/backend/metal/src/device.rs +++ b/src/backend/metal/src/device.rs @@ -971,6 +971,7 @@ impl hal::device::Device for Device { attachments: IA, subpasses: IS, _dependencies: ID, + _correlation_masks: Option<&[u32]>, ) -> Result where IA: IntoIterator, diff --git a/src/backend/webgpu/src/device.rs b/src/backend/webgpu/src/device.rs index 083454c7e0c..1163fe099de 100644 --- a/src/backend/webgpu/src/device.rs +++ b/src/backend/webgpu/src/device.rs @@ -56,6 +56,7 @@ impl hal::device::Device for Device { _attachments: IA, _subpasses: IS, _dependencies: ID, + _correlation_masks: Option<&[u32]>, ) -> Result<::RenderPass, OutOfMemory> where IA: IntoIterator, From 2bfbd4d4efaa4bc127e8392f679cf51cfc2e2c43 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Hlusi=C4=8Dka?= Date: Sun, 2 Aug 2020 17:47:40 +0200 Subject: [PATCH 3/5] Apply suggestions from code review Co-authored-by: monocodus[bot] <49363530+monocodus[bot]@users.noreply.github.com> --- src/backend/vulkan/src/device.rs | 62 ++++++++++++++++---------------- 1 file changed, 30 insertions(+), 32 deletions(-) diff --git a/src/backend/vulkan/src/device.rs b/src/backend/vulkan/src/device.rs index 5ee299822d8..fee0adaaca6 100644 --- a/src/backend/vulkan/src/device.rs +++ b/src/backend/vulkan/src/device.rs @@ -693,39 +693,37 @@ impl d::Device for Device { None }; - let view_offsets = if multiview_enabled { - Some( - dependencies - .iter() - .map(|dependency| { - dependency.borrow().view_offset - }) - .collect::>() - ) - } else { - None - }; + let view_offsets = if multiview_enabled { + Some( + dependencies + .iter() + .map(|dependency| dependency.borrow().view_offset) + .collect::>(), + ) + } else { + None + }; - let result = inplace_it::inplace_or_alloc_array(vk_dependencies.len(), |uninit_guard| { - let vk_dependencies = - uninit_guard.init_with_iter(vk_dependencies); - - let mut info_builder = vk::RenderPassCreateInfo::builder() - .flags(vk::RenderPassCreateFlags::empty()) - .attachments(&attachments) - .subpasses(&subpasses) - .dependencies(&vk_dependencies); - let mut multiview; - - if multiview_enabled { - multiview = vk::RenderPassMultiviewCreateInfo::builder() - .view_masks(&view_masks.unwrap()) - .view_offsets(&view_offsets.unwrap()) - .correlation_masks(correlation_masks.unwrap()) - .build(); - - info_builder = info_builder.push_next(&mut multiview); - } + let result = + inplace_it::inplace_or_alloc_array(vk_dependencies.len(), |uninit_guard| { + let vk_dependencies = uninit_guard.init_with_iter(vk_dependencies); + + let mut info_builder = vk::RenderPassCreateInfo::builder() + .flags(vk::RenderPassCreateFlags::empty()) + .attachments(&attachments) + .subpasses(&subpasses) + .dependencies(&vk_dependencies); + let mut multiview; + + if multiview_enabled { + multiview = vk::RenderPassMultiviewCreateInfo::builder() + .view_masks(&view_masks.unwrap()) + .view_offsets(&view_offsets.unwrap()) + .correlation_masks(correlation_masks.unwrap()) + .build(); + + info_builder = info_builder.push_next(&mut multiview); + } self.shared.raw.create_render_pass(&info_builder.build(), None) }); From cdca5ddb2e476ca3fd71cd2abca518e10032761a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Hlusi=C4=8Dka?= Date: Mon, 3 Aug 2020 15:35:38 +0200 Subject: [PATCH 4/5] Apply suggestions --- src/backend/vulkan/src/device.rs | 238 ++++++++++++++++--------------- src/backend/vulkan/src/lib.rs | 40 +++--- src/hal/src/lib.rs | 6 +- 3 files changed, 143 insertions(+), 141 deletions(-) diff --git a/src/backend/vulkan/src/device.rs b/src/backend/vulkan/src/device.rs index fee0adaaca6..fbd1051b1d9 100644 --- a/src/backend/vulkan/src/device.rs +++ b/src/backend/vulkan/src/device.rs @@ -588,110 +588,110 @@ impl d::Device for Device { final_layout: conv::map_image_layout(attachment.layouts.end), } }); + let dependencies = dependencies.into_iter(); - let dependencies = dependencies - .into_iter() - .collect::>(); + let (clear_attachments_mask, result) = inplace_it::inplace_or_alloc_array(dependencies.len(), |uninit_guard_dependencies| { + let dependencies = uninit_guard_dependencies.init_with_iter(dependencies); - let vk_dependencies = dependencies - .iter() - .map(|subpass_dep| { - let sdep = subpass_dep.borrow(); - // TODO: checks - vk::SubpassDependency { - src_subpass: sdep - .passes - .start - .map_or(vk::SUBPASS_EXTERNAL, |id| id as u32), - dst_subpass: sdep.passes.end.map_or(vk::SUBPASS_EXTERNAL, |id| id as u32), - src_stage_mask: conv::map_pipeline_stage(sdep.stages.start), - dst_stage_mask: conv::map_pipeline_stage(sdep.stages.end), - src_access_mask: conv::map_image_access(sdep.accesses.start), - dst_access_mask: conv::map_image_access(sdep.accesses.end), - dependency_flags: mem::transmute(sdep.flags), - } - }); - - let (clear_attachments_mask, result) = inplace_it::inplace_or_alloc_array(attachments.len(), |uninit_guard| { - let attachments = uninit_guard.init_with_iter(attachments); - - let clear_attachments_mask = attachments + let vk_dependencies = dependencies .iter() - .enumerate() - .filter_map(|(i, at)| { - if at.load_op == vk::AttachmentLoadOp::CLEAR - || at.stencil_load_op == vk::AttachmentLoadOp::CLEAR - { - Some(1 << i as u64) - } else { - None + .map(|subpass_dep| { + let sdep = subpass_dep.borrow(); + // TODO: checks + vk::SubpassDependency { + src_subpass: sdep + .passes + .start + .map_or(vk::SUBPASS_EXTERNAL, |id| id as u32), + dst_subpass: sdep.passes.end.map_or(vk::SUBPASS_EXTERNAL, |id| id as u32), + src_stage_mask: conv::map_pipeline_stage(sdep.stages.start), + dst_stage_mask: conv::map_pipeline_stage(sdep.stages.end), + src_access_mask: conv::map_image_access(sdep.accesses.start), + dst_access_mask: conv::map_image_access(sdep.accesses.end), + dependency_flags: mem::transmute(sdep.flags), } - }) - .sum(); + }); - let attachment_refs = subpasses - .into_iter() - .map(|subpass| { - let subpass = subpass.borrow(); - fn make_ref(&(id, layout): &pass::AttachmentRef) -> vk::AttachmentReference { - vk::AttachmentReference { - attachment: id as _, - layout: conv::map_image_layout(layout), - } - } - let colors = subpass.colors.iter().map(make_ref).collect::>(); - let depth_stencil = subpass.depth_stencil.map(make_ref); - let inputs = subpass.inputs.iter().map(make_ref).collect::>(); - let preserves = subpass - .preserves - .iter() - .map(|&id| id as u32) - .collect::>(); - let resolves = subpass.resolves.iter().map(make_ref).collect::>(); - - (colors, depth_stencil, inputs, preserves, resolves, subpass.view_mask) - }) - .collect::>(); + let (clear_attachments_mask, result) = inplace_it::inplace_or_alloc_array(attachments.len(), |uninit_guard_attachments| { + let attachments = uninit_guard_attachments.init_with_iter(attachments); - let subpasses = attachment_refs - .iter() - .map(|(colors, depth_stencil, inputs, preserves, resolves, _view_mask)| { - vk::SubpassDescription { - flags: vk::SubpassDescriptionFlags::empty(), - pipeline_bind_point: vk::PipelineBindPoint::GRAPHICS, - input_attachment_count: inputs.len() as u32, - p_input_attachments: inputs.as_ptr(), - color_attachment_count: colors.len() as u32, - p_color_attachments: colors.as_ptr(), - p_resolve_attachments: if resolves.is_empty() { - ptr::null() + let clear_attachments_mask = attachments + .iter() + .enumerate() + .filter_map(|(i, at)| { + if at.load_op == vk::AttachmentLoadOp::CLEAR + || at.stencil_load_op == vk::AttachmentLoadOp::CLEAR + { + Some(1 << i as u64) } else { - resolves.as_ptr() - }, - p_depth_stencil_attachment: match depth_stencil { - Some(ref aref) => aref as *const _, - None => ptr::null(), - }, - preserve_attachment_count: preserves.len() as u32, - p_preserve_attachments: preserves.as_ptr(), - } - }) - .collect::>(); - - let multiview_enabled = self.shared.features.contains(Features::MULTIVIEW); - - let view_masks = if multiview_enabled { - Some( - attachment_refs - .iter() - .map(|(_colors, _depth_stencil, _inputs, _preserves, _resolves, view_mask)| { - *view_mask - }) - .collect::>() - ) - } else { - None - }; + None + } + }) + .sum(); + + let attachment_refs = subpasses + .into_iter() + .map(|subpass| { + let subpass = subpass.borrow(); + fn make_ref(&(id, layout): &pass::AttachmentRef) -> vk::AttachmentReference { + vk::AttachmentReference { + attachment: id as _, + layout: conv::map_image_layout(layout), + } + } + let colors = subpass.colors.iter().map(make_ref).collect::>(); + let depth_stencil = subpass.depth_stencil.map(make_ref); + let inputs = subpass.inputs.iter().map(make_ref).collect::>(); + let preserves = subpass + .preserves + .iter() + .map(|&id| id as u32) + .collect::>(); + let resolves = subpass.resolves.iter().map(make_ref).collect::>(); + + (colors, depth_stencil, inputs, preserves, resolves, subpass.view_mask) + }) + .collect::>(); + + let subpasses = attachment_refs + .iter() + .map(|(colors, depth_stencil, inputs, preserves, resolves, _view_mask)| { + vk::SubpassDescription { + flags: vk::SubpassDescriptionFlags::empty(), + pipeline_bind_point: vk::PipelineBindPoint::GRAPHICS, + input_attachment_count: inputs.len() as u32, + p_input_attachments: inputs.as_ptr(), + color_attachment_count: colors.len() as u32, + p_color_attachments: colors.as_ptr(), + p_resolve_attachments: if resolves.is_empty() { + ptr::null() + } else { + resolves.as_ptr() + }, + p_depth_stencil_attachment: match depth_stencil { + Some(ref aref) => aref as *const _, + None => ptr::null(), + }, + preserve_attachment_count: preserves.len() as u32, + p_preserve_attachments: preserves.as_ptr(), + } + }) + .collect::>(); + + let multiview_enabled = self.shared.features.contains(Features::MULTIVIEW); + + let view_masks = if multiview_enabled { + Some( + attachment_refs + .iter() + .map(|(_colors, _depth_stencil, _inputs, _preserves, _resolves, view_mask)| { + *view_mask + }) + .collect::>() + ) + } else { + None + }; let view_offsets = if multiview_enabled { Some( @@ -704,28 +704,30 @@ impl d::Device for Device { None }; - let result = - inplace_it::inplace_or_alloc_array(vk_dependencies.len(), |uninit_guard| { - let vk_dependencies = uninit_guard.init_with_iter(vk_dependencies); - - let mut info_builder = vk::RenderPassCreateInfo::builder() - .flags(vk::RenderPassCreateFlags::empty()) - .attachments(&attachments) - .subpasses(&subpasses) - .dependencies(&vk_dependencies); - let mut multiview; - - if multiview_enabled { - multiview = vk::RenderPassMultiviewCreateInfo::builder() - .view_masks(&view_masks.unwrap()) - .view_offsets(&view_offsets.unwrap()) - .correlation_masks(correlation_masks.unwrap()) - .build(); - - info_builder = info_builder.push_next(&mut multiview); - } + let result = inplace_it::inplace_or_alloc_array(vk_dependencies.len(), |uninit_guard| { + let vk_dependencies = uninit_guard.init_with_iter(vk_dependencies); + + let mut info_builder = vk::RenderPassCreateInfo::builder() + .flags(vk::RenderPassCreateFlags::empty()) + .attachments(&attachments) + .subpasses(&subpasses) + .dependencies(&vk_dependencies); + let mut multiview; + + if multiview_enabled { + multiview = vk::RenderPassMultiviewCreateInfo::builder() + .view_masks(&view_masks.unwrap()) + .view_offsets(&view_offsets.unwrap()) + .correlation_masks(correlation_masks.unwrap()) + .build(); + + info_builder = info_builder.push_next(&mut multiview); + } + + self.shared.raw.create_render_pass(&info_builder.build(), None) + }); - self.shared.raw.create_render_pass(&info_builder.build(), None) + (clear_attachments_mask, result) }); (clear_attachments_mask, result) diff --git a/src/backend/vulkan/src/lib.rs b/src/backend/vulkan/src/lib.rs index fb1e5f77f32..891038f91cd 100644 --- a/src/backend/vulkan/src/lib.rs +++ b/src/backend/vulkan/src/lib.rs @@ -505,14 +505,14 @@ impl hal::Instance for Instance { let properties = PhysicalDeviceProperties::load(&self, &device, &extensions); let info = adapter::AdapterInfo { name: unsafe { - CStr::from_ptr(properties.properties.device_name.as_ptr()) + CStr::from_ptr(properties.inner.device_name.as_ptr()) .to_str() .unwrap_or("Unknown") .to_owned() }, - vendor: properties.properties.vendor_id as usize, - device: properties.properties.device_id as usize, - device_type: match properties.properties.device_type { + vendor: properties.inner.vendor_id as usize, + device: properties.inner.device_id as usize, + device_type: match properties.inner.device_type { ash::vk::PhysicalDeviceType::OTHER => adapter::DeviceType::Other, ash::vk::PhysicalDeviceType::INTEGRATED_GPU => { adapter::DeviceType::IntegratedGpu @@ -667,7 +667,7 @@ impl From for PhysicalDeviceMultiviewProp } pub struct PhysicalDeviceProperties { - properties: vk::PhysicalDeviceProperties, + inner: vk::PhysicalDeviceProperties, multiview: Option, } @@ -694,12 +694,12 @@ impl PhysicalDeviceProperties { unsafe { get_device_properties.get_physical_device_properties2_khr(*device, &mut properties2 as *mut _); } Self { - properties: properties2.properties, - multiview: multiview.map(|multiview| multiview.into()), + inner: properties2.properties, + multiview: multiview.map(PhysicalDeviceMultiviewProperties::from), } } else { Self { - properties: unsafe { instance.raw.inner.get_physical_device_properties(*device) }, + inner: unsafe { instance.raw.inner.get_physical_device_properties(*device) }, multiview: None, } } @@ -861,7 +861,7 @@ impl adapter::PhysicalDevice for PhysicalDevice { mesh_fn, maintenance_level, }), - vendor_id: self.properties.properties.vendor_id, + vendor_id: self.properties.inner.vendor_id, valid_ash_memory_types, }; @@ -1004,10 +1004,10 @@ impl adapter::PhysicalDevice for PhysicalDevice { fn features(&self) -> Features { // see https://github.com/gfx-rs/gfx/issues/1930 let is_windows_intel_dual_src_bug = cfg!(windows) - && self.properties.properties.vendor_id == info::intel::VENDOR - && (self.properties.properties.device_id & info::intel::DEVICE_KABY_LAKE_MASK + && self.properties.inner.vendor_id == info::intel::VENDOR + && (self.properties.inner.device_id & info::intel::DEVICE_KABY_LAKE_MASK == info::intel::DEVICE_KABY_LAKE_MASK - || self.properties.properties.device_id & info::intel::DEVICE_SKY_LAKE_MASK + || self.properties.inner.device_id & info::intel::DEVICE_SKY_LAKE_MASK == info::intel::DEVICE_SKY_LAKE_MASK); let mut descriptor_indexing_features = None; @@ -1025,7 +1025,7 @@ impl adapter::PhysicalDevice for PhysicalDevice { mut_ref.p_next = mem::replace(&mut features2.p_next, mut_ref as *mut _ as *mut _); } - if self.supports_extension(*KHR_MULTIVIEW) { + if self.properties.multiview.is_some() { multiview = Some(vk::PhysicalDeviceMultiviewFeatures::builder().build()); let mut_ref = multiview.as_mut().unwrap(); @@ -1255,7 +1255,7 @@ impl adapter::PhysicalDevice for PhysicalDevice { } fn limits(&self) -> Limits { - let limits = &self.properties.properties.limits; + let limits = &self.properties.inner.limits; let max_group_count = limits.max_compute_work_group_count; let max_group_size = limits.max_compute_work_group_size; @@ -1402,27 +1402,27 @@ impl adapter::PhysicalDevice for PhysicalDevice { } // vendor id - if vendor_id != self.properties.properties.vendor_id { + if vendor_id != self.properties.inner.vendor_id { warn!( "Vendor ID mismatch. Device: {:?}, cache: {:?}.", - self.properties.properties.vendor_id, vendor_id, + self.properties.inner.vendor_id, vendor_id, ); return false; } // device id - if device_id != self.properties.properties.device_id { + if device_id != self.properties.inner.device_id { warn!( "Device ID mismatch. Device: {:?}, cache: {:?}.", - self.properties.properties.device_id, device_id, + self.properties.inner.device_id, device_id, ); return false; } - if self.properties.properties.pipeline_cache_uuid != cache[16 .. 16 + vk::UUID_SIZE] { + if self.properties.inner.pipeline_cache_uuid != cache[16 .. 16 + vk::UUID_SIZE] { warn!( "Pipeline cache UUID mismatch. Device: {:?}, cache: {:?}.", - self.properties.properties.pipeline_cache_uuid, + self.properties.inner.pipeline_cache_uuid, &cache[16 .. 16 + vk::UUID_SIZE], ); return false; diff --git a/src/hal/src/lib.rs b/src/hal/src/lib.rs index cea6740ec5e..f8e08554f56 100644 --- a/src/hal/src/lib.rs +++ b/src/hal/src/lib.rs @@ -259,11 +259,11 @@ bitflags! { const MESH_SHADER = 0x0000_0002_0000_0000 << 64; /// Supports multiview - const MULTIVIEW = 0x0001_0000_0000_0000 << 64; + const MULTIVIEW = 0x0000_0004_0000_0000 << 64; /// Supports multiview geometry shader - const MULTIVIEW_GEOMETRY_SHADER = 0x0002_0000_0000_0000 << 64; + const MULTIVIEW_GEOMETRY_SHADER = 0x0000_0008_0000_0000 << 64; /// Supports multiview tessellation shader - const MULTIVIEW_TESSELLATION_SHADER = 0x0004_0000_0000_0000 << 64; + const MULTIVIEW_TESSELLATION_SHADER = 0x0000_0010_0000_0000 << 64; } } From ce44c9f230e05dac0754e687f9c97838c64aeea3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Hlusi=C4=8Dka?= Date: Mon, 3 Aug 2020 18:37:59 +0200 Subject: [PATCH 5/5] WIP: Begin work on a Multiview example --- examples/Cargo.toml | 4 + examples/quad-multiview/README.md | 5 + examples/quad-multiview/data/index.html | 17 + examples/quad-multiview/data/logo.png | Bin 0 -> 24899 bytes examples/quad-multiview/data/quad.frag | 17 + examples/quad-multiview/data/quad.vert | 18 + examples/quad-multiview/main.rs | 1078 +++++++++++++++++++++++ examples/quad-multiview/screenshot.png | Bin 0 -> 38794 bytes src/backend/vulkan/src/device.rs | 2 +- 9 files changed, 1140 insertions(+), 1 deletion(-) create mode 100644 examples/quad-multiview/README.md create mode 100644 examples/quad-multiview/data/index.html create mode 100644 examples/quad-multiview/data/logo.png create mode 100644 examples/quad-multiview/data/quad.frag create mode 100644 examples/quad-multiview/data/quad.vert create mode 100644 examples/quad-multiview/main.rs create mode 100644 examples/quad-multiview/screenshot.png diff --git a/examples/Cargo.toml b/examples/Cargo.toml index 12460131825..7cb22041b93 100644 --- a/examples/Cargo.toml +++ b/examples/Cargo.toml @@ -22,6 +22,10 @@ path = "colour-uniform/main.rs" name = "quad" path = "quad/main.rs" +[[bin]] +name = "quad-multiview" +path = "quad-multiview/main.rs" + [[bin]] name = "compute" path = "compute/main.rs" diff --git a/examples/quad-multiview/README.md b/examples/quad-multiview/README.md new file mode 100644 index 00000000000..621baf7760e --- /dev/null +++ b/examples/quad-multiview/README.md @@ -0,0 +1,5 @@ +# Quad + +The following image is an output of `cargo run --bin quad --features=gl`: + +![screenshot](screenshot.png "Quad") diff --git a/examples/quad-multiview/data/index.html b/examples/quad-multiview/data/index.html new file mode 100644 index 00000000000..e2b6395bae5 --- /dev/null +++ b/examples/quad-multiview/data/index.html @@ -0,0 +1,17 @@ + + + + + + + + + diff --git a/examples/quad-multiview/data/logo.png b/examples/quad-multiview/data/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..e0cc9047bc3bd743adbdf53d392fcd38d4bdcd7a GIT binary patch literal 24899 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 match event { + winit::event::WindowEvent::CloseRequested => { + *control_flow = winit::event_loop::ControlFlow::Exit + } + winit::event::WindowEvent::KeyboardInput { + input: + winit::event::KeyboardInput { + virtual_keycode: Some(winit::event::VirtualKeyCode::Escape), + .. + }, + .. + } => *control_flow = winit::event_loop::ControlFlow::Exit, + winit::event::WindowEvent::Resized(dims) => { + let window_index = renderer.get_window_index(window_id); + renderer.dimensions[window_index] = window::Extent2D { + width: dims.width, + height: dims.height, + }; + renderer.recreate_swapchains(); + } + _ => {} + }, + winit::event::Event::RedrawEventsCleared => { + renderer.render(); + } + _ => {} + } + }); +} + +struct Renderer { + instance: Option, + device: B::Device, + queue_group: QueueGroup, + desc_pool: ManuallyDrop, + surfaces: ManuallyDrop<[B::Surface; 2]>, + adapter: hal::adapter::Adapter, + windows: [winit::window::Window; 2], + format: [hal::format::Format; 2], + dimensions: [window::Extent2D; 2], + viewports: [pso::Viewport; 2], + render_pass: ManuallyDrop, + pipeline: ManuallyDrop, + pipeline_layout: ManuallyDrop, + desc_set: B::DescriptorSet, + set_layout: ManuallyDrop, + submission_complete_semaphores: Vec, + submission_complete_fences: Vec, + cmd_pools: Vec, + cmd_buffers: Vec, + vertex_buffer: ManuallyDrop, + image_upload_buffer: ManuallyDrop, + image_logo: ManuallyDrop, + image_srv: ManuallyDrop, + buffer_memory: ManuallyDrop, + image_memory: ManuallyDrop, + image_upload_memory: ManuallyDrop, + sampler: ManuallyDrop, + frames_in_flight: usize, + frame: u64, +} + +impl Renderer +where + B: hal::Backend, +{ + fn new( + instance: Option, + windows: [winit::window::Window; 2], + mut surfaces: [B::Surface; 2], + adapter: hal::adapter::Adapter, + ) -> Renderer { + let memory_types = adapter.physical_device.memory_properties().memory_types; + let limits = adapter.physical_device.limits(); + + // Build a new device and associated command queues + let family = adapter + .queue_families + .iter() + .find(|family| { + surfaces[0].supports_queue_family(family) + && surfaces[1].supports_queue_family(family) + && family.queue_type().supports_graphics() + }) + .unwrap(); + dbg!(adapter.physical_device.features()); + let mut gpu = unsafe { + adapter + .physical_device + .open(&[(family, &[1.0])], hal::Features::empty() | hal::Features::MULTIVIEW) + .unwrap() + }; + + let mut queue_group = gpu.queue_groups.pop().unwrap(); + let device = gpu.device; + + let mut command_pool = unsafe { + device.create_command_pool(queue_group.family, pool::CommandPoolCreateFlags::empty()) + } + .expect("Can't create command pool"); + + // Setup renderpass and pipeline + let set_layout = ManuallyDrop::new( + unsafe { + device.create_descriptor_set_layout( + &[ + pso::DescriptorSetLayoutBinding { + binding: 0, + ty: pso::DescriptorType::Image { + ty: pso::ImageDescriptorType::Sampled { + with_sampler: false, + }, + }, + count: 1, + stage_flags: ShaderStageFlags::FRAGMENT, + immutable_samplers: false, + }, + pso::DescriptorSetLayoutBinding { + binding: 1, + ty: pso::DescriptorType::Sampler, + count: 1, + stage_flags: ShaderStageFlags::FRAGMENT, + immutable_samplers: false, + }, + ], + &[], + ) + } + .expect("Can't create descriptor set layout"), + ); + + // Descriptors + let mut desc_pool = ManuallyDrop::new( + unsafe { + device.create_descriptor_pool( + 1, // sets + &[ + pso::DescriptorRangeDesc { + ty: pso::DescriptorType::Image { + ty: pso::ImageDescriptorType::Sampled { + with_sampler: false, + }, + }, + count: 1, + }, + pso::DescriptorRangeDesc { + ty: pso::DescriptorType::Sampler, + count: 1, + }, + ], + pso::DescriptorPoolCreateFlags::empty(), + ) + } + .expect("Can't create descriptor pool"), + ); + let desc_set = unsafe { desc_pool.allocate_set(&set_layout) }.unwrap(); + + // Buffer allocations + println!("Memory types: {:?}", memory_types); + let non_coherent_alignment = limits.non_coherent_atom_size as u64; + + let buffer_stride = mem::size_of::() as u64; + let buffer_len = QUAD.len() as u64 * buffer_stride; + assert_ne!(buffer_len, 0); + let padded_buffer_len = ((buffer_len + non_coherent_alignment - 1) + / non_coherent_alignment) + * non_coherent_alignment; + + let mut vertex_buffer = ManuallyDrop::new( + unsafe { device.create_buffer(padded_buffer_len, buffer::Usage::VERTEX) }.unwrap(), + ); + + let buffer_req = unsafe { device.get_buffer_requirements(&vertex_buffer) }; + + let upload_type = memory_types + .iter() + .enumerate() + .position(|(id, mem_type)| { + // type_mask is a bit field where each bit represents a memory type. If the bit is set + // to 1 it means we can use that type for our buffer. So this code finds the first + // memory type that has a `1` (or, is allowed), and is visible to the CPU. + buffer_req.type_mask & (1 << id) != 0 + && mem_type.properties.contains(m::Properties::CPU_VISIBLE) + }) + .unwrap() + .into(); + + // TODO: check transitions: read/write mapping and vertex buffer read + let buffer_memory = unsafe { + let memory = device + .allocate_memory(upload_type, buffer_req.size) + .unwrap(); + device + .bind_buffer_memory(&memory, 0, &mut vertex_buffer) + .unwrap(); + let mapping = device.map_memory(&memory, m::Segment::ALL).unwrap(); + ptr::copy_nonoverlapping(QUAD.as_ptr() as *const u8, mapping, buffer_len as usize); + device + .flush_mapped_memory_ranges(iter::once((&memory, m::Segment::ALL))) + .unwrap(); + device.unmap_memory(&memory); + ManuallyDrop::new(memory) + }; + + // Image + let img_data = include_bytes!("data/logo.png"); + + let img = image::load(Cursor::new(&img_data[..]), image::PNG) + .unwrap() + .to_rgba(); + let (width, height) = img.dimensions(); + let kind = i::Kind::D2(width as i::Size, height as i::Size, 1, 1); + let row_alignment_mask = limits.optimal_buffer_copy_pitch_alignment as u32 - 1; + let image_stride = 4usize; + let row_pitch = (width * image_stride as u32 + row_alignment_mask) & !row_alignment_mask; + let upload_size = (height * row_pitch) as u64; + let padded_upload_size = ((upload_size + non_coherent_alignment - 1) + / non_coherent_alignment) + * non_coherent_alignment; + + let mut image_upload_buffer = ManuallyDrop::new( + unsafe { device.create_buffer(padded_upload_size, buffer::Usage::TRANSFER_SRC) } + .unwrap(), + ); + let image_mem_reqs = unsafe { device.get_buffer_requirements(&image_upload_buffer) }; + + // copy image data into staging buffer + let image_upload_memory = unsafe { + let memory = device + .allocate_memory(upload_type, image_mem_reqs.size) + .unwrap(); + device + .bind_buffer_memory(&memory, 0, &mut image_upload_buffer) + .unwrap(); + let mapping = device.map_memory(&memory, m::Segment::ALL).unwrap(); + for y in 0 .. height as usize { + let row = &(*img)[y * (width as usize) * image_stride + .. (y + 1) * (width as usize) * image_stride]; + ptr::copy_nonoverlapping( + row.as_ptr(), + mapping.offset(y as isize * row_pitch as isize), + width as usize * image_stride, + ); + } + device + .flush_mapped_memory_ranges(iter::once((&memory, m::Segment::ALL))) + .unwrap(); + device.unmap_memory(&memory); + ManuallyDrop::new(memory) + }; + + let mut image_logo = ManuallyDrop::new( + unsafe { + device.create_image( + kind, + 1, + ColorFormat::SELF, + i::Tiling::Optimal, + i::Usage::TRANSFER_DST | i::Usage::SAMPLED, + i::ViewCapabilities::empty(), + ) + } + .unwrap(), + ); + let image_req = unsafe { device.get_image_requirements(&image_logo) }; + + let device_type = memory_types + .iter() + .enumerate() + .position(|(id, memory_type)| { + image_req.type_mask & (1 << id) != 0 + && memory_type.properties.contains(m::Properties::DEVICE_LOCAL) + }) + .unwrap() + .into(); + let image_memory = ManuallyDrop::new( + unsafe { device.allocate_memory(device_type, image_req.size) }.unwrap(), + ); + + unsafe { device.bind_image_memory(&image_memory, 0, &mut image_logo) }.unwrap(); + let image_srv = ManuallyDrop::new( + unsafe { + device.create_image_view( + &image_logo, + i::ViewKind::D2, + ColorFormat::SELF, + Swizzle::NO, + COLOR_RANGE.clone(), + ) + } + .unwrap(), + ); + + let sampler = ManuallyDrop::new( + unsafe { + device.create_sampler(&i::SamplerDesc::new(i::Filter::Linear, i::WrapMode::Clamp)) + } + .expect("Can't create sampler"), + ); + + unsafe { + device.write_descriptor_sets(vec![ + pso::DescriptorSetWrite { + set: &desc_set, + binding: 0, + array_offset: 0, + descriptors: Some(pso::Descriptor::Image( + &*image_srv, + i::Layout::ShaderReadOnlyOptimal, + )), + }, + pso::DescriptorSetWrite { + set: &desc_set, + binding: 1, + array_offset: 0, + descriptors: Some(pso::Descriptor::Sampler(&*sampler)), + }, + ]); + } + + // copy buffer to texture + let mut copy_fence = device.create_fence(false).expect("Could not create fence"); + unsafe { + let mut cmd_buffer = command_pool.allocate_one(command::Level::Primary); + cmd_buffer.begin_primary(command::CommandBufferFlags::ONE_TIME_SUBMIT); + + let image_barrier = m::Barrier::Image { + states: (i::Access::empty(), i::Layout::Undefined) + .. (i::Access::TRANSFER_WRITE, i::Layout::TransferDstOptimal), + target: &*image_logo, + families: None, + range: COLOR_RANGE.clone(), + }; + + cmd_buffer.pipeline_barrier( + PipelineStage::TOP_OF_PIPE .. PipelineStage::TRANSFER, + m::Dependencies::empty(), + &[image_barrier], + ); + + cmd_buffer.copy_buffer_to_image( + &image_upload_buffer, + &image_logo, + i::Layout::TransferDstOptimal, + &[command::BufferImageCopy { + buffer_offset: 0, + buffer_width: row_pitch / (image_stride as u32), + buffer_height: height as u32, + image_layers: i::SubresourceLayers { + aspects: f::Aspects::COLOR, + level: 0, + layers: 0 .. 1, + }, + image_offset: i::Offset { x: 0, y: 0, z: 0 }, + image_extent: i::Extent { + width, + height, + depth: 1, + }, + }], + ); + + let image_barrier = m::Barrier::Image { + states: (i::Access::TRANSFER_WRITE, i::Layout::TransferDstOptimal) + .. (i::Access::SHADER_READ, i::Layout::ShaderReadOnlyOptimal), + target: &*image_logo, + families: None, + range: COLOR_RANGE.clone(), + }; + cmd_buffer.pipeline_barrier( + PipelineStage::TRANSFER .. PipelineStage::FRAGMENT_SHADER, + m::Dependencies::empty(), + &[image_barrier], + ); + + cmd_buffer.finish(); + + queue_group.queues[0] + .submit_without_semaphores(Some(&cmd_buffer), Some(&mut copy_fence)); + + device + .wait_for_fence(©_fence, !0) + .expect("Can't wait for fence"); + } + + unsafe { + device.destroy_fence(copy_fence); + } + + let caps = [ + surfaces[0].capabilities(&adapter.physical_device), + surfaces[1].capabilities(&adapter.physical_device), + ]; + let formats = [ + surfaces[0].supported_formats(&adapter.physical_device), + surfaces[1].supported_formats(&adapter.physical_device), + ]; + println!("formats: {:?}", formats); + let mut format = (0..2).map(|i| { + Some(formats[i].clone().map_or(f::Format::Rgba8Srgb, |formats| { + formats + .iter() + .find(|format| format.base_format().1 == ChannelType::Srgb) + .map(|format| *format) + .unwrap_or(formats[0]) + })) + }).collect::>(); + let format = [format[0].take().unwrap(), format[1].take().unwrap()]; + + let swap_configs = [ + window::SwapchainConfig::from_caps(&caps[0], format[0], DIMS), + window::SwapchainConfig::from_caps(&caps[1], format[1], DIMS), + ]; + println!("{:?}", swap_configs); + let extents = [ + swap_configs[0].extent, + swap_configs[1].extent, + ]; + for i in 0..2 { + unsafe { + surfaces[i] + .configure_swapchain(&device, swap_configs[i].clone()) + .expect("Can't configure swapchain"); + } + } + + let render_pass = { + let attachments = [ + pass::Attachment { + format: Some(format[0]), + samples: 1, + ops: pass::AttachmentOps::new( + pass::AttachmentLoadOp::Clear, + pass::AttachmentStoreOp::Store, + ), + stencil_ops: pass::AttachmentOps::DONT_CARE, + layouts: i::Layout::Undefined .. i::Layout::Present, + }, + pass::Attachment { + format: Some(format[1]), + samples: 1, + ops: pass::AttachmentOps::new( + pass::AttachmentLoadOp::Clear, + pass::AttachmentStoreOp::Store, + ), + stencil_ops: pass::AttachmentOps::DONT_CARE, + layouts: i::Layout::Undefined .. i::Layout::Present, + }, + ]; + + let subpass = pass::SubpassDesc { + colors: &[(0, i::Layout::ColorAttachmentOptimal)], + depth_stencil: None, + inputs: &[], + resolves: &[], + preserves: &[], + view_mask: 0b11, + }; + + ManuallyDrop::new( + unsafe { device.create_render_pass(&attachments, &[subpass], &[], Some(&[0b11])) } + .expect("Can't create render pass"), + ) + }; + + // Define maximum number of frames we want to be able to be "in flight" (being computed + // simultaneously) at once + let frames_in_flight = 3; + + // The number of the rest of the resources is based on the frames in flight. + let mut submission_complete_semaphores = Vec::with_capacity(frames_in_flight * 2); + let mut submission_complete_fences = Vec::with_capacity(frames_in_flight * 2); + // Note: We don't really need a different command pool per frame in such a simple demo like this, + // but in a more 'real' application, it's generally seen as optimal to have one command pool per + // thread per frame. There is a flag that lets a command pool reset individual command buffers + // which are created from it, but by default the whole pool (and therefore all buffers in it) + // must be reset at once. Furthermore, it is often the case that resetting a whole pool is actually + // faster and more efficient for the hardware than resetting individual command buffers, so it's + // usually best to just make a command pool for each set of buffers which need to be reset at the + // same time (each frame). In our case, each pool will only have one command buffer created from it, + // though. + let mut cmd_pools = Vec::with_capacity(frames_in_flight * 2); + let mut cmd_buffers = Vec::with_capacity(frames_in_flight * 2); + + cmd_pools.push(command_pool); + for _ in 1 .. frames_in_flight * 2 { + unsafe { + cmd_pools.push( + device + .create_command_pool( + queue_group.family, + pool::CommandPoolCreateFlags::empty(), + ) + .expect("Can't create command pool"), + ); + } + } + + for i in 0 .. frames_in_flight * 2 { + submission_complete_semaphores.push( + device + .create_semaphore() + .expect("Could not create semaphore"), + ); + submission_complete_fences + .push(device.create_fence(true).expect("Could not create fence")); + cmd_buffers.push(unsafe { cmd_pools[i].allocate_one(command::Level::Primary) }); + } + + let pipeline_layout = ManuallyDrop::new( + unsafe { + device.create_pipeline_layout( + iter::once(&*set_layout), + &[(pso::ShaderStageFlags::VERTEX, 0 .. 8)], + ) + } + .expect("Can't create pipeline layout"), + ); + let pipeline = { + let vs_module = { + let glsl = std::fs::read_to_string("quad-multiview/data/quad.vert").unwrap(); + let file = + glsl_to_spirv::compile(&glsl, glsl_to_spirv::ShaderType::Vertex).unwrap(); + let spirv = auxil::read_spirv(file).unwrap(); + unsafe { device.create_shader_module(&spirv) }.unwrap() + }; + let fs_module = { + let glsl = std::fs::read_to_string("quad-multiview/data/quad.frag").unwrap(); + let file = + glsl_to_spirv::compile(&glsl, glsl_to_spirv::ShaderType::Fragment).unwrap(); + let spirv = auxil::read_spirv(file).unwrap(); + unsafe { device.create_shader_module(&spirv) }.unwrap() + }; + + let pipeline = { + let (vs_entry, fs_entry) = ( + pso::EntryPoint { + entry: ENTRY_NAME, + module: &vs_module, + specialization: hal::spec_const_list![0.8f32], + }, + pso::EntryPoint { + entry: ENTRY_NAME, + module: &fs_module, + specialization: pso::Specialization::default(), + }, + ); + + let subpass = Subpass { + index: 0, + main_pass: &*render_pass, + }; + + let vertex_buffers = vec![ + pso::VertexBufferDesc { + binding: 0, + stride: mem::size_of::() as u32, + rate: VertexInputRate::Vertex, + }, + ]; + + let attributes = vec![ + pso::AttributeDesc { + location: 0, + binding: 0, + element: pso::Element { + format: f::Format::Rg32Sfloat, + offset: 0, + }, + }, + pso::AttributeDesc { + location: 1, + binding: 0, + element: pso::Element { + format: f::Format::Rg32Sfloat, + offset: 8, + }, + }, + ]; + + let mut pipeline_desc = pso::GraphicsPipelineDesc::new( + pso::PrimitiveAssembler::Vertex { + buffers: &vertex_buffers, + attributes: &attributes, + input_assembler: pso::InputAssemblerDesc { + primitive: pso::Primitive::TriangleList, + with_adjacency: false, + restart_index: None, + }, + vertex: vs_entry, + geometry: None, + tessellation: None, + }, + pso::Rasterizer::FILL, + Some(fs_entry), + &*pipeline_layout, + subpass, + ); + + pipeline_desc.blender.targets.push(pso::ColorBlendDesc { + mask: pso::ColorMask::ALL, + blend: Some(pso::BlendState::ALPHA), + }); + + unsafe { device.create_graphics_pipeline(&pipeline_desc, None) } + }; + + unsafe { + device.destroy_shader_module(vs_module); + } + unsafe { + device.destroy_shader_module(fs_module); + } + + ManuallyDrop::new(pipeline.unwrap()) + }; + + // Rendering setup + let viewports = [ + pso::Viewport { + rect: pso::Rect { + x: 0, + y: 0, + w: extents[0].width as _, + h: extents[0].height as _, + }, + depth: 0.0 .. 1.0, + }, + pso::Viewport { + rect: pso::Rect { + x: 0, + y: 0, + w: extents[1].width as _, + h: extents[1].height as _, + }, + depth: 0.0 .. 1.0, + }, + ]; + + Renderer { + instance, + device, + queue_group, + desc_pool, + windows, + surfaces: ManuallyDrop::new(surfaces), + adapter, + format, + dimensions: [DIMS, DIMS], + viewports, + render_pass, + pipeline, + pipeline_layout, + desc_set, + set_layout, + submission_complete_semaphores, + submission_complete_fences, + cmd_pools, + cmd_buffers, + vertex_buffer, + image_upload_buffer, + image_logo, + image_srv, + buffer_memory, + image_memory, + image_upload_memory, + sampler, + frames_in_flight, + frame: 0, + } + } + + fn get_window_index(&self, window_id: winit::window::WindowId) -> usize { + if window_id == self.windows[0].id() { 0 } else { 1 } + } + + fn recreate_swapchains(&mut self) { + for i in 0..2 { + let caps = self.surfaces[i].capabilities(&self.adapter.physical_device); + let swap_config = window::SwapchainConfig::from_caps(&caps, self.format[i], self.dimensions[i]); + println!("{:?}", swap_config); + let extent = swap_config.extent.to_extent(); + + unsafe { + self.surfaces[i] + .configure_swapchain(&self.device, swap_config) + .expect("Can't create swapchain"); + } + + self.viewports[i].rect.w = extent.width as _; + self.viewports[i].rect.h = extent.height as _; + } + } + + fn render(&mut self) { + let surface_images = [ + unsafe { + match self.surfaces[0].acquire_image(!0) { + Ok((image, _)) => image, + Err(_) => { + self.recreate_swapchains(); + return; + } + } + }, + unsafe { + match self.surfaces[1].acquire_image(!0) { + Ok((image, _)) => image, + Err(_) => { + self.recreate_swapchains(); + return; + } + } + }, + ]; + + let framebuffer = unsafe { + self.device + .create_framebuffer( + &self.render_pass, + vec![surface_images[0].borrow(), surface_images[1].borrow()], + i::Extent { + width: self.dimensions[0].width, + height: self.dimensions[0].height, + depth: 1, + }, + ) + .unwrap() + }; + + // Compute index into our resource ring buffers based on the frame number + // and number of frames in flight. Pay close attention to where this index is needed + // versus when the swapchain image index we got from acquire_image is needed. + let frame_idx = self.frame as usize % self.frames_in_flight; + + // Wait for the fence of the previous submission of this frame and reset it; ensures we are + // submitting only up to maximum number of frames_in_flight if we are submitting faster than + // the gpu can keep up with. This would also guarantee that any resources which need to be + // updated with a CPU->GPU data copy are not in use by the GPU, so we can perform those updates. + // In this case there are none to be done, however. + unsafe { + for i in 0..2 { + let fence = &self.submission_complete_fences[frame_idx * 2 + i]; + self.device + .wait_for_fence(fence, !0) + .expect("Failed to wait for fence"); + self.device + .reset_fence(fence) + .expect("Failed to reset fence"); + self.cmd_pools[frame_idx].reset(false); + } + } + + // Rendering + let cmd_buffer = &mut self.cmd_buffers[frame_idx]; + unsafe { + cmd_buffer.begin_primary(command::CommandBufferFlags::ONE_TIME_SUBMIT); + + cmd_buffer.set_viewports(0, &[self.viewports[0].clone()]); + cmd_buffer.set_scissors(0, &[self.viewports[0].rect]); + cmd_buffer.bind_graphics_pipeline(&self.pipeline); + cmd_buffer.bind_vertex_buffers( + 0, + iter::once((&*self.vertex_buffer, buffer::SubRange::WHOLE)), + ); + cmd_buffer.bind_graphics_descriptor_sets( + &self.pipeline_layout, + 0, + iter::once(&self.desc_set), + &[], + ); + + cmd_buffer.begin_render_pass( + &self.render_pass, + &framebuffer, + self.viewports[0].rect, + &[ + command::ClearValue { + color: command::ClearColor { + float32: [0.8, 0.8, 0.8, 1.0], + }, + }, + command::ClearValue { + color: command::ClearColor { + float32: [0.8, 0.8, 0.8, 1.0], + }, + }, + ], + command::SubpassContents::Inline, + ); + cmd_buffer.draw(0 .. 6, 0 .. 1); + cmd_buffer.end_render_pass(); + cmd_buffer.finish(); + + let submission = Submission { + command_buffers: iter::once(&*cmd_buffer), + wait_semaphores: None, + signal_semaphores: vec![ + &self.submission_complete_semaphores[frame_idx * 2 + 0], + ], + }; + self.queue_group.queues[0].submit( + submission, + Some(&self.submission_complete_fences[frame_idx * 2 + 0]), + ); + let submission = Submission { + command_buffers: iter::once(&*cmd_buffer), + wait_semaphores: None, + signal_semaphores: vec![ + &self.submission_complete_semaphores[frame_idx * 2 + 1], + ], + }; + self.queue_group.queues[0].submit( + submission, + Some(&self.submission_complete_fences[frame_idx * 2 + 1]), + ); + + // present frames + let [surface_image_0, surface_image_1] = surface_images; + let mut result = self.queue_group.queues[0].present_surface( + &mut self.surfaces[0], + surface_image_0, + Some(&self.submission_complete_semaphores[frame_idx * 2 + 0]), + ).is_ok(); + result &= self.queue_group.queues[0].present_surface( + &mut self.surfaces[1], + surface_image_1, + Some(&self.submission_complete_semaphores[frame_idx * 2 + 1]), + ).is_ok(); + + self.device.destroy_framebuffer(framebuffer); + + if !result { + self.recreate_swapchains(); + } + } + + // Increment our frame + self.frame += 1; + } +} + +impl Drop for Renderer +where + B: hal::Backend, +{ + fn drop(&mut self) { + self.device.wait_idle().unwrap(); + unsafe { + // TODO: When ManuallyDrop::take (soon to be renamed to ManuallyDrop::read) is stabilized we should use that instead. + self.device + .destroy_descriptor_pool(ManuallyDrop::into_inner(ptr::read(&self.desc_pool))); + self.device + .destroy_descriptor_set_layout(ManuallyDrop::into_inner(ptr::read( + &self.set_layout, + ))); + + self.device + .destroy_buffer(ManuallyDrop::into_inner(ptr::read(&self.vertex_buffer))); + self.device + .destroy_buffer(ManuallyDrop::into_inner(ptr::read( + &self.image_upload_buffer, + ))); + self.device + .destroy_image(ManuallyDrop::into_inner(ptr::read(&self.image_logo))); + self.device + .destroy_image_view(ManuallyDrop::into_inner(ptr::read(&self.image_srv))); + self.device + .destroy_sampler(ManuallyDrop::into_inner(ptr::read(&self.sampler))); + for p in self.cmd_pools.drain(..) { + self.device.destroy_command_pool(p); + } + for s in self.submission_complete_semaphores.drain(..) { + self.device.destroy_semaphore(s); + } + for f in self.submission_complete_fences.drain(..) { + self.device.destroy_fence(f); + } + self.device + .destroy_render_pass(ManuallyDrop::into_inner(ptr::read(&self.render_pass))); + for i in 0..2 { + self.surfaces[i].unconfigure_swapchain(&self.device); + } + self.device + .free_memory(ManuallyDrop::into_inner(ptr::read(&self.buffer_memory))); + self.device + .free_memory(ManuallyDrop::into_inner(ptr::read(&self.image_memory))); + self.device.free_memory(ManuallyDrop::into_inner(ptr::read( + &self.image_upload_memory, + ))); + self.device + .destroy_graphics_pipeline(ManuallyDrop::into_inner(ptr::read(&self.pipeline))); + self.device + .destroy_pipeline_layout(ManuallyDrop::into_inner(ptr::read( + &self.pipeline_layout, + ))); + if let Some(instance) = &self.instance { + let [surface_0, surface_1] = ManuallyDrop::into_inner(ptr::read(&self.surfaces)); + instance.destroy_surface(surface_0); + instance.destroy_surface(surface_1); + } + } + println!("DROPPED!"); + } +} diff --git a/examples/quad-multiview/screenshot.png b/examples/quad-multiview/screenshot.png new file mode 100644 index 0000000000000000000000000000000000000000..0ff90a13af0ace533a796c3b0895dae13f373093 GIT binary patch literal 38794 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 B>ZJex literal 0 HcmV?d00001 diff --git a/src/backend/vulkan/src/device.rs b/src/backend/vulkan/src/device.rs index fbd1051b1d9..413066eff59 100644 --- a/src/backend/vulkan/src/device.rs +++ b/src/backend/vulkan/src/device.rs @@ -678,7 +678,7 @@ impl d::Device for Device { }) .collect::>(); - let multiview_enabled = self.shared.features.contains(Features::MULTIVIEW); + let multiview_enabled = correlation_masks.is_some() && self.shared.features.contains(Features::MULTIVIEW); let view_masks = if multiview_enabled { Some(