]> git.nega.tv - josh/narcissus/commitdiff
narcissus-gpu: Add support for mutable swapchain images
authorJosh Simmons <josh@nega.tv>
Sun, 12 May 2024 10:09:28 +0000 (12:09 +0200)
committerJosh Simmons <josh@nega.tv>
Sun, 12 May 2024 10:09:28 +0000 (12:09 +0200)
engine/narcissus-gpu/src/backend/vulkan/convert.rs
engine/narcissus-gpu/src/backend/vulkan/mod.rs
engine/narcissus-gpu/src/backend/vulkan/wsi.rs
engine/narcissus-gpu/src/lib.rs
title/shark/src/main.rs
title/shark/src/pipelines/basic.rs
title/shark/src/pipelines/ui.rs

index deb5457b66a3ff921cac7cb85658b8726fd0ab1f..3fa185084e2e2bd6d2949ecb6f398a9b4386a9d5 100644 (file)
@@ -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 {
index 905ce338fb6aa1779433ceef99d1516b1713560a..46f63aa9a348371b59e973da277958e70a735e28 100644 (file)
@@ -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<SwapchainImage, SwapchainOutOfDateError> {
+        self.acquire_swapchain(frame, window, width, height, usage, formats, images)
     }
 
     fn destroy_swapchain(&self, window: &dyn AsRawWindow) {
index 5731b5eef65cff25d2a58d386ad93b8dcf1d32b0..33352efa96c9adac4b0781e1fccd45a82ddd5256 100644 (file)
@@ -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<SwapchainImage, SwapchainOutOfDateError> {
+        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::<Vec<_>>();
+        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::<Box<_>>();
+
+                            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 });
                 }
             }
         }
index 2e9ed1fa9d0ae789c8666a9cb071a4a44df72e4b..c5a7165947fd07353482412b271ecf544ba19376 100644 (file)
@@ -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<SwapchainImage, SwapchainOutOfDateError>;
 
     fn destroy_swapchain(&self, window: &dyn AsRawWindow);
 
index 1b331a7334b1773ef8bfdd5a1b7db1e5811dffe6..9e3d2c8428b72d16ebb4f35c3bd7822d27b1c8b6 100644 (file)
@@ -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,
             );
         }
 
index 2659f9a287ea18632aa6d929c4bdc77b0e7ae1c5..ac04836fd0384d61586c606a05043fe794f6649b 100644 (file)
@@ -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,
             },
index 9a30c4598bf7027e9f93182c9eaba9f30b669d1f..03ef8f3fa79f30281d6a48a9645198c48dfd234e 100644 (file)
@@ -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,
             },