From: Josh Simmons Date: Sun, 12 May 2024 10:09:28 +0000 (+0200) Subject: narcissus-gpu: Add support for mutable swapchain images X-Git-Url: https://git.nega.tv//gitweb.cgi?a=commitdiff_plain;h=c0797c70e1790c983dee12f87d129d077085f60f;p=josh%2Fnarcissus narcissus-gpu: Add support for mutable swapchain images --- diff --git a/engine/narcissus-gpu/src/backend/vulkan/convert.rs b/engine/narcissus-gpu/src/backend/vulkan/convert.rs index deb5457..3fa1850 100644 --- a/engine/narcissus-gpu/src/backend/vulkan/convert.rs +++ b/engine/narcissus-gpu/src/backend/vulkan/convert.rs @@ -6,8 +6,8 @@ use vulkan_sys as vk; use crate::{ BindingType, BlendMode, BufferUsageFlags, ClearValue, CompareOp, CullingMode, FrontFace, ImageAspectFlags, ImageDimension, ImageFormat, ImageSubresourceLayers, ImageSubresourceRange, - ImageTiling, IndexType, LoadOp, PolygonMode, ShaderStageFlags, StencilOp, StencilOpState, - StoreOp, Topology, + ImageTiling, ImageUsageFlags, IndexType, LoadOp, PolygonMode, ShaderStageFlags, StencilOp, + StencilOpState, StoreOp, Topology, }; #[must_use] @@ -74,6 +74,26 @@ pub fn vulkan_buffer_usage_flags(usage: BufferUsageFlags) -> vk::BufferUsageFlag usage_flags } +pub fn vulkan_image_usage_flags(usage: ImageUsageFlags) -> vk::ImageUsageFlags { + let mut usage_flags = vk::ImageUsageFlags::default(); + if usage.contains(ImageUsageFlags::SAMPLED) { + usage_flags |= vk::ImageUsageFlags::SAMPLED; + } + if usage.contains(ImageUsageFlags::STORAGE) { + usage_flags |= vk::ImageUsageFlags::STORAGE; + } + if usage.contains(ImageUsageFlags::DEPTH_STENCIL_ATTACHMENT) { + usage_flags |= vk::ImageUsageFlags::DEPTH_STENCIL_ATTACHMENT; + } + if usage.contains(ImageUsageFlags::COLOR_ATTACHMENT) { + usage_flags |= vk::ImageUsageFlags::COLOR_ATTACHMENT; + } + if usage.contains(ImageUsageFlags::TRANSFER) { + usage_flags |= vk::ImageUsageFlags::TRANSFER_DST | vk::ImageUsageFlags::TRANSFER_SRC; + } + usage_flags +} + #[must_use] pub fn vulkan_clear_value(clear_value: ClearValue) -> vk::ClearValue { match clear_value { diff --git a/engine/narcissus-gpu/src/backend/vulkan/mod.rs b/engine/narcissus-gpu/src/backend/vulkan/mod.rs index 905ce33..46f63aa 100644 --- a/engine/narcissus-gpu/src/backend/vulkan/mod.rs +++ b/engine/narcissus-gpu/src/backend/vulkan/mod.rs @@ -22,7 +22,8 @@ use crate::{ ImageBarrier, ImageBlit, ImageDesc, ImageDimension, ImageFormat, ImageLayout, ImageTiling, ImageUsageFlags, ImageViewDesc, IndexType, MemoryLocation, Offset2d, Offset3d, PersistentBuffer, Pipeline, Sampler, SamplerAddressMode, SamplerCompareOp, SamplerDesc, - SamplerFilter, SwapchainOutOfDateError, ThreadToken, TransientBuffer, TypedBind, + SamplerFilter, SwapchainImage, SwapchainOutOfDateError, ThreadToken, TransientBuffer, + TypedBind, }; mod allocator; @@ -971,26 +972,7 @@ impl Device for VulkanDevice { }; let tiling = vulkan_image_tiling(desc.tiling); - - let mut usage = default(); - if desc.usage.contains(ImageUsageFlags::SAMPLED) { - usage |= vk::ImageUsageFlags::SAMPLED; - } - if desc.usage.contains(ImageUsageFlags::STORAGE) { - usage |= vk::ImageUsageFlags::STORAGE; - } - if desc - .usage - .contains(ImageUsageFlags::DEPTH_STENCIL_ATTACHMENT) - { - usage |= vk::ImageUsageFlags::DEPTH_STENCIL_ATTACHMENT; - } - if desc.usage.contains(ImageUsageFlags::COLOR_ATTACHMENT) { - usage |= vk::ImageUsageFlags::COLOR_ATTACHMENT; - } - if desc.usage.contains(ImageUsageFlags::TRANSFER) { - usage |= vk::ImageUsageFlags::TRANSFER_DST | vk::ImageUsageFlags::TRANSFER_SRC; - } + let usage = vulkan_image_usage_flags(desc.usage); let queue_family_indices = &[self.universal_queue_family_index]; let create_info = vk::ImageCreateInfo { @@ -2331,9 +2313,11 @@ impl Device for VulkanDevice { window: &dyn AsRawWindow, width: u32, height: u32, - format: ImageFormat, - ) -> Result<(u32, u32, Image), SwapchainOutOfDateError> { - self.acquire_swapchain(frame, window, width, height, format) + usage: ImageUsageFlags, + formats: &[ImageFormat], + images: &mut [Image], + ) -> Result { + self.acquire_swapchain(frame, window, width, height, usage, formats, images) } fn destroy_swapchain(&self, window: &dyn AsRawWindow) { diff --git a/engine/narcissus-gpu/src/backend/vulkan/wsi.rs b/engine/narcissus-gpu/src/backend/vulkan/wsi.rs index 5731b5e..33352ef 100644 --- a/engine/narcissus-gpu/src/backend/vulkan/wsi.rs +++ b/engine/narcissus-gpu/src/backend/vulkan/wsi.rs @@ -11,8 +11,10 @@ use narcissus_core::{ use vulkan_sys as vk; use crate::{ - backend::vulkan::{vk_vec, vulkan_format, VulkanImageHolder, VulkanImageSwapchain}, - vk_check, Frame, Image, ImageFormat, SwapchainOutOfDateError, + backend::vulkan::{ + vk_vec, vulkan_format, vulkan_image_usage_flags, VulkanImageHolder, VulkanImageSwapchain, + }, + vk_check, Frame, Image, ImageFormat, ImageUsageFlags, SwapchainImage, SwapchainOutOfDateError, }; use super::{VulkanDevice, VulkanFrame, VULKAN_CONSTANTS}; @@ -52,6 +54,7 @@ pub struct VulkanWsiSupport { xcb: bool, surface_maintenance1: bool, swapchain_maintenance1: bool, + swapchain_mutable_format: bool, } struct RecycleSwapchainSemaphore { @@ -143,10 +146,15 @@ impl VulkanWsi { wsi_support.swapchain_maintenance1 = true; enabled_extensions.push(extension_name); } + "VK_KHR_swapchain_mutable_format" => { + wsi_support.swapchain_mutable_format = true; + enabled_extensions.push(extension_name); + } _ => {} } } assert!(khr_swapchain_support); + assert!(wsi_support.swapchain_mutable_format); } pub fn new( @@ -206,8 +214,26 @@ impl VulkanDevice { window: &dyn AsRawWindow, width: u32, height: u32, - format: ImageFormat, - ) -> Result<(u32, u32, Image), SwapchainOutOfDateError> { + usage: ImageUsageFlags, + formats: &[ImageFormat], + images: &mut [Image], + ) -> Result { + assert!( + !formats.is_empty(), + "must provide at least one swapchain format" + ); + assert!( + formats.len() == images.len(), + "number of requested formats and number of output images must match" + ); + + if formats.len() > 1 { + assert!( + self.wsi.support.swapchain_mutable_format, + "VK_KHR_swapchain_mutable_format support required for multiple swapchain formats" + ); + } + let raw_window = window.as_raw_window(); let mut surfaces = self.wsi.surfaces.lock(); let surface = *surfaces @@ -225,7 +251,7 @@ impl VulkanDevice { .xcb_surface_fn .as_ref() .unwrap() - .create_xcb_surface(self.instance, &create_info, None, &mut surface,)); + .create_xcb_surface(self.instance, &create_info, None, &mut surface)); surface } RawWindow::Xlib(xlib) => { @@ -240,7 +266,7 @@ impl VulkanDevice { .xlib_surface_fn .as_ref() .unwrap() - .create_xlib_surface(self.instance, &create_info, None, &mut surface,)); + .create_xlib_surface(self.instance, &create_info, None, &mut surface)); surface } RawWindow::Wayland(wayland) => { @@ -255,12 +281,17 @@ impl VulkanDevice { .wayland_surface_fn .as_ref() .unwrap() - .create_wayland_surface(self.instance, &create_info, None, &mut surface,)); + .create_wayland_surface(self.instance, &create_info, None, &mut surface)); surface } }); - let format = vulkan_format(format); + let formats = formats + .iter() + .copied() + .map(vulkan_format) + .collect::>(); + let format = formats[0]; let mut swapchains = self.wsi.swapchains.lock(); let vulkan_swapchain = swapchains.entry(surface).or_insert_with(|| { @@ -278,7 +309,7 @@ impl VulkanDevice { "universal queue does not support presenting this surface" ); - let formats = vk_vec(|count, ptr| unsafe { + let surface_formats = vk_vec(|count, ptr| unsafe { self.wsi.surface_fn.get_physical_device_surface_formats( self.physical_device, surface, @@ -310,7 +341,7 @@ impl VulkanDevice { &mut capabilities )); - let surface_format = formats + let surface_format = surface_formats .iter() .copied() .find(|&x| x.format == format) @@ -319,13 +350,16 @@ impl VulkanDevice { VulkanSwapchain { surface_format, state: VulkanSwapchainState::Vacant, - _formats: formats, + _formats: surface_formats, _present_modes: present_modes, capabilities, } }); - assert_eq!(format, vulkan_swapchain.surface_format.format); + assert_eq!( + format, vulkan_swapchain.surface_format.format, + "cannot change swapchain format after creation" + ); let frame = self.frame(frame); let mut image_pool = self.image_pool.lock(); @@ -361,15 +395,18 @@ impl VulkanDevice { match &mut vulkan_swapchain.state { VulkanSwapchainState::Vacant => { let mut new_swapchain = vk::SwapchainKHR::null(); - let create_info = vk::SwapchainCreateInfoKHR { + + let format_list_create_info = vk::ImageFormatListCreateInfo { + view_formats: formats.as_slice().into(), + ..default() + }; + let mut create_info = vk::SwapchainCreateInfoKHR { surface, min_image_count: vulkan_swapchain.capabilities.min_image_count, image_format: vulkan_swapchain.surface_format.format, image_color_space: vulkan_swapchain.surface_format.color_space, image_extent: vk::Extent2d { width, height }, - image_usage: vk::ImageUsageFlags::COLOR_ATTACHMENT - | vk::ImageUsageFlags::TRANSFER_SRC - | vk::ImageUsageFlags::TRANSFER_DST, + image_usage: vulkan_image_usage_flags(usage), image_array_layers: 1, image_sharing_mode: vk::SharingMode::Exclusive, pre_transform: vk::SurfaceTransformFlagsKHR::IDENTITY, @@ -379,6 +416,14 @@ impl VulkanDevice { old_swapchain, ..default() }; + + // Ask for mutable if we need it. + if formats.len() > 1 { + create_info._next = + &format_list_create_info as *const _ as *const core::ffi::c_void; + create_info.flags |= vk::SwapchainCreateFlagsKHR::MUTABLE_FORMAT; + } + vk_check!(self.wsi.swapchain_fn.create_swapchain( self.device, &create_info, @@ -387,7 +432,7 @@ impl VulkanDevice { )); assert!(!new_swapchain.is_null()); - let images = vk_vec(|count, ptr| unsafe { + let swapchain_images = vk_vec(|count, ptr| unsafe { self.wsi.swapchain_fn.get_swapchain_images( self.device, new_swapchain, @@ -396,13 +441,15 @@ impl VulkanDevice { ) }); - let image_views = images - .iter() - .map(|&image| { + let mut image_views = + Vec::with_capacity(swapchain_images.len() * formats.len()); + + for &swapchain_image in &swapchain_images { + for &format in &formats { let create_info = vk::ImageViewCreateInfo { - image, + image: swapchain_image, view_type: vk::ImageViewType::Type2d, - format: vulkan_swapchain.surface_format.format, + format, subresource_range: vk::ImageSubresourceRange { aspect_mask: vk::ImageAspectFlags::COLOR, base_mip_level: 0, @@ -423,13 +470,16 @@ impl VulkanDevice { let handle = image_pool.insert(VulkanImageHolder::Swapchain( VulkanImageSwapchain { surface, - image, + image: swapchain_image, view, }, )); - Image(handle) - }) - .collect::>(); + + image_views.push(Image(handle)); + } + } + + let image_views = image_views.into_boxed_slice(); vulkan_swapchain.state = VulkanSwapchainState::Occupied { width, @@ -518,9 +568,11 @@ impl VulkanDevice { present_info.acquire = acquire; present_info.image_index = image_index; present_info.swapchain = swapchain; - let view = image_views[image_index.widen()]; - return Ok((width, height, view)); + let base_index = image_index.widen() * formats.len(); + images.copy_from_slice(&image_views[base_index..base_index + formats.len()]); + + return Ok(SwapchainImage { width, height }); } } } diff --git a/engine/narcissus-gpu/src/lib.rs b/engine/narcissus-gpu/src/lib.rs index 2e9ed1f..c5a7165 100644 --- a/engine/narcissus-gpu/src/lib.rs +++ b/engine/narcissus-gpu/src/lib.rs @@ -695,6 +695,11 @@ impl std::fmt::Display for SwapchainOutOfDateError { impl std::error::Error for SwapchainOutOfDateError {} +pub struct SwapchainImage { + pub width: u32, + pub height: u32, +} + pub trait Device { fn create_buffer(&self, desc: &BufferDesc) -> Buffer; fn create_persistent_buffer<'device>( @@ -721,8 +726,10 @@ pub trait Device { window: &dyn AsRawWindow, width: u32, height: u32, - format: ImageFormat, - ) -> Result<(u32, u32, Image), SwapchainOutOfDateError>; + usage: ImageUsageFlags, + formats: &[ImageFormat], + images: &mut [Image], + ) -> Result; fn destroy_swapchain(&self, window: &dyn AsRawWindow); diff --git a/title/shark/src/main.rs b/title/shark/src/main.rs index 1b331a7..9e3d2c8 100644 --- a/title/shark/src/main.rs +++ b/title/shark/src/main.rs @@ -14,7 +14,7 @@ use narcissus_gpu::{ DeviceExt, Extent2d, Extent3d, Frame, Image, ImageAspectFlags, ImageBarrier, ImageDesc, ImageDimension, ImageFormat, ImageLayout, ImageTiling, ImageUsageFlags, IndexType, LoadOp, MemoryLocation, Offset2d, PersistentBuffer, RenderingAttachment, RenderingDesc, Scissor, - StoreOp, ThreadToken, TypedBind, Viewport, + StoreOp, SwapchainImage, ThreadToken, TypedBind, Viewport, }; use narcissus_image as image; use narcissus_maths::{ @@ -824,7 +824,7 @@ impl<'device> DrawState<'device> { usage: ImageUsageFlags::SAMPLED | ImageUsageFlags::TRANSFER, host_mapped: false, dimension: ImageDimension::Type2d, - format: ImageFormat::R8_UNORM, + format: ImageFormat::R8_SRGB, tiling: ImageTiling::Optimal, width: ui_state.glyph_cache.width() as u32, height: ui_state.glyph_cache.height() as u32, @@ -1154,7 +1154,7 @@ pub fn main() { height: 600, }); - let scale = 2.0; + let scale = 1.0; let thread_token = ThreadToken::new(); let thread_token = &thread_token; @@ -1177,20 +1177,30 @@ pub fn main() { { let frame = &frame; - let (width, height, swapchain_image) = loop { - let (width, height) = window.drawable_extent(); + let formats = &[ImageFormat::RGBA8_SRGB]; + let mut swapchain_images = [Image::default(); 1]; + + let SwapchainImage { width, height } = loop { + let (_width, height) = window.extent(); + let (drawable_width, drawable_height) = window.drawable_extent(); + + ui_state.scale = drawable_height as f32 / height as f32; if let Ok(result) = gpu.acquire_swapchain( frame, window.upcast(), - width, - height, - ImageFormat::BGRA8_SRGB, + drawable_width, + drawable_height, + ImageUsageFlags::COLOR_ATTACHMENT, + formats, + &mut swapchain_images, ) { break result; } }; + let [swapchain_image_srgb] = swapchain_images; + let tick_start = Instant::now(); 'tick: loop { 'poll_events: while let Some(event) = app.poll_event() { @@ -1296,7 +1306,7 @@ pub fn main() { &game_state, width, height, - swapchain_image, + swapchain_image_srgb, ); } diff --git a/title/shark/src/pipelines/basic.rs b/title/shark/src/pipelines/basic.rs index 2659f9a..ac04836 100644 --- a/title/shark/src/pipelines/basic.rs +++ b/title/shark/src/pipelines/basic.rs @@ -90,7 +90,7 @@ impl BasicPipeline { }, bind_group_layouts: &[uniforms_bind_group_layout, storage_bind_group_layout], layout: GraphicsPipelineLayout { - color_attachment_formats: &[ImageFormat::BGRA8_SRGB], + color_attachment_formats: &[ImageFormat::RGBA8_SRGB], depth_attachment_format: Some(ImageFormat::DEPTH_F32), stencil_attachment_format: None, }, diff --git a/title/shark/src/pipelines/ui.rs b/title/shark/src/pipelines/ui.rs index 9a30c45..03ef8f3 100644 --- a/title/shark/src/pipelines/ui.rs +++ b/title/shark/src/pipelines/ui.rs @@ -113,7 +113,7 @@ impl UiPipeline { }, bind_group_layouts: &[bind_group_layout], layout: GraphicsPipelineLayout { - color_attachment_formats: &[ImageFormat::BGRA8_SRGB], + color_attachment_formats: &[ImageFormat::RGBA8_SRGB], depth_attachment_format: Some(ImageFormat::DEPTH_F32), stencil_attachment_format: None, },