From: Josh Simmons Date: Tue, 14 May 2024 19:49:53 +0000 (+0200) Subject: narcissus-gpu: Configure swapchain with available modes X-Git-Url: https://git.nega.tv//gitweb.cgi?a=commitdiff_plain;h=bd58a13e6ff46cb7f96058f282f3d1cdba370b0d;p=josh%2Fnarcissus narcissus-gpu: Configure swapchain with available modes --- diff --git a/engine/narcissus-gpu/src/backend/vulkan/convert.rs b/engine/narcissus-gpu/src/backend/vulkan/convert.rs index d2ee3eb..e94f747 100644 --- a/engine/narcissus-gpu/src/backend/vulkan/convert.rs +++ b/engine/narcissus-gpu/src/backend/vulkan/convert.rs @@ -4,10 +4,10 @@ use narcissus_core::default; use vulkan_sys as vk; use crate::{ - BindingType, BlendMode, BufferUsageFlags, ClearValue, CompareOp, CullingMode, FrontFace, - ImageAspectFlags, ImageDimension, ImageFormat, ImageSubresourceLayers, ImageSubresourceRange, - ImageTiling, ImageUsageFlags, IndexType, LoadOp, PolygonMode, ShaderStageFlags, StencilOp, - StencilOpState, StoreOp, Topology, + BindingType, BlendMode, BufferUsageFlags, ClearValue, ColorSpace, CompareOp, CullingMode, + FrontFace, ImageAspectFlags, ImageDimension, ImageFormat, ImageSubresourceLayers, + ImageSubresourceRange, ImageTiling, ImageUsageFlags, IndexType, LoadOp, PolygonMode, + PresentMode, ShaderStageFlags, StencilOp, StencilOpState, StoreOp, Topology, }; #[must_use] @@ -18,6 +18,13 @@ pub fn vulkan_bool32(b: bool) -> vk::Bool32 { } } +#[must_use] +pub fn vulkan_color_space(color_space: ColorSpace) -> vk::ColorSpaceKHR { + match color_space { + ColorSpace::Srgb => vk::ColorSpaceKHR::SrgbNonlinearKhr, + } +} + #[must_use] pub fn vulkan_format(format: ImageFormat) -> vk::Format { match format { @@ -65,6 +72,15 @@ pub fn vulkan_aspect(aspect: ImageAspectFlags) -> vk::ImageAspectFlags { aspect_flags } +pub fn vulkan_present_mode(present_mode: PresentMode) -> vk::PresentModeKHR { + match present_mode { + PresentMode::Immediate => vk::PresentModeKHR::Immediate, + PresentMode::Mailbox => vk::PresentModeKHR::Mailbox, + PresentMode::Fifo => vk::PresentModeKHR::Fifo, + PresentMode::FifoRelaxed => vk::PresentModeKHR::FifoRelaxed, + } +} + pub fn vulkan_buffer_usage_flags(usage: BufferUsageFlags) -> vk::BufferUsageFlags { let mut usage_flags = vk::BufferUsageFlags::default(); if usage.contains(BufferUsageFlags::UNIFORM) { @@ -102,6 +118,26 @@ pub fn vulkan_image_usage_flags(usage: ImageUsageFlags) -> vk::ImageUsageFlags { usage_flags } +pub fn from_vulkan_image_usage_flags(usage: vk::ImageUsageFlags) -> ImageUsageFlags { + let mut usage_flags = ImageUsageFlags::default(); + if usage.contains(vk::ImageUsageFlags::SAMPLED) { + usage_flags |= ImageUsageFlags::SAMPLED; + } + if usage.contains(vk::ImageUsageFlags::STORAGE) { + usage_flags |= ImageUsageFlags::STORAGE; + } + if usage.contains(vk::ImageUsageFlags::DEPTH_STENCIL_ATTACHMENT) { + usage_flags |= ImageUsageFlags::DEPTH_STENCIL_ATTACHMENT; + } + if usage.contains(vk::ImageUsageFlags::COLOR_ATTACHMENT) { + usage_flags |= ImageUsageFlags::COLOR_ATTACHMENT; + } + if usage.contains(vk::ImageUsageFlags::TRANSFER_DST | vk::ImageUsageFlags::TRANSFER_SRC) { + usage_flags |= ImageUsageFlags::TRANSFER; + } + 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 b7e1b02..a5743c2 100644 --- a/engine/narcissus-gpu/src/backend/vulkan/mod.rs +++ b/engine/narcissus-gpu/src/backend/vulkan/mod.rs @@ -19,11 +19,10 @@ use crate::{ frame_counter::FrameCounter, Bind, BindGroupLayout, BindGroupLayoutDesc, Buffer, BufferArg, BufferDesc, BufferImageCopy, BufferUsageFlags, CmdEncoder, ComputePipelineDesc, Device, Extent2d, Extent3d, Frame, GlobalBarrier, GpuConcurrent, GraphicsPipelineDesc, Image, - ImageBarrier, ImageBlit, ImageDesc, ImageDimension, ImageFormat, ImageLayout, ImageTiling, - ImageUsageFlags, ImageViewDesc, IndexType, MemoryLocation, Offset2d, Offset3d, - PersistentBuffer, Pipeline, Sampler, SamplerAddressMode, SamplerCompareOp, SamplerDesc, - SamplerFilter, SwapchainImage, SwapchainOutOfDateError, ThreadToken, TransientBuffer, - TypedBind, + ImageBarrier, ImageBlit, ImageDesc, ImageDimension, ImageLayout, ImageTiling, ImageViewDesc, + IndexType, MemoryLocation, Offset2d, Offset3d, PersistentBuffer, Pipeline, Sampler, + SamplerAddressMode, SamplerCompareOp, SamplerDesc, SamplerFilter, SwapchainConfigurator, + SwapchainImage, SwapchainOutOfDateError, ThreadToken, TransientBuffer, TypedBind, }; mod allocator; @@ -2414,11 +2413,9 @@ impl Device for VulkanDevice { window: &dyn AsRawWindow, width: u32, height: u32, - usage: ImageUsageFlags, - formats: &[ImageFormat], - images: &mut [Image], + configurator: &mut dyn SwapchainConfigurator, ) -> Result { - self.acquire_swapchain(frame, window, width, height, usage, formats, images) + self.acquire_swapchain(frame, window, width, height, configurator) } 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 33352ef..a806230 100644 --- a/engine/narcissus-gpu/src/backend/vulkan/wsi.rs +++ b/engine/narcissus-gpu/src/backend/vulkan/wsi.rs @@ -12,9 +12,11 @@ use vulkan_sys as vk; use crate::{ backend::vulkan::{ - vk_vec, vulkan_format, vulkan_image_usage_flags, VulkanImageHolder, VulkanImageSwapchain, + from_vulkan_image_usage_flags, vk_vec, vulkan_color_space, vulkan_format, + vulkan_image_usage_flags, vulkan_present_mode, VulkanImageHolder, VulkanImageSwapchain, }, - vk_check, Frame, Image, ImageFormat, ImageUsageFlags, SwapchainImage, SwapchainOutOfDateError, + vk_check, ColorSpace, Frame, Image, ImageFormat, PresentMode, SwapchainConfigurator, + SwapchainImage, SwapchainOutOfDateError, }; use super::{VulkanDevice, VulkanFrame, VULKAN_CONSTANTS}; @@ -38,13 +40,11 @@ enum VulkanSwapchainState { } pub struct VulkanSwapchain { + present_mode: vk::PresentModeKHR, surface_format: vk::SurfaceFormatKHR, - - state: VulkanSwapchainState, - - _formats: Box<[vk::SurfaceFormatKHR]>, - _present_modes: Box<[vk::PresentModeKHR]>, + usage_flags: vk::ImageUsageFlags, capabilities: vk::SurfaceCapabilitiesKHR, + state: VulkanSwapchainState, } #[derive(Default, Clone, Copy, Debug)] @@ -214,26 +214,8 @@ impl VulkanDevice { window: &dyn AsRawWindow, width: u32, height: u32, - usage: ImageUsageFlags, - formats: &[ImageFormat], - images: &mut [Image], + configurator: &mut dyn SwapchainConfigurator, ) -> 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 @@ -286,13 +268,6 @@ impl VulkanDevice { } }); - 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(|| { let mut supported = vk::Bool32::False; @@ -309,17 +284,7 @@ impl VulkanDevice { "universal queue does not support presenting this surface" ); - let surface_formats = vk_vec(|count, ptr| unsafe { - self.wsi.surface_fn.get_physical_device_surface_formats( - self.physical_device, - surface, - count, - ptr, - ) - }) - .into_boxed_slice(); - - let present_modes = vk_vec(|count, ptr| unsafe { + let available_present_modes = vk_vec(|count, ptr| unsafe { self.wsi .surface_fn .get_physical_device_surface_present_modes( @@ -329,7 +294,57 @@ impl VulkanDevice { ptr, ) }) - .into_boxed_slice(); + .into_iter() + .filter_map(|present_mode| match present_mode { + vk::PresentModeKHR::Immediate => Some(PresentMode::Immediate), + vk::PresentModeKHR::Mailbox => Some(PresentMode::Mailbox), + vk::PresentModeKHR::Fifo => Some(PresentMode::Fifo), + vk::PresentModeKHR::FifoRelaxed => Some(PresentMode::FifoRelaxed), + vk::PresentModeKHR::SharedDemandRefresh => None, + vk::PresentModeKHR::SharedContinuousRefresh => None, + }) + .collect::>(); + + let supported_surface_formats = vk_vec(|count, ptr| unsafe { + self.wsi.surface_fn.get_physical_device_surface_formats( + self.physical_device, + surface, + count, + ptr, + ) + }) + .into_iter() + .filter_map( + |vk::SurfaceFormatKHR { + format, + color_space, + }| { + let color_space = match color_space { + vk::ColorSpaceKHR::SrgbNonlinearKhr => Some(ColorSpace::Srgb), + _ => None, + }?; + let format = match format { + vk::Format::R8_SRGB => Some(ImageFormat::R8_SRGB), + vk::Format::R8_UNORM => Some(ImageFormat::R8_UNORM), + vk::Format::R8G8B8A8_SRGB => Some(ImageFormat::RGBA8_SRGB), + vk::Format::R8G8B8A8_UNORM => Some(ImageFormat::RGBA8_UNORM), + vk::Format::R16G16B16A16_SFLOAT => Some(ImageFormat::RGBA16_FLOAT), + vk::Format::B8G8R8A8_SRGB => Some(ImageFormat::BGRA8_SRGB), + vk::Format::B8G8R8A8_UNORM => Some(ImageFormat::BGRA8_UNORM), + vk::Format::A2R10G10B10_UNORM_PACK32 => { + Some(ImageFormat::A2R10G10B10_UNORM) + } + vk::Format::A2B10G10R10_UNORM_PACK32 => { + Some(ImageFormat::A2B10G10R10_UNORM) + } + vk::Format::E5B9G9R9_UFLOAT_PACK32 => Some(ImageFormat::E5B9G9R9_UFLOAT), + vk::Format::D32_SFLOAT => Some(ImageFormat::DEPTH_F32), + _ => None, + }?; + Some((format, color_space)) + }, + ) + .collect::>(); let mut capabilities = vk::SurfaceCapabilitiesKHR::default(); vk_check!(self @@ -341,26 +356,34 @@ impl VulkanDevice { &mut capabilities )); - let surface_format = surface_formats - .iter() - .copied() - .find(|&x| x.format == format) - .expect("failed to find matching surface format"); + let supported_usage_flags = + from_vulkan_image_usage_flags(capabilities.supported_usage_flags); + + let present_mode = configurator.choose_present_mode(&available_present_modes); + let (usage_flags, surface_format) = configurator + .choose_surface_format(supported_usage_flags, &supported_surface_formats); + assert!(available_present_modes.contains(&present_mode)); + assert!((!supported_usage_flags.as_raw() & usage_flags.as_raw()) == 0); + assert!(supported_surface_formats + .iter() + .any(|&supported_format| { supported_format == surface_format })); + + let present_mode = vulkan_present_mode(present_mode); + let usage_flags = vulkan_image_usage_flags(usage_flags); + let surface_format = vk::SurfaceFormatKHR { + format: vulkan_format(surface_format.0), + color_space: vulkan_color_space(surface_format.1), + }; VulkanSwapchain { + present_mode, surface_format, + usage_flags, state: VulkanSwapchainState::Vacant, - _formats: surface_formats, - _present_modes: present_modes, capabilities, } }); - 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(); @@ -395,35 +418,23 @@ impl VulkanDevice { match &mut vulkan_swapchain.state { VulkanSwapchainState::Vacant => { let mut new_swapchain = vk::SwapchainKHR::null(); - - let format_list_create_info = vk::ImageFormatListCreateInfo { - view_formats: formats.as_slice().into(), - ..default() - }; - let mut create_info = vk::SwapchainCreateInfoKHR { + let 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: vulkan_image_usage_flags(usage), + image_usage: vulkan_swapchain.usage_flags, image_array_layers: 1, image_sharing_mode: vk::SharingMode::Exclusive, pre_transform: vk::SurfaceTransformFlagsKHR::IDENTITY, composite_alpha: vk::CompositeAlphaFlagsKHR::OPAQUE, - present_mode: vk::PresentModeKHR::Fifo, + present_mode: vulkan_swapchain.present_mode, clipped: vk::Bool32::True, 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, @@ -441,15 +452,13 @@ impl VulkanDevice { ) }); - let mut image_views = - Vec::with_capacity(swapchain_images.len() * formats.len()); - - for &swapchain_image in &swapchain_images { - for &format in &formats { + let image_views = swapchain_images + .into_iter() + .map(|swapchain_image| { let create_info = vk::ImageViewCreateInfo { image: swapchain_image, view_type: vk::ImageViewType::Type2d, - format, + format: vulkan_swapchain.surface_format.format, subresource_range: vk::ImageSubresourceRange { aspect_mask: vk::ImageAspectFlags::COLOR, base_mip_level: 0, @@ -475,11 +484,10 @@ impl VulkanDevice { }, )); - image_views.push(Image(handle)); - } - } - - let image_views = image_views.into_boxed_slice(); + Image(handle) + }) + .collect::>() + .into_boxed_slice(); vulkan_swapchain.state = VulkanSwapchainState::Occupied { width, @@ -569,10 +577,11 @@ impl VulkanDevice { present_info.image_index = image_index; present_info.swapchain = swapchain; - 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 }); + return Ok(SwapchainImage { + width, + height, + image: image_views[image_index.widen()], + }); } } } @@ -587,9 +596,9 @@ impl VulkanDevice { if let Some(VulkanSwapchain { surface_format: _, + present_mode: _, + usage_flags: _, state, - _formats: _, - _present_modes: _, capabilities: _, }) = self.wsi.swapchains.lock().remove(&surface) { diff --git a/engine/narcissus-gpu/src/lib.rs b/engine/narcissus-gpu/src/lib.rs index 491c788..95404a4 100644 --- a/engine/narcissus-gpu/src/lib.rs +++ b/engine/narcissus-gpu/src/lib.rs @@ -79,6 +79,19 @@ pub enum MemoryLocation { Device, } +#[derive(Clone, Copy, PartialEq, Eq, Debug)] +pub enum PresentMode { + Immediate, + Mailbox, + Fifo, + FifoRelaxed, +} + +#[derive(Clone, Copy, PartialEq, Eq, Debug)] +pub enum ColorSpace { + Srgb, +} + #[repr(C)] pub struct Viewport { pub x: f32, @@ -120,7 +133,7 @@ pub enum ImageDimension { TypeCube, } -#[derive(Clone, Copy, PartialEq, Eq)] +#[derive(Clone, Copy, PartialEq, Eq, Debug)] #[allow(non_camel_case_types)] pub enum ImageFormat { R8_SRGB, @@ -704,6 +717,16 @@ impl std::error::Error for SwapchainOutOfDateError {} pub struct SwapchainImage { pub width: u32, pub height: u32, + pub image: Image, +} + +pub trait SwapchainConfigurator { + fn choose_present_mode(&mut self, supported_present_modes: &[PresentMode]) -> PresentMode; + fn choose_surface_format( + &mut self, + supported_usage_flags: ImageUsageFlags, + supported_surface_formats: &[(ImageFormat, ColorSpace)], + ) -> (ImageUsageFlags, (ImageFormat, ColorSpace)); } pub trait Device { @@ -732,9 +755,7 @@ pub trait Device { window: &dyn AsRawWindow, width: u32, height: u32, - usage: ImageUsageFlags, - formats: &[ImageFormat], - images: &mut [Image], + configurator: &mut dyn SwapchainConfigurator, ) -> Result; fn destroy_swapchain(&self, window: &dyn AsRawWindow); diff --git a/title/shark/src/main.rs b/title/shark/src/main.rs index 624197f..01b4aff 100644 --- a/title/shark/src/main.rs +++ b/title/shark/src/main.rs @@ -11,11 +11,12 @@ use narcissus_app::{create_app, Event, Key, PressedState, WindowDesc}; use narcissus_core::{box_assume_init, default, rand::Pcg64, zeroed_box, BitIter}; use narcissus_font::{FontCollection, GlyphCache, HorizontalMetrics}; use narcissus_gpu::{ - create_device, Access, Bind, BufferImageCopy, BufferUsageFlags, ClearValue, CmdEncoder, Device, - DeviceExt, Extent2d, Extent3d, Frame, Image, ImageAspectFlags, ImageBarrier, ImageDesc, - ImageDimension, ImageFormat, ImageLayout, ImageSubresourceRange, ImageTiling, ImageUsageFlags, - IndexType, LoadOp, MemoryLocation, Offset2d, PersistentBuffer, RenderingAttachment, - RenderingDesc, Scissor, StoreOp, SwapchainImage, ThreadToken, TypedBind, Viewport, + create_device, Access, Bind, BufferImageCopy, BufferUsageFlags, ClearValue, CmdEncoder, + ColorSpace, Device, DeviceExt, Extent2d, Extent3d, Frame, Image, ImageAspectFlags, + ImageBarrier, ImageDesc, ImageDimension, ImageFormat, ImageLayout, ImageSubresourceRange, + ImageTiling, ImageUsageFlags, IndexType, LoadOp, MemoryLocation, Offset2d, PersistentBuffer, + PresentMode, RenderingAttachment, RenderingDesc, Scissor, StoreOp, SwapchainConfigurator, + SwapchainImage, ThreadToken, TypedBind, Viewport, }; use narcissus_image as image; use narcissus_maths::{ @@ -1364,15 +1365,55 @@ pub fn main() { let mut last_frame = Instant::now(); let mut tick_accumulator = target_dt; + struct Configurator(); + impl SwapchainConfigurator for Configurator { + fn choose_present_mode(&mut self, _available_present_modes: &[PresentMode]) -> PresentMode { + PresentMode::Fifo + } + + fn choose_surface_format( + &mut self, + _available_usage_flags: ImageUsageFlags, + available_surface_formats: &[(ImageFormat, ColorSpace)], + ) -> (ImageUsageFlags, (ImageFormat, ColorSpace)) { + let image_usage_flags = ImageUsageFlags::STORAGE; + + if let Some(&swapchain_format) = + available_surface_formats + .iter() + .find(|(image_format, _color_space)| { + image_format == &ImageFormat::A2R10G10B10_UNORM + }) + { + return (image_usage_flags, swapchain_format); + } + + if let Some(&swapchain_format) = available_surface_formats + .iter() + .find(|(image_format, _color_space)| image_format == &ImageFormat::BGRA8_UNORM) + { + return (image_usage_flags, swapchain_format); + } + + // Default + ( + image_usage_flags, + (ImageFormat::RGBA8_UNORM, ColorSpace::Srgb), + ) + } + } + let mut swapchain_configurator = Configurator(); + 'main: loop { let frame = gpu.begin_frame(); { let frame = &frame; - let formats = &[ImageFormat::A2R10G10B10_UNORM]; - let mut swapchain_images = [Image::default(); 1]; - - let SwapchainImage { width, height } = loop { + let SwapchainImage { + width, + height, + image: swapchain_image, + } = loop { let (_width, height) = window.extent(); let (drawable_width, drawable_height) = window.drawable_extent(); @@ -1383,16 +1424,12 @@ pub fn main() { window.upcast(), drawable_width, drawable_height, - ImageUsageFlags::COLOR_ATTACHMENT | ImageUsageFlags::STORAGE, - formats, - &mut swapchain_images, + &mut swapchain_configurator, ) { break result; } }; - let [swapchain_image_unorm] = swapchain_images; - let tick_start = Instant::now(); 'tick: loop { 'poll_events: while let Some(event) = app.poll_event() { @@ -1475,7 +1512,7 @@ pub fn main() { &game_state, width, height, - swapchain_image_unorm, + swapchain_image, ); }