From 60760ba67f27ea58db429010e2134cc9f03353fc Mon Sep 17 00:00:00 2001 From: janis Date: Sat, 4 Apr 2026 00:28:43 +0200 Subject: [PATCH] almost done! --- .cargo/config.toml | 9 -- crates/game/src/main.rs | 38 +++++--- crates/renderer/shaders/compile.sh | 7 +- crates/renderer/shaders/egui.slang | 12 +-- crates/renderer/shaders/egui.spv | Bin 2416 -> 2416 bytes crates/renderer/shaders/egui_frag.spv | Bin 1196 -> 1180 bytes crates/renderer/shaders/egui_vert.spv | Bin 1432 -> 1444 bytes crates/renderer/shaders/font.slang | 4 +- crates/renderer/shaders/font.spv | Bin 1612 -> 1616 bytes crates/renderer/shaders/wireframe.slang | 6 +- crates/renderer/shaders/wireframe.spv | Bin 1796 -> 1800 bytes crates/renderer/src/buffers.rs | 16 +++- crates/renderer/src/device.rs | 12 ++- crates/renderer/src/egui.rs | 5 +- crates/renderer/src/images.rs | 81 ++++++++++++----- crates/renderer/src/instance.rs | 93 ++++++++++++++++--- crates/renderer/src/lib.rs | 46 +++++++--- crates/renderer/src/pipeline.rs | 7 +- crates/renderer/src/swapchain.rs | 115 ++++++++++++++++-------- 19 files changed, 323 insertions(+), 128 deletions(-) delete mode 100644 .cargo/config.toml diff --git a/.cargo/config.toml b/.cargo/config.toml deleted file mode 100644 index 1a80232..0000000 --- a/.cargo/config.toml +++ /dev/null @@ -1,9 +0,0 @@ -[target.x86_64-unknown-linux-gnu] -linker = "clang" -rustflags = [ - "-Clink-arg=-fuse-ld=mold", - - # Nightly - "-Zshare-generics=y", - "-Zthreads=0", -] diff --git a/crates/game/src/main.rs b/crates/game/src/main.rs index 7c2de50..7faf8d3 100644 --- a/crates/game/src/main.rs +++ b/crates/game/src/main.rs @@ -1,7 +1,10 @@ use std::collections::BTreeMap; use rand::{Rng, SeedableRng}; -use renderer::{Renderer2, render_graph, swapchain::WindowSurface}; +use renderer::{ + Renderer2, render_graph, + swapchain::{Surface, SwapchainConfiguration}, +}; use tracing::info; use tracing_subscriber::EnvFilter; use winit::{ @@ -18,7 +21,7 @@ struct WindowState { egui_platform: egui_winit_platform::Platform, demo_app: egui_demo_lib::DemoWindows, scale_factor: f64, - surface: WindowSurface, + surface: Surface, } struct WinitState { @@ -53,13 +56,24 @@ impl WinitState { fn handle_final_resize(&mut self, window_id: WindowId, new_size: PhysicalSize) { _ = (window_id, new_size); info!("TODO: implement resize events"); - if let Some(window) = self.windows.get(&window_id) { - window - .surface - .recreate_with(Some(renderer::Extent2D { - width: new_size.width, - height: new_size.height, - })) + if let Some(WindowState { surface, .. }) = self.windows.get(&window_id) { + let config = surface + .swapchain() + .as_ref() + .map(|swapchain| swapchain.config().clone()) + .unwrap_or(SwapchainConfiguration::default()); + + surface + .configure( + self.renderer.device(), + SwapchainConfiguration { + extent: renderer::Extent2D { + width: new_size.width, + height: new_size.height, + }, + ..config + }, + ) .expect("swapchain recreation"); } } @@ -91,10 +105,8 @@ impl WinitState { let dev = renderer.device().clone(); use renderer::ash::vk::Handle; - use renderer::device::DeviceOwned; let [r, g, b]: [f32; 3] = - rand::prelude::StdRng::seed_from_u64(window.surface.surface.handle().as_raw()) - .random(); + rand::prelude::StdRng::seed_from_u64(window.surface.raw().as_raw()).random(); render_graph::clear_pass(rg, renderer::util::Rgba([r, g, b, 1.0]), framebuffer); egui_pre_pass( &dev, @@ -290,7 +302,7 @@ impl ApplicationHandler for WinitState { } fn main() { - let _ = tracing_subscriber::fmt() + _ = tracing_subscriber::fmt() .with_env_filter(EnvFilter::from_default_env()) .init(); let ev = EventLoop::new().unwrap(); diff --git a/crates/renderer/shaders/compile.sh b/crates/renderer/shaders/compile.sh index d70a059..44ae158 100755 --- a/crates/renderer/shaders/compile.sh +++ b/crates/renderer/shaders/compile.sh @@ -1,12 +1,13 @@ -#!/bin/bash +#! /usr/bin/env nix-shell +#! nix-shell -i bash -p shader-slang set -e -SLANGC="/opt/shader-slang-bin/bin/slangc" +SLANGC="slangc" $SLANGC egui.slang -profile glsl_450 -target spirv -o egui_vert.spv -entry vertex $SLANGC egui.slang -profile glsl_450 -target spirv -o egui_frag.spv -entry fragment $SLANGC egui.slang -profile glsl_450 -target spirv -entry vertex -entry fragment -o egui.spv $SLANGC wireframe.slang -profile glsl_450 -target spirv -entry vertex -entry fragment -o wireframe.spv $SLANGC font.slang -profile glsl_450 -target spirv -entry vertex -entry fragment -o font.spv -$SLANGC font.slang -profile glsl_450 -target spirv -entry mesh -entry task -entry fragment_barycentric -o font_mesh.spv +# $SLANGC font.slang -profile glsl_450 -target spirv -entry mesh -entry task -entry fragment_barycentric -o font_mesh.spv diff --git a/crates/renderer/shaders/egui.slang b/crates/renderer/shaders/egui.slang index 34397d5..fe690e7 100644 --- a/crates/renderer/shaders/egui.slang +++ b/crates/renderer/shaders/egui.slang @@ -3,14 +3,14 @@ struct Fragment { } struct VertexIn { - [[vk::layout(0)]] float2 pos; - [[vk::layout(1)]] float2 uv; - [[vk::layout(2)]] float4 color; + [[vk::location(0)]] float2 pos; + [[vk::location(1)]] float2 uv; + [[vk::location(2)]] float4 color; } struct VertexOut { - [[vk::layout(0)]] float4 color; - [[vk::layout(1)]] float2 uv; - [[vk::layout(2), flat]] uint draw_id; + [[vk::location(0)]] float4 color; + [[vk::location(1)]] float2 uv; + nointerpolation [[vk::location(2)]] uint draw_id; float4 position : SV_Position; } diff --git a/crates/renderer/shaders/egui.spv b/crates/renderer/shaders/egui.spv index 576f2c7cf72c46f9b3cc71beaa9e6c8b70529f68..66dcd6a8dd4595e01252e76d3823b96f8ba52751 100644 GIT binary patch delta 69 zcmew$^g)Q5nMs+Qftitkfk9&;cO+X`YEemQ#l$QXAua|Mpr9WRrxhip=ceYBY@9QT ViJh6j11Q8d*^qht<`2vbtN diff --git a/crates/renderer/shaders/egui_vert.spv b/crates/renderer/shaders/egui_vert.spv index 74ef25b45dd119c0a6ec1feabb5b78ed062941d2..459cbdf7718dd106463b58760ca6766d77c61c5f 100644 GIT binary patch delta 33 ncmbQiy@Z>anMs+Qftitkfk9&bB2eTnMs+Qftis(gMncpx28&NVrCu#5ZD5-0}%59F+T%40}GIL24ax>#+Wax E00i0zI{*Lx diff --git a/crates/renderer/shaders/wireframe.slang b/crates/renderer/shaders/wireframe.slang index 0ab2c86..59e07bd 100644 --- a/crates/renderer/shaders/wireframe.slang +++ b/crates/renderer/shaders/wireframe.slang @@ -1,9 +1,9 @@ struct VertexIn { - [[vk::layout(0)]] float2 pos; - [[vk::layout(1)]] float4 color; + [[vk::location(0)]] float2 pos; + [[vk::location(1)]] float4 color; } struct VertexOut { - [[vk::layout(0)]] float4 color; + [[vk::location(0)]] float4 color; float4 position : SV_Position; } diff --git a/crates/renderer/shaders/wireframe.spv b/crates/renderer/shaders/wireframe.spv index 7d54b4830b1631e622d461832bffe0fc935cf830..49364dddeb59cf2d29f00f13b7624e27ad22fe9b 100644 GIT binary patch literal 1800 zcmZXTYfn=_5QeumP(%tbTvWuCdO^X8QV~TorX(d8VvW(S%?T8(X?vQ}1F7Hq3I1Ha znD{>1UESa|)0ug9?z1PCtxTF+#u&3^KI@+|XR-!l#^kJyTr`)hoYnt%J4xFkWA-IC zL{~+#qN2HE^5Tc0(QihZB%R3C`!TF<9iaN5xk%Z z=Ea+5!>{$YKTM;3+HwvP>VP{=?|fkExFLBU+t#r&mR>MZwtuyld~e2`e%g$ZsMq?x z-n^#id&93_XVK{o&eH0*En+dFi?RF+otgE$INOHmWp*nf4m!I)XSV2z;u9k914Ute zoXVKVCTDx5PVRmG|L*g)*DwcrXMY@Wu;1{We!PCoy{EF>`nawI$<$O5Ey|xafzJCu zpK>}im84IL(+@a$(dqcjOP{m&*k;}7OTvh^tbb;JE!c{3GXpSW`b8)1b#Y!gn2&Q) zGKc$Raqh8SlD;EOT=HN~54$2`e!SVxEUEXU2n-@dU7Rlqzg_9{#tt)1r#E)xb!y@b zk{5gY_eI3;dLz*BXCK}ld-j3-wrT=rt{kvm(x`t=|J2J~sRx}JXGGNVR=)IqOGG`) z1si6;uE9UZhPmE$n3%wDi}q1~Fp!W<7A7Up;;j2xlex^!@kuWv&#SeWCH!$RMx!@?Yog^|N!%xY5v z&ha^(NCpdYJat%@W6NR0Yid%=swOInm=E<6-`_oxyeeW&^g$kfw=KCMVqWw^K7aRI zGJ9ce^hI9o_)cDk$VqHsVB2(idnK87$G7s+VSAFn@q4XF@aB1o$PIDsf2;Eyk$=~R Y!Z`HFyM8C`-|>6Nd>8ohJ)nF4KPl>lxc~qF literal 1796 zcmZXUTWb?R6vrpG25W6nq!+c;N?!^}5ey=T+L{M15ZEI6;={7$(xxW6akGi3zW5RR zxV{Mff0G#ZVdMhsKz1BF=^>o09mtsaPGkVXj!YtnYC$Oi|v5 zI-+Z$rl?`+rYatZyv+y6??p|;0G#- z>3KU#r$v$$v2$2a_1tmB^NFqFrsRoit z2YuD))U+>sO`Lwf(Hl<3Z&Uh)#g{hsoW3KBc)R*$2H1k_DK|3!L#AJJ;@%MFm4o>> zwvuL{2>(&>#ImYhy+?8@uZ z#2X|p_W1L=BZk-6hu86EAKo8(_QCxl)dbF5Ibc7fQU6nYQ!jg^9&~D47g5iNeChwT zhcWH9RVSl?mP?XiKw=mCrzXCiWh zzUK}X`{O%wSZH_QurS9bhlM#t!pITYjirNge0}^uf`vIUhlRcqhlM#l3nPcem=%A3 z;2fVrf0Y&sa}*8>bIcq@yhxK`R&!BT#C)iq`2OyTWcI+E=z~1|ZXua{FfaNcpTE15 z%wCuqeUXr-+6j^9b, - desc: BufferDesc, + pub desc: BufferDesc, alloc: Allocation, } @@ -106,6 +106,12 @@ impl Buffer { }, })?; + unsafe { + device + .raw + .bind_buffer_memory(buffer, alloc.memory(), alloc.offset())?; + } + Ok(Self { buffer: DeviceObject::new(buffer, device.clone(), desc.name.clone()), desc, @@ -138,7 +144,9 @@ impl Buffer { pub fn map(&mut self) -> Option<&[u8]> { if let Some(alloc) = self.alloc.allocation() { - alloc.mapped_slice() + alloc + .mapped_slice() + .map(|slice| &slice[..self.desc.size as usize]) } else { None } @@ -146,7 +154,9 @@ impl Buffer { pub fn map_mut(&mut self) -> Option<&mut [u8]> { if let Some(alloc) = self.alloc.allocation_mut() { - alloc.mapped_slice_mut() + alloc + .mapped_slice_mut() + .map(|slice| &mut slice[..self.desc.size as usize]) } else { None } diff --git a/crates/renderer/src/device.rs b/crates/renderer/src/device.rs index 1836a9e..6253588 100644 --- a/crates/renderer/src/device.rs +++ b/crates/renderer/src/device.rs @@ -568,7 +568,7 @@ impl DeviceInner { let buffer_vec: Vec; let name_bytes = if name.is_empty() { - &[] + &[0] } else if name.len() < buffer.len() { buffer[..name.len()].copy_from_slice(name.as_bytes()); &buffer[..] @@ -582,8 +582,14 @@ impl DeviceInner { &buffer_vec }; - let name = CStr::from_bytes_with_nul(name_bytes) - .expect("there is always a nul terminator because we added one"); + let name = CStr::from_bytes_until_nul(name_bytes) + .inspect_err(|_| { + panic!( + "debug name {:?} contains interior nul byte, which is not allowed", + name_bytes + ) + }) + .expect("{name_bytes:?} there is always a nul terminator because we added one"); unsafe { _ = self diff --git a/crates/renderer/src/egui.rs b/crates/renderer/src/egui.rs index 02caf87..113e2d6 100644 --- a/crates/renderer/src/egui.rs +++ b/crates/renderer/src/egui.rs @@ -373,7 +373,7 @@ pub fn egui_pre_pass( extent: vk::Extent3D { width: size.0, height: size.1, - depth: 0, + depth: 1, }, }], ); @@ -508,7 +508,8 @@ pub fn egui_pass( let map = staging.map_mut().unwrap(); let (st_vertices, rest) = map.split_at_mut(vertices_size); - let (st_indices, st_drawcalls) = rest.split_at_mut(indices_size); + let (st_indices, rest) = rest.split_at_mut(indices_size); + let (st_drawcalls, _rest) = rest.split_at_mut(draw_calls_size); st_vertices.copy_from_slice(bytemuck::cast_slice(&vertices)); st_indices.copy_from_slice(bytemuck::cast_slice(&indices)); st_drawcalls.copy_from_slice(bytemuck::cast_slice(&draw_calls)); diff --git a/crates/renderer/src/images.rs b/crates/renderer/src/images.rs index 65f85d5..41caff1 100644 --- a/crates/renderer/src/images.rs +++ b/crates/renderer/src/images.rs @@ -7,7 +7,7 @@ use crate::{ }; use super::Device; -use ash::vk; +use ash::vk::{self, Handle}; use gpu_allocator::vulkan::{AllocationCreateDesc, AllocationScheme}; use parking_lot::Mutex; @@ -172,10 +172,33 @@ impl Image { }, })?; + Self::new_with_allocation_unchecked( + device.clone(), + image, + Allocation::Owned(DeviceObject::new_without_name(alloc, device)), + desc, + ) + } + + fn new_with_allocation_unchecked( + device: Device, + image: vk::Image, + allocation: Allocation, + desc: ImageDesc, + ) -> crate::Result { + // bind memory + if let Some(alloc) = allocation.allocation() { + unsafe { + device + .raw + .bind_image_memory(image, alloc.memory(), alloc.offset())?; + } + } + Ok(Self { image: ImageInner::Allocated( - DeviceObject::new(image, device.clone(), desc.name.clone()), - Allocation::Owned(DeviceObject::new_without_name(alloc, device)), + DeviceObject::new(image, device, desc.name.clone()), + allocation, ), desc, views: Default::default(), @@ -213,14 +236,7 @@ impl Image { return Err(crate::Error::Unspecified); } - Ok(Self { - image: ImageInner::Allocated( - DeviceObject::new(image, device.clone(), desc.name.clone()), - allocation, - ), - desc, - views: Default::default(), - }) + Self::new_with_allocation_unchecked(device, image, allocation, desc) } pub fn from_swapchain_image(image: vk::Image, swapchain: &Swapchain) -> Self { @@ -252,7 +268,7 @@ impl Image { device: Device, desc: &ImageDesc, ) -> crate::Result<(vk::Image, vk::MemoryRequirements)> { - tracing::trace!("allocate new image with desc={desc:?}"); + tracing::trace!("new image with desc={desc:?}"); let ImageDesc { flags, format, @@ -406,7 +422,7 @@ impl Image { }); } - if desc.mip_range.0 > self.desc.mip_levels || desc.mip_range.1 > self.desc.mip_levels { + if !desc.mip_range.fits_in(self.desc.mip_levels) { tracing::error!( "image view mip range {:?} exceeds image mip levels {}", desc.mip_range, @@ -432,6 +448,11 @@ impl Image { )); } + tracing::trace!( + "new image view with desc={desc:?} for image {:x}", + self.image().as_raw() + ); + let create_info = vk::ImageViewCreateInfo::default() .flags(desc.flags) .image(self.image()) @@ -441,9 +462,9 @@ impl Image { .subresource_range( vk::ImageSubresourceRange::default() .aspect_mask(desc.aspect) - .base_mip_level(desc.mip_range.0) + .base_mip_level(desc.mip_range.start) .level_count(desc.mip_range.count()) - .base_array_layer(desc.layer_range.0) + .base_array_layer(desc.layer_range.start) .layer_count(desc.layer_range.count()), ); @@ -497,6 +518,8 @@ impl ImageViewDesc { Self { kind: vk::ImageViewType::TYPE_2D, aspect: vk::ImageAspectFlags::COLOR, + mip_range: MipRange { start: 0, end: 1 }, + layer_range: MipRange { start: 0, end: 1 }, ..Default::default() } } @@ -557,17 +580,35 @@ impl ImageViewDesc { } #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] -pub struct MipRange(u32, u32); +pub struct MipRange { + start: u32, + end: u32, +} impl Default for MipRange { fn default() -> Self { - Self(0, vk::REMAINING_ARRAY_LAYERS) + Self { + start: 0, + end: vk::REMAINING_ARRAY_LAYERS, + } } } impl MipRange { - fn count(&self) -> u32 { - self.1 + pub fn count(&self) -> u32 { + self.end + } + + pub fn fits_in(&self, total_mips: u32) -> bool { + self.start < total_mips && (self.end == vk::REMAINING_MIP_LEVELS || self.end <= total_mips) + } + + pub(crate) fn end(&self) -> Option { + if self.end == vk::REMAINING_ARRAY_LAYERS { + None + } else { + Some(self.end) + } } } @@ -584,7 +625,7 @@ impl> From for MipRange { std::ops::Bound::Unbounded => vk::REMAINING_MIP_LEVELS, }; - Self(start, count) + Self { start, end: count } } } diff --git a/crates/renderer/src/instance.rs b/crates/renderer/src/instance.rs index 62edc7b..60945ff 100644 --- a/crates/renderer/src/instance.rs +++ b/crates/renderer/src/instance.rs @@ -1,6 +1,7 @@ use std::{ cmp::Ordering, ffi::{CStr, CString}, + mem::MaybeUninit, ops::Deref, sync::Arc, }; @@ -20,8 +21,14 @@ pub struct DebugUtilsCreateInfo { pub message_type: vk::DebugUtilsMessageTypeFlagsEXT, } +pub(crate) struct InstanceExtensions { + pub(crate) get_surface_capabilities2: Option, + pub(crate) surface_maintenance1: Option<()>, +} + pub struct InstanceInner { pub raw: ash::Instance, + pub(crate) extensions: InstanceExtensions, pub entry: Entry, pub(crate) _debug_utils: DebugUtils, } @@ -65,7 +72,7 @@ const DEBUG_LAYER_SETTINGS: &[vk::LayerSettingEXT] = &[]; const DEBUG_LAYER_SETTINGS: &[vk::LayerSettingEXT] = &[ vk::LayerSettingEXT { p_layer_name: VALIDATION_LAYER.as_ptr(), - p_setting_name: c"VK_KHRONOS_VALIDATION_VALIDATE_BEST_PRACTICES".as_ptr(), + p_setting_name: c"validate_best_practices".as_ptr(), value_count: 1, p_values: &[1u8; 1] as *const u8 as _, ty: vk::LayerSettingTypeEXT::BOOL32, @@ -73,7 +80,7 @@ const DEBUG_LAYER_SETTINGS: &[vk::LayerSettingEXT] = &[ }, vk::LayerSettingEXT { p_layer_name: VALIDATION_LAYER.as_ptr(), - p_setting_name: c"VK_KHRONOS_VALIDATION_VALIDATE_BEST_PRACTICES_AMD".as_ptr(), + p_setting_name: c"validate_best_practices_amd".as_ptr(), value_count: 1, p_values: &[1u8; 1] as *const u8 as _, ty: vk::LayerSettingTypeEXT::BOOL32, @@ -81,7 +88,7 @@ const DEBUG_LAYER_SETTINGS: &[vk::LayerSettingEXT] = &[ }, vk::LayerSettingEXT { p_layer_name: VALIDATION_LAYER.as_ptr(), - p_setting_name: c"VK_KHRONOS_VALIDATION_VALIDATE_SYNC".as_ptr(), + p_setting_name: c"validate_sync".as_ptr(), value_count: 1, p_values: &[1u8; 1] as *const u8 as _, ty: vk::LayerSettingTypeEXT::BOOL32, @@ -147,6 +154,12 @@ impl Instance { #[cfg(debug_assertions)] requested_extensions.push(make_extension!(ext::layer_settings)); + // These are wanted for surface capabilities querying, but not strictly + // required, and are only supported on newer devices, especially on + // NVIDIA cards. + requested_extensions.push(make_extension!(khr::get_surface_capabilities2)); + requested_extensions.push(make_extension!(ext::surface_maintenance1)); + let (extensions, unsupported_extensions) = get_extensions(&entry, &layers, requested_extensions, desc.display_handle)?; @@ -161,14 +174,14 @@ impl Instance { .iter() .map(|layer| layer.as_ptr()) .collect::>(); - let extensions = extensions + let extension_names = extensions .iter() .map(|ext| ext.name.as_ptr()) .collect::>(); let create_info = vk::InstanceCreateInfo::default() .application_info(&app_info) - .enabled_extension_names(&extensions) + .enabled_extension_names(&extension_names) .enabled_layer_names(&layers) .push_next(&mut validation_info); @@ -201,8 +214,18 @@ impl Instance { } }; + let extensions = InstanceExtensions { + get_surface_capabilities2: extensions + .contains(&make_extension!(khr::get_surface_capabilities2)) + .then(|| khr::get_surface_capabilities2::Instance::new(&entry, &instance)), + surface_maintenance1: extensions + .contains(&make_extension!(ext::surface_maintenance1)) + .then_some(()), + }; + let instance = Arc::new(InstanceInner { raw: instance, + extensions, _debug_utils: debug_utils, entry, }); @@ -225,17 +248,19 @@ impl Instance { pdevs.pop().ok_or(Error::NoAdapter) } + /// Queries the surface capabilities of the given physical device and + /// surface, including supported formats and present modes. + /// If a present mode is provided, and the + /// `VK_KHR_get_surface_capabilities2` and `VK_EXT_surface_maintenance1` + /// extensions are available, it will also query present mode compatibility + /// information to filter the present modes based on the provided initial + /// mode. pub(crate) fn get_adapter_surface_capabilities( &self, pdev: vk::PhysicalDevice, surface: &Surface, + present_mode: Option, ) -> crate::Result { - let capabilities = unsafe { - surface - .functor - .get_physical_device_surface_capabilities(pdev, surface.raw)? - }; - let formats = unsafe { surface .functor @@ -248,8 +273,52 @@ impl Instance { .get_physical_device_surface_present_modes(pdev, surface.raw)? }; + let generic_caps = unsafe { + surface + .functor + .get_physical_device_surface_capabilities(pdev, surface.raw)? + }; + + let (capabilities, present_modes) = if let Some(ref functor) = + self.extensions.get_surface_capabilities2 + && self.extensions.surface_maintenance1.is_some() + { + let mut capabilities = vk::SurfaceCapabilities2KHR::default(); + let mut surface_info = + vk::PhysicalDeviceSurfaceInfo2KHR::default().surface(surface.raw()); + + let present_mode = present_mode.unwrap_or(present_modes[0]); + let mut surface_present_mode = + vk::SurfacePresentModeEXT::default().present_mode(present_mode); + surface_info = surface_info.push_next(&mut surface_present_mode); + + let mut present_modes = MaybeUninit::<[vk::PresentModeKHR; 8]>::uninit(); + let mut present_mode_info = vk::SurfacePresentModeCompatibilityEXT::default() + .present_modes(unsafe { present_modes.assume_init_mut() }); + capabilities = capabilities.push_next(&mut present_mode_info); + + unsafe { + functor.get_physical_device_surface_capabilities2( + pdev, + &surface_info, + &mut capabilities, + )?; + } + + (capabilities.surface_capabilities, unsafe { + let num_present_modes = present_mode_info.present_mode_count as usize; + present_modes.assume_init()[..num_present_modes].to_vec() + }) + } else { + (generic_caps, present_modes) + }; + + // surface.functor + // .get_physical_device_surface_capabilities2(pdev, surface.raw, present_mode)?; + Ok(SurfaceCapabilities { capabilities, + min_image_count: generic_caps.min_image_count, formats, present_modes, }) @@ -260,7 +329,7 @@ impl Instance { pdev: vk::PhysicalDevice, ) -> crate::Result { let properties = get_physical_device_properties(&self.inner, pdev)?; - let features = get_physical_device_features(&self.inner, pdev, &properties)?; + let features = get_physical_device_features(&self.inner, pdev, &properties); Ok(PhysicalDeviceInfo { pdev, diff --git a/crates/renderer/src/lib.rs b/crates/renderer/src/lib.rs index 9b62a1e..da5d773 100644 --- a/crates/renderer/src/lib.rs +++ b/crates/renderer/src/lib.rs @@ -5,7 +5,7 @@ slice_partition_dedup )] -use std::{collections::HashMap, fmt::Debug, sync::Arc}; +use std::{collections::HashMap, fmt::Debug, path::PathBuf, sync::Arc}; use raw_window_handle::{RawDisplayHandle, RawWindowHandle}; @@ -115,6 +115,8 @@ pub enum Error { view_kind: vk::ImageViewType, image_kind: vk::ImageType, }, + #[error("Asset missing at path {0:?}")] + AssetMissing(PathBuf), #[error("Unspecified Error")] Unspecified, #[error("BEEP BOOP create an error variant for {0} BEEP BOOP")] @@ -181,6 +183,9 @@ pub struct PhysicalDeviceInfo { #[derive(Debug)] pub struct SurfaceCapabilities { pub capabilities: SurfaceCapabilitiesKHR, + /// The minimum number of images that must be present in the swapchain for + /// the surface for any present mode supported by the surface. + pub min_image_count: u32, pub formats: Vec, pub present_modes: Vec, } @@ -558,10 +563,15 @@ fn get_physical_device_features( instance: &InstanceInner, pdev: vk::PhysicalDevice, properties: &PhysicalDeviceProperties, -) -> Result { +) -> PhysicalDeviceFeatures { let mut features = PhysicalDeviceFeatures::default(); let mut features2 = vk::PhysicalDeviceFeatures2::default(); + features2 = features2 + .push_next(&mut features.core11) + .push_next(&mut features.core12) + .push_next(&mut features.core13); + if properties.supports_extension(make_extension!(ext::mesh_shader)) { features2 = features2.push_next( features @@ -576,7 +586,9 @@ fn get_physical_device_features( .get_physical_device_features2(pdev, &mut features2); } - todo!() + features.core = features2.features; + + features } fn get_physical_device_properties( @@ -738,7 +750,6 @@ impl EguiState { }, ], max_sets: 1, - ..Default::default() }, )?; let descriptor_layout = pipeline::DescriptorSetLayout::new( @@ -931,7 +942,25 @@ impl Renderer2 { let device = adapter.create_logical_device( &instance, &[], - PhysicalDeviceFeatures::default(), + PhysicalDeviceFeatures { + core11: vk::PhysicalDeviceVulkan11Features { + shader_draw_parameters: vk::TRUE, + ..Default::default() + }, + core12: vk::PhysicalDeviceVulkan12Features { + descriptor_binding_partially_bound: vk::TRUE, + descriptor_binding_sampled_image_update_after_bind: vk::TRUE, + runtime_descriptor_array: vk::TRUE, + ..Default::default() + }, + core13: vk::PhysicalDeviceVulkan13Features { + synchronization2: vk::TRUE, + dynamic_rendering: vk::TRUE, + maintenance4: vk::TRUE, + ..Default::default() + }, + ..Default::default() + }, Some(display), )?; @@ -975,13 +1004,8 @@ impl Renderer2 { surface.configure( &self.device, swapchain::SwapchainConfiguration { - present_mode: vk::PresentModeKHR::MAILBOX, - format: vk::Format::R8G8B8A8_UNORM, - color_space: vk::ColorSpaceKHR::SRGB_NONLINEAR, - image_count: 3, extent, - composite_alpha_mode: vk::CompositeAlphaFlagsKHR::OPAQUE, - usage: vk::ImageUsageFlags::COLOR_ATTACHMENT | vk::ImageUsageFlags::TRANSFER_DST, + ..Default::default() }, )?; Ok(surface) diff --git a/crates/renderer/src/pipeline.rs b/crates/renderer/src/pipeline.rs index e87bd96..d48d2df 100644 --- a/crates/renderer/src/pipeline.rs +++ b/crates/renderer/src/pipeline.rs @@ -449,13 +449,14 @@ impl ShaderModule { pub fn new_from_path>(device: Device, path: P) -> crate::Result { use std::io::{BufReader, Read, Seek}; - let mut file = std::fs::File::open(path)?; + let path = path.as_ref(); + let mut file = + std::fs::File::open(path).map_err(|_| crate::Error::AssetMissing(path.to_owned()))?; let size = file.seek(std::io::SeekFrom::End(0))? / 4; file.seek(std::io::SeekFrom::Start(0))?; let mut reader = BufReader::new(file); - let mut buffer = Vec::::with_capacity(size as usize); - buffer.resize(size as usize, 0); + let mut buffer = vec![0; size as usize]; let size = reader.read(bytemuck::cast_slice_mut(buffer.as_mut_slice()))?; buffer.resize(size / 4, 0); diff --git a/crates/renderer/src/swapchain.rs b/crates/renderer/src/swapchain.rs index 5e7fe85..2bd5ef5 100644 --- a/crates/renderer/src/swapchain.rs +++ b/crates/renderer/src/swapchain.rs @@ -1,5 +1,6 @@ use std::{ collections::HashMap, + num::NonZero, ops::Deref, sync::{ Arc, @@ -30,7 +31,6 @@ pub struct Surface { #[debug(skip)] pub(crate) functor: khr::surface::Instance, pub(crate) instance: Instance, - pub(crate) capabilities: Mutex>, pub(crate) swapchain: RwLock>>, } @@ -43,23 +43,6 @@ impl Drop for Surface { } impl Surface { - pub fn get_capabilities<'a>( - &'a self, - adapter: vk::PhysicalDevice, - ) -> parking_lot::MappedMutexGuard<'a, SurfaceCapabilities> { - let lock = self.capabilities.lock(); - parking_lot::MutexGuard::map( - lock, - |caps: &mut HashMap| { - caps.entry(adapter).or_insert_with(|| { - self.instance - .get_adapter_surface_capabilities(adapter, self) - .expect("surface is not compatible with the adapter") - }) - }, - ) - } - pub fn configure(&self, device: &Device, config: SwapchainConfiguration) -> Result<()> { let guard = self.swapchain.read(); if let Some(swapchain) = guard.as_ref() @@ -80,7 +63,7 @@ impl Surface { drop(guard); self.swapchain.write().replace(Arc::new(new_swapchain)); - todo!() + Ok(()) } #[allow(dead_code)] @@ -100,7 +83,6 @@ impl Surface { Ok(Self { raw, functor, - capabilities: Mutex::new(HashMap::new()), swapchain: RwLock::new(None), instance: instance.clone(), }) @@ -136,7 +118,6 @@ impl Surface { Ok(Self { raw, functor, - capabilities: Mutex::new(HashMap::new()), swapchain: RwLock::new(None), instance: instance.clone(), }) @@ -150,8 +131,8 @@ impl Surface { instance: &Instance, adapter: &PhysicalDeviceInfo, config: &mut SwapchainConfiguration, - ) -> Result<()> { - let surface_caps = instance.get_adapter_surface_capabilities(adapter.pdev, self)?; + ) -> Result { + let surface_caps = instance.get_adapter_surface_capabilities(adapter.pdev, self, None)?; let max_image_dim = adapter.properties.core.limits.max_image_dimension2_d; if config.extent.width > max_image_dim || config.extent.height > max_image_dim { @@ -182,10 +163,39 @@ impl Surface { config.present_mode = fallback_mode; } + // We do this calculation with the original surface capabilities, which + // gives us the maximum min_image_count of all present modes, which + // allows us to switch present modes without needing to recreate the + // swapchain. + let max_image_count = NonZero::new(surface_caps.capabilities.max_image_count) + .map(|n| n.get()) + .unwrap_or(u32::MAX); + let min_image_count = surface_caps.min_image_count; + + // we want `config.image_count` images acquired at the same time, but we + // also need to respect the surface's minimum and maximum image count + // limits. + let image_count = (min_image_count + config.image_count).min(max_image_count); + + // in case the surface's image count limits prevent us from having the + // desired number of images in flight, reduce the requested image count + // to fit within the limits. + // this value corresponds to `S-M` in the Vulkan spec. + config.image_count = image_count - min_image_count; + + // different present modes have different image counts/extents: now that + // we know we have a supported present mode, re-query the surface + // capabilities to get the correct image counts. + let surface_caps = instance.get_adapter_surface_capabilities( + adapter.pdev, + self, + Some(config.present_mode), + )?; + if !surface_caps.formats.iter().any(|&format| { format.format == config.format && format.color_space == config.color_space }) { - // wgpu just rejects the swapchain if the format is not supported. is that smarter? + // (note): wgpu just rejects the swapchain if the format is not supported. is that smarter? // find a fallback format let format = surface_caps .formats @@ -206,16 +216,16 @@ impl Surface { config.color_space = format.color_space; } - Ok(()) + Ok(surface_caps) } #[allow(dead_code)] - fn get_fallback_swapchain_configuration( + pub fn get_fallback_swapchain_configuration( &self, instance: &Instance, adapter: &PhysicalDeviceInfo, ) -> Result { - let surface_caps = instance.get_adapter_surface_capabilities(adapter.pdev, self)?; + let surface_caps = instance.get_adapter_surface_capabilities(adapter.pdev, self, None)?; let present_mode = surface_caps .present_modes @@ -279,11 +289,15 @@ impl Surface { ) -> Option>> { // ensure lock does not block for the entire duration of the async image acquisition. let swapchain = self.swapchain.read().as_ref().cloned(); - if let Some(swapchain) = swapchain { - Some(swapchain.acquire_image()) - } else { - None - } + swapchain.map(|swapchain| swapchain.acquire_image()) + } + + pub fn swapchain(&self) -> parking_lot::RwLockReadGuard<'_, Option>> { + self.swapchain.read() + } + + pub fn raw(&self) -> vk::SurfaceKHR { + self.raw } } @@ -364,10 +378,13 @@ impl Swapchain { mut config: SwapchainConfiguration, old_swapchain: Option<&Self>, ) -> Result { - surface.validate_swapchain_configuration(&device.instance, &device.adapter, &mut config)?; - let surface_caps = device - .instance - .get_adapter_surface_capabilities(device.adapter.pdev, surface)?; + let surface_caps = surface.validate_swapchain_configuration( + &device.instance, + &device.adapter, + &mut config, + )?; + + tracing::trace!("surface capabilities: {surface_caps:#?}"); let functor = device .device_extensions @@ -388,7 +405,7 @@ impl Swapchain { .present_mode(config.present_mode) .image_color_space(config.color_space) .image_format(config.format) - .min_image_count(surface_caps.capabilities.min_image_count) + .min_image_count(surface_caps.min_image_count + config.image_count) .image_usage(config.usage) .image_array_layers(1) .image_extent(config.extent) @@ -463,7 +480,11 @@ impl Swapchain { .collect::>>()? }; - tracing::trace!("fences: {fences:?}"); + tracing::trace!( + image_count = images.len(), + min_image_count = surface_caps.capabilities.min_image_count, + config = ?config, + "created swapchain:"); Ok(Swapchain { functor: device @@ -590,6 +611,10 @@ impl Swapchain { Ok(()) }) } + + pub fn config(&self) -> &SwapchainConfiguration { + &self.config + } } #[derive(Debug)] @@ -691,6 +716,20 @@ pub struct SwapchainConfiguration { pub usage: vk::ImageUsageFlags, } +impl Default for SwapchainConfiguration { + fn default() -> Self { + Self { + present_mode: vk::PresentModeKHR::MAILBOX, + format: vk::Format::R8G8B8A8_UNORM, + color_space: vk::ColorSpaceKHR::SRGB_NONLINEAR, + image_count: 3, + extent: vk::Extent2D::default().width(1).height(1), + composite_alpha_mode: vk::CompositeAlphaFlagsKHR::OPAQUE, + usage: vk::ImageUsageFlags::TRANSFER_DST | vk::ImageUsageFlags::COLOR_ATTACHMENT, + } + } +} + impl Swapchain { pub fn with_locked T>(&self, f: F) -> T { let _lock = self.guard.lock();