From 0fdf3b2ea02dafde5fc8b0211b6ec68b55b09445 Mon Sep 17 00:00:00 2001 From: firestar99 Date: Tue, 30 Sep 2025 10:58:28 +0200 Subject: [PATCH 01/11] ash runner: make debug msgs easier to read --- examples/runners/ash/src/main.rs | 16 ++-------------- 1 file changed, 2 insertions(+), 14 deletions(-) diff --git a/examples/runners/ash/src/main.rs b/examples/runners/ash/src/main.rs index eb04acc277..800b16de6c 100644 --- a/examples/runners/ash/src/main.rs +++ b/examples/runners/ash/src/main.rs @@ -1309,33 +1309,21 @@ unsafe fn any_as_u8_slice(p: &T) -> &[u8] { unsafe extern "system" fn vulkan_debug_callback( message_severity: vk::DebugUtilsMessageSeverityFlagsEXT, - message_type: vk::DebugUtilsMessageTypeFlagsEXT, + _message_type: vk::DebugUtilsMessageTypeFlagsEXT, p_callback_data: *const vk::DebugUtilsMessengerCallbackDataEXT<'_>, _user_data: *mut std::os::raw::c_void, ) -> vk::Bool32 { let callback_data = unsafe { *p_callback_data }; - let message_id_number: i32 = callback_data.message_id_number; - let message_id_name = if callback_data.p_message_id_name.is_null() { Cow::from("") } else { unsafe { CStr::from_ptr(callback_data.p_message_id_name).to_string_lossy() } }; - let message = if callback_data.p_message.is_null() { Cow::from("") } else { unsafe { CStr::from_ptr(callback_data.p_message).to_string_lossy() } }; - - println!( - "{:?}:\n{:?} [{} ({})] : {}\n", - message_severity, - message_type, - message_id_name, - &message_id_number.to_string(), - message, - ); - + println!("{message_severity:?}: [{message_id_name}] : {message}"); vk::FALSE } From a0b2030b32fc6c4e98e2009d59ec84819b8bd893 Mon Sep 17 00:00:00 2001 From: firestar99 Date: Tue, 30 Sep 2025 10:24:15 +0200 Subject: [PATCH 02/11] ash runner: constant window updates, don't wait for new events --- examples/runners/ash/src/main.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/runners/ash/src/main.rs b/examples/runners/ash/src/main.rs index 800b16de6c..eea6ea85d4 100644 --- a/examples/runners/ash/src/main.rs +++ b/examples/runners/ash/src/main.rs @@ -225,7 +225,7 @@ pub fn main() { WindowEvent::CloseRequested => event_loop_window_target.exit(), _ => {} }, - _ => event_loop_window_target.set_control_flow(ControlFlow::Wait), + _ => event_loop_window_target.set_control_flow(ControlFlow::Poll), }) .unwrap(); } From 18eda6f6db0f17a45bb52e3955296a2c11ba74fb Mon Sep 17 00:00:00 2001 From: firestar99 Date: Tue, 30 Sep 2025 10:22:37 +0200 Subject: [PATCH 03/11] ash runner: remove multimodule, simplify pipeline creation --- Cargo.lock | 1 + examples/runners/ash/Cargo.toml | 1 + examples/runners/ash/src/main.rs | 411 ++++++++++--------------------- 3 files changed, 138 insertions(+), 275 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e34454bb23..98d6025998 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -934,6 +934,7 @@ dependencies = [ name = "example-runner-ash" version = "0.0.0" dependencies = [ + "anyhow", "ash", "ash-molten", "ash-window", diff --git a/examples/runners/ash/Cargo.toml b/examples/runners/ash/Cargo.toml index ea00020feb..3818c3b6b3 100644 --- a/examples/runners/ash/Cargo.toml +++ b/examples/runners/ash/Cargo.toml @@ -22,6 +22,7 @@ clap = { version = "4", features = ["derive"] } cfg-if = "1.0.0" shared = { path = "../../shaders/shared" } spirv-builder = { workspace = true, default-features = false } +anyhow = "1.0.98" [target.'cfg(target_os = "macos")'.dependencies] ash-molten = { version = "0.20", features = ["pre-built"] } diff --git a/examples/runners/ash/src/main.rs b/examples/runners/ash/src/main.rs index eea6ea85d4..3d06d417e3 100644 --- a/examples/runners/ash/src/main.rs +++ b/examples/runners/ash/src/main.rs @@ -71,13 +71,10 @@ // #![allow()] use ash::{ext, khr, util::read_spv, vk}; - +use clap::{Parser, ValueEnum}; use raw_window_handle::{HasDisplayHandle as _, HasWindowHandle as _}; -use winit::{ - event::{Event, WindowEvent}, - event_loop::{ControlFlow, EventLoop}, -}; - +use shared::ShaderConstants; +use spirv_builder::{MetadataPrintout, SpirvBuilder}; use std::{ borrow::Cow, ffi::{CStr, CString}, @@ -87,12 +84,10 @@ use std::{ sync::mpsc::{TryRecvError, TrySendError, sync_channel}, thread, }; - -use clap::{Parser, ValueEnum}; - -use spirv_builder::{MetadataPrintout, SpirvBuilder}; - -use shared::ShaderConstants; +use winit::{ + event::{Event, WindowEvent}, + event_loop::{ControlFlow, EventLoop}, +}; // This runner currently doesn't run the `compute` shader example. #[derive(Debug, PartialEq, Eq, Copy, Clone, ValueEnum)] @@ -125,7 +120,7 @@ pub struct Options { pub fn main() { let options = Options::parse(); - let (vert_data, frag_data) = compile_shaders(&options.shader); + let shader_data = compile_shaders(&options.shader).unwrap(); // runtime setup let event_loop = EventLoop::new().unwrap(); @@ -142,15 +137,9 @@ pub fn main() { )), ) .unwrap(); - let mut ctx = RenderBase::new(window, &options).into_ctx(); - - // Insert shader modules. - ctx.update_shader_modules(&vert_data, &frag_data); - - // Create pipeline. - ctx.rebuild_pipeline(vk::PipelineCache::null()); - - let (compiler_sender, compiler_receiver) = sync_channel::<(Vec, Vec)>(1); + let mut ctx = RenderCtx::new(RenderBase::new(window, &options), shader_data); + ctx.rebuild_pipeline(); + let (compiler_sender, compiler_receiver) = sync_channel::>(1); // FIXME(eddyb) incomplete `winit` upgrade, follow the guides in: // https://github.com/rust-windowing/winit/releases/tag/v0.30.0 @@ -170,10 +159,10 @@ pub fn main() { ctx.render(); } } - Ok((new_vert_data, new_frag_data)) => { - ctx.update_shader_modules(&new_vert_data, &new_frag_data); + Ok(shader_data) => { ctx.recompiling_shaders = false; - ctx.rebuild_pipeline(vk::PipelineCache::null()); + ctx.shader_code = shader_data; + ctx.rebuild_pipeline(); } Err(TryRecvError::Disconnected) => { panic!("compiler receiver disconnected unexpectedly"); @@ -196,8 +185,8 @@ pub fn main() { ctx.recompiling_shaders = true; let compiler_sender = compiler_sender.clone(); thread::spawn(move || { - if let Err(TrySendError::Disconnected(_)) = - compiler_sender.try_send(compile_shaders(&options.shader)) + if let Err(TrySendError::Disconnected(_)) = compiler_sender + .try_send(compile_shaders(&options.shader).unwrap()) { panic!("compiler sender disconnected unexpectedly"); }; @@ -215,7 +204,7 @@ pub fn main() { // HACK(eddyb) to see any changes, re-specializing the // shader module is needed (e.g. during pipeline rebuild). - ctx.rebuild_pipeline(vk::PipelineCache::null()); + ctx.rebuild_pipeline(); } _ => {} }, @@ -230,48 +219,29 @@ pub fn main() { .unwrap(); } -pub fn compile_shaders(shader: &RustGPUShader) -> (Vec, Vec) { +pub fn compile_shaders(shader: &RustGPUShader) -> anyhow::Result> { let manifest_dir = env!("CARGO_MANIFEST_DIR"); let crate_path = [manifest_dir, "..", "..", "shaders", shader.crate_name()] .iter() .copied() .collect::(); - let mut shaders = SpirvBuilder::new(crate_path, "spirv-unknown-vulkan1.1") + let compile_result = SpirvBuilder::new(crate_path, "spirv-unknown-vulkan1.1") .print_metadata(MetadataPrintout::None) .shader_panic_strategy(spirv_builder::ShaderPanicStrategy::DebugPrintfThenExit { print_inputs: true, print_backtrace: true, }) - // TODO: `multimodule` is no longer needed since - // https://github.com/KhronosGroup/SPIRV-Tools/issues/4892 was fixed, but removing it is - // non-trivial and hasn't been done yet. - .multimodule(true) - .build() - .unwrap() - .module - .unwrap_multi() - .iter() - .map(|(name, path)| { - ( - name.clone(), - read_spv(&mut File::open(path).unwrap()).unwrap(), - ) - }) - .collect::>(); - - // We always have two shaders. And the fragment shader is always before the - // vertex shader in `shaders`. This is because `unwrap_multi` returns a - // `BTreeMap` sorted by shader name, and `main_fs` comes before `main_vs`, - // alphabetically. We still check the names to make sure they are in the - // order we expect. That way if the order ever changes we'll get an - // assertion failure here as opposed to a harder-to-debug failure later on. + .build()?; + let spv_path = compile_result.module.unwrap_single(); + + // Assert that we always have these two shaders + let shaders = &compile_result.entry_points; assert_eq!(shaders.len(), 2); - assert_eq!(shaders[0].0, "main_fs"); - assert_eq!(shaders[1].0, "main_vs"); - let vert = shaders.pop().unwrap().1; - let frag = shaders.pop().unwrap().1; - (vert, frag) + assert!(shaders.contains(&"main_vs".to_string())); + assert!(shaders.contains(&"main_fs".to_string())); + + Ok(read_spv(&mut File::open(spv_path)?)?) } pub struct RenderBase { @@ -651,10 +621,6 @@ impl RenderBase { pub fn create_render_sync(&self) -> RenderSync { RenderSync::new(self) } - - pub fn into_ctx(self) -> RenderCtx { - RenderCtx::from_base(self) - } } impl Drop for RenderBase { @@ -684,9 +650,8 @@ pub struct RenderCtx { pub commands: RenderCommandPool, pub viewports: Box<[vk::Viewport]>, pub scissors: Box<[vk::Rect2D]>, + pub shader_code: Vec, pub pipeline: Option, - pub vert_module: Option, - pub frag_module: Option, pub rendering_paused: bool, pub recompiling_shaders: bool, @@ -698,7 +663,7 @@ pub struct RenderCtx { } impl RenderCtx { - pub fn from_base(base: RenderBase) -> Self { + pub fn new(base: RenderBase, shader_code: Vec) -> Self { let sync = RenderSync::new(&base); let (swapchain, extent) = base.create_swapchain(); @@ -734,9 +699,8 @@ impl RenderCtx { framebuffers, viewports, scissors, + shader_code, pipeline: None, - vert_module: None, - frag_module: None, rendering_paused: false, recompiling_shaders: false, start: std::time::Instant::now(), @@ -745,92 +709,119 @@ impl RenderCtx { } } - pub fn create_pipeline_layout(&self) -> vk::PipelineLayout { - let push_constant_range = vk::PushConstantRange::default() - .offset(0) - .size(std::mem::size_of::() as u32) - .stage_flags(vk::ShaderStageFlags::ALL); + /// Update shaders and rebuild the pipeline + pub fn rebuild_pipeline(&mut self) { unsafe { - self.base + self.cleanup_pipeline(); + + let shader_module = self + .base .device - .create_pipeline_layout( - &vk::PipelineLayoutCreateInfo::default() - .push_constant_ranges(&[push_constant_range]), + .create_shader_module( + &vk::ShaderModuleCreateInfo::default().code(&self.shader_code), None, ) - .unwrap() - } - } + .unwrap(); - pub fn rebuild_pipeline(&mut self, pipeline_cache: vk::PipelineCache) { - // NOTE(eddyb) this acts like an integration test for specialization constants. - let spec_const_entries = [vk::SpecializationMapEntry::default() - .constant_id(0x5007) - .offset(0) - .size(4)]; - let spec_const_data = - u32::to_le_bytes(self.sky_fs_spec_id_0x5007_sun_intensity_extra_spec_const_factor); - let specialization_info = vk::SpecializationInfo::default() - .map_entries(&spec_const_entries) - .data(&spec_const_data); - - self.cleanup_pipeline(); - let pipeline_layout = self.create_pipeline_layout(); - let viewport = vk::PipelineViewportStateCreateInfo::default() - .scissor_count(1) - .viewport_count(1); - - let vs_entry_point = "main_vs"; - let fs_entry_point = "main_fs"; - let vert_module = self.vert_module.as_ref().unwrap(); - let frag_module = self.frag_module.as_ref().unwrap(); - let vert_name = CString::new(vs_entry_point).unwrap(); - let frag_name = CString::new(fs_entry_point).unwrap(); - let desc = PipelineDescriptor::new(Box::new([ - vk::PipelineShaderStageCreateInfo { - module: *vert_module, - p_name: (*vert_name).as_ptr(), - stage: vk::ShaderStageFlags::VERTEX, - ..Default::default() - }, - vk::PipelineShaderStageCreateInfo { - s_type: vk::StructureType::PIPELINE_SHADER_STAGE_CREATE_INFO, - module: *frag_module, - p_name: (*frag_name).as_ptr(), - stage: vk::ShaderStageFlags::FRAGMENT, - p_specialization_info: &specialization_info, - ..Default::default() - }, - ])); - let desc_indirect_parts = desc.indirect_parts(); - let pipeline_info = vk::GraphicsPipelineCreateInfo::default() - .stages(&desc.shader_stages) - .vertex_input_state(&desc.vertex_input) - .input_assembly_state(&desc.input_assembly) - .rasterization_state(&desc.rasterization) - .multisample_state(&desc.multisample) - .depth_stencil_state(&desc.depth_stencil) - .color_blend_state(&desc_indirect_parts.color_blend) - .dynamic_state(&desc_indirect_parts.dynamic_state_info) - .viewport_state(&viewport) - .layout(pipeline_layout) - .render_pass(self.render_pass); - - let mut pipelines = unsafe { - self.base + let pipeline_layout = self + .base .device - .create_graphics_pipelines(pipeline_cache, &[pipeline_info], None) - .expect("Unable to create graphics pipeline") - }; - // A single `pipeline_info` results in a single pipeline. - assert_eq!(pipelines.len(), 1); - self.pipeline = pipelines.pop().map(|pipeline| Pipeline { - pipeline, - pipeline_layout, - }); + .create_pipeline_layout( + &vk::PipelineLayoutCreateInfo::default().push_constant_ranges(&[ + vk::PushConstantRange::default() + .offset(0) + .size(size_of::() as u32) + .stage_flags(vk::ShaderStageFlags::ALL), + ]), + None, + ) + .unwrap(); + + let mut pipelines = + self.base + .device + .create_graphics_pipelines(vk::PipelineCache::null(), &[vk::GraphicsPipelineCreateInfo::default() + .stages( + &[ + vk::PipelineShaderStageCreateInfo { + module: shader_module, + p_name: c"main_vs".as_ptr(), + stage: vk::ShaderStageFlags::VERTEX, + ..Default::default() + }, + vk::PipelineShaderStageCreateInfo { + module: shader_module, + p_name: c"main_fs".as_ptr(), + stage: vk::ShaderStageFlags::FRAGMENT, + // NOTE(eddyb) this acts like an integration test for specialization constants. + p_specialization_info: &vk::SpecializationInfo::default() + .map_entries(&[vk::SpecializationMapEntry::default() + .constant_id(0x5007) + .offset(0) + .size(4)]) + .data(&u32::to_le_bytes( + self.sky_fs_spec_id_0x5007_sun_intensity_extra_spec_const_factor, + )), + ..Default::default() + }, + ], + ) + .vertex_input_state(&vk::PipelineVertexInputStateCreateInfo::default()) + .input_assembly_state(&vk::PipelineInputAssemblyStateCreateInfo { + topology: vk::PrimitiveTopology::TRIANGLE_LIST, + ..Default::default() + }) + .rasterization_state(&vk::PipelineRasterizationStateCreateInfo { + front_face: vk::FrontFace::COUNTER_CLOCKWISE, + line_width: 1.0, + ..Default::default() + }) + .multisample_state(&vk::PipelineMultisampleStateCreateInfo { + rasterization_samples: vk::SampleCountFlags::TYPE_1, + ..Default::default() + }) + .depth_stencil_state(&vk::PipelineDepthStencilStateCreateInfo::default()) + .color_blend_state( + &vk::PipelineColorBlendStateCreateInfo::default() + .attachments( + &[vk::PipelineColorBlendAttachmentState { + blend_enable: 0, + src_color_blend_factor: vk::BlendFactor::SRC_COLOR, + dst_color_blend_factor: vk::BlendFactor::ONE_MINUS_DST_COLOR, + color_blend_op: vk::BlendOp::ADD, + src_alpha_blend_factor: vk::BlendFactor::ZERO, + dst_alpha_blend_factor: vk::BlendFactor::ZERO, + alpha_blend_op: vk::BlendOp::ADD, + color_write_mask: vk::ColorComponentFlags::RGBA, + }], + ), + ) + .dynamic_state( + &vk::PipelineDynamicStateCreateInfo::default() + .dynamic_states(&[vk::DynamicState::VIEWPORT, vk::DynamicState::SCISSOR]), + ) + .viewport_state( + &vk::PipelineViewportStateCreateInfo::default() + .scissor_count(1) + .viewport_count(1), + ) + .layout(pipeline_layout) + .render_pass(self.render_pass)], None) + .expect("Unable to create graphics pipeline"); + + // A single `pipeline_info` results in a single pipeline. + assert_eq!(pipelines.len(), 1); + self.pipeline = pipelines.pop().map(|pipeline| Pipeline { + pipeline, + pipeline_layout, + }); + + // shader modules are allowed to be deleted after the pipeline has been created + self.base.device.destroy_shader_module(shader_module, None); + } } - pub fn cleanup_pipeline(&mut self) { + pub unsafe fn cleanup_pipeline(&mut self) { unsafe { self.base.device.device_wait_idle().unwrap(); if let Some(pipeline) = self.pipeline.take() { @@ -842,37 +833,6 @@ impl RenderCtx { } } - /// Update the vertex and fragment shader modules. Does not rebuild - /// pipelines that may be using the shader module, nor does it invalidate - /// them. - pub fn update_shader_modules(&mut self, vert_data: &[u32], frag_data: &[u32]) { - let shader_info = vk::ShaderModuleCreateInfo::default().code(vert_data); - let shader_module = unsafe { - self.base - .device - .create_shader_module(&shader_info, None) - .expect("Vertex shader module error") - }; - if let Some(old_module) = self.vert_module.replace(shader_module) { - unsafe { - self.base.device.destroy_shader_module(old_module, None); - } - } - - let shader_info = vk::ShaderModuleCreateInfo::default().code(frag_data); - let shader_module = unsafe { - self.base - .device - .create_shader_module(&shader_info, None) - .expect("Fragment shader module error") - }; - if let Some(old_module) = self.frag_module.replace(shader_module) { - unsafe { - self.base.device.destroy_shader_module(old_module, None); - } - } - } - /// Destroys the swapchain, as well as the renderpass and frame and command buffers pub fn cleanup_swapchain(&mut self) { unsafe { @@ -1119,12 +1079,6 @@ impl Drop for RenderCtx { self.base .device .destroy_command_pool(self.commands.pool, None); - self.base - .device - .destroy_shader_module(self.vert_module.unwrap(), None); - self.base - .device - .destroy_shader_module(self.frag_module.unwrap(), None); } } } @@ -1208,99 +1162,6 @@ pub struct Pipeline { pub pipeline_layout: vk::PipelineLayout, } -pub struct PipelineDescriptor<'a> { - pub color_blend_attachments: Box<[vk::PipelineColorBlendAttachmentState]>, - pub dynamic_state: Box<[vk::DynamicState]>, - pub shader_stages: Box<[vk::PipelineShaderStageCreateInfo<'a>]>, - pub vertex_input: vk::PipelineVertexInputStateCreateInfo<'static>, - pub input_assembly: vk::PipelineInputAssemblyStateCreateInfo<'static>, - pub rasterization: vk::PipelineRasterizationStateCreateInfo<'static>, - pub multisample: vk::PipelineMultisampleStateCreateInfo<'static>, - pub depth_stencil: vk::PipelineDepthStencilStateCreateInfo<'static>, -} - -// HACK(eddyb) these fields need to borrow from `PipelineDescriptor` itself. -pub struct PipelineDescriptorIndirectParts<'a> { - pub color_blend: vk::PipelineColorBlendStateCreateInfo<'a>, - pub dynamic_state_info: vk::PipelineDynamicStateCreateInfo<'a>, -} - -impl<'a> PipelineDescriptor<'a> { - fn new(shader_stages: Box<[vk::PipelineShaderStageCreateInfo<'a>]>) -> Self { - let vertex_input = vk::PipelineVertexInputStateCreateInfo { - vertex_attribute_description_count: 0, - vertex_binding_description_count: 0, - ..Default::default() - }; - let input_assembly = vk::PipelineInputAssemblyStateCreateInfo { - topology: vk::PrimitiveTopology::TRIANGLE_LIST, - ..Default::default() - }; - - let rasterization = vk::PipelineRasterizationStateCreateInfo { - front_face: vk::FrontFace::COUNTER_CLOCKWISE, - line_width: 1.0, - polygon_mode: vk::PolygonMode::FILL, - ..Default::default() - }; - let multisample = vk::PipelineMultisampleStateCreateInfo { - rasterization_samples: vk::SampleCountFlags::TYPE_1, - ..Default::default() - }; - let noop_stencil_state = vk::StencilOpState { - fail_op: vk::StencilOp::KEEP, - pass_op: vk::StencilOp::KEEP, - depth_fail_op: vk::StencilOp::KEEP, - compare_op: vk::CompareOp::ALWAYS, - ..Default::default() - }; - let depth_stencil = vk::PipelineDepthStencilStateCreateInfo { - depth_test_enable: 0, - depth_write_enable: 0, - depth_compare_op: vk::CompareOp::ALWAYS, - front: noop_stencil_state, - back: noop_stencil_state, - max_depth_bounds: 1.0, - ..Default::default() - }; - let color_blend_attachments = Box::new([vk::PipelineColorBlendAttachmentState { - blend_enable: 0, - src_color_blend_factor: vk::BlendFactor::SRC_COLOR, - dst_color_blend_factor: vk::BlendFactor::ONE_MINUS_DST_COLOR, - color_blend_op: vk::BlendOp::ADD, - src_alpha_blend_factor: vk::BlendFactor::ZERO, - dst_alpha_blend_factor: vk::BlendFactor::ZERO, - alpha_blend_op: vk::BlendOp::ADD, - color_write_mask: vk::ColorComponentFlags::R - | vk::ColorComponentFlags::G - | vk::ColorComponentFlags::B - | vk::ColorComponentFlags::A, - }]); - let dynamic_state = Box::new([vk::DynamicState::VIEWPORT, vk::DynamicState::SCISSOR]); - - Self { - color_blend_attachments, - dynamic_state, - shader_stages, - vertex_input, - input_assembly, - rasterization, - multisample, - depth_stencil, - } - } - - fn indirect_parts(&self) -> PipelineDescriptorIndirectParts<'_> { - PipelineDescriptorIndirectParts { - color_blend: vk::PipelineColorBlendStateCreateInfo::default() - .logic_op(vk::LogicOp::CLEAR) - .attachments(&self.color_blend_attachments), - dynamic_state_info: vk::PipelineDynamicStateCreateInfo::default() - .dynamic_states(&self.dynamic_state), - } - } -} - unsafe fn any_as_u8_slice(p: &T) -> &[u8] { unsafe { ::std::slice::from_raw_parts((p as *const T).cast::(), ::std::mem::size_of::()) From 712da5832109cce394c68bb22403e530b3130d63 Mon Sep 17 00:00:00 2001 From: firestar99 Date: Tue, 30 Sep 2025 10:53:52 +0200 Subject: [PATCH 04/11] ash runner: use bytemuck --- Cargo.lock | 1 + examples/runners/ash/Cargo.toml | 1 + examples/runners/ash/src/main.rs | 8 +------- 3 files changed, 3 insertions(+), 7 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 98d6025998..c2f558af5a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -938,6 +938,7 @@ dependencies = [ "ash", "ash-molten", "ash-window", + "bytemuck", "cfg-if", "clap", "raw-window-handle 0.6.2", diff --git a/examples/runners/ash/Cargo.toml b/examples/runners/ash/Cargo.toml index 3818c3b6b3..0fa5fb6104 100644 --- a/examples/runners/ash/Cargo.toml +++ b/examples/runners/ash/Cargo.toml @@ -23,6 +23,7 @@ cfg-if = "1.0.0" shared = { path = "../../shaders/shared" } spirv-builder = { workspace = true, default-features = false } anyhow = "1.0.98" +bytemuck.workspace = true [target.'cfg(target_os = "macos")'.dependencies] ash-molten = { version = "0.20", features = ["pre-built"] } diff --git a/examples/runners/ash/src/main.rs b/examples/runners/ash/src/main.rs index 3d06d417e3..4db8026a5f 100644 --- a/examples/runners/ash/src/main.rs +++ b/examples/runners/ash/src/main.rs @@ -982,7 +982,7 @@ impl RenderCtx { pipeline.pipeline_layout, ash::vk::ShaderStageFlags::ALL, 0, - any_as_u8_slice(&push_constants), + bytemuck::bytes_of(&push_constants), ); device.cmd_draw(draw_command_buffer, 3, 1, 0, 0); @@ -1162,12 +1162,6 @@ pub struct Pipeline { pub pipeline_layout: vk::PipelineLayout, } -unsafe fn any_as_u8_slice(p: &T) -> &[u8] { - unsafe { - ::std::slice::from_raw_parts((p as *const T).cast::(), ::std::mem::size_of::()) - } -} - unsafe extern "system" fn vulkan_debug_callback( message_severity: vk::DebugUtilsMessageSeverityFlagsEXT, _message_type: vk::DebugUtilsMessageTypeFlagsEXT, From 87e455869b5beb4d0b08851eb1a5d36c162cc1e4 Mon Sep 17 00:00:00 2001 From: firestar99 Date: Tue, 30 Sep 2025 10:52:24 +0200 Subject: [PATCH 05/11] ash runner: general code cleanup --- examples/runners/ash/src/main.rs | 618 +++++++++++++++---------------- 1 file changed, 300 insertions(+), 318 deletions(-) diff --git a/examples/runners/ash/src/main.rs b/examples/runners/ash/src/main.rs index 4db8026a5f..f9a79d7671 100644 --- a/examples/runners/ash/src/main.rs +++ b/examples/runners/ash/src/main.rs @@ -70,6 +70,7 @@ // crate-specific exceptions: // #![allow()] +use anyhow::{Context, anyhow}; use ash::{ext, khr, util::read_spv, vk}; use clap::{Parser, ValueEnum}; use raw_window_handle::{HasDisplayHandle as _, HasWindowHandle as _}; @@ -77,13 +78,13 @@ use shared::ShaderConstants; use spirv_builder::{MetadataPrintout, SpirvBuilder}; use std::{ borrow::Cow, - ffi::{CStr, CString}, + ffi::CStr, fs::File, - os::raw::c_char, path::PathBuf, sync::mpsc::{TryRecvError, TrySendError, sync_channel}, thread, }; +use winit::event_loop::ActiveEventLoop; use winit::{ event::{Event, WindowEvent}, event_loop::{ControlFlow, EventLoop}, @@ -118,7 +119,7 @@ pub struct Options { debug_layer: bool, } -pub fn main() { +pub fn main() -> anyhow::Result<()> { let options = Options::parse(); let shader_data = compile_shaders(&options.shader).unwrap(); @@ -137,26 +138,23 @@ pub fn main() { )), ) .unwrap(); - let mut ctx = RenderCtx::new(RenderBase::new(window, &options), shader_data); + let mut ctx = RenderCtx::new(RenderBase::new(window, &options)?, shader_data)?; ctx.rebuild_pipeline(); let (compiler_sender, compiler_receiver) = sync_channel::>(1); - // FIXME(eddyb) incomplete `winit` upgrade, follow the guides in: - // https://github.com/rust-windowing/winit/releases/tag/v0.30.0 - #[allow(deprecated)] - event_loop - .run(move |event, event_loop_window_target| match event { + let mut event_handler = + move |event: Event<_>, event_loop_window_target: &ActiveEventLoop| match event { Event::AboutToWait => { match compiler_receiver.try_recv() { Err(TryRecvError::Empty) => { if ctx.rendering_paused { let vk::Extent2D { width, height } = ctx.base.surface_resolution(); if height > 0 && width > 0 { - ctx.recreate_swapchain(); - ctx.render(); + ctx.recreate_swapchain()?; + ctx.render()?; } } else { - ctx.render(); + ctx.render()?; } } Ok(shader_data) => { @@ -165,58 +163,74 @@ pub fn main() { ctx.rebuild_pipeline(); } Err(TryRecvError::Disconnected) => { - panic!("compiler receiver disconnected unexpectedly"); + return Err(anyhow!("compiler receiver disconnected unexpectedly")); } }; + Ok(()) } - Event::WindowEvent { event, .. } => match event { - WindowEvent::KeyboardInput { - event: - winit::event::KeyEvent { - logical_key: winit::keyboard::Key::Named(key), - state: winit::event::ElementState::Pressed, - .. - }, - .. - } => match key { - winit::keyboard::NamedKey::Escape => event_loop_window_target.exit(), - winit::keyboard::NamedKey::F5 => { - if !ctx.recompiling_shaders { - ctx.recompiling_shaders = true; - let compiler_sender = compiler_sender.clone(); - thread::spawn(move || { - if let Err(TrySendError::Disconnected(_)) = compiler_sender - .try_send(compile_shaders(&options.shader).unwrap()) - { - panic!("compiler sender disconnected unexpectedly"); - }; - }); + Event::WindowEvent { event, .. } => { + match event { + WindowEvent::KeyboardInput { + event: + winit::event::KeyEvent { + logical_key: winit::keyboard::Key::Named(key), + state: winit::event::ElementState::Pressed, + .. + }, + .. + } => match key { + winit::keyboard::NamedKey::Escape => event_loop_window_target.exit(), + winit::keyboard::NamedKey::F5 => { + if !ctx.recompiling_shaders { + ctx.recompiling_shaders = true; + let compiler_sender = compiler_sender.clone(); + thread::spawn(move || { + if let Err(TrySendError::Disconnected(_)) = compiler_sender + .try_send(compile_shaders(&options.shader).unwrap()) + { + panic!("compiler sender disconnected unexpectedly"); + }; + }); + } } - } - winit::keyboard::NamedKey::ArrowUp | winit::keyboard::NamedKey::ArrowDown => { - let factor = - &mut ctx.sky_fs_spec_id_0x5007_sun_intensity_extra_spec_const_factor; - *factor = if key == winit::keyboard::NamedKey::ArrowUp { - factor.saturating_add(1) - } else { - factor.saturating_sub(1) - }; + winit::keyboard::NamedKey::ArrowUp + | winit::keyboard::NamedKey::ArrowDown => { + let factor = &mut ctx + .sky_fs_spec_id_0x5007_sun_intensity_extra_spec_const_factor; + *factor = if key == winit::keyboard::NamedKey::ArrowUp { + factor.saturating_add(1) + } else { + factor.saturating_sub(1) + }; - // HACK(eddyb) to see any changes, re-specializing the - // shader module is needed (e.g. during pipeline rebuild). - ctx.rebuild_pipeline(); + // HACK(eddyb) to see any changes, re-specializing the + // shader module is needed (e.g. during pipeline rebuild). + ctx.rebuild_pipeline(); + } + _ => {} + }, + WindowEvent::Resized(_) => { + ctx.recreate_swapchain()?; } + WindowEvent::CloseRequested => event_loop_window_target.exit(), _ => {} - }, - WindowEvent::Resized(_) => { - ctx.recreate_swapchain(); } - WindowEvent::CloseRequested => event_loop_window_target.exit(), - _ => {} - }, - _ => event_loop_window_target.set_control_flow(ControlFlow::Poll), - }) - .unwrap(); + + Ok(()) + } + _ => { + event_loop_window_target.set_control_flow(ControlFlow::Poll); + Ok(()) + } + }; + + // FIXME(eddyb) incomplete `winit` upgrade, follow the guides in: + // https://github.com/rust-windowing/winit/releases/tag/v0.30.0 + #[allow(deprecated)] + event_loop.run(move |event, event_loop_window_target| { + event_handler(event, event_loop_window_target).unwrap(); + })?; + Ok(()) } pub fn compile_shaders(shader: &RustGPUShader) -> anyhow::Result> { @@ -258,7 +272,7 @@ pub struct RenderBase { pub pdevice: vk::PhysicalDevice, pub queue_family_index: u32, - pub present_queue: vk::Queue, + pub main_queue: vk::Queue, pub surface: vk::SurfaceKHR, pub surface_loader: khr::surface::Instance, @@ -266,100 +280,88 @@ pub struct RenderBase { } impl RenderBase { - pub fn new(window: winit::window::Window, options: &Options) -> Self { - cfg_if::cfg_if! { - if #[cfg(target_os = "macos")] { - let entry = ash_molten::load(); - } else { - let entry = unsafe{ash::Entry::load()}.unwrap(); - } - } - - let instance: ash::Instance = { - let app_name = CString::new("VulkanTriangle").unwrap(); - - let layer_names = if options.debug_layer { - vec![CString::new("VK_LAYER_KHRONOS_validation").unwrap()] - } else { - vec![] - }; - let layers_names_raw: Vec<*const c_char> = layer_names - .iter() - .map(|raw_name| raw_name.as_ptr()) - .collect(); - - let mut extension_names_raw = - ash_window::enumerate_required_extensions(window.display_handle().unwrap().into()) - .unwrap() - .to_vec(); - if options.debug_layer { - extension_names_raw.push(ext::debug_utils::NAME.as_ptr()); + pub fn new(window: winit::window::Window, options: &Options) -> anyhow::Result { + unsafe { + cfg_if::cfg_if! { + if #[cfg(target_os = "macos")] { + let entry = ash_molten::load(); + } else { + let entry = ash::Entry::load()?; + } } - let appinfo = vk::ApplicationInfo::default() - .application_name(&app_name) - .application_version(0) - .engine_name(&app_name) - .engine_version(0) - .api_version(vk::make_api_version(0, 1, 2, 0)); + let instance: ash::Instance = { + let layer_names: &'static [_] = if options.debug_layer { + const { &[c"VK_LAYER_KHRONOS_validation".as_ptr()] } + } else { + &[] + }; - let instance_create_info = vk::InstanceCreateInfo::default() - .application_info(&appinfo) - .enabled_layer_names(&layers_names_raw) - .enabled_extension_names(&extension_names_raw); + let mut extension_names_raw = + ash_window::enumerate_required_extensions(window.display_handle()?.into())? + .to_vec(); + if options.debug_layer { + extension_names_raw.push(ext::debug_utils::NAME.as_ptr()); + } - unsafe { + let app_name = c"VulkanTriangle"; entry - .create_instance(&instance_create_info, None) - .expect("Instance creation error") - } - }; + .create_instance( + &vk::InstanceCreateInfo::default() + .application_info( + &vk::ApplicationInfo::default() + .application_name(app_name) + .application_version(0) + .engine_name(app_name) + .engine_version(0) + .api_version(vk::make_api_version(0, 1, 2, 0)), + ) + .enabled_layer_names(layer_names) + .enabled_extension_names(&extension_names_raw), + None, + ) + .context("create_instance")? + }; - let surface = unsafe { - ash_window::create_surface( + let surface = ash_window::create_surface( &entry, &instance, window.display_handle().unwrap().into(), window.window_handle().unwrap().into(), None, ) - .unwrap() - }; - - let (debug_utils_loader, debug_call_back) = if options.debug_layer { - let debug_utils_loader = ext::debug_utils::Instance::new(&entry, &instance); - let debug_call_back = { - let debug_info = vk::DebugUtilsMessengerCreateInfoEXT::default() - .message_severity( - vk::DebugUtilsMessageSeverityFlagsEXT::ERROR - | vk::DebugUtilsMessageSeverityFlagsEXT::WARNING - | vk::DebugUtilsMessageSeverityFlagsEXT::INFO, - ) - .message_type( - vk::DebugUtilsMessageTypeFlagsEXT::GENERAL - | vk::DebugUtilsMessageTypeFlagsEXT::VALIDATION - | vk::DebugUtilsMessageTypeFlagsEXT::PERFORMANCE, - ) - .pfn_user_callback(Some(vulkan_debug_callback)); + .context("create_surface")?; + + let (debug_utils_loader, debug_call_back) = if options.debug_layer { + let debug_utils_loader = ext::debug_utils::Instance::new(&entry, &instance); + let debug_call_back = { + debug_utils_loader.create_debug_utils_messenger( + &vk::DebugUtilsMessengerCreateInfoEXT::default() + .message_severity( + vk::DebugUtilsMessageSeverityFlagsEXT::ERROR + | vk::DebugUtilsMessageSeverityFlagsEXT::WARNING + | vk::DebugUtilsMessageSeverityFlagsEXT::INFO, + ) + .message_type( + vk::DebugUtilsMessageTypeFlagsEXT::GENERAL + | vk::DebugUtilsMessageTypeFlagsEXT::VALIDATION + | vk::DebugUtilsMessageTypeFlagsEXT::PERFORMANCE, + ) + .pfn_user_callback(Some(vulkan_debug_callback)), + None, + )? + }; - unsafe { - debug_utils_loader - .create_debug_utils_messenger(&debug_info, None) - .unwrap() - } + (Some(debug_utils_loader), Some(debug_call_back)) + } else { + (None, None) }; - (Some(debug_utils_loader), Some(debug_call_back)) - } else { - (None, None) - }; - - let surface_loader = khr::surface::Instance::new(&entry, &instance); + let surface_loader = khr::surface::Instance::new(&entry, &instance); - let (pdevice, queue_family_index) = unsafe { - instance + let (pdevice, queue_family_index) = instance .enumerate_physical_devices() - .expect("Physical device error") + .context("enumerate_physical_devices")? .iter() .find_map(|pdevice| { instance @@ -382,76 +384,63 @@ impl RenderBase { } }) }) - .expect("Couldn't find suitable device.") - }; - - let device: ash::Device = { - let device_extension_names_raw = [ - khr::swapchain::NAME.as_ptr(), - khr::shader_non_semantic_info::NAME.as_ptr(), - ]; - let features = vk::PhysicalDeviceFeatures { - shader_clip_distance: 1, - ..Default::default() - }; - let priorities = [1.0]; - let queue_info = [vk::DeviceQueueCreateInfo::default() - .queue_family_index(queue_family_index) - .queue_priorities(&priorities)]; - - let mut vulkan_memory_model_features = - vk::PhysicalDeviceVulkanMemoryModelFeatures::default().vulkan_memory_model(true); - - let device_create_info = vk::DeviceCreateInfo::default() - .push_next(&mut vulkan_memory_model_features) - .queue_create_infos(&queue_info) - .enabled_extension_names(&device_extension_names_raw) - .enabled_features(&features); - unsafe { - instance - .create_device(pdevice, &device_create_info, None) - .unwrap() - } - }; + .context("Couldn't find suitable device.")?; + + let device = instance + .create_device( + pdevice, + &vk::DeviceCreateInfo::default() + .push_next( + &mut vk::PhysicalDeviceVulkanMemoryModelFeatures::default() + .vulkan_memory_model(true), + ) + .queue_create_infos(&[vk::DeviceQueueCreateInfo::default() + .queue_family_index(queue_family_index) + .queue_priorities(&[1.0])]) + .enabled_extension_names(&[ + khr::swapchain::NAME.as_ptr(), + khr::shader_non_semantic_info::NAME.as_ptr(), + ]), + None, + ) + .context("create_device")?; - let swapchain_loader = khr::swapchain::Device::new(&instance, &device); + let swapchain_loader = khr::swapchain::Device::new(&instance, &device); - let present_queue = unsafe { device.get_device_queue(queue_family_index, 0) }; + let main_queue = device.get_device_queue(queue_family_index, 0); - let surface_format = { - let acceptable_formats = { - [ - vk::Format::R8G8B8_SRGB, - vk::Format::B8G8R8_SRGB, - vk::Format::R8G8B8A8_SRGB, - vk::Format::B8G8R8A8_SRGB, - vk::Format::A8B8G8R8_SRGB_PACK32, - ] - }; - unsafe { + let surface_format = { + let acceptable_formats = { + [ + vk::Format::R8G8B8_SRGB, + vk::Format::B8G8R8_SRGB, + vk::Format::R8G8B8A8_SRGB, + vk::Format::B8G8R8A8_SRGB, + vk::Format::A8B8G8R8_SRGB_PACK32, + ] + }; *surface_loader - .get_physical_device_surface_formats(pdevice, surface) - .unwrap() + .get_physical_device_surface_formats(pdevice, surface)? .iter() .find(|sfmt| acceptable_formats.contains(&sfmt.format)) - .expect("Unable to find suitable surface format.") - } - }; + .context("Unable to find suitable surface format.")? + }; - Self { - window, - entry, - instance, - device, - swapchain_loader, - debug_utils_loader, - debug_call_back, - pdevice, - queue_family_index, - present_queue, - surface, - surface_loader, - surface_format, + Ok(Self { + window, + entry, + instance, + device, + swapchain_loader, + debug_utils_loader, + debug_call_back, + pdevice, + queue_family_index, + main_queue, + surface, + surface_loader, + surface_format, + }) } } @@ -477,82 +466,86 @@ impl RenderBase { } } - pub fn create_swapchain(&self) -> (vk::SwapchainKHR, vk::Extent2D) { - let surface_capabilities = self.surface_capabilities(); - let mut desired_image_count = surface_capabilities.min_image_count + 1; - if surface_capabilities.max_image_count > 0 - && desired_image_count > surface_capabilities.max_image_count - { - desired_image_count = surface_capabilities.max_image_count; - } - let pre_transform = if surface_capabilities - .supported_transforms - .contains(vk::SurfaceTransformFlagsKHR::IDENTITY) - { - vk::SurfaceTransformFlagsKHR::IDENTITY - } else { - surface_capabilities.current_transform - }; - let present_mode = unsafe { - self.surface_loader - .get_physical_device_surface_present_modes(self.pdevice, self.surface) - .unwrap() + pub fn create_swapchain(&self) -> anyhow::Result<(vk::SwapchainKHR, vk::Extent2D)> { + unsafe { + let surface_capabilities = self.surface_capabilities(); + let mut desired_image_count = surface_capabilities.min_image_count + 1; + if surface_capabilities.max_image_count > 0 + && desired_image_count > surface_capabilities.max_image_count + { + desired_image_count = surface_capabilities.max_image_count; + } + let pre_transform = if surface_capabilities + .supported_transforms + .contains(vk::SurfaceTransformFlagsKHR::IDENTITY) + { + vk::SurfaceTransformFlagsKHR::IDENTITY + } else { + surface_capabilities.current_transform + }; + let present_mode = self + .surface_loader + .get_physical_device_surface_present_modes(self.pdevice, self.surface)? .iter() .cloned() .find(|&mode| mode == vk::PresentModeKHR::MAILBOX) - .unwrap_or(vk::PresentModeKHR::FIFO) - }; - let extent = self.surface_resolution(); - let swapchain_create_info = vk::SwapchainCreateInfoKHR::default() - .surface(self.surface) - .min_image_count(desired_image_count) - .image_color_space(self.surface_format.color_space) - .image_format(self.surface_format.format) - .image_extent(extent) - .image_usage(vk::ImageUsageFlags::COLOR_ATTACHMENT) - .image_sharing_mode(vk::SharingMode::EXCLUSIVE) - .pre_transform(pre_transform) - .composite_alpha(vk::CompositeAlphaFlagsKHR::OPAQUE) - .present_mode(present_mode) - .clipped(true) - .image_array_layers(1); - let swapchain = unsafe { - self.swapchain_loader - .create_swapchain(&swapchain_create_info, None) - .unwrap() - }; - (swapchain, extent) + .unwrap_or(vk::PresentModeKHR::FIFO); + let extent = self.surface_resolution(); + let swapchain = self + .swapchain_loader + .create_swapchain( + &vk::SwapchainCreateInfoKHR::default() + .surface(self.surface) + .min_image_count(desired_image_count) + .image_color_space(self.surface_format.color_space) + .image_format(self.surface_format.format) + .image_extent(extent) + .image_usage(vk::ImageUsageFlags::COLOR_ATTACHMENT) + .image_sharing_mode(vk::SharingMode::EXCLUSIVE) + .pre_transform(pre_transform) + .composite_alpha(vk::CompositeAlphaFlagsKHR::OPAQUE) + .present_mode(present_mode) + .clipped(true) + .image_array_layers(1), + None, + ) + .context("create_swapchain")?; + Ok((swapchain, extent)) + } } - pub fn create_image_views(&self, swapchain: vk::SwapchainKHR) -> Vec { + pub fn create_image_views( + &self, + swapchain: vk::SwapchainKHR, + ) -> anyhow::Result> { unsafe { - self.swapchain_loader - .get_swapchain_images(swapchain) - .unwrap() + Ok(self + .swapchain_loader + .get_swapchain_images(swapchain)? .iter() .map(|&image| { - let create_view_info = vk::ImageViewCreateInfo::default() - .view_type(vk::ImageViewType::TYPE_2D) - .format(self.surface_format.format) - .components(vk::ComponentMapping { - r: vk::ComponentSwizzle::R, - g: vk::ComponentSwizzle::G, - b: vk::ComponentSwizzle::B, - a: vk::ComponentSwizzle::A, - }) - .subresource_range(vk::ImageSubresourceRange { - aspect_mask: vk::ImageAspectFlags::COLOR, - base_mip_level: 0, - level_count: 1, - base_array_layer: 0, - layer_count: 1, - }) - .image(image); - self.device - .create_image_view(&create_view_info, None) - .unwrap() + self.device.create_image_view( + &vk::ImageViewCreateInfo::default() + .view_type(vk::ImageViewType::TYPE_2D) + .format(self.surface_format.format) + .components(vk::ComponentMapping { + r: vk::ComponentSwizzle::R, + g: vk::ComponentSwizzle::G, + b: vk::ComponentSwizzle::B, + a: vk::ComponentSwizzle::A, + }) + .subresource_range(vk::ImageSubresourceRange { + aspect_mask: vk::ImageAspectFlags::COLOR, + base_mip_level: 0, + level_count: 1, + base_array_layer: 0, + layer_count: 1, + }) + .image(image), + None, + ) }) - .collect() + .collect::, _>>()?) } } @@ -561,26 +554,24 @@ impl RenderBase { image_views: &[vk::ImageView], render_pass: vk::RenderPass, extent: vk::Extent2D, - ) -> Vec { - image_views + ) -> anyhow::Result> { + Ok(image_views .iter() .map(|&present_image_view| { let framebuffer_attachments = [present_image_view]; unsafe { - self.device - .create_framebuffer( - &vk::FramebufferCreateInfo::default() - .render_pass(render_pass) - .attachments(&framebuffer_attachments) - .width(extent.width) - .height(extent.height) - .layers(1), - None, - ) - .unwrap() + self.device.create_framebuffer( + &vk::FramebufferCreateInfo::default() + .render_pass(render_pass) + .attachments(&framebuffer_attachments) + .width(extent.width) + .height(extent.height) + .layers(1), + None, + ) } }) - .collect() + .collect::>()?) } pub fn create_render_pass(&self) -> vk::RenderPass { @@ -617,10 +608,6 @@ impl RenderBase { .unwrap() } } - - pub fn create_render_sync(&self) -> RenderSync { - RenderSync::new(self) - } } impl Drop for RenderBase { @@ -663,13 +650,13 @@ pub struct RenderCtx { } impl RenderCtx { - pub fn new(base: RenderBase, shader_code: Vec) -> Self { - let sync = RenderSync::new(&base); + pub fn new(base: RenderBase, shader_code: Vec) -> anyhow::Result { + let sync = RenderSync::new(&base)?; - let (swapchain, extent) = base.create_swapchain(); - let image_views = base.create_image_views(swapchain); + let (swapchain, extent) = base.create_swapchain()?; + let image_views = base.create_image_views(swapchain)?; let render_pass = base.create_render_pass(); - let framebuffers = base.create_framebuffers(&image_views, render_pass, extent); + let framebuffers = base.create_framebuffers(&image_views, render_pass, extent)?; let commands = RenderCommandPool::new(&base); let (viewports, scissors) = { ( @@ -688,7 +675,7 @@ impl RenderCtx { ) }; - Self { + Ok(Self { sync, base, swapchain, @@ -706,7 +693,7 @@ impl RenderCtx { start: std::time::Instant::now(), sky_fs_spec_id_0x5007_sun_intensity_extra_spec_const_factor: 100, - } + }) } /// Update shaders and rebuild the pipeline @@ -821,7 +808,7 @@ impl RenderCtx { } } - pub unsafe fn cleanup_pipeline(&mut self) { + pub fn cleanup_pipeline(&mut self) { unsafe { self.base.device.device_wait_idle().unwrap(); if let Some(pipeline) = self.pipeline.take() { @@ -853,25 +840,25 @@ impl RenderCtx { } /// Recreates the swapchain, but does not recreate the pipelines because they use dynamic state. - pub fn recreate_swapchain(&mut self) { + pub fn recreate_swapchain(&mut self) -> anyhow::Result<()> { let surface_resolution = self.base.surface_resolution(); if surface_resolution.width == 0 || surface_resolution.height == 0 { self.rendering_paused = true; - return; + return Ok(()); } else if self.rendering_paused { self.rendering_paused = false; }; self.cleanup_swapchain(); - let (swapchain, extent) = self.base.create_swapchain(); + let (swapchain, extent) = self.base.create_swapchain()?; self.swapchain = swapchain; self.extent = extent; - self.image_views = self.base.create_image_views(self.swapchain); + self.image_views = self.base.create_image_views(self.swapchain)?; self.framebuffers = self.base - .create_framebuffers(&self.image_views, self.render_pass, extent); + .create_framebuffers(&self.image_views, self.render_pass, extent)?; self.viewports = Box::new([vk::Viewport { x: 0.0, y: extent.height as f32, @@ -884,9 +871,11 @@ impl RenderCtx { offset: vk::Offset2D { x: 0, y: 0 }, extent, }]); + + Ok(()) } - pub fn render(&mut self) { + pub fn render(&mut self) -> anyhow::Result<()> { let present_index = unsafe { match self.base.swapchain_loader.acquire_next_image( self.swapchain, @@ -896,8 +885,8 @@ impl RenderCtx { ) { Ok((idx, _)) => idx, Err(vk::Result::ERROR_OUT_OF_DATE_KHR) => { - self.recreate_swapchain(); - return; + self.recreate_swapchain()?; + return Ok(()); } Err(err) => panic!("failed to acquire next image: {err:?}"), } @@ -923,12 +912,12 @@ impl RenderCtx { match self .base .swapchain_loader - .queue_present(self.base.present_queue, &present_info) + .queue_present(self.base.main_queue, &present_info) { Err(vk::Result::ERROR_OUT_OF_DATE_KHR) | Ok(true) => self.recreate_swapchain(), - Ok(false) => {} - Err(err) => panic!("failed to present queue: {err:?}"), - }; + Ok(false) => Ok(()), + Err(err) => Err(anyhow!("failed to present queue: {err:?}")), + } } } @@ -1048,7 +1037,7 @@ impl RenderCtx { self.base .device .queue_submit( - self.base.present_queue, + self.base.main_queue, &[submit_info], self.sync.draw_commands_reuse_fence, ) @@ -1090,31 +1079,24 @@ pub struct RenderSync { } impl RenderSync { - pub fn new(base: &RenderBase) -> Self { + pub fn new(base: &RenderBase) -> anyhow::Result { let fence_create_info = vk::FenceCreateInfo::default().flags(vk::FenceCreateFlags::SIGNALED); let semaphore_create_info = vk::SemaphoreCreateInfo::default(); unsafe { - let draw_commands_reuse_fence = base - .device - .create_fence(&fence_create_info, None) - .expect("Create fence failed."); - let present_complete_semaphore = base - .device - .create_semaphore(&semaphore_create_info, None) - .unwrap(); - let rendering_complete_semaphore = base - .device - .create_semaphore(&semaphore_create_info, None) - .unwrap(); + let draw_commands_reuse_fence = base.device.create_fence(&fence_create_info, None)?; + let present_complete_semaphore = + base.device.create_semaphore(&semaphore_create_info, None)?; + let rendering_complete_semaphore = + base.device.create_semaphore(&semaphore_create_info, None)?; - Self { + Ok(Self { present_complete_semaphore, rendering_complete_semaphore, draw_commands_reuse_fence, - } + }) } } } From 488a87abceba1b82c38f496baa4d8fcc6c98355b Mon Sep 17 00:00:00 2001 From: firestar99 Date: Tue, 30 Sep 2025 16:06:42 +0200 Subject: [PATCH 06/11] ash runner: rewrite * use Vulkan 1.3 * use DynamicRendering feature replacing Framebuffers * extract `MyDevice` struct containing vk initialization * decouple rendering from swapchain management * redo swapchain sync, fixes validation errors, intentionally kept basic * make rendering lazily recreate its pipeline --- examples/runners/ash/src/main.rs | 1372 +++++++++++++++--------------- 1 file changed, 699 insertions(+), 673 deletions(-) diff --git a/examples/runners/ash/src/main.rs b/examples/runners/ash/src/main.rs index f9a79d7671..7f943ba8c5 100644 --- a/examples/runners/ash/src/main.rs +++ b/examples/runners/ash/src/main.rs @@ -76,12 +76,15 @@ use clap::{Parser, ValueEnum}; use raw_window_handle::{HasDisplayHandle as _, HasWindowHandle as _}; use shared::ShaderConstants; use spirv_builder::{MetadataPrintout, SpirvBuilder}; +use std::ffi::c_char; +use std::ops::Deref; +use std::sync::Arc; use std::{ borrow::Cow, ffi::CStr, fs::File, path::PathBuf, - sync::mpsc::{TryRecvError, TrySendError, sync_channel}, + sync::mpsc::{TryRecvError, sync_channel}, thread, }; use winit::event_loop::ActiveEventLoop; @@ -121,52 +124,69 @@ pub struct Options { pub fn main() -> anyhow::Result<()> { let options = Options::parse(); - let shader_data = compile_shaders(&options.shader).unwrap(); + let shader_code = compile_shaders(&options.shader)?; // runtime setup - let event_loop = EventLoop::new().unwrap(); + let event_loop = EventLoop::new()?; // FIXME(eddyb) incomplete `winit` upgrade, follow the guides in: // https://github.com/rust-windowing/winit/releases/tag/v0.30.0 #[allow(deprecated)] - let window = event_loop - .create_window( - winit::window::Window::default_attributes() - .with_title("Rust GPU - ash") - .with_inner_size(winit::dpi::LogicalSize::new( - f64::from(1280), - f64::from(720), - )), - ) - .unwrap(); - let mut ctx = RenderCtx::new(RenderBase::new(window, &options)?, shader_data)?; - ctx.rebuild_pipeline(); + let window = event_loop.create_window( + winit::window::Window::default_attributes() + .with_title("Rust GPU - ash") + .with_inner_size(winit::dpi::LogicalSize::new( + f64::from(1280), + f64::from(720), + )), + )?; + + let extensions = ash_window::enumerate_required_extensions(window.display_handle()?.as_raw())?; + let device = MyDevice::new(extensions, &options)?; + let mut swapchain = MySwapchainManager::new(device.clone(), window)?; + let mut renderer = MyRenderer::new(MyRenderPipelineManager::new( + device.clone(), + swapchain.surface_format.format, + shader_code, + )?)?; + + let mut recompiling_shaders = false; + let start = std::time::Instant::now(); let (compiler_sender, compiler_receiver) = sync_channel::>(1); - let mut event_handler = move |event: Event<_>, event_loop_window_target: &ActiveEventLoop| match event { Event::AboutToWait => { match compiler_receiver.try_recv() { - Err(TryRecvError::Empty) => { - if ctx.rendering_paused { - let vk::Extent2D { width, height } = ctx.base.surface_resolution(); - if height > 0 && width > 0 { - ctx.recreate_swapchain()?; - ctx.render()?; - } - } else { - ctx.render()?; - } - } - Ok(shader_data) => { - ctx.recompiling_shaders = false; - ctx.shader_code = shader_data; - ctx.rebuild_pipeline(); + Err(TryRecvError::Empty) => (), + Ok(shader_code) => { + recompiling_shaders = false; + renderer.pipeline.set_shader_code(shader_code); } Err(TryRecvError::Disconnected) => { return Err(anyhow!("compiler receiver disconnected unexpectedly")); } }; - Ok(()) + + swapchain.render(|frame| { + let extent = frame.extent; + let push_constants = ShaderConstants { + width: extent.width, + height: extent.height, + time: start.elapsed().as_secs_f32(), + + // FIXME(eddyb) implement mouse support for the ash runner. + cursor_x: 0.0, + cursor_y: 0.0, + drag_start_x: 0.0, + drag_start_y: 0.0, + drag_end_x: 0.0, + drag_end_y: 0.0, + mouse_button_pressed: 0, + mouse_button_press_time: [f32::NEG_INFINITY; 3], + }; + + renderer.render_frame(frame, push_constants)?; + Ok(()) + }) } Event::WindowEvent { event, .. } => { match event { @@ -181,36 +201,31 @@ pub fn main() -> anyhow::Result<()> { } => match key { winit::keyboard::NamedKey::Escape => event_loop_window_target.exit(), winit::keyboard::NamedKey::F5 => { - if !ctx.recompiling_shaders { - ctx.recompiling_shaders = true; + if !recompiling_shaders { + recompiling_shaders = true; let compiler_sender = compiler_sender.clone(); thread::spawn(move || { - if let Err(TrySendError::Disconnected(_)) = compiler_sender - .try_send(compile_shaders(&options.shader).unwrap()) - { - panic!("compiler sender disconnected unexpectedly"); - }; + let shader_code = compile_shaders(&options.shader) + .context("Compiling shaders failed") + .unwrap(); + compiler_sender.try_send(shader_code).unwrap(); }); } } winit::keyboard::NamedKey::ArrowUp | winit::keyboard::NamedKey::ArrowDown => { - let factor = &mut ctx - .sky_fs_spec_id_0x5007_sun_intensity_extra_spec_const_factor; - *factor = if key == winit::keyboard::NamedKey::ArrowUp { + let mut factor = renderer.pipeline.get_sky_fs_sun_intensity_factor(); + factor = if key == winit::keyboard::NamedKey::ArrowUp { factor.saturating_add(1) } else { factor.saturating_sub(1) }; - - // HACK(eddyb) to see any changes, re-specializing the - // shader module is needed (e.g. during pipeline rebuild). - ctx.rebuild_pipeline(); + renderer.pipeline.set_sky_fs_sun_intensity_factor(factor); } _ => {} }, WindowEvent::Resized(_) => { - ctx.recreate_swapchain()?; + swapchain.should_recreate(); } WindowEvent::CloseRequested => event_loop_window_target.exit(), _ => {} @@ -240,7 +255,7 @@ pub fn compile_shaders(shader: &RustGPUShader) -> anyhow::Result> { .copied() .collect::(); - let compile_result = SpirvBuilder::new(crate_path, "spirv-unknown-vulkan1.1") + let compile_result = SpirvBuilder::new(crate_path, "spirv-unknown-vulkan1.3") .print_metadata(MetadataPrintout::None) .shader_panic_strategy(spirv_builder::ShaderPanicStrategy::DebugPrintfThenExit { print_inputs: true, @@ -258,29 +273,31 @@ pub fn compile_shaders(shader: &RustGPUShader) -> anyhow::Result> { Ok(read_spv(&mut File::open(spv_path)?)?) } -pub struct RenderBase { - pub window: winit::window::Window, - +/// Central struct containing the Vulkan instance and device, among others +pub struct MyDevice { pub entry: ash::Entry, - pub instance: ash::Instance, + pub physical_device: vk::PhysicalDevice, pub device: ash::Device, - pub swapchain_loader: khr::swapchain::Device, - - pub debug_utils_loader: Option, - pub debug_call_back: Option, - - pub pdevice: vk::PhysicalDevice, - pub queue_family_index: u32, + pub main_queue_family: u32, pub main_queue: vk::Queue, + pub debug_ext_instance: ext::debug_utils::Instance, + pub debug_ext_device: ext::debug_utils::Device, + pub surface_ext: khr::surface::Instance, + pub swapchain_ext: khr::swapchain::Device, + debug_callback: vk::DebugUtilsMessengerEXT, +} - pub surface: vk::SurfaceKHR, - pub surface_loader: khr::surface::Instance, - pub surface_format: vk::SurfaceFormatKHR, +impl Deref for MyDevice { + type Target = ash::Device; + + fn deref(&self) -> &Self::Target { + &self.device + } } -impl RenderBase { - pub fn new(window: winit::window::Window, options: &Options) -> anyhow::Result { +impl MyDevice { + pub fn new(extension_names: &[*const c_char], options: &Options) -> anyhow::Result> { unsafe { cfg_if::cfg_if! { if #[cfg(target_os = "macos")] { @@ -290,19 +307,15 @@ impl RenderBase { } } - let instance: ash::Instance = { + let instance = { let layer_names: &'static [_] = if options.debug_layer { const { &[c"VK_LAYER_KHRONOS_validation".as_ptr()] } } else { &[] }; - let mut extension_names_raw = - ash_window::enumerate_required_extensions(window.display_handle()?.into())? - .to_vec(); - if options.debug_layer { - extension_names_raw.push(ext::debug_utils::NAME.as_ptr()); - } + let mut extension_names_raw = extension_names.to_vec(); + extension_names_raw.push(ext::debug_utils::NAME.as_ptr()); let app_name = c"VulkanTriangle"; entry @@ -314,7 +327,7 @@ impl RenderBase { .application_version(0) .engine_name(app_name) .engine_version(0) - .api_version(vk::make_api_version(0, 1, 2, 0)), + .api_version(vk::make_api_version(0, 1, 3, 0)), ) .enabled_layer_names(layer_names) .enabled_extension_names(&extension_names_raw), @@ -323,79 +336,68 @@ impl RenderBase { .context("create_instance")? }; - let surface = ash_window::create_surface( - &entry, - &instance, - window.display_handle().unwrap().into(), - window.window_handle().unwrap().into(), - None, - ) - .context("create_surface")?; - - let (debug_utils_loader, debug_call_back) = if options.debug_layer { - let debug_utils_loader = ext::debug_utils::Instance::new(&entry, &instance); - let debug_call_back = { - debug_utils_loader.create_debug_utils_messenger( - &vk::DebugUtilsMessengerCreateInfoEXT::default() - .message_severity( - vk::DebugUtilsMessageSeverityFlagsEXT::ERROR - | vk::DebugUtilsMessageSeverityFlagsEXT::WARNING - | vk::DebugUtilsMessageSeverityFlagsEXT::INFO, - ) - .message_type( - vk::DebugUtilsMessageTypeFlagsEXT::GENERAL - | vk::DebugUtilsMessageTypeFlagsEXT::VALIDATION - | vk::DebugUtilsMessageTypeFlagsEXT::PERFORMANCE, - ) - .pfn_user_callback(Some(vulkan_debug_callback)), - None, - )? - }; - - (Some(debug_utils_loader), Some(debug_call_back)) - } else { - (None, None) + let debug_instance = ext::debug_utils::Instance::new(&entry, &instance); + let debug_callback = { + debug_instance.create_debug_utils_messenger( + &vk::DebugUtilsMessengerCreateInfoEXT::default() + .message_severity( + vk::DebugUtilsMessageSeverityFlagsEXT::ERROR + | vk::DebugUtilsMessageSeverityFlagsEXT::WARNING + | vk::DebugUtilsMessageSeverityFlagsEXT::INFO, + ) + .message_type( + vk::DebugUtilsMessageTypeFlagsEXT::GENERAL + | vk::DebugUtilsMessageTypeFlagsEXT::VALIDATION + | vk::DebugUtilsMessageTypeFlagsEXT::PERFORMANCE, + ) + .pfn_user_callback(Some(vulkan_debug_callback)), + None, + )? }; - let surface_loader = khr::surface::Instance::new(&entry, &instance); + let physical_device = { + instance + .enumerate_physical_devices()? + .into_iter() + .min_by_key(|phy| { + match instance.get_physical_device_properties(*phy).device_type { + vk::PhysicalDeviceType::DISCRETE_GPU => 1, + vk::PhysicalDeviceType::VIRTUAL_GPU => 2, + vk::PhysicalDeviceType::INTEGRATED_GPU => 3, + vk::PhysicalDeviceType::CPU => 4, + _ => 5, + } + }) + .ok_or(anyhow!("No physical devices available"))? + }; - let (pdevice, queue_family_index) = instance - .enumerate_physical_devices() - .context("enumerate_physical_devices")? - .iter() - .find_map(|pdevice| { - instance - .get_physical_device_queue_family_properties(*pdevice) - .iter() - .enumerate() - .find_map(|(index, info)| { - if info.queue_flags.contains(vk::QueueFlags::GRAPHICS) - && surface_loader - .get_physical_device_surface_support( - *pdevice, - index as u32, - surface, - ) - .unwrap() - { - Some((*pdevice, index as u32)) - } else { - None - } - }) - }) - .context("Couldn't find suitable device.")?; + let main_queue_family = { + instance + .get_physical_device_queue_family_properties(physical_device) + .into_iter() + .enumerate() + .find(|(_, prop)| prop.queue_flags.contains(vk::QueueFlags::GRAPHICS)) + .ok_or(anyhow!( + "No graphics + compute queues on physical device available" + ))? + .0 as u32 + }; let device = instance .create_device( - pdevice, + physical_device, &vk::DeviceCreateInfo::default() .push_next( &mut vk::PhysicalDeviceVulkanMemoryModelFeatures::default() .vulkan_memory_model(true), ) + .push_next( + &mut vk::PhysicalDeviceVulkan13Features::default() + .synchronization2(true) + .dynamic_rendering(true), + ) .queue_create_infos(&[vk::DeviceQueueCreateInfo::default() - .queue_family_index(queue_family_index) + .queue_family_index(main_queue_family) .queue_priorities(&[1.0])]) .enabled_extension_names(&[ khr::swapchain::NAME.as_ptr(), @@ -404,10 +406,105 @@ impl RenderBase { None, ) .context("create_device")?; + let main_queue = device.get_device_queue(main_queue_family, 0); + + Ok(Arc::new(Self { + debug_ext_device: ext::debug_utils::Device::new(&instance, &device), + surface_ext: khr::surface::Instance::new(&entry, &instance), + swapchain_ext: khr::swapchain::Device::new(&instance, &device), + entry, + instance, + physical_device, + device, + main_queue_family, + main_queue, + debug_ext_instance: debug_instance, + debug_callback, + })) + } + } +} - let swapchain_loader = khr::swapchain::Device::new(&instance, &device); +impl Drop for MyDevice { + fn drop(&mut self) { + unsafe { + self.debug_ext_instance + .destroy_debug_utils_messenger(self.debug_callback, None); + self.device.destroy_device(None); + self.instance.destroy_instance(None); + } + } +} - let main_queue = device.get_device_queue(queue_family_index, 0); +/// A binary semaphore for swapchain operations +pub struct SwapchainSync { + acquire_semaphore: vk::Semaphore, + render_semaphore: vk::Semaphore, + render_fence: vk::Fence, +} + +impl SwapchainSync { + pub unsafe fn new(device: &MyDevice) -> anyhow::Result { + unsafe { + let signaled_fence = + vk::FenceCreateInfo::default().flags(vk::FenceCreateFlags::SIGNALED); + Ok(Self { + acquire_semaphore: device + .create_semaphore(&vk::SemaphoreCreateInfo::default(), None)?, + render_semaphore: device + .create_semaphore(&vk::SemaphoreCreateInfo::default(), None)?, + render_fence: device.create_fence(&signaled_fence, None)?, + }) + } + } + + pub unsafe fn destroy(&self, device: &MyDevice) { + unsafe { + device.destroy_semaphore(self.acquire_semaphore, None); + device.destroy_semaphore(self.render_semaphore, None); + device.destroy_fence(self.render_fence, None); + } + } +} + +/// Takes care of all things swapchain related +/// +/// Intentionally kept simple and does not offer support for multiple frames in flight +pub struct MySwapchainManager { + pub device: Arc, + pub window: winit::window::Window, + pub surface: vk::SurfaceKHR, + pub surface_format: vk::SurfaceFormatKHR, + pub surface_capabilities: vk::SurfaceCapabilitiesKHR, + pub present_mode: vk::PresentModeKHR, + pub image_count: u32, + pub pre_transform: vk::SurfaceTransformFlagsKHR, + + // state below + active: Option, + should_recreate: bool, + sync: SwapchainSync, +} + +struct ActiveSwapchain { + extent: vk::Extent2D, + swapchain: vk::SwapchainKHR, + images: Vec<(vk::Image, vk::ImageView)>, +} + +impl MySwapchainManager { + pub fn new(device: Arc, window: winit::window::Window) -> anyhow::Result { + unsafe { + let surface_ext = &device.surface_ext; + + let surface = ash_window::create_surface( + &device.entry, + &device.instance, + window.display_handle().unwrap().into(), + window.window_handle().unwrap().into(), + None, + ) + .context("create_surface")?; let surface_format = { let acceptable_formats = { @@ -419,313 +516,355 @@ impl RenderBase { vk::Format::A8B8G8R8_SRGB_PACK32, ] }; - *surface_loader - .get_physical_device_surface_formats(pdevice, surface)? + *surface_ext + .get_physical_device_surface_formats(device.physical_device, surface)? .iter() .find(|sfmt| acceptable_formats.contains(&sfmt.format)) .context("Unable to find suitable surface format.")? }; + let surface_capabilities = surface_ext + .get_physical_device_surface_capabilities(device.physical_device, surface)?; + let pre_transform = surface_capabilities.current_transform; + + let present_mode = surface_ext + .get_physical_device_surface_present_modes(device.physical_device, surface)? + .iter() + .cloned() + // Mailbox is preferred + .find(|&mode| mode == vk::PresentModeKHR::MAILBOX) + // FIFO is guaranteed to be available + .unwrap_or(vk::PresentModeKHR::FIFO); + + let image_count = { + let mut image_count = match present_mode { + // tripple buffering in mailbox mode:: one presenting, one ready and one drawing + vk::PresentModeKHR::MAILBOX => 3, + // double buffering in fifo mode: one presenting, one drawing + vk::PresentModeKHR::FIFO => 2, + _ => unreachable!(), + }; + if surface_capabilities.max_image_count != 0 { + image_count = image_count.min(surface_capabilities.max_image_count); + } + image_count.max(surface_capabilities.min_image_count) + }; + + let sync = SwapchainSync::new(&device)?; Ok(Self { - window, - entry, - instance, device, - swapchain_loader, - debug_utils_loader, - debug_call_back, - pdevice, - queue_family_index, - main_queue, + window, surface, - surface_loader, surface_format, + surface_capabilities, + present_mode, + image_count, + pre_transform, + + active: None, + should_recreate: true, + sync, }) } } - pub fn surface_resolution(&self) -> vk::Extent2D { - let surface_capabilities = self.surface_capabilities(); - match surface_capabilities.current_extent.width { - std::u32::MAX => { - let window_inner = self.window.inner_size(); - vk::Extent2D { - width: window_inner.width, - height: window_inner.height, - } - } - _ => surface_capabilities.current_extent, - } + #[inline] + fn should_recreate(&mut self) { + self.should_recreate = true; } - pub fn surface_capabilities(&self) -> vk::SurfaceCapabilitiesKHR { + /// After this function is called, `Self.active` is initialized + unsafe fn recreate_swapchain(&mut self) -> anyhow::Result<()> { unsafe { - self.surface_loader - .get_physical_device_surface_capabilities(self.pdevice, self.surface) - .unwrap() - } - } + let device = &self.device; + let swapchain_ext = &device.swapchain_ext; + let surface_ext = &self.device.surface_ext; + let format = self.surface_format.format; + + let extent = { + let window_size = self.window.inner_size(); + let capabilities = surface_ext.get_physical_device_surface_capabilities( + self.device.physical_device, + self.surface, + )?; + let min = capabilities.min_image_extent; + let max = capabilities.max_image_extent; + vk::Extent2D { + width: u32::clamp(window_size.width, min.width, max.width), + height: u32::clamp(window_size.height, min.height, max.height), + } + }; - pub fn create_swapchain(&self) -> anyhow::Result<(vk::SwapchainKHR, vk::Extent2D)> { - unsafe { - let surface_capabilities = self.surface_capabilities(); - let mut desired_image_count = surface_capabilities.min_image_count + 1; - if surface_capabilities.max_image_count > 0 - && desired_image_count > surface_capabilities.max_image_count - { - desired_image_count = surface_capabilities.max_image_count; + let old = self.active.take(); + if let Some(old) = old.as_ref() { + old.destroy_image_views(device); } - let pre_transform = if surface_capabilities - .supported_transforms - .contains(vk::SurfaceTransformFlagsKHR::IDENTITY) - { - vk::SurfaceTransformFlagsKHR::IDENTITY - } else { - surface_capabilities.current_transform - }; - let present_mode = self - .surface_loader - .get_physical_device_surface_present_modes(self.pdevice, self.surface)? - .iter() - .cloned() - .find(|&mode| mode == vk::PresentModeKHR::MAILBOX) - .unwrap_or(vk::PresentModeKHR::FIFO); - let extent = self.surface_resolution(); - let swapchain = self - .swapchain_loader + + let swapchain = swapchain_ext .create_swapchain( &vk::SwapchainCreateInfoKHR::default() .surface(self.surface) - .min_image_count(desired_image_count) + .min_image_count(self.image_count) .image_color_space(self.surface_format.color_space) - .image_format(self.surface_format.format) + .image_format(format) .image_extent(extent) .image_usage(vk::ImageUsageFlags::COLOR_ATTACHMENT) .image_sharing_mode(vk::SharingMode::EXCLUSIVE) - .pre_transform(pre_transform) + .pre_transform(self.pre_transform) .composite_alpha(vk::CompositeAlphaFlagsKHR::OPAQUE) - .present_mode(present_mode) + .present_mode(self.present_mode) .clipped(true) - .image_array_layers(1), + .image_array_layers(1) + .old_swapchain( + old.as_ref() + .map_or(vk::SwapchainKHR::null(), |old| old.swapchain), + ), None, ) .context("create_swapchain")?; - Ok((swapchain, extent)) - } - } - pub fn create_image_views( - &self, - swapchain: vk::SwapchainKHR, - ) -> anyhow::Result> { - unsafe { - Ok(self - .swapchain_loader - .get_swapchain_images(swapchain)? - .iter() - .map(|&image| { - self.device.create_image_view( + if let Some(old) = old.as_ref() { + old.destroy_swapchain(device); + } + + let images = device.swapchain_ext.get_swapchain_images(swapchain)?; + let images = images + .into_iter() + .map(|image| { + let image_view = device.create_image_view( &vk::ImageViewCreateInfo::default() + .image(image) .view_type(vk::ImageViewType::TYPE_2D) - .format(self.surface_format.format) - .components(vk::ComponentMapping { - r: vk::ComponentSwizzle::R, - g: vk::ComponentSwizzle::G, - b: vk::ComponentSwizzle::B, - a: vk::ComponentSwizzle::A, - }) + .format(format) + .components(vk::ComponentMapping::default()) // identity .subresource_range(vk::ImageSubresourceRange { aspect_mask: vk::ImageAspectFlags::COLOR, base_mip_level: 0, level_count: 1, base_array_layer: 0, layer_count: 1, - }) - .image(image), + }), None, - ) + )?; + Ok::<_, anyhow::Error>((image, image_view)) }) - .collect::, _>>()?) - } - } + .collect::, _>>()?; - pub fn create_framebuffers( - &self, - image_views: &[vk::ImageView], - render_pass: vk::RenderPass, - extent: vk::Extent2D, - ) -> anyhow::Result> { - Ok(image_views - .iter() - .map(|&present_image_view| { - let framebuffer_attachments = [present_image_view]; - unsafe { - self.device.create_framebuffer( - &vk::FramebufferCreateInfo::default() - .render_pass(render_pass) - .attachments(&framebuffer_attachments) - .width(extent.width) - .height(extent.height) - .layers(1), - None, - ) - } - }) - .collect::>()?) + self.active = Some(ActiveSwapchain { + swapchain, + images, + extent, + }); + Ok(()) + } } +} - pub fn create_render_pass(&self) -> vk::RenderPass { - let renderpass_attachments = [vk::AttachmentDescription { - format: self.surface_format.format, - samples: vk::SampleCountFlags::TYPE_1, - load_op: vk::AttachmentLoadOp::CLEAR, - store_op: vk::AttachmentStoreOp::STORE, - final_layout: vk::ImageLayout::PRESENT_SRC_KHR, - ..Default::default() - }]; - let color_attachment_refs = [vk::AttachmentReference { - attachment: 0, - layout: vk::ImageLayout::COLOR_ATTACHMENT_OPTIMAL, - }]; - let dependencies = [vk::SubpassDependency { - src_subpass: vk::SUBPASS_EXTERNAL, - src_stage_mask: vk::PipelineStageFlags::COLOR_ATTACHMENT_OUTPUT, - dst_access_mask: vk::AccessFlags::COLOR_ATTACHMENT_READ - | vk::AccessFlags::COLOR_ATTACHMENT_WRITE, - dst_stage_mask: vk::PipelineStageFlags::COLOR_ATTACHMENT_OUTPUT, - ..Default::default() - }]; - let subpasses = [vk::SubpassDescription::default() - .color_attachments(&color_attachment_refs) - .pipeline_bind_point(vk::PipelineBindPoint::GRAPHICS)]; - let renderpass_create_info = vk::RenderPassCreateInfo::default() - .attachments(&renderpass_attachments) - .subpasses(&subpasses) - .dependencies(&dependencies); +impl ActiveSwapchain { + /// We must destroy the image views we own, but not the images, those are owned by the swapchain. + unsafe fn destroy_image_views(&self, device: &MyDevice) { unsafe { - self.device - .create_render_pass(&renderpass_create_info, None) - .unwrap() + for (_, image_view) in &self.images { + device.destroy_image_view(*image_view, None); + } } } + + /// Destroying the swapchain destroys* the images, so image views must be destroyed beforehand. + unsafe fn destroy_swapchain(&self, device: &MyDevice) { + unsafe { device.swapchain_ext.destroy_swapchain(self.swapchain, None) } + } } -impl Drop for RenderBase { +impl Drop for MySwapchainManager { fn drop(&mut self) { unsafe { - self.device.destroy_device(None); - self.surface_loader.destroy_surface(self.surface, None); - if let Some((debug_utils, call_back)) = - Option::zip(self.debug_utils_loader.take(), self.debug_call_back.take()) - { - debug_utils.destroy_debug_utils_messenger(call_back, None); + self.sync.destroy(&self.device); + if let Some(active) = self.active.as_ref() { + active.destroy_image_views(&self.device); + active.destroy_swapchain(&self.device); } - self.instance.destroy_instance(None); + self.device.surface_ext.destroy_surface(self.surface, None); } } } -pub struct RenderCtx { - pub base: RenderBase, - pub sync: RenderSync, - - pub swapchain: vk::SwapchainKHR, +/// Metadata on drawing a single frame on some image +pub struct DrawFrame { + /// the size of the image pub extent: vk::Extent2D, - pub image_views: Vec, - pub render_pass: vk::RenderPass, - pub framebuffers: Vec, - pub commands: RenderCommandPool, - pub viewports: Box<[vk::Viewport]>, - pub scissors: Box<[vk::Rect2D]>, - pub shader_code: Vec, - pub pipeline: Option, - - pub rendering_paused: bool, - pub recompiling_shaders: bool, - pub start: std::time::Instant, + /// the [`vk::Image`] to draw to + pub image: vk::Image, + /// the [`vk::ImageImage`] to draw to, created from `image` + pub image_view: vk::ImageView, + /// the `acquire_image` semaphore that must be waited for before draw commands are executed + pub acquire_semaphore: vk::Semaphore, + /// the `draw_finished` semaphore that must be signaled when drawing to the image has finished + pub draw_finished_semaphore: vk::Semaphore, + /// the `draw_finished` fence that must be signaled when drawing to the image has finished + pub draw_finished_fence: vk::Fence, +} + +impl MySwapchainManager { + pub fn render( + &mut self, + f: impl FnOnce(DrawFrame) -> anyhow::Result<()>, + ) -> anyhow::Result<()> { + unsafe { + self.device + .wait_for_fences(&[self.sync.render_fence], true, !0)?; + self.device.reset_fences(&[self.sync.render_fence])?; + + const RECREATE_ATTEMPTS: u32 = 10; + for _ in 0..RECREATE_ATTEMPTS { + if self.should_recreate { + self.should_recreate = false; + + // *In theory*, recreating the swapchain allows you to present any acquired images from the old + // swapchain. Which (iirc) requires you to wait for all images to be presented before destroying + // the old swapchain. But we just use the [`ash::Device::device_wait_idle`] "hack" to wait for all + // previous images to finish before immediately destroying the swapchain. + self.device.device_wait_idle()?; + self.recreate_swapchain()?; + } + + let active = self.active.as_ref().unwrap(); + let swapchain_ext = &self.device.swapchain_ext; + match swapchain_ext.acquire_next_image( + active.swapchain, + !0, + self.sync.acquire_semaphore, + vk::Fence::null(), + ) { + Ok((id, suboptimal)) => { + if suboptimal { + self.should_recreate = true; + } + let (image, image_view) = active.images[id as usize]; + f(DrawFrame { + extent: active.extent, + image, + image_view, + acquire_semaphore: self.sync.acquire_semaphore, + draw_finished_semaphore: self.sync.render_semaphore, + draw_finished_fence: self.sync.render_fence, + })?; + + let suboptimal = swapchain_ext.queue_present( + self.device.main_queue, + &vk::PresentInfoKHR::default() + .swapchains(&[active.swapchain]) + .image_indices(&[id]) + .wait_semaphores(&[self.sync.render_semaphore]), + )?; + if suboptimal { + self.should_recreate = true; + } + return Ok(()); + } + Err(vk::Result::ERROR_OUT_OF_DATE_KHR) => { + // retry + self.should_recreate = true; + } + Err(e) => { + return Err(e.into()); + } + } + } + panic!( + "looped {} times trying to acquire swapchain image and failed repeatedly!", + RECREATE_ATTEMPTS + ); + } + } +} + +/// Manages the creation and recreation of [`MyRenderPipeline`], whenever new shader code ([`Self::set_shader_code`]) +/// is submitted or the spec constant is changed ([`Self::set_sky_fs_sun_intensity_factor`]) +pub struct MyRenderPipelineManager { + pub device: Arc, + color_out_format: vk::Format, + shader_code: Vec, + pipeline: Option, // Only used for sky-shader. // NOTE(eddyb) this acts like an integration test for specialization constants. - pub sky_fs_spec_id_0x5007_sun_intensity_extra_spec_const_factor: u32, + sky_fs_spec_id_0x5007_sun_intensity_extra_spec_const_factor: u32, + should_recreate: bool, } -impl RenderCtx { - pub fn new(base: RenderBase, shader_code: Vec) -> anyhow::Result { - let sync = RenderSync::new(&base)?; - - let (swapchain, extent) = base.create_swapchain()?; - let image_views = base.create_image_views(swapchain)?; - let render_pass = base.create_render_pass(); - let framebuffers = base.create_framebuffers(&image_views, render_pass, extent)?; - let commands = RenderCommandPool::new(&base); - let (viewports, scissors) = { - ( - Box::new([vk::Viewport { - x: 0.0, - y: extent.height as f32, - width: extent.width as f32, - height: -(extent.height as f32), - min_depth: 0.0, - max_depth: 1.0, - }]), - Box::new([vk::Rect2D { - offset: vk::Offset2D { x: 0, y: 0 }, - extent, - }]), - ) - }; +pub struct MyRenderPipeline { + pub pipeline: vk::Pipeline, + pub pipeline_layout: vk::PipelineLayout, +} +impl MyRenderPipelineManager { + pub fn new( + device: Arc, + color_out_format: vk::Format, + shader_code: Vec, + ) -> anyhow::Result { Ok(Self { - sync, - base, - swapchain, - extent, - image_views, - commands, - render_pass, - framebuffers, - viewports, - scissors, + device, + color_out_format, shader_code, pipeline: None, - rendering_paused: false, - recompiling_shaders: false, - start: std::time::Instant::now(), - sky_fs_spec_id_0x5007_sun_intensity_extra_spec_const_factor: 100, + should_recreate: true, }) } + #[inline] + pub fn set_sky_fs_sun_intensity_factor(&mut self, factor: u32) { + self.sky_fs_spec_id_0x5007_sun_intensity_extra_spec_const_factor = factor; + self.should_recreate(); + } + + #[inline] + pub fn get_sky_fs_sun_intensity_factor(&self) -> u32 { + self.sky_fs_spec_id_0x5007_sun_intensity_extra_spec_const_factor + } + + #[inline] + pub fn set_shader_code(&mut self, shader_code: Vec) { + self.shader_code = shader_code; + self.should_recreate(); + } + + #[inline] + pub fn should_recreate(&mut self) { + self.should_recreate = true; + } + + pub fn get_pipeline(&mut self) -> anyhow::Result<&MyRenderPipeline> { + if self.should_recreate { + self.rebuild_pipeline()?; + } + Ok(self.pipeline.as_ref().unwrap()) + } + /// Update shaders and rebuild the pipeline - pub fn rebuild_pipeline(&mut self) { + fn rebuild_pipeline(&mut self) -> anyhow::Result<()> { unsafe { - self.cleanup_pipeline(); + self.destroy_pipeline()?; - let shader_module = self - .base - .device - .create_shader_module( - &vk::ShaderModuleCreateInfo::default().code(&self.shader_code), - None, - ) - .unwrap(); - - let pipeline_layout = self - .base - .device - .create_pipeline_layout( - &vk::PipelineLayoutCreateInfo::default().push_constant_ranges(&[ - vk::PushConstantRange::default() - .offset(0) - .size(size_of::() as u32) - .stage_flags(vk::ShaderStageFlags::ALL), - ]), - None, - ) - .unwrap(); + let shader_module = self.device.create_shader_module( + &vk::ShaderModuleCreateInfo::default().code(&self.shader_code), + None, + )?; + + let pipeline_layout = self.device.create_pipeline_layout( + &vk::PipelineLayoutCreateInfo::default().push_constant_ranges(&[ + vk::PushConstantRange::default() + .offset(0) + .size(size_of::() as u32) + .stage_flags(vk::ShaderStageFlags::ALL), + ]), + None, + )?; let mut pipelines = - self.base + self .device .create_graphics_pipelines(vk::PipelineCache::null(), &[vk::GraphicsPipelineCreateInfo::default() .stages( @@ -764,9 +903,9 @@ impl RenderCtx { ..Default::default() }) .multisample_state(&vk::PipelineMultisampleStateCreateInfo { - rasterization_samples: vk::SampleCountFlags::TYPE_1, - ..Default::default() - }) + rasterization_samples: vk::SampleCountFlags::TYPE_1, + ..Default::default() + }) .depth_stencil_state(&vk::PipelineDepthStencilStateCreateInfo::default()) .color_blend_state( &vk::PipelineColorBlendStateCreateInfo::default() @@ -793,355 +932,242 @@ impl RenderCtx { .viewport_count(1), ) .layout(pipeline_layout) - .render_pass(self.render_pass)], None) - .expect("Unable to create graphics pipeline"); + .push_next(&mut vk::PipelineRenderingCreateInfo::default().color_attachment_formats(&[self.color_out_format])) + ], None).map_err(|(_, e)| e) + .context("Unable to create graphics pipeline")?; // A single `pipeline_info` results in a single pipeline. assert_eq!(pipelines.len(), 1); - self.pipeline = pipelines.pop().map(|pipeline| Pipeline { + self.pipeline = pipelines.pop().map(|pipeline| MyRenderPipeline { pipeline, pipeline_layout, }); // shader modules are allowed to be deleted after the pipeline has been created - self.base.device.destroy_shader_module(shader_module, None); + self.device.destroy_shader_module(shader_module, None); + Ok(()) } } - pub fn cleanup_pipeline(&mut self) { + unsafe fn destroy_pipeline(&mut self) -> anyhow::Result<()> { unsafe { - self.base.device.device_wait_idle().unwrap(); if let Some(pipeline) = self.pipeline.take() { - self.base.device.destroy_pipeline(pipeline.pipeline, None); - self.base - .device - .destroy_pipeline_layout(pipeline.pipeline_layout, None); - } - } - } + // Figuring out when the pipeline stops being used is hard, so we take this shortcut + self.device.device_wait_idle()?; - /// Destroys the swapchain, as well as the renderpass and frame and command buffers - pub fn cleanup_swapchain(&mut self) { - unsafe { - self.base.device.device_wait_idle().unwrap(); - // framebuffers - for framebuffer in self.framebuffers.drain(..) { - self.base.device.destroy_framebuffer(framebuffer, None); - } - // image views - for image_view in self.image_views.drain(..) { - self.base.device.destroy_image_view(image_view, None); - } - // swapchain - self.base - .swapchain_loader - .destroy_swapchain(self.swapchain, None); - } - } - - /// Recreates the swapchain, but does not recreate the pipelines because they use dynamic state. - pub fn recreate_swapchain(&mut self) -> anyhow::Result<()> { - let surface_resolution = self.base.surface_resolution(); - - if surface_resolution.width == 0 || surface_resolution.height == 0 { - self.rendering_paused = true; - return Ok(()); - } else if self.rendering_paused { - self.rendering_paused = false; - }; - - self.cleanup_swapchain(); - - let (swapchain, extent) = self.base.create_swapchain()?; - self.swapchain = swapchain; - self.extent = extent; - self.image_views = self.base.create_image_views(self.swapchain)?; - self.framebuffers = - self.base - .create_framebuffers(&self.image_views, self.render_pass, extent)?; - self.viewports = Box::new([vk::Viewport { - x: 0.0, - y: extent.height as f32, - width: extent.width as f32, - height: -(extent.height as f32), - min_depth: 0.0, - max_depth: 1.0, - }]); - self.scissors = Box::new([vk::Rect2D { - offset: vk::Offset2D { x: 0, y: 0 }, - extent, - }]); - - Ok(()) - } - - pub fn render(&mut self) -> anyhow::Result<()> { - let present_index = unsafe { - match self.base.swapchain_loader.acquire_next_image( - self.swapchain, - u64::MAX, - self.sync.present_complete_semaphore, - vk::Fence::null(), - ) { - Ok((idx, _)) => idx, - Err(vk::Result::ERROR_OUT_OF_DATE_KHR) => { - self.recreate_swapchain()?; - return Ok(()); - } - Err(err) => panic!("failed to acquire next image: {err:?}"), - } - }; - - let framebuffer = self.framebuffers[present_index as usize]; - let clear_values = [vk::ClearValue { - color: vk::ClearColorValue { - float32: [0.0, 1.0, 0.0, 0.0], - }, - }]; - - self.draw(self.pipeline.as_ref().unwrap(), framebuffer, &clear_values); - - let wait_semaphors = [self.sync.rendering_complete_semaphore]; - let swapchains = [self.swapchain]; - let image_indices = [present_index]; - let present_info = vk::PresentInfoKHR::default() - .wait_semaphores(&wait_semaphors) - .swapchains(&swapchains) - .image_indices(&image_indices); - unsafe { - match self - .base - .swapchain_loader - .queue_present(self.base.main_queue, &present_info) - { - Err(vk::Result::ERROR_OUT_OF_DATE_KHR) | Ok(true) => self.recreate_swapchain(), - Ok(false) => Ok(()), - Err(err) => Err(anyhow!("failed to present queue: {err:?}")), + self.device.destroy_pipeline(pipeline.pipeline, None); + self.device + .destroy_pipeline_layout(pipeline.pipeline_layout, None); } + Ok(()) } } +} - pub fn draw( - &self, - pipeline: &Pipeline, - framebuffer: vk::Framebuffer, - clear_values: &[vk::ClearValue], - ) { - let render_pass_begin_info = vk::RenderPassBeginInfo::default() - .render_pass(self.render_pass) - .framebuffer(framebuffer) - .render_area(vk::Rect2D { - offset: vk::Offset2D { x: 0, y: 0 }, - extent: self.scissors[0].extent, - }) - .clear_values(clear_values); - self.record_submit_commandbuffer( - &[vk::PipelineStageFlags::COLOR_ATTACHMENT_OUTPUT], - |device, draw_command_buffer| unsafe { - device.cmd_begin_render_pass( - draw_command_buffer, - &render_pass_begin_info, - vk::SubpassContents::INLINE, - ); - device.cmd_bind_pipeline( - draw_command_buffer, - vk::PipelineBindPoint::GRAPHICS, - pipeline.pipeline, - ); - device.cmd_set_viewport(draw_command_buffer, 0, &self.viewports); - device.cmd_set_scissor(draw_command_buffer, 0, &self.scissors); - - let push_constants = ShaderConstants { - width: self.scissors[0].extent.width, - height: self.scissors[0].extent.height, - time: self.start.elapsed().as_secs_f32(), - - // FIXME(eddyb) implement mouse support for the ash runner. - cursor_x: 0.0, - cursor_y: 0.0, - drag_start_x: 0.0, - drag_start_y: 0.0, - drag_end_x: 0.0, - drag_end_y: 0.0, - mouse_button_pressed: 0, - mouse_button_press_time: [f32::NEG_INFINITY; 3], - }; - device.cmd_push_constants( - draw_command_buffer, - pipeline.pipeline_layout, - ash::vk::ShaderStageFlags::ALL, - 0, - bytemuck::bytes_of(&push_constants), - ); - - device.cmd_draw(draw_command_buffer, 3, 1, 0, 0); - device.cmd_end_render_pass(draw_command_buffer); - }, - ); - } - - /// Helper function for submitting command buffers. Immediately waits for the fence before the command buffer - /// is executed. That way we can delay the waiting for the fences by 1 frame which is good for performance. - /// Make sure to create the fence in a signaled state on the first use. - pub fn record_submit_commandbuffer( +impl MyRenderPipeline { + pub fn render( &self, - wait_mask: &[vk::PipelineStageFlags], - f: F, - ) { + device: &MyDevice, + cmd: vk::CommandBuffer, + color_out: vk::ImageView, + extent: vk::Extent2D, + push_constants: ShaderConstants, + ) -> anyhow::Result<()> { unsafe { - self.base - .device - .wait_for_fences(&[self.sync.draw_commands_reuse_fence], true, u64::MAX) - .expect("Wait for fence failed."); - - self.base - .device - .reset_fences(&[self.sync.draw_commands_reuse_fence]) - .expect("Reset fences failed."); - - // As we only have a single command buffer, we can simply reset the entire pool instead of just the buffer. - // Doing this is a little bit faster, see - // https://arm-software.github.io/vulkan_best_practice_for_mobile_developers/samples/performance/command_buffer_usage/command_buffer_usage_tutorial.html#resetting-the-command-pool - self.base - .device - .reset_command_pool(self.commands.pool, vk::CommandPoolResetFlags::empty()) - .expect("Reset command pool failed."); - - let command_buffer_begin_info = vk::CommandBufferBeginInfo::default() - .flags(vk::CommandBufferUsageFlags::ONE_TIME_SUBMIT); - - self.base - .device - .begin_command_buffer( - self.commands.draw_command_buffer, - &command_buffer_begin_info, - ) - .expect("Begin commandbuffer"); - - f(&self.base.device, self.commands.draw_command_buffer); - - self.base - .device - .end_command_buffer(self.commands.draw_command_buffer) - .expect("End commandbuffer"); - - let command_buffers = vec![self.commands.draw_command_buffer]; - let wait_semaphores = &[self.sync.present_complete_semaphore]; - let signal_semaphores = &[self.sync.rendering_complete_semaphore]; - let submit_info = vk::SubmitInfo::default() - .wait_semaphores(wait_semaphores) - .wait_dst_stage_mask(wait_mask) - .command_buffers(&command_buffers) - .signal_semaphores(signal_semaphores); - - self.base - .device - .queue_submit( - self.base.main_queue, - &[submit_info], - self.sync.draw_commands_reuse_fence, - ) - .expect("queue submit failed."); + let render_area = vk::Rect2D { + offset: vk::Offset2D::default(), + extent, + }; + + device.cmd_begin_rendering( + cmd, + &vk::RenderingInfo::default() + .render_area(render_area) + .layer_count(1) + .color_attachments(&[vk::RenderingAttachmentInfo::default() + .image_view(color_out) + .load_op(vk::AttachmentLoadOp::CLEAR) + .store_op(vk::AttachmentStoreOp::STORE) + .clear_value(vk::ClearValue { + color: vk::ClearColorValue { + float32: [0.0, 1.0, 0.0, 0.0], + }, + }) + .image_layout(vk::ImageLayout::COLOR_ATTACHMENT_OPTIMAL)]), + ); + device.cmd_bind_pipeline(cmd, vk::PipelineBindPoint::GRAPHICS, self.pipeline); + device.cmd_set_viewport( + cmd, + 0, + &[vk::Viewport { + // contains a y-flip + x: 0.0, + y: extent.height as f32, + width: extent.width as f32, + height: -(extent.height as f32), + min_depth: 0.0, + max_depth: 1.0, + }], + ); + device.cmd_set_scissor(cmd, 0, &[render_area]); + device.cmd_push_constants( + cmd, + self.pipeline_layout, + vk::ShaderStageFlags::ALL, + 0, + bytemuck::bytes_of(&push_constants), + ); + device.cmd_draw(cmd, 3, 1, 0, 0); + device.cmd_end_rendering(cmd); + Ok(()) } } } -impl Drop for RenderCtx { +impl Drop for MyRenderPipelineManager { fn drop(&mut self) { unsafe { - self.base.device.device_wait_idle().unwrap(); - self.base - .device - .destroy_semaphore(self.sync.present_complete_semaphore, None); - self.base - .device - .destroy_semaphore(self.sync.rendering_complete_semaphore, None); - self.base - .device - .destroy_fence(self.sync.draw_commands_reuse_fence, None); - self.base - .device - .free_command_buffers(self.commands.pool, &[self.commands.draw_command_buffer]); - self.base.device.destroy_render_pass(self.render_pass, None); - self.cleanup_pipeline(); - self.cleanup_swapchain(); - self.base - .device - .destroy_command_pool(self.commands.pool, None); + self.destroy_pipeline().ok(); } } } -pub struct RenderSync { - pub present_complete_semaphore: vk::Semaphore, - pub rendering_complete_semaphore: vk::Semaphore, - pub draw_commands_reuse_fence: vk::Fence, +/// The renderer manages our command buffer and submits the commands, using [`MyRenderPipeline`] for drawing. +pub struct MyRenderer { + pub device: Arc, + pub pipeline: MyRenderPipelineManager, + pub command: SingleCommandBuffer, } -impl RenderSync { - pub fn new(base: &RenderBase) -> anyhow::Result { - let fence_create_info = - vk::FenceCreateInfo::default().flags(vk::FenceCreateFlags::SIGNALED); - - let semaphore_create_info = vk::SemaphoreCreateInfo::default(); +impl MyRenderer { + pub fn new(pipeline: MyRenderPipelineManager) -> anyhow::Result { + Ok(Self { + command: SingleCommandBuffer::new(pipeline.device.clone())?, + device: pipeline.device.clone(), + pipeline, + }) + } + pub fn render_frame( + &mut self, + frame: DrawFrame, + push_constants: ShaderConstants, + ) -> anyhow::Result<()> { unsafe { - let draw_commands_reuse_fence = base.device.create_fence(&fence_create_info, None)?; - let present_complete_semaphore = - base.device.create_semaphore(&semaphore_create_info, None)?; - let rendering_complete_semaphore = - base.device.create_semaphore(&semaphore_create_info, None)?; + let device = &self.device; + let pipeline = self.pipeline.get_pipeline()?; + let cmd = self.command.cmd; - Ok(Self { - present_complete_semaphore, - rendering_complete_semaphore, - draw_commands_reuse_fence, - }) + device.reset_command_pool(self.command.pool, vk::CommandPoolResetFlags::default())?; + + { + device.begin_command_buffer( + cmd, + &vk::CommandBufferBeginInfo::default() + .flags(vk::CommandBufferUsageFlags::ONE_TIME_SUBMIT), + )?; + device.cmd_pipeline_barrier2( + cmd, + &vk::DependencyInfo::default().image_memory_barriers(&[ + vk::ImageMemoryBarrier2::default() + .image(frame.image) + .src_access_mask(vk::AccessFlags2::NONE) + .src_stage_mask(vk::PipelineStageFlags2::ALL_COMMANDS) + .old_layout(vk::ImageLayout::UNDEFINED) + .dst_access_mask(vk::AccessFlags2::COLOR_ATTACHMENT_WRITE) + .dst_stage_mask(vk::PipelineStageFlags2::COLOR_ATTACHMENT_OUTPUT) + .new_layout(vk::ImageLayout::COLOR_ATTACHMENT_OPTIMAL) + .subresource_range( + vk::ImageSubresourceRange::default() + .aspect_mask(vk::ImageAspectFlags::COLOR) + .base_mip_level(0) + .level_count(1) + .base_array_layer(0) + .layer_count(1), + ), + ]), + ); + pipeline.render(&device, cmd, frame.image_view, frame.extent, push_constants)?; + device.cmd_pipeline_barrier2( + cmd, + &vk::DependencyInfo::default().image_memory_barriers(&[ + vk::ImageMemoryBarrier2::default() + .image(frame.image) + .src_access_mask(vk::AccessFlags2::COLOR_ATTACHMENT_WRITE) + .src_stage_mask(vk::PipelineStageFlags2::COLOR_ATTACHMENT_OUTPUT) + .old_layout(vk::ImageLayout::COLOR_ATTACHMENT_OPTIMAL) + .dst_access_mask(vk::AccessFlags2::NONE) + .dst_stage_mask(vk::PipelineStageFlags2::ALL_COMMANDS) + .new_layout(vk::ImageLayout::PRESENT_SRC_KHR) + .subresource_range( + vk::ImageSubresourceRange::default() + .aspect_mask(vk::ImageAspectFlags::COLOR) + .base_mip_level(0) + .level_count(1) + .base_array_layer(0) + .layer_count(1), + ), + ]), + ); + device.end_command_buffer(cmd)?; + } + + device.queue_submit2( + device.main_queue, + &[vk::SubmitInfo2::default() + .wait_semaphore_infos(&[vk::SemaphoreSubmitInfo::default() + .semaphore(frame.acquire_semaphore) + .stage_mask(vk::PipelineStageFlags2::TOP_OF_PIPE)]) + .command_buffer_infos(&[ + vk::CommandBufferSubmitInfo::default().command_buffer(cmd) + ]) + .signal_semaphore_infos(&[vk::SemaphoreSubmitInfo::default() + .semaphore(frame.draw_finished_semaphore) + .stage_mask(vk::PipelineStageFlags2::BOTTOM_OF_PIPE)])], + frame.draw_finished_fence, + )?; + Ok(()) } } } -pub struct RenderCommandPool { +/// A single command buffer with a pool +pub struct SingleCommandBuffer { + pub device: Arc, pub pool: vk::CommandPool, - pub draw_command_buffer: vk::CommandBuffer, + pub cmd: vk::CommandBuffer, } -impl RenderCommandPool { - pub fn new(base: &RenderBase) -> Self { - let pool = { - let pool_create_info = - vk::CommandPoolCreateInfo::default().queue_family_index(base.queue_family_index); - - unsafe { - base.device - .create_command_pool(&pool_create_info, None) - .unwrap() - } - }; - - let command_buffers = { - let command_buffer_allocate_info = vk::CommandBufferAllocateInfo::default() - .command_buffer_count(1) - .command_pool(pool) - .level(vk::CommandBufferLevel::PRIMARY); - - unsafe { - base.device - .allocate_command_buffers(&command_buffer_allocate_info) - .unwrap() - } - }; - - Self { - pool, - draw_command_buffer: command_buffers[0], +impl SingleCommandBuffer { + pub fn new(device: Arc) -> anyhow::Result { + unsafe { + let pool = device.device.create_command_pool( + &vk::CommandPoolCreateInfo::default().queue_family_index(device.main_queue_family), + None, + )?; + + let command_buffers = device.device.allocate_command_buffers( + &vk::CommandBufferAllocateInfo::default() + .command_buffer_count(1) + .command_pool(pool) + .level(vk::CommandBufferLevel::PRIMARY), + )?; + assert_eq!(command_buffers.len(), 1); + let cmd = command_buffers[0]; + + Ok(Self { device, pool, cmd }) } } } -pub struct Pipeline { - pub pipeline: vk::Pipeline, - pub pipeline_layout: vk::PipelineLayout, +impl Drop for SingleCommandBuffer { + fn drop(&mut self) { + unsafe { + let device = &self.device; + device.free_command_buffers(self.pool, &[self.cmd]); + device.destroy_command_pool(self.pool, None); + } + } } unsafe extern "system" fn vulkan_debug_callback( From 2f7e8e45518733e8807d3d496209200f80205fb0 Mon Sep 17 00:00:00 2001 From: firestar99 Date: Tue, 30 Sep 2025 16:13:53 +0200 Subject: [PATCH 07/11] ash runner: split it into different modules --- examples/runners/ash/src/device.rs | 191 ++++ examples/runners/ash/src/graphics.rs | 359 +++++++ examples/runners/ash/src/main.rs | 935 +----------------- .../runners/ash/src/single_command_buffer.rs | 42 + examples/runners/ash/src/swapchain.rs | 348 +++++++ 5 files changed, 950 insertions(+), 925 deletions(-) create mode 100644 examples/runners/ash/src/device.rs create mode 100644 examples/runners/ash/src/graphics.rs create mode 100644 examples/runners/ash/src/single_command_buffer.rs create mode 100644 examples/runners/ash/src/swapchain.rs diff --git a/examples/runners/ash/src/device.rs b/examples/runners/ash/src/device.rs new file mode 100644 index 0000000000..9f658ad465 --- /dev/null +++ b/examples/runners/ash/src/device.rs @@ -0,0 +1,191 @@ +use crate::Options; +use anyhow::{Context, anyhow}; +use ash::{ext, khr, vk}; +use std::borrow::Cow; +use std::ffi::{CStr, c_char}; +use std::ops::Deref; +use std::sync::Arc; + +/// Central struct containing the Vulkan instance and device, among others +pub struct MyDevice { + pub entry: ash::Entry, + pub instance: ash::Instance, + pub physical_device: vk::PhysicalDevice, + pub device: ash::Device, + pub main_queue_family: u32, + pub main_queue: vk::Queue, + pub debug_ext_instance: ext::debug_utils::Instance, + pub debug_ext_device: ext::debug_utils::Device, + pub surface_ext: khr::surface::Instance, + pub swapchain_ext: khr::swapchain::Device, + debug_callback: vk::DebugUtilsMessengerEXT, +} + +impl Deref for MyDevice { + type Target = ash::Device; + + fn deref(&self) -> &Self::Target { + &self.device + } +} + +impl MyDevice { + pub fn new(extension_names: &[*const c_char], options: &Options) -> anyhow::Result> { + unsafe { + cfg_if::cfg_if! { + if #[cfg(target_os = "macos")] { + let entry = ash_molten::load(); + } else { + let entry = ash::Entry::load()?; + } + } + + let instance = { + let layer_names: &'static [_] = if options.debug_layer { + const { &[c"VK_LAYER_KHRONOS_validation".as_ptr()] } + } else { + &[] + }; + + let mut extension_names_raw = extension_names.to_vec(); + extension_names_raw.push(ext::debug_utils::NAME.as_ptr()); + + let app_name = c"VulkanTriangle"; + entry + .create_instance( + &vk::InstanceCreateInfo::default() + .application_info( + &vk::ApplicationInfo::default() + .application_name(app_name) + .application_version(0) + .engine_name(app_name) + .engine_version(0) + .api_version(vk::make_api_version(0, 1, 3, 0)), + ) + .enabled_layer_names(layer_names) + .enabled_extension_names(&extension_names_raw), + None, + ) + .context("create_instance")? + }; + + let debug_instance = ext::debug_utils::Instance::new(&entry, &instance); + let debug_callback = { + debug_instance.create_debug_utils_messenger( + &vk::DebugUtilsMessengerCreateInfoEXT::default() + .message_severity( + vk::DebugUtilsMessageSeverityFlagsEXT::ERROR + | vk::DebugUtilsMessageSeverityFlagsEXT::WARNING + | vk::DebugUtilsMessageSeverityFlagsEXT::INFO, + ) + .message_type( + vk::DebugUtilsMessageTypeFlagsEXT::GENERAL + | vk::DebugUtilsMessageTypeFlagsEXT::VALIDATION + | vk::DebugUtilsMessageTypeFlagsEXT::PERFORMANCE, + ) + .pfn_user_callback(Some(vulkan_debug_callback)), + None, + )? + }; + + let physical_device = { + instance + .enumerate_physical_devices()? + .into_iter() + .min_by_key(|phy| { + match instance.get_physical_device_properties(*phy).device_type { + vk::PhysicalDeviceType::DISCRETE_GPU => 1, + vk::PhysicalDeviceType::VIRTUAL_GPU => 2, + vk::PhysicalDeviceType::INTEGRATED_GPU => 3, + vk::PhysicalDeviceType::CPU => 4, + _ => 5, + } + }) + .ok_or(anyhow!("No physical devices available"))? + }; + + let main_queue_family = { + instance + .get_physical_device_queue_family_properties(physical_device) + .into_iter() + .enumerate() + .find(|(_, prop)| prop.queue_flags.contains(vk::QueueFlags::GRAPHICS)) + .ok_or(anyhow!( + "No graphics + compute queues on physical device available" + ))? + .0 as u32 + }; + + let device = instance + .create_device( + physical_device, + &vk::DeviceCreateInfo::default() + .push_next( + &mut vk::PhysicalDeviceVulkanMemoryModelFeatures::default() + .vulkan_memory_model(true), + ) + .push_next( + &mut vk::PhysicalDeviceVulkan13Features::default() + .synchronization2(true) + .dynamic_rendering(true), + ) + .queue_create_infos(&[vk::DeviceQueueCreateInfo::default() + .queue_family_index(main_queue_family) + .queue_priorities(&[1.0])]) + .enabled_extension_names(&[ + khr::swapchain::NAME.as_ptr(), + khr::shader_non_semantic_info::NAME.as_ptr(), + ]), + None, + ) + .context("create_device")?; + let main_queue = device.get_device_queue(main_queue_family, 0); + + Ok(Arc::new(Self { + debug_ext_device: ext::debug_utils::Device::new(&instance, &device), + surface_ext: khr::surface::Instance::new(&entry, &instance), + swapchain_ext: khr::swapchain::Device::new(&instance, &device), + entry, + instance, + physical_device, + device, + main_queue_family, + main_queue, + debug_ext_instance: debug_instance, + debug_callback, + })) + } + } +} + +impl Drop for MyDevice { + fn drop(&mut self) { + unsafe { + self.debug_ext_instance + .destroy_debug_utils_messenger(self.debug_callback, None); + self.device.destroy_device(None); + self.instance.destroy_instance(None); + } + } +} + +unsafe extern "system" fn vulkan_debug_callback( + message_severity: vk::DebugUtilsMessageSeverityFlagsEXT, + _message_type: vk::DebugUtilsMessageTypeFlagsEXT, + p_callback_data: *const vk::DebugUtilsMessengerCallbackDataEXT<'_>, + _user_data: *mut std::os::raw::c_void, +) -> vk::Bool32 { + let callback_data = unsafe { *p_callback_data }; + let message_id_name = if callback_data.p_message_id_name.is_null() { + Cow::from("") + } else { + unsafe { CStr::from_ptr(callback_data.p_message_id_name).to_string_lossy() } + }; + let message = if callback_data.p_message.is_null() { + Cow::from("") + } else { + unsafe { CStr::from_ptr(callback_data.p_message).to_string_lossy() } + }; + println!("{message_severity:?}: [{message_id_name}] : {message}"); + vk::FALSE +} diff --git a/examples/runners/ash/src/graphics.rs b/examples/runners/ash/src/graphics.rs new file mode 100644 index 0000000000..58bfc970a3 --- /dev/null +++ b/examples/runners/ash/src/graphics.rs @@ -0,0 +1,359 @@ +use crate::device::MyDevice; +use crate::single_command_buffer::SingleCommandBuffer; +use crate::swapchain::DrawFrame; +use anyhow::Context; +use ash::vk; +use shared::ShaderConstants; +use std::sync::Arc; + +/// Manages the creation and recreation of [`MyRenderPipeline`], whenever new shader code ([`Self::set_shader_code`]) +/// is submitted or the spec constant is changed ([`Self::set_sky_fs_sun_intensity_factor`]) +pub struct MyRenderPipelineManager { + pub device: Arc, + color_out_format: vk::Format, + shader_code: Vec, + pipeline: Option, + + // Only used for sky-shader. + // NOTE(eddyb) this acts like an integration test for specialization constants. + sky_fs_spec_id_0x5007_sun_intensity_extra_spec_const_factor: u32, + should_recreate: bool, +} + +pub struct MyRenderPipeline { + pub pipeline: vk::Pipeline, + pub pipeline_layout: vk::PipelineLayout, +} + +impl MyRenderPipelineManager { + pub fn new( + device: Arc, + color_out_format: vk::Format, + shader_code: Vec, + ) -> anyhow::Result { + Ok(Self { + device, + color_out_format, + shader_code, + pipeline: None, + sky_fs_spec_id_0x5007_sun_intensity_extra_spec_const_factor: 100, + should_recreate: true, + }) + } + + #[inline] + pub fn set_sky_fs_sun_intensity_factor(&mut self, factor: u32) { + self.sky_fs_spec_id_0x5007_sun_intensity_extra_spec_const_factor = factor; + self.should_recreate(); + } + + #[inline] + pub fn get_sky_fs_sun_intensity_factor(&self) -> u32 { + self.sky_fs_spec_id_0x5007_sun_intensity_extra_spec_const_factor + } + + #[inline] + pub fn set_shader_code(&mut self, shader_code: Vec) { + self.shader_code = shader_code; + self.should_recreate(); + } + + #[inline] + pub fn should_recreate(&mut self) { + self.should_recreate = true; + } + + pub fn get_pipeline(&mut self) -> anyhow::Result<&MyRenderPipeline> { + if self.should_recreate { + self.rebuild_pipeline()?; + } + Ok(self.pipeline.as_ref().unwrap()) + } + + /// Update shaders and rebuild the pipeline + fn rebuild_pipeline(&mut self) -> anyhow::Result<()> { + unsafe { + self.destroy_pipeline()?; + + let shader_module = self.device.create_shader_module( + &vk::ShaderModuleCreateInfo::default().code(&self.shader_code), + None, + )?; + + let pipeline_layout = self.device.create_pipeline_layout( + &vk::PipelineLayoutCreateInfo::default().push_constant_ranges(&[ + vk::PushConstantRange::default() + .offset(0) + .size(size_of::() as u32) + .stage_flags(vk::ShaderStageFlags::ALL), + ]), + None, + )?; + + let mut pipelines = + self + .device + .create_graphics_pipelines(vk::PipelineCache::null(), &[vk::GraphicsPipelineCreateInfo::default() + .stages( + &[ + vk::PipelineShaderStageCreateInfo { + module: shader_module, + p_name: c"main_vs".as_ptr(), + stage: vk::ShaderStageFlags::VERTEX, + ..Default::default() + }, + vk::PipelineShaderStageCreateInfo { + module: shader_module, + p_name: c"main_fs".as_ptr(), + stage: vk::ShaderStageFlags::FRAGMENT, + // NOTE(eddyb) this acts like an integration test for specialization constants. + p_specialization_info: &vk::SpecializationInfo::default() + .map_entries(&[vk::SpecializationMapEntry::default() + .constant_id(0x5007) + .offset(0) + .size(4)]) + .data(&u32::to_le_bytes( + self.sky_fs_spec_id_0x5007_sun_intensity_extra_spec_const_factor, + )), + ..Default::default() + }, + ], + ) + .vertex_input_state(&vk::PipelineVertexInputStateCreateInfo::default()) + .input_assembly_state(&vk::PipelineInputAssemblyStateCreateInfo { + topology: vk::PrimitiveTopology::TRIANGLE_LIST, + ..Default::default() + }) + .rasterization_state(&vk::PipelineRasterizationStateCreateInfo { + front_face: vk::FrontFace::COUNTER_CLOCKWISE, + line_width: 1.0, + ..Default::default() + }) + .multisample_state(&vk::PipelineMultisampleStateCreateInfo { + rasterization_samples: vk::SampleCountFlags::TYPE_1, + ..Default::default() + }) + .depth_stencil_state(&vk::PipelineDepthStencilStateCreateInfo::default()) + .color_blend_state( + &vk::PipelineColorBlendStateCreateInfo::default() + .attachments( + &[vk::PipelineColorBlendAttachmentState { + blend_enable: 0, + src_color_blend_factor: vk::BlendFactor::SRC_COLOR, + dst_color_blend_factor: vk::BlendFactor::ONE_MINUS_DST_COLOR, + color_blend_op: vk::BlendOp::ADD, + src_alpha_blend_factor: vk::BlendFactor::ZERO, + dst_alpha_blend_factor: vk::BlendFactor::ZERO, + alpha_blend_op: vk::BlendOp::ADD, + color_write_mask: vk::ColorComponentFlags::RGBA, + }], + ), + ) + .dynamic_state( + &vk::PipelineDynamicStateCreateInfo::default() + .dynamic_states(&[vk::DynamicState::VIEWPORT, vk::DynamicState::SCISSOR]), + ) + .viewport_state( + &vk::PipelineViewportStateCreateInfo::default() + .scissor_count(1) + .viewport_count(1), + ) + .layout(pipeline_layout) + .push_next(&mut vk::PipelineRenderingCreateInfo::default().color_attachment_formats(&[self.color_out_format])) + ], None).map_err(|(_, e)| e) + .context("Unable to create graphics pipeline")?; + + // A single `pipeline_info` results in a single pipeline. + assert_eq!(pipelines.len(), 1); + self.pipeline = pipelines.pop().map(|pipeline| MyRenderPipeline { + pipeline, + pipeline_layout, + }); + + // shader modules are allowed to be deleted after the pipeline has been created + self.device.destroy_shader_module(shader_module, None); + Ok(()) + } + } + + unsafe fn destroy_pipeline(&mut self) -> anyhow::Result<()> { + unsafe { + if let Some(pipeline) = self.pipeline.take() { + // Figuring out when the pipeline stops being used is hard, so we take this shortcut + self.device.device_wait_idle()?; + + self.device.destroy_pipeline(pipeline.pipeline, None); + self.device + .destroy_pipeline_layout(pipeline.pipeline_layout, None); + } + Ok(()) + } + } +} + +impl MyRenderPipeline { + pub fn render( + &self, + device: &MyDevice, + cmd: vk::CommandBuffer, + color_out: vk::ImageView, + extent: vk::Extent2D, + push_constants: ShaderConstants, + ) -> anyhow::Result<()> { + unsafe { + let render_area = vk::Rect2D { + offset: vk::Offset2D::default(), + extent, + }; + + device.cmd_begin_rendering( + cmd, + &vk::RenderingInfo::default() + .render_area(render_area) + .layer_count(1) + .color_attachments(&[vk::RenderingAttachmentInfo::default() + .image_view(color_out) + .load_op(vk::AttachmentLoadOp::CLEAR) + .store_op(vk::AttachmentStoreOp::STORE) + .clear_value(vk::ClearValue { + color: vk::ClearColorValue { + float32: [0.0, 1.0, 0.0, 0.0], + }, + }) + .image_layout(vk::ImageLayout::COLOR_ATTACHMENT_OPTIMAL)]), + ); + device.cmd_bind_pipeline(cmd, vk::PipelineBindPoint::GRAPHICS, self.pipeline); + device.cmd_set_viewport( + cmd, + 0, + &[vk::Viewport { + // contains a y-flip + x: 0.0, + y: extent.height as f32, + width: extent.width as f32, + height: -(extent.height as f32), + min_depth: 0.0, + max_depth: 1.0, + }], + ); + device.cmd_set_scissor(cmd, 0, &[render_area]); + device.cmd_push_constants( + cmd, + self.pipeline_layout, + vk::ShaderStageFlags::ALL, + 0, + bytemuck::bytes_of(&push_constants), + ); + device.cmd_draw(cmd, 3, 1, 0, 0); + device.cmd_end_rendering(cmd); + Ok(()) + } + } +} + +impl Drop for MyRenderPipelineManager { + fn drop(&mut self) { + unsafe { + self.destroy_pipeline().ok(); + } + } +} + +/// The renderer manages our command buffer and submits the commands, using [`MyRenderPipeline`] for drawing. +pub struct MyRenderer { + pub device: Arc, + pub pipeline: MyRenderPipelineManager, + pub command: SingleCommandBuffer, +} + +impl MyRenderer { + pub fn new(pipeline: MyRenderPipelineManager) -> anyhow::Result { + Ok(Self { + command: SingleCommandBuffer::new(pipeline.device.clone())?, + device: pipeline.device.clone(), + pipeline, + }) + } + + pub fn render_frame( + &mut self, + frame: DrawFrame, + push_constants: ShaderConstants, + ) -> anyhow::Result<()> { + unsafe { + let device = &self.device; + let pipeline = self.pipeline.get_pipeline()?; + let cmd = self.command.cmd; + + device.reset_command_pool(self.command.pool, vk::CommandPoolResetFlags::default())?; + + { + device.begin_command_buffer( + cmd, + &vk::CommandBufferBeginInfo::default() + .flags(vk::CommandBufferUsageFlags::ONE_TIME_SUBMIT), + )?; + device.cmd_pipeline_barrier2( + cmd, + &vk::DependencyInfo::default().image_memory_barriers(&[ + vk::ImageMemoryBarrier2::default() + .image(frame.image) + .src_access_mask(vk::AccessFlags2::NONE) + .src_stage_mask(vk::PipelineStageFlags2::ALL_COMMANDS) + .old_layout(vk::ImageLayout::UNDEFINED) + .dst_access_mask(vk::AccessFlags2::COLOR_ATTACHMENT_WRITE) + .dst_stage_mask(vk::PipelineStageFlags2::COLOR_ATTACHMENT_OUTPUT) + .new_layout(vk::ImageLayout::COLOR_ATTACHMENT_OPTIMAL) + .subresource_range( + vk::ImageSubresourceRange::default() + .aspect_mask(vk::ImageAspectFlags::COLOR) + .base_mip_level(0) + .level_count(1) + .base_array_layer(0) + .layer_count(1), + ), + ]), + ); + pipeline.render(&device, cmd, frame.image_view, frame.extent, push_constants)?; + device.cmd_pipeline_barrier2( + cmd, + &vk::DependencyInfo::default().image_memory_barriers(&[ + vk::ImageMemoryBarrier2::default() + .image(frame.image) + .src_access_mask(vk::AccessFlags2::COLOR_ATTACHMENT_WRITE) + .src_stage_mask(vk::PipelineStageFlags2::COLOR_ATTACHMENT_OUTPUT) + .old_layout(vk::ImageLayout::COLOR_ATTACHMENT_OPTIMAL) + .dst_access_mask(vk::AccessFlags2::NONE) + .dst_stage_mask(vk::PipelineStageFlags2::ALL_COMMANDS) + .new_layout(vk::ImageLayout::PRESENT_SRC_KHR) + .subresource_range( + vk::ImageSubresourceRange::default() + .aspect_mask(vk::ImageAspectFlags::COLOR) + .base_mip_level(0) + .level_count(1) + .base_array_layer(0) + .layer_count(1), + ), + ]), + ); + device.end_command_buffer(cmd)?; + } + + device.queue_submit2( + device.main_queue, + &[vk::SubmitInfo2::default() + .wait_semaphore_infos(&[vk::SemaphoreSubmitInfo::default() + .semaphore(frame.acquire_semaphore) + .stage_mask(vk::PipelineStageFlags2::TOP_OF_PIPE)]) + .command_buffer_infos(&[ + vk::CommandBufferSubmitInfo::default().command_buffer(cmd) + ]) + .signal_semaphore_infos(&[vk::SemaphoreSubmitInfo::default() + .semaphore(frame.draw_finished_semaphore) + .stage_mask(vk::PipelineStageFlags2::BOTTOM_OF_PIPE)])], + frame.draw_finished_fence, + )?; + Ok(()) + } + } +} diff --git a/examples/runners/ash/src/main.rs b/examples/runners/ash/src/main.rs index 7f943ba8c5..7f97c281ad 100644 --- a/examples/runners/ash/src/main.rs +++ b/examples/runners/ash/src/main.rs @@ -70,18 +70,16 @@ // crate-specific exceptions: // #![allow()] +use crate::device::MyDevice; +use crate::graphics::{MyRenderPipelineManager, MyRenderer}; +use crate::swapchain::MySwapchainManager; use anyhow::{Context, anyhow}; -use ash::{ext, khr, util::read_spv, vk}; +use ash::util::read_spv; use clap::{Parser, ValueEnum}; -use raw_window_handle::{HasDisplayHandle as _, HasWindowHandle as _}; +use raw_window_handle::HasDisplayHandle as _; use shared::ShaderConstants; use spirv_builder::{MetadataPrintout, SpirvBuilder}; -use std::ffi::c_char; -use std::ops::Deref; -use std::sync::Arc; use std::{ - borrow::Cow, - ffi::CStr, fs::File, path::PathBuf, sync::mpsc::{TryRecvError, sync_channel}, @@ -93,6 +91,11 @@ use winit::{ event_loop::{ControlFlow, EventLoop}, }; +pub mod device; +pub mod graphics; +pub mod single_command_buffer; +pub mod swapchain; + // This runner currently doesn't run the `compute` shader example. #[derive(Debug, PartialEq, Eq, Copy, Clone, ValueEnum)] pub enum RustGPUShader { @@ -272,921 +275,3 @@ pub fn compile_shaders(shader: &RustGPUShader) -> anyhow::Result> { Ok(read_spv(&mut File::open(spv_path)?)?) } - -/// Central struct containing the Vulkan instance and device, among others -pub struct MyDevice { - pub entry: ash::Entry, - pub instance: ash::Instance, - pub physical_device: vk::PhysicalDevice, - pub device: ash::Device, - pub main_queue_family: u32, - pub main_queue: vk::Queue, - pub debug_ext_instance: ext::debug_utils::Instance, - pub debug_ext_device: ext::debug_utils::Device, - pub surface_ext: khr::surface::Instance, - pub swapchain_ext: khr::swapchain::Device, - debug_callback: vk::DebugUtilsMessengerEXT, -} - -impl Deref for MyDevice { - type Target = ash::Device; - - fn deref(&self) -> &Self::Target { - &self.device - } -} - -impl MyDevice { - pub fn new(extension_names: &[*const c_char], options: &Options) -> anyhow::Result> { - unsafe { - cfg_if::cfg_if! { - if #[cfg(target_os = "macos")] { - let entry = ash_molten::load(); - } else { - let entry = ash::Entry::load()?; - } - } - - let instance = { - let layer_names: &'static [_] = if options.debug_layer { - const { &[c"VK_LAYER_KHRONOS_validation".as_ptr()] } - } else { - &[] - }; - - let mut extension_names_raw = extension_names.to_vec(); - extension_names_raw.push(ext::debug_utils::NAME.as_ptr()); - - let app_name = c"VulkanTriangle"; - entry - .create_instance( - &vk::InstanceCreateInfo::default() - .application_info( - &vk::ApplicationInfo::default() - .application_name(app_name) - .application_version(0) - .engine_name(app_name) - .engine_version(0) - .api_version(vk::make_api_version(0, 1, 3, 0)), - ) - .enabled_layer_names(layer_names) - .enabled_extension_names(&extension_names_raw), - None, - ) - .context("create_instance")? - }; - - let debug_instance = ext::debug_utils::Instance::new(&entry, &instance); - let debug_callback = { - debug_instance.create_debug_utils_messenger( - &vk::DebugUtilsMessengerCreateInfoEXT::default() - .message_severity( - vk::DebugUtilsMessageSeverityFlagsEXT::ERROR - | vk::DebugUtilsMessageSeverityFlagsEXT::WARNING - | vk::DebugUtilsMessageSeverityFlagsEXT::INFO, - ) - .message_type( - vk::DebugUtilsMessageTypeFlagsEXT::GENERAL - | vk::DebugUtilsMessageTypeFlagsEXT::VALIDATION - | vk::DebugUtilsMessageTypeFlagsEXT::PERFORMANCE, - ) - .pfn_user_callback(Some(vulkan_debug_callback)), - None, - )? - }; - - let physical_device = { - instance - .enumerate_physical_devices()? - .into_iter() - .min_by_key(|phy| { - match instance.get_physical_device_properties(*phy).device_type { - vk::PhysicalDeviceType::DISCRETE_GPU => 1, - vk::PhysicalDeviceType::VIRTUAL_GPU => 2, - vk::PhysicalDeviceType::INTEGRATED_GPU => 3, - vk::PhysicalDeviceType::CPU => 4, - _ => 5, - } - }) - .ok_or(anyhow!("No physical devices available"))? - }; - - let main_queue_family = { - instance - .get_physical_device_queue_family_properties(physical_device) - .into_iter() - .enumerate() - .find(|(_, prop)| prop.queue_flags.contains(vk::QueueFlags::GRAPHICS)) - .ok_or(anyhow!( - "No graphics + compute queues on physical device available" - ))? - .0 as u32 - }; - - let device = instance - .create_device( - physical_device, - &vk::DeviceCreateInfo::default() - .push_next( - &mut vk::PhysicalDeviceVulkanMemoryModelFeatures::default() - .vulkan_memory_model(true), - ) - .push_next( - &mut vk::PhysicalDeviceVulkan13Features::default() - .synchronization2(true) - .dynamic_rendering(true), - ) - .queue_create_infos(&[vk::DeviceQueueCreateInfo::default() - .queue_family_index(main_queue_family) - .queue_priorities(&[1.0])]) - .enabled_extension_names(&[ - khr::swapchain::NAME.as_ptr(), - khr::shader_non_semantic_info::NAME.as_ptr(), - ]), - None, - ) - .context("create_device")?; - let main_queue = device.get_device_queue(main_queue_family, 0); - - Ok(Arc::new(Self { - debug_ext_device: ext::debug_utils::Device::new(&instance, &device), - surface_ext: khr::surface::Instance::new(&entry, &instance), - swapchain_ext: khr::swapchain::Device::new(&instance, &device), - entry, - instance, - physical_device, - device, - main_queue_family, - main_queue, - debug_ext_instance: debug_instance, - debug_callback, - })) - } - } -} - -impl Drop for MyDevice { - fn drop(&mut self) { - unsafe { - self.debug_ext_instance - .destroy_debug_utils_messenger(self.debug_callback, None); - self.device.destroy_device(None); - self.instance.destroy_instance(None); - } - } -} - -/// A binary semaphore for swapchain operations -pub struct SwapchainSync { - acquire_semaphore: vk::Semaphore, - render_semaphore: vk::Semaphore, - render_fence: vk::Fence, -} - -impl SwapchainSync { - pub unsafe fn new(device: &MyDevice) -> anyhow::Result { - unsafe { - let signaled_fence = - vk::FenceCreateInfo::default().flags(vk::FenceCreateFlags::SIGNALED); - Ok(Self { - acquire_semaphore: device - .create_semaphore(&vk::SemaphoreCreateInfo::default(), None)?, - render_semaphore: device - .create_semaphore(&vk::SemaphoreCreateInfo::default(), None)?, - render_fence: device.create_fence(&signaled_fence, None)?, - }) - } - } - - pub unsafe fn destroy(&self, device: &MyDevice) { - unsafe { - device.destroy_semaphore(self.acquire_semaphore, None); - device.destroy_semaphore(self.render_semaphore, None); - device.destroy_fence(self.render_fence, None); - } - } -} - -/// Takes care of all things swapchain related -/// -/// Intentionally kept simple and does not offer support for multiple frames in flight -pub struct MySwapchainManager { - pub device: Arc, - pub window: winit::window::Window, - pub surface: vk::SurfaceKHR, - pub surface_format: vk::SurfaceFormatKHR, - pub surface_capabilities: vk::SurfaceCapabilitiesKHR, - pub present_mode: vk::PresentModeKHR, - pub image_count: u32, - pub pre_transform: vk::SurfaceTransformFlagsKHR, - - // state below - active: Option, - should_recreate: bool, - sync: SwapchainSync, -} - -struct ActiveSwapchain { - extent: vk::Extent2D, - swapchain: vk::SwapchainKHR, - images: Vec<(vk::Image, vk::ImageView)>, -} - -impl MySwapchainManager { - pub fn new(device: Arc, window: winit::window::Window) -> anyhow::Result { - unsafe { - let surface_ext = &device.surface_ext; - - let surface = ash_window::create_surface( - &device.entry, - &device.instance, - window.display_handle().unwrap().into(), - window.window_handle().unwrap().into(), - None, - ) - .context("create_surface")?; - - let surface_format = { - let acceptable_formats = { - [ - vk::Format::R8G8B8_SRGB, - vk::Format::B8G8R8_SRGB, - vk::Format::R8G8B8A8_SRGB, - vk::Format::B8G8R8A8_SRGB, - vk::Format::A8B8G8R8_SRGB_PACK32, - ] - }; - *surface_ext - .get_physical_device_surface_formats(device.physical_device, surface)? - .iter() - .find(|sfmt| acceptable_formats.contains(&sfmt.format)) - .context("Unable to find suitable surface format.")? - }; - - let surface_capabilities = surface_ext - .get_physical_device_surface_capabilities(device.physical_device, surface)?; - let pre_transform = surface_capabilities.current_transform; - - let present_mode = surface_ext - .get_physical_device_surface_present_modes(device.physical_device, surface)? - .iter() - .cloned() - // Mailbox is preferred - .find(|&mode| mode == vk::PresentModeKHR::MAILBOX) - // FIFO is guaranteed to be available - .unwrap_or(vk::PresentModeKHR::FIFO); - - let image_count = { - let mut image_count = match present_mode { - // tripple buffering in mailbox mode:: one presenting, one ready and one drawing - vk::PresentModeKHR::MAILBOX => 3, - // double buffering in fifo mode: one presenting, one drawing - vk::PresentModeKHR::FIFO => 2, - _ => unreachable!(), - }; - if surface_capabilities.max_image_count != 0 { - image_count = image_count.min(surface_capabilities.max_image_count); - } - image_count.max(surface_capabilities.min_image_count) - }; - - let sync = SwapchainSync::new(&device)?; - Ok(Self { - device, - window, - surface, - surface_format, - surface_capabilities, - present_mode, - image_count, - pre_transform, - - active: None, - should_recreate: true, - sync, - }) - } - } - - #[inline] - fn should_recreate(&mut self) { - self.should_recreate = true; - } - - /// After this function is called, `Self.active` is initialized - unsafe fn recreate_swapchain(&mut self) -> anyhow::Result<()> { - unsafe { - let device = &self.device; - let swapchain_ext = &device.swapchain_ext; - let surface_ext = &self.device.surface_ext; - let format = self.surface_format.format; - - let extent = { - let window_size = self.window.inner_size(); - let capabilities = surface_ext.get_physical_device_surface_capabilities( - self.device.physical_device, - self.surface, - )?; - let min = capabilities.min_image_extent; - let max = capabilities.max_image_extent; - vk::Extent2D { - width: u32::clamp(window_size.width, min.width, max.width), - height: u32::clamp(window_size.height, min.height, max.height), - } - }; - - let old = self.active.take(); - if let Some(old) = old.as_ref() { - old.destroy_image_views(device); - } - - let swapchain = swapchain_ext - .create_swapchain( - &vk::SwapchainCreateInfoKHR::default() - .surface(self.surface) - .min_image_count(self.image_count) - .image_color_space(self.surface_format.color_space) - .image_format(format) - .image_extent(extent) - .image_usage(vk::ImageUsageFlags::COLOR_ATTACHMENT) - .image_sharing_mode(vk::SharingMode::EXCLUSIVE) - .pre_transform(self.pre_transform) - .composite_alpha(vk::CompositeAlphaFlagsKHR::OPAQUE) - .present_mode(self.present_mode) - .clipped(true) - .image_array_layers(1) - .old_swapchain( - old.as_ref() - .map_or(vk::SwapchainKHR::null(), |old| old.swapchain), - ), - None, - ) - .context("create_swapchain")?; - - if let Some(old) = old.as_ref() { - old.destroy_swapchain(device); - } - - let images = device.swapchain_ext.get_swapchain_images(swapchain)?; - let images = images - .into_iter() - .map(|image| { - let image_view = device.create_image_view( - &vk::ImageViewCreateInfo::default() - .image(image) - .view_type(vk::ImageViewType::TYPE_2D) - .format(format) - .components(vk::ComponentMapping::default()) // identity - .subresource_range(vk::ImageSubresourceRange { - aspect_mask: vk::ImageAspectFlags::COLOR, - base_mip_level: 0, - level_count: 1, - base_array_layer: 0, - layer_count: 1, - }), - None, - )?; - Ok::<_, anyhow::Error>((image, image_view)) - }) - .collect::, _>>()?; - - self.active = Some(ActiveSwapchain { - swapchain, - images, - extent, - }); - Ok(()) - } - } -} - -impl ActiveSwapchain { - /// We must destroy the image views we own, but not the images, those are owned by the swapchain. - unsafe fn destroy_image_views(&self, device: &MyDevice) { - unsafe { - for (_, image_view) in &self.images { - device.destroy_image_view(*image_view, None); - } - } - } - - /// Destroying the swapchain destroys* the images, so image views must be destroyed beforehand. - unsafe fn destroy_swapchain(&self, device: &MyDevice) { - unsafe { device.swapchain_ext.destroy_swapchain(self.swapchain, None) } - } -} - -impl Drop for MySwapchainManager { - fn drop(&mut self) { - unsafe { - self.sync.destroy(&self.device); - if let Some(active) = self.active.as_ref() { - active.destroy_image_views(&self.device); - active.destroy_swapchain(&self.device); - } - self.device.surface_ext.destroy_surface(self.surface, None); - } - } -} - -/// Metadata on drawing a single frame on some image -pub struct DrawFrame { - /// the size of the image - pub extent: vk::Extent2D, - /// the [`vk::Image`] to draw to - pub image: vk::Image, - /// the [`vk::ImageImage`] to draw to, created from `image` - pub image_view: vk::ImageView, - /// the `acquire_image` semaphore that must be waited for before draw commands are executed - pub acquire_semaphore: vk::Semaphore, - /// the `draw_finished` semaphore that must be signaled when drawing to the image has finished - pub draw_finished_semaphore: vk::Semaphore, - /// the `draw_finished` fence that must be signaled when drawing to the image has finished - pub draw_finished_fence: vk::Fence, -} - -impl MySwapchainManager { - pub fn render( - &mut self, - f: impl FnOnce(DrawFrame) -> anyhow::Result<()>, - ) -> anyhow::Result<()> { - unsafe { - self.device - .wait_for_fences(&[self.sync.render_fence], true, !0)?; - self.device.reset_fences(&[self.sync.render_fence])?; - - const RECREATE_ATTEMPTS: u32 = 10; - for _ in 0..RECREATE_ATTEMPTS { - if self.should_recreate { - self.should_recreate = false; - - // *In theory*, recreating the swapchain allows you to present any acquired images from the old - // swapchain. Which (iirc) requires you to wait for all images to be presented before destroying - // the old swapchain. But we just use the [`ash::Device::device_wait_idle`] "hack" to wait for all - // previous images to finish before immediately destroying the swapchain. - self.device.device_wait_idle()?; - self.recreate_swapchain()?; - } - - let active = self.active.as_ref().unwrap(); - let swapchain_ext = &self.device.swapchain_ext; - match swapchain_ext.acquire_next_image( - active.swapchain, - !0, - self.sync.acquire_semaphore, - vk::Fence::null(), - ) { - Ok((id, suboptimal)) => { - if suboptimal { - self.should_recreate = true; - } - let (image, image_view) = active.images[id as usize]; - f(DrawFrame { - extent: active.extent, - image, - image_view, - acquire_semaphore: self.sync.acquire_semaphore, - draw_finished_semaphore: self.sync.render_semaphore, - draw_finished_fence: self.sync.render_fence, - })?; - - let suboptimal = swapchain_ext.queue_present( - self.device.main_queue, - &vk::PresentInfoKHR::default() - .swapchains(&[active.swapchain]) - .image_indices(&[id]) - .wait_semaphores(&[self.sync.render_semaphore]), - )?; - if suboptimal { - self.should_recreate = true; - } - return Ok(()); - } - Err(vk::Result::ERROR_OUT_OF_DATE_KHR) => { - // retry - self.should_recreate = true; - } - Err(e) => { - return Err(e.into()); - } - } - } - panic!( - "looped {} times trying to acquire swapchain image and failed repeatedly!", - RECREATE_ATTEMPTS - ); - } - } -} - -/// Manages the creation and recreation of [`MyRenderPipeline`], whenever new shader code ([`Self::set_shader_code`]) -/// is submitted or the spec constant is changed ([`Self::set_sky_fs_sun_intensity_factor`]) -pub struct MyRenderPipelineManager { - pub device: Arc, - color_out_format: vk::Format, - shader_code: Vec, - pipeline: Option, - - // Only used for sky-shader. - // NOTE(eddyb) this acts like an integration test for specialization constants. - sky_fs_spec_id_0x5007_sun_intensity_extra_spec_const_factor: u32, - should_recreate: bool, -} - -pub struct MyRenderPipeline { - pub pipeline: vk::Pipeline, - pub pipeline_layout: vk::PipelineLayout, -} - -impl MyRenderPipelineManager { - pub fn new( - device: Arc, - color_out_format: vk::Format, - shader_code: Vec, - ) -> anyhow::Result { - Ok(Self { - device, - color_out_format, - shader_code, - pipeline: None, - sky_fs_spec_id_0x5007_sun_intensity_extra_spec_const_factor: 100, - should_recreate: true, - }) - } - - #[inline] - pub fn set_sky_fs_sun_intensity_factor(&mut self, factor: u32) { - self.sky_fs_spec_id_0x5007_sun_intensity_extra_spec_const_factor = factor; - self.should_recreate(); - } - - #[inline] - pub fn get_sky_fs_sun_intensity_factor(&self) -> u32 { - self.sky_fs_spec_id_0x5007_sun_intensity_extra_spec_const_factor - } - - #[inline] - pub fn set_shader_code(&mut self, shader_code: Vec) { - self.shader_code = shader_code; - self.should_recreate(); - } - - #[inline] - pub fn should_recreate(&mut self) { - self.should_recreate = true; - } - - pub fn get_pipeline(&mut self) -> anyhow::Result<&MyRenderPipeline> { - if self.should_recreate { - self.rebuild_pipeline()?; - } - Ok(self.pipeline.as_ref().unwrap()) - } - - /// Update shaders and rebuild the pipeline - fn rebuild_pipeline(&mut self) -> anyhow::Result<()> { - unsafe { - self.destroy_pipeline()?; - - let shader_module = self.device.create_shader_module( - &vk::ShaderModuleCreateInfo::default().code(&self.shader_code), - None, - )?; - - let pipeline_layout = self.device.create_pipeline_layout( - &vk::PipelineLayoutCreateInfo::default().push_constant_ranges(&[ - vk::PushConstantRange::default() - .offset(0) - .size(size_of::() as u32) - .stage_flags(vk::ShaderStageFlags::ALL), - ]), - None, - )?; - - let mut pipelines = - self - .device - .create_graphics_pipelines(vk::PipelineCache::null(), &[vk::GraphicsPipelineCreateInfo::default() - .stages( - &[ - vk::PipelineShaderStageCreateInfo { - module: shader_module, - p_name: c"main_vs".as_ptr(), - stage: vk::ShaderStageFlags::VERTEX, - ..Default::default() - }, - vk::PipelineShaderStageCreateInfo { - module: shader_module, - p_name: c"main_fs".as_ptr(), - stage: vk::ShaderStageFlags::FRAGMENT, - // NOTE(eddyb) this acts like an integration test for specialization constants. - p_specialization_info: &vk::SpecializationInfo::default() - .map_entries(&[vk::SpecializationMapEntry::default() - .constant_id(0x5007) - .offset(0) - .size(4)]) - .data(&u32::to_le_bytes( - self.sky_fs_spec_id_0x5007_sun_intensity_extra_spec_const_factor, - )), - ..Default::default() - }, - ], - ) - .vertex_input_state(&vk::PipelineVertexInputStateCreateInfo::default()) - .input_assembly_state(&vk::PipelineInputAssemblyStateCreateInfo { - topology: vk::PrimitiveTopology::TRIANGLE_LIST, - ..Default::default() - }) - .rasterization_state(&vk::PipelineRasterizationStateCreateInfo { - front_face: vk::FrontFace::COUNTER_CLOCKWISE, - line_width: 1.0, - ..Default::default() - }) - .multisample_state(&vk::PipelineMultisampleStateCreateInfo { - rasterization_samples: vk::SampleCountFlags::TYPE_1, - ..Default::default() - }) - .depth_stencil_state(&vk::PipelineDepthStencilStateCreateInfo::default()) - .color_blend_state( - &vk::PipelineColorBlendStateCreateInfo::default() - .attachments( - &[vk::PipelineColorBlendAttachmentState { - blend_enable: 0, - src_color_blend_factor: vk::BlendFactor::SRC_COLOR, - dst_color_blend_factor: vk::BlendFactor::ONE_MINUS_DST_COLOR, - color_blend_op: vk::BlendOp::ADD, - src_alpha_blend_factor: vk::BlendFactor::ZERO, - dst_alpha_blend_factor: vk::BlendFactor::ZERO, - alpha_blend_op: vk::BlendOp::ADD, - color_write_mask: vk::ColorComponentFlags::RGBA, - }], - ), - ) - .dynamic_state( - &vk::PipelineDynamicStateCreateInfo::default() - .dynamic_states(&[vk::DynamicState::VIEWPORT, vk::DynamicState::SCISSOR]), - ) - .viewport_state( - &vk::PipelineViewportStateCreateInfo::default() - .scissor_count(1) - .viewport_count(1), - ) - .layout(pipeline_layout) - .push_next(&mut vk::PipelineRenderingCreateInfo::default().color_attachment_formats(&[self.color_out_format])) - ], None).map_err(|(_, e)| e) - .context("Unable to create graphics pipeline")?; - - // A single `pipeline_info` results in a single pipeline. - assert_eq!(pipelines.len(), 1); - self.pipeline = pipelines.pop().map(|pipeline| MyRenderPipeline { - pipeline, - pipeline_layout, - }); - - // shader modules are allowed to be deleted after the pipeline has been created - self.device.destroy_shader_module(shader_module, None); - Ok(()) - } - } - - unsafe fn destroy_pipeline(&mut self) -> anyhow::Result<()> { - unsafe { - if let Some(pipeline) = self.pipeline.take() { - // Figuring out when the pipeline stops being used is hard, so we take this shortcut - self.device.device_wait_idle()?; - - self.device.destroy_pipeline(pipeline.pipeline, None); - self.device - .destroy_pipeline_layout(pipeline.pipeline_layout, None); - } - Ok(()) - } - } -} - -impl MyRenderPipeline { - pub fn render( - &self, - device: &MyDevice, - cmd: vk::CommandBuffer, - color_out: vk::ImageView, - extent: vk::Extent2D, - push_constants: ShaderConstants, - ) -> anyhow::Result<()> { - unsafe { - let render_area = vk::Rect2D { - offset: vk::Offset2D::default(), - extent, - }; - - device.cmd_begin_rendering( - cmd, - &vk::RenderingInfo::default() - .render_area(render_area) - .layer_count(1) - .color_attachments(&[vk::RenderingAttachmentInfo::default() - .image_view(color_out) - .load_op(vk::AttachmentLoadOp::CLEAR) - .store_op(vk::AttachmentStoreOp::STORE) - .clear_value(vk::ClearValue { - color: vk::ClearColorValue { - float32: [0.0, 1.0, 0.0, 0.0], - }, - }) - .image_layout(vk::ImageLayout::COLOR_ATTACHMENT_OPTIMAL)]), - ); - device.cmd_bind_pipeline(cmd, vk::PipelineBindPoint::GRAPHICS, self.pipeline); - device.cmd_set_viewport( - cmd, - 0, - &[vk::Viewport { - // contains a y-flip - x: 0.0, - y: extent.height as f32, - width: extent.width as f32, - height: -(extent.height as f32), - min_depth: 0.0, - max_depth: 1.0, - }], - ); - device.cmd_set_scissor(cmd, 0, &[render_area]); - device.cmd_push_constants( - cmd, - self.pipeline_layout, - vk::ShaderStageFlags::ALL, - 0, - bytemuck::bytes_of(&push_constants), - ); - device.cmd_draw(cmd, 3, 1, 0, 0); - device.cmd_end_rendering(cmd); - Ok(()) - } - } -} - -impl Drop for MyRenderPipelineManager { - fn drop(&mut self) { - unsafe { - self.destroy_pipeline().ok(); - } - } -} - -/// The renderer manages our command buffer and submits the commands, using [`MyRenderPipeline`] for drawing. -pub struct MyRenderer { - pub device: Arc, - pub pipeline: MyRenderPipelineManager, - pub command: SingleCommandBuffer, -} - -impl MyRenderer { - pub fn new(pipeline: MyRenderPipelineManager) -> anyhow::Result { - Ok(Self { - command: SingleCommandBuffer::new(pipeline.device.clone())?, - device: pipeline.device.clone(), - pipeline, - }) - } - - pub fn render_frame( - &mut self, - frame: DrawFrame, - push_constants: ShaderConstants, - ) -> anyhow::Result<()> { - unsafe { - let device = &self.device; - let pipeline = self.pipeline.get_pipeline()?; - let cmd = self.command.cmd; - - device.reset_command_pool(self.command.pool, vk::CommandPoolResetFlags::default())?; - - { - device.begin_command_buffer( - cmd, - &vk::CommandBufferBeginInfo::default() - .flags(vk::CommandBufferUsageFlags::ONE_TIME_SUBMIT), - )?; - device.cmd_pipeline_barrier2( - cmd, - &vk::DependencyInfo::default().image_memory_barriers(&[ - vk::ImageMemoryBarrier2::default() - .image(frame.image) - .src_access_mask(vk::AccessFlags2::NONE) - .src_stage_mask(vk::PipelineStageFlags2::ALL_COMMANDS) - .old_layout(vk::ImageLayout::UNDEFINED) - .dst_access_mask(vk::AccessFlags2::COLOR_ATTACHMENT_WRITE) - .dst_stage_mask(vk::PipelineStageFlags2::COLOR_ATTACHMENT_OUTPUT) - .new_layout(vk::ImageLayout::COLOR_ATTACHMENT_OPTIMAL) - .subresource_range( - vk::ImageSubresourceRange::default() - .aspect_mask(vk::ImageAspectFlags::COLOR) - .base_mip_level(0) - .level_count(1) - .base_array_layer(0) - .layer_count(1), - ), - ]), - ); - pipeline.render(&device, cmd, frame.image_view, frame.extent, push_constants)?; - device.cmd_pipeline_barrier2( - cmd, - &vk::DependencyInfo::default().image_memory_barriers(&[ - vk::ImageMemoryBarrier2::default() - .image(frame.image) - .src_access_mask(vk::AccessFlags2::COLOR_ATTACHMENT_WRITE) - .src_stage_mask(vk::PipelineStageFlags2::COLOR_ATTACHMENT_OUTPUT) - .old_layout(vk::ImageLayout::COLOR_ATTACHMENT_OPTIMAL) - .dst_access_mask(vk::AccessFlags2::NONE) - .dst_stage_mask(vk::PipelineStageFlags2::ALL_COMMANDS) - .new_layout(vk::ImageLayout::PRESENT_SRC_KHR) - .subresource_range( - vk::ImageSubresourceRange::default() - .aspect_mask(vk::ImageAspectFlags::COLOR) - .base_mip_level(0) - .level_count(1) - .base_array_layer(0) - .layer_count(1), - ), - ]), - ); - device.end_command_buffer(cmd)?; - } - - device.queue_submit2( - device.main_queue, - &[vk::SubmitInfo2::default() - .wait_semaphore_infos(&[vk::SemaphoreSubmitInfo::default() - .semaphore(frame.acquire_semaphore) - .stage_mask(vk::PipelineStageFlags2::TOP_OF_PIPE)]) - .command_buffer_infos(&[ - vk::CommandBufferSubmitInfo::default().command_buffer(cmd) - ]) - .signal_semaphore_infos(&[vk::SemaphoreSubmitInfo::default() - .semaphore(frame.draw_finished_semaphore) - .stage_mask(vk::PipelineStageFlags2::BOTTOM_OF_PIPE)])], - frame.draw_finished_fence, - )?; - Ok(()) - } - } -} - -/// A single command buffer with a pool -pub struct SingleCommandBuffer { - pub device: Arc, - pub pool: vk::CommandPool, - pub cmd: vk::CommandBuffer, -} - -impl SingleCommandBuffer { - pub fn new(device: Arc) -> anyhow::Result { - unsafe { - let pool = device.device.create_command_pool( - &vk::CommandPoolCreateInfo::default().queue_family_index(device.main_queue_family), - None, - )?; - - let command_buffers = device.device.allocate_command_buffers( - &vk::CommandBufferAllocateInfo::default() - .command_buffer_count(1) - .command_pool(pool) - .level(vk::CommandBufferLevel::PRIMARY), - )?; - assert_eq!(command_buffers.len(), 1); - let cmd = command_buffers[0]; - - Ok(Self { device, pool, cmd }) - } - } -} - -impl Drop for SingleCommandBuffer { - fn drop(&mut self) { - unsafe { - let device = &self.device; - device.free_command_buffers(self.pool, &[self.cmd]); - device.destroy_command_pool(self.pool, None); - } - } -} - -unsafe extern "system" fn vulkan_debug_callback( - message_severity: vk::DebugUtilsMessageSeverityFlagsEXT, - _message_type: vk::DebugUtilsMessageTypeFlagsEXT, - p_callback_data: *const vk::DebugUtilsMessengerCallbackDataEXT<'_>, - _user_data: *mut std::os::raw::c_void, -) -> vk::Bool32 { - let callback_data = unsafe { *p_callback_data }; - let message_id_name = if callback_data.p_message_id_name.is_null() { - Cow::from("") - } else { - unsafe { CStr::from_ptr(callback_data.p_message_id_name).to_string_lossy() } - }; - let message = if callback_data.p_message.is_null() { - Cow::from("") - } else { - unsafe { CStr::from_ptr(callback_data.p_message).to_string_lossy() } - }; - println!("{message_severity:?}: [{message_id_name}] : {message}"); - vk::FALSE -} diff --git a/examples/runners/ash/src/single_command_buffer.rs b/examples/runners/ash/src/single_command_buffer.rs new file mode 100644 index 0000000000..15137a1bd8 --- /dev/null +++ b/examples/runners/ash/src/single_command_buffer.rs @@ -0,0 +1,42 @@ +use crate::device::MyDevice; +use ash::vk; +use std::sync::Arc; + +/// A single command buffer with a pool +pub struct SingleCommandBuffer { + pub device: Arc, + pub pool: vk::CommandPool, + pub cmd: vk::CommandBuffer, +} + +impl SingleCommandBuffer { + pub fn new(device: Arc) -> anyhow::Result { + unsafe { + let pool = device.device.create_command_pool( + &vk::CommandPoolCreateInfo::default().queue_family_index(device.main_queue_family), + None, + )?; + + let command_buffers = device.device.allocate_command_buffers( + &vk::CommandBufferAllocateInfo::default() + .command_buffer_count(1) + .command_pool(pool) + .level(vk::CommandBufferLevel::PRIMARY), + )?; + assert_eq!(command_buffers.len(), 1); + let cmd = command_buffers[0]; + + Ok(Self { device, pool, cmd }) + } + } +} + +impl Drop for SingleCommandBuffer { + fn drop(&mut self) { + unsafe { + let device = &self.device; + device.free_command_buffers(self.pool, &[self.cmd]); + device.destroy_command_pool(self.pool, None); + } + } +} diff --git a/examples/runners/ash/src/swapchain.rs b/examples/runners/ash/src/swapchain.rs new file mode 100644 index 0000000000..90b96d4eb1 --- /dev/null +++ b/examples/runners/ash/src/swapchain.rs @@ -0,0 +1,348 @@ +use crate::device::MyDevice; +use anyhow::Context; +use ash::vk; +use raw_window_handle::{HasDisplayHandle, HasWindowHandle}; +use std::sync::Arc; + +/// A binary semaphore for swapchain operations +pub struct SwapchainSync { + acquire_semaphore: vk::Semaphore, + render_semaphore: vk::Semaphore, + render_fence: vk::Fence, +} + +impl SwapchainSync { + pub unsafe fn new(device: &MyDevice) -> anyhow::Result { + unsafe { + let signaled_fence = + vk::FenceCreateInfo::default().flags(vk::FenceCreateFlags::SIGNALED); + Ok(Self { + acquire_semaphore: device + .create_semaphore(&vk::SemaphoreCreateInfo::default(), None)?, + render_semaphore: device + .create_semaphore(&vk::SemaphoreCreateInfo::default(), None)?, + render_fence: device.create_fence(&signaled_fence, None)?, + }) + } + } + + pub unsafe fn destroy(&self, device: &MyDevice) { + unsafe { + device.destroy_semaphore(self.acquire_semaphore, None); + device.destroy_semaphore(self.render_semaphore, None); + device.destroy_fence(self.render_fence, None); + } + } +} + +/// Takes care of all things swapchain related +/// +/// Intentionally kept simple and does not offer support for multiple frames in flight +pub struct MySwapchainManager { + pub device: Arc, + pub window: winit::window::Window, + pub surface: vk::SurfaceKHR, + pub surface_format: vk::SurfaceFormatKHR, + pub surface_capabilities: vk::SurfaceCapabilitiesKHR, + pub present_mode: vk::PresentModeKHR, + pub image_count: u32, + pub pre_transform: vk::SurfaceTransformFlagsKHR, + + // state below + active: Option, + should_recreate: bool, + sync: SwapchainSync, +} + +struct ActiveSwapchain { + extent: vk::Extent2D, + swapchain: vk::SwapchainKHR, + images: Vec<(vk::Image, vk::ImageView)>, +} + +impl MySwapchainManager { + pub fn new(device: Arc, window: winit::window::Window) -> anyhow::Result { + unsafe { + let surface_ext = &device.surface_ext; + + let surface = ash_window::create_surface( + &device.entry, + &device.instance, + window.display_handle().unwrap().into(), + window.window_handle().unwrap().into(), + None, + ) + .context("create_surface")?; + + let surface_format = { + let acceptable_formats = { + [ + vk::Format::R8G8B8_SRGB, + vk::Format::B8G8R8_SRGB, + vk::Format::R8G8B8A8_SRGB, + vk::Format::B8G8R8A8_SRGB, + vk::Format::A8B8G8R8_SRGB_PACK32, + ] + }; + *surface_ext + .get_physical_device_surface_formats(device.physical_device, surface)? + .iter() + .find(|sfmt| acceptable_formats.contains(&sfmt.format)) + .context("Unable to find suitable surface format.")? + }; + + let surface_capabilities = surface_ext + .get_physical_device_surface_capabilities(device.physical_device, surface)?; + let pre_transform = surface_capabilities.current_transform; + + let present_mode = surface_ext + .get_physical_device_surface_present_modes(device.physical_device, surface)? + .iter() + .cloned() + // Mailbox is preferred + .find(|&mode| mode == vk::PresentModeKHR::MAILBOX) + // FIFO is guaranteed to be available + .unwrap_or(vk::PresentModeKHR::FIFO); + + let image_count = { + let mut image_count = match present_mode { + // tripple buffering in mailbox mode:: one presenting, one ready and one drawing + vk::PresentModeKHR::MAILBOX => 3, + // double buffering in fifo mode: one presenting, one drawing + vk::PresentModeKHR::FIFO => 2, + _ => unreachable!(), + }; + if surface_capabilities.max_image_count != 0 { + image_count = image_count.min(surface_capabilities.max_image_count); + } + image_count.max(surface_capabilities.min_image_count) + }; + + let sync = SwapchainSync::new(&device)?; + Ok(Self { + device, + window, + surface, + surface_format, + surface_capabilities, + present_mode, + image_count, + pre_transform, + + active: None, + should_recreate: true, + sync, + }) + } + } + + #[inline] + pub fn should_recreate(&mut self) { + self.should_recreate = true; + } + + /// After this function is called, `Self.active` is initialized + unsafe fn recreate_swapchain(&mut self) -> anyhow::Result<()> { + unsafe { + let device = &self.device; + let swapchain_ext = &device.swapchain_ext; + let surface_ext = &self.device.surface_ext; + let format = self.surface_format.format; + + let extent = { + let window_size = self.window.inner_size(); + let capabilities = surface_ext.get_physical_device_surface_capabilities( + self.device.physical_device, + self.surface, + )?; + let min = capabilities.min_image_extent; + let max = capabilities.max_image_extent; + vk::Extent2D { + width: u32::clamp(window_size.width, min.width, max.width), + height: u32::clamp(window_size.height, min.height, max.height), + } + }; + + let old = self.active.take(); + if let Some(old) = old.as_ref() { + old.destroy_image_views(device); + } + + let swapchain = swapchain_ext + .create_swapchain( + &vk::SwapchainCreateInfoKHR::default() + .surface(self.surface) + .min_image_count(self.image_count) + .image_color_space(self.surface_format.color_space) + .image_format(format) + .image_extent(extent) + .image_usage(vk::ImageUsageFlags::COLOR_ATTACHMENT) + .image_sharing_mode(vk::SharingMode::EXCLUSIVE) + .pre_transform(self.pre_transform) + .composite_alpha(vk::CompositeAlphaFlagsKHR::OPAQUE) + .present_mode(self.present_mode) + .clipped(true) + .image_array_layers(1) + .old_swapchain( + old.as_ref() + .map_or(vk::SwapchainKHR::null(), |old| old.swapchain), + ), + None, + ) + .context("create_swapchain")?; + + if let Some(old) = old.as_ref() { + old.destroy_swapchain(device); + } + + let images = device.swapchain_ext.get_swapchain_images(swapchain)?; + let images = images + .into_iter() + .map(|image| { + let image_view = device.create_image_view( + &vk::ImageViewCreateInfo::default() + .image(image) + .view_type(vk::ImageViewType::TYPE_2D) + .format(format) + .components(vk::ComponentMapping::default()) // identity + .subresource_range(vk::ImageSubresourceRange { + aspect_mask: vk::ImageAspectFlags::COLOR, + base_mip_level: 0, + level_count: 1, + base_array_layer: 0, + layer_count: 1, + }), + None, + )?; + Ok::<_, anyhow::Error>((image, image_view)) + }) + .collect::, _>>()?; + + self.active = Some(ActiveSwapchain { + swapchain, + images, + extent, + }); + Ok(()) + } + } +} + +impl ActiveSwapchain { + /// We must destroy the image views we own, but not the images, those are owned by the swapchain. + unsafe fn destroy_image_views(&self, device: &MyDevice) { + unsafe { + for (_, image_view) in &self.images { + device.destroy_image_view(*image_view, None); + } + } + } + + /// Destroying the swapchain destroys* the images, so image views must be destroyed beforehand. + unsafe fn destroy_swapchain(&self, device: &MyDevice) { + unsafe { device.swapchain_ext.destroy_swapchain(self.swapchain, None) } + } +} + +impl Drop for MySwapchainManager { + fn drop(&mut self) { + unsafe { + self.sync.destroy(&self.device); + if let Some(active) = self.active.as_ref() { + active.destroy_image_views(&self.device); + active.destroy_swapchain(&self.device); + } + self.device.surface_ext.destroy_surface(self.surface, None); + } + } +} + +/// Metadata on drawing a single frame on some image +pub struct DrawFrame { + /// the size of the image + pub extent: vk::Extent2D, + /// the [`vk::Image`] to draw to + pub image: vk::Image, + /// the [`vk::ImageImage`] to draw to, created from `image` + pub image_view: vk::ImageView, + /// the `acquire_image` semaphore that must be waited for before draw commands are executed + pub acquire_semaphore: vk::Semaphore, + /// the `draw_finished` semaphore that must be signaled when drawing to the image has finished + pub draw_finished_semaphore: vk::Semaphore, + /// the `draw_finished` fence that must be signaled when drawing to the image has finished + pub draw_finished_fence: vk::Fence, +} + +impl MySwapchainManager { + pub fn render( + &mut self, + f: impl FnOnce(DrawFrame) -> anyhow::Result<()>, + ) -> anyhow::Result<()> { + unsafe { + self.device + .wait_for_fences(&[self.sync.render_fence], true, !0)?; + self.device.reset_fences(&[self.sync.render_fence])?; + + const RECREATE_ATTEMPTS: u32 = 10; + for _ in 0..RECREATE_ATTEMPTS { + if self.should_recreate { + self.should_recreate = false; + + // *In theory*, recreating the swapchain allows you to present any acquired images from the old + // swapchain. Which (iirc) requires you to wait for all images to be presented before destroying + // the old swapchain. But we just use the [`ash::Device::device_wait_idle`] "hack" to wait for all + // previous images to finish before immediately destroying the swapchain. + self.device.device_wait_idle()?; + self.recreate_swapchain()?; + } + + let active = self.active.as_ref().unwrap(); + let swapchain_ext = &self.device.swapchain_ext; + match swapchain_ext.acquire_next_image( + active.swapchain, + !0, + self.sync.acquire_semaphore, + vk::Fence::null(), + ) { + Ok((id, suboptimal)) => { + if suboptimal { + self.should_recreate = true; + } + let (image, image_view) = active.images[id as usize]; + f(DrawFrame { + extent: active.extent, + image, + image_view, + acquire_semaphore: self.sync.acquire_semaphore, + draw_finished_semaphore: self.sync.render_semaphore, + draw_finished_fence: self.sync.render_fence, + })?; + + let suboptimal = swapchain_ext.queue_present( + self.device.main_queue, + &vk::PresentInfoKHR::default() + .swapchains(&[active.swapchain]) + .image_indices(&[id]) + .wait_semaphores(&[self.sync.render_semaphore]), + )?; + if suboptimal { + self.should_recreate = true; + } + return Ok(()); + } + Err(vk::Result::ERROR_OUT_OF_DATE_KHR) => { + // retry + self.should_recreate = true; + } + Err(e) => { + return Err(e.into()); + } + } + } + panic!( + "looped {} times trying to acquire swapchain image and failed repeatedly!", + RECREATE_ATTEMPTS + ); + } + } +} From 4b2a1ba36f5827774e1e502e8173e12f71b7f50e Mon Sep 17 00:00:00 2001 From: firestar99 Date: Tue, 30 Sep 2025 16:44:15 +0200 Subject: [PATCH 08/11] ash runner: fix lints --- examples/runners/ash/src/graphics.rs | 2 +- examples/runners/ash/src/swapchain.rs | 11 +++++------ 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/examples/runners/ash/src/graphics.rs b/examples/runners/ash/src/graphics.rs index 58bfc970a3..c5e1431114 100644 --- a/examples/runners/ash/src/graphics.rs +++ b/examples/runners/ash/src/graphics.rs @@ -314,7 +314,7 @@ impl MyRenderer { ), ]), ); - pipeline.render(&device, cmd, frame.image_view, frame.extent, push_constants)?; + pipeline.render(device, cmd, frame.image_view, frame.extent, push_constants)?; device.cmd_pipeline_barrier2( cmd, &vk::DependencyInfo::default().image_memory_barriers(&[ diff --git a/examples/runners/ash/src/swapchain.rs b/examples/runners/ash/src/swapchain.rs index 90b96d4eb1..a3d703c649 100644 --- a/examples/runners/ash/src/swapchain.rs +++ b/examples/runners/ash/src/swapchain.rs @@ -5,14 +5,14 @@ use raw_window_handle::{HasDisplayHandle, HasWindowHandle}; use std::sync::Arc; /// A binary semaphore for swapchain operations -pub struct SwapchainSync { +struct SwapchainSync { acquire_semaphore: vk::Semaphore, render_semaphore: vk::Semaphore, render_fence: vk::Fence, } impl SwapchainSync { - pub unsafe fn new(device: &MyDevice) -> anyhow::Result { + unsafe fn new(device: &MyDevice) -> anyhow::Result { unsafe { let signaled_fence = vk::FenceCreateInfo::default().flags(vk::FenceCreateFlags::SIGNALED); @@ -26,7 +26,7 @@ impl SwapchainSync { } } - pub unsafe fn destroy(&self, device: &MyDevice) { + unsafe fn destroy(&self, device: &MyDevice) { unsafe { device.destroy_semaphore(self.acquire_semaphore, None); device.destroy_semaphore(self.render_semaphore, None); @@ -263,7 +263,7 @@ pub struct DrawFrame { pub extent: vk::Extent2D, /// the [`vk::Image`] to draw to pub image: vk::Image, - /// the [`vk::ImageImage`] to draw to, created from `image` + /// the [`vk::Image`] to draw to, created from `image` pub image_view: vk::ImageView, /// the `acquire_image` semaphore that must be waited for before draw commands are executed pub acquire_semaphore: vk::Semaphore, @@ -340,8 +340,7 @@ impl MySwapchainManager { } } panic!( - "looped {} times trying to acquire swapchain image and failed repeatedly!", - RECREATE_ATTEMPTS + "looped {RECREATE_ATTEMPTS} times trying to acquire swapchain image and failed repeatedly!" ); } } From 9f0d3d68dffb17301dbf7aa6da04328d6a3c25e7 Mon Sep 17 00:00:00 2001 From: firestar99 Date: Wed, 1 Oct 2025 14:59:00 +0200 Subject: [PATCH 09/11] ash runner: remove `ash-molten`, require vulkan sdk on macos --- Cargo.lock | 88 +----------------------------- examples/README.md | 1 + examples/runners/ash/Cargo.toml | 3 - examples/runners/ash/src/device.rs | 8 +-- 4 files changed, 3 insertions(+), 97 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c2f558af5a..75f85838b0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -194,18 +194,6 @@ dependencies = [ "libloading", ] -[[package]] -name = "ash-molten" -version = "0.20.0+1.2.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b4c4960621009149a2877457f6875185b894fe1b4f7a602167a3ac00758dc40" -dependencies = [ - "anyhow", - "ash", - "plist", - "serde", -] - [[package]] name = "ash-window" version = "0.13.0" @@ -728,15 +716,6 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f27ae1dd37df86211c42e150270f82743308803d90a6f6e6651cd730d5e1732f" -[[package]] -name = "deranged" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d630bccd429a5bb5a64b5e94f693bfc48c9f8566418fda4c494cc94f911f87cc" -dependencies = [ - "powerfmt", -] - [[package]] name = "derive_more" version = "0.99.20" @@ -936,7 +915,6 @@ version = "0.0.0" dependencies = [ "anyhow", "ash", - "ash-molten", "ash-window", "bytemuck", "cfg-if", @@ -2037,12 +2015,6 @@ dependencies = [ "windows-sys 0.52.0", ] -[[package]] -name = "num-conv" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" - [[package]] name = "num-traits" version = "0.2.19" @@ -2476,19 +2448,6 @@ version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" -[[package]] -name = "plist" -version = "1.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3af6b589e163c5a788fab00ce0c0366f6efbb9959c2f9874b224936af7fce7e1" -dependencies = [ - "base64", - "indexmap 2.11.0", - "quick-xml 0.38.3", - "serde", - "time", -] - [[package]] name = "polling" version = "3.10.0" @@ -2527,12 +2486,6 @@ dependencies = [ "zerovec", ] -[[package]] -name = "powerfmt" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" - [[package]] name = "pp-rs" version = "0.2.1" @@ -2591,15 +2544,6 @@ dependencies = [ "memchr", ] -[[package]] -name = "quick-xml" -version = "0.38.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42a232e7487fc2ef313d96dde7948e7a3c05101870d8985e4fd8d26aedd27b89" -dependencies = [ - "memchr", -] - [[package]] name = "quote" version = "1.0.40" @@ -3418,36 +3362,6 @@ dependencies = [ "cfg-if", ] -[[package]] -name = "time" -version = "0.3.43" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83bde6f1ec10e72d583d91623c939f623002284ef622b87de38cfd546cbf2031" -dependencies = [ - "deranged", - "num-conv", - "powerfmt", - "serde", - "time-core", - "time-macros", -] - -[[package]] -name = "time-core" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40868e7c1d2f0b8d73e4a8c7f0ff63af4f6d19be117e90bd73eb1d62cf831c6b" - -[[package]] -name = "time-macros" -version = "0.2.24" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30cfb0125f12d9c277f35663a0a33f8c30190f4e4574868a330595412d34ebf3" -dependencies = [ - "num-conv", - "time-core", -] - [[package]] name = "tiny-skia" version = "0.11.4" @@ -4062,7 +3976,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "54cb1e9dc49da91950bdfd8b848c49330536d9d1fb03d4bfec8cae50caa50ae3" dependencies = [ "proc-macro2", - "quick-xml 0.37.5", + "quick-xml", "quote", ] diff --git a/examples/README.md b/examples/README.md index c90060e415..3b420400e0 100644 --- a/examples/README.md +++ b/examples/README.md @@ -47,6 +47,7 @@ supported. on WGPU+wasm.) - ash runner: + - MacOS: requires MoltenVK (or the Vulkan SDK) to be installed - `cargo run --release -p example-runner-ash` runs the sky shader. Use the up and down arrows to adjust the sun's intensity. Use F5 to recompile the shader code (but note that the image won't redraw afterwards unless the intensity is diff --git a/examples/runners/ash/Cargo.toml b/examples/runners/ash/Cargo.toml index 0fa5fb6104..f25777d9f6 100644 --- a/examples/runners/ash/Cargo.toml +++ b/examples/runners/ash/Cargo.toml @@ -24,6 +24,3 @@ shared = { path = "../../shaders/shared" } spirv-builder = { workspace = true, default-features = false } anyhow = "1.0.98" bytemuck.workspace = true - -[target.'cfg(target_os = "macos")'.dependencies] -ash-molten = { version = "0.20", features = ["pre-built"] } diff --git a/examples/runners/ash/src/device.rs b/examples/runners/ash/src/device.rs index 9f658ad465..ec1beecb7f 100644 --- a/examples/runners/ash/src/device.rs +++ b/examples/runners/ash/src/device.rs @@ -32,13 +32,7 @@ impl Deref for MyDevice { impl MyDevice { pub fn new(extension_names: &[*const c_char], options: &Options) -> anyhow::Result> { unsafe { - cfg_if::cfg_if! { - if #[cfg(target_os = "macos")] { - let entry = ash_molten::load(); - } else { - let entry = ash::Entry::load()?; - } - } + let entry = ash::Entry::load()?; let instance = { let layer_names: &'static [_] = if options.debug_layer { From d6847d1a6314c57bbdbc290007f3964b69021107 Mon Sep 17 00:00:00 2001 From: firestar99 Date: Wed, 1 Oct 2025 12:28:30 +0200 Subject: [PATCH 10/11] ash runner: switch Vulkan Memory Model from extension to core feature --- examples/runners/ash/src/device.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/runners/ash/src/device.rs b/examples/runners/ash/src/device.rs index ec1beecb7f..fd2af61370 100644 --- a/examples/runners/ash/src/device.rs +++ b/examples/runners/ash/src/device.rs @@ -115,7 +115,7 @@ impl MyDevice { physical_device, &vk::DeviceCreateInfo::default() .push_next( - &mut vk::PhysicalDeviceVulkanMemoryModelFeatures::default() + &mut vk::PhysicalDeviceVulkan12Features::default() .vulkan_memory_model(true), ) .push_next( From bf587272a8c9d3ed5bb61b7f31e4570c25bc47b8 Mon Sep 17 00:00:00 2001 From: firestar99 Date: Thu, 2 Oct 2025 12:44:08 +0200 Subject: [PATCH 11/11] ash runner: cleanup debug callback --- examples/runners/ash/src/device.rs | 25 ++++++++++++------------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/examples/runners/ash/src/device.rs b/examples/runners/ash/src/device.rs index fd2af61370..cd409419f0 100644 --- a/examples/runners/ash/src/device.rs +++ b/examples/runners/ash/src/device.rs @@ -169,17 +169,16 @@ unsafe extern "system" fn vulkan_debug_callback( p_callback_data: *const vk::DebugUtilsMessengerCallbackDataEXT<'_>, _user_data: *mut std::os::raw::c_void, ) -> vk::Bool32 { - let callback_data = unsafe { *p_callback_data }; - let message_id_name = if callback_data.p_message_id_name.is_null() { - Cow::from("") - } else { - unsafe { CStr::from_ptr(callback_data.p_message_id_name).to_string_lossy() } - }; - let message = if callback_data.p_message.is_null() { - Cow::from("") - } else { - unsafe { CStr::from_ptr(callback_data.p_message).to_string_lossy() } - }; - println!("{message_severity:?}: [{message_id_name}] : {message}"); - vk::FALSE + unsafe { + let callback_data = *p_callback_data; + let message_id_name = callback_data + .message_id_name_as_c_str() + .map_or(Cow::Borrowed(""), CStr::to_string_lossy); + let message = callback_data + .message_as_c_str() + .map_or(Cow::Borrowed(""), CStr::to_string_lossy); + + println!("{message_severity:?}: [{message_id_name}] : {message}"); + vk::FALSE + } }