From 2c8110d93e04a1bfd976fe20c8c7493a41d51eae Mon Sep 17 00:00:00 2001 From: Irene Knapp Date: Thu, 6 Aug 2026 16:43:02 -0700 Subject: state needed when rendering is now in its own struct subdividing the lifetimes of all our various objects may feel questionable in general, but this particular division is a very natural one Force-Push: yes Change-Id: I499a32875a4d75965944af720e7bfd6ec5965c15 --- src/graphics_scene.rs | 39 +++-- src/graphics_window_dressing.rs | 309 ++++++++++++++++++++++++---------------- src/main.rs | 36 +++-- 3 files changed, 225 insertions(+), 159 deletions(-) (limited to 'src') diff --git a/src/graphics_scene.rs b/src/graphics_scene.rs index 771255a..f3694f7 100644 --- a/src/graphics_scene.rs +++ b/src/graphics_scene.rs @@ -1,5 +1,6 @@ #![deny(unsafe_code)] use crate::error::*; +use crate::graphics_window_dressing::RenderState; use crate::linear_algebra::{ Vec3, Vec4, Transformation }; use crate::shader_data::VertexPushBlock; @@ -11,19 +12,15 @@ use vulkanalia::vk::{ self, HasBuilder, DeviceV1_0 }; #[allow(unsafe_code)] -pub fn generate_scene_commands(command_buffer: &vk::CommandBuffer, - time: f32, device: &Device, - extent: &vk::Extent2D, - framebuffer: &vk::Framebuffer, - render_pass: &vk::RenderPass, - pipeline_layout: &vk::PipelineLayout, - pipeline: &vk::Pipeline, - vertex_buffer: &vk::Buffer, - index_buffer: &vk::Buffer, - index_count: usize, - descriptor_set: &vk::DescriptorSet) - -> Result<()> +pub fn generate_scene_commands<'a>(image_index: usize, time: f32, + render_state: &'a RenderState, + device: &Device, extent: &vk::Extent2D) + -> Result<&'a vk::CommandBuffer> { + let command_buffer = &render_state.command_buffers[image_index]; + let framebuffer = &render_state.framebuffers[image_index]; + let descriptor_set = &render_state.descriptor_sets[image_index]; + let inheritance_info = vk::CommandBufferInheritanceInfo::builder(); let command_buffer_begin_info = vk::CommandBufferBeginInfo::builder() @@ -52,7 +49,7 @@ pub fn generate_scene_commands(command_buffer: &vk::CommandBuffer, let clear_values = [color_clear_value, depth_clear_value]; let begin_pass_info = vk::RenderPassBeginInfo::builder() - .render_pass(*render_pass) + .render_pass(render_state.render_pass) .framebuffer(*framebuffer) .render_area(render_area) .clear_values(&clear_values); @@ -65,23 +62,23 @@ pub fn generate_scene_commands(command_buffer: &vk::CommandBuffer, unsafe { device.cmd_bind_pipeline(*command_buffer, vk::PipelineBindPoint::GRAPHICS, - *pipeline) + render_state.pipeline) }; unsafe { device.cmd_bind_vertex_buffers(*command_buffer, 0, - &[*vertex_buffer], &[0]) + &[render_state.vertex_buffer], &[0]) }; unsafe { - device.cmd_bind_index_buffer(*command_buffer, *index_buffer, 0, - vk::IndexType::UINT32) + device.cmd_bind_index_buffer(*command_buffer, render_state.index_buffer, + 0, vk::IndexType::UINT32) }; unsafe { device.cmd_bind_descriptor_sets(*command_buffer, vk::PipelineBindPoint::GRAPHICS, - *pipeline_layout, + render_state.pipeline_layout, 0, &[*descriptor_set], &[]) @@ -101,14 +98,14 @@ pub fn generate_scene_commands(command_buffer: &vk::CommandBuffer, }; unsafe { - device.cmd_push_constants(*command_buffer, *pipeline_layout, + device.cmd_push_constants(*command_buffer, render_state.pipeline_layout, vk::ShaderStageFlags::VERTEX, 0, push_block_bytes) }; unsafe { - device.cmd_draw_indexed(*command_buffer, index_count as u32, + device.cmd_draw_indexed(*command_buffer, render_state.index_count as u32, 1, 0, 0, 0) }; @@ -116,6 +113,6 @@ pub fn generate_scene_commands(command_buffer: &vk::CommandBuffer, unsafe { device.end_command_buffer(*command_buffer) }?; - Ok(()) + Ok(command_buffer) } diff --git a/src/graphics_window_dressing.rs b/src/graphics_window_dressing.rs index 51483cf..1b5b602 100644 --- a/src/graphics_window_dressing.rs +++ b/src/graphics_window_dressing.rs @@ -27,8 +27,9 @@ pub const N_SIMULTANEOUS_FRAMES: usize = 5; // The WindowDressing collects the Vulkan graphics objects which need to be // regenerated or modified when the window changes in certain ways, such as -// resizing. The ones which don't need that are collected above, in -// PermanentGraphicsState. +// resizing, but are not needed during rendering. The ones which don't need to +// be regenerated are collected in PermanentGraphicsState. The ones which are +// needed during rendering are collected in RenderState, below. #[derive(Debug)] pub struct WindowDressing { pub swapchain: Swapchain, @@ -42,23 +43,9 @@ pub struct WindowDressing { depth_image_view: vk::ImageView, depth_format: vk::Format, - pub render_pass: vk::RenderPass, - - pub pipeline: vk::Pipeline, - pub pipeline_layout: vk::PipelineLayout, - - pub framebuffers: Vec, - primary_command_pool: vk::CommandPool, transient_command_pool: vk::CommandPool, - pub vertex_buffer: vk::Buffer, - vertex_buffer_memory: vk::DeviceMemory, - - pub index_buffer: vk::Buffer, - index_buffer_memory: vk::DeviceMemory, - pub index_count: usize, - texture_image: vk::Image, texture_image_memory: vk::DeviceMemory, texture_image_view: vk::ImageView, @@ -69,13 +56,32 @@ pub struct WindowDressing { pub uniform_buffer_memory: Vec, descriptor_pool: vk::DescriptorPool, - pub descriptor_sets: Vec, - - pub command_buffers: Vec, pub concurrency: Concurrency, } +// The RenderState collects the Vulkan graphics objects which need to be +// regenerated or modified when the window changes, as with WindowDressing, +// and which are also used as part of rendering. +#[derive(Debug)] +pub struct RenderState { + pub render_pass: vk::RenderPass, + + pub pipeline: vk::Pipeline, + pub pipeline_layout: vk::PipelineLayout, + + pub vertex_buffer: vk::Buffer, + vertex_buffer_memory: vk::DeviceMemory, + + pub index_buffer: vk::Buffer, + index_buffer_memory: vk::DeviceMemory, + pub index_count: usize, + + pub framebuffers: Vec, + pub command_buffers: Vec, + pub descriptor_sets: Vec, +} + // A swapchain is the generalized facility that is used to implement // double buffering, triple buffering, rendering passes that feed into each // other, and other things of that nature. It's a first-class thing but for @@ -125,7 +131,6 @@ impl WindowDressing { let physical_device = &for_reinit.physical_device; let sample_count = for_reinit.sample_count; let indices = &for_reinit.indices; - let descriptor_set_layout = &for_reinit.descriptor_set_layout; let swapchain = init_swapchain( window, instance, surface, &physical_device, device, &indices)?; @@ -138,30 +143,9 @@ impl WindowDressing { = init_depth(instance, &physical_device, device, &swapchain.extent, sample_count)?; - let render_pass = init_render_pass(device, sample_count, - &swapchain.format, &depth_format)?; - - let (pipeline_layout, pipeline) - = init_pipeline(device, descriptor_set_layout, &swapchain.extent, - sample_count, &render_pass)?; - - let framebuffers = init_framebuffers( - device, &swapchain.extent, &swapchain.image_views, - &color_image_view, &depth_image_view, &render_pass)?; - let (primary_command_pool, transient_command_pool) = init_command_pools(device, indices)?; - let (vertices, indices) = load_model()?; - let index_count = indices.len(); - - let (vertex_buffer, vertex_buffer_memory) - = init_vertex_buffer(vertices, instance, physical_device, device, - graphics_queue, &transient_command_pool)?; - let (index_buffer, index_buffer_memory) - = init_index_buffer(indices, instance, physical_device, device, - graphics_queue, &transient_command_pool)?; - let (texture_image, texture_image_memory, texture_image_view, mip_count) = init_texture(instance, physical_device, device, graphics_queue, &transient_command_pool)?; @@ -174,16 +158,6 @@ impl WindowDressing { let descriptor_pool = init_descriptor_pool(device, swapchain.images.len())?; - let descriptor_sets - = init_descriptor_sets(device, descriptor_set_layout, - &uniform_buffers, &descriptor_pool, - swapchain.images.len(), - &texture_image_view, &sampler)?; - - let command_buffers = init_command_buffers( - device, &swapchain.extent, &framebuffers, &render_pass, - &pipeline_layout, &pipeline, &vertex_buffer, &index_buffer, - index_count, &descriptor_sets, &primary_command_pool)?; let concurrency = init_concurrency(device, &swapchain.images)?; @@ -196,15 +170,6 @@ impl WindowDressing { depth_image_memory, depth_image_view, depth_format, - render_pass, - pipeline, - pipeline_layout, - framebuffers, - vertex_buffer, - vertex_buffer_memory, - index_buffer, - index_buffer_memory, - index_count, texture_image, texture_image_memory, texture_image_view, @@ -213,10 +178,8 @@ impl WindowDressing { uniform_buffers, uniform_buffer_memory, descriptor_pool, - descriptor_sets, primary_command_pool, transient_command_pool, - command_buffers, concurrency, }) } @@ -251,17 +214,6 @@ impl WindowDressing { = init_depth(instance, &physical_device, device, &swapchain.extent, sample_count)?; - let render_pass = init_render_pass(device, sample_count, - &swapchain.format, &depth_format)?; - - let (pipeline_layout, pipeline) - = init_pipeline(device, descriptor_set_layout, &swapchain.extent, - sample_count, &render_pass)?; - - let framebuffers = init_framebuffers( - device, &swapchain.extent, &swapchain.image_views, - &color_image_view, &depth_image_view, &render_pass)?; - let (uniform_buffers, uniform_buffer_memory) = init_uniform_buffers(instance, physical_device, device, swapchain.images.len())?; @@ -270,19 +222,6 @@ impl WindowDressing { let descriptor_pool = init_descriptor_pool(device, swapchain.images.len())?; - let descriptor_sets - = init_descriptor_sets(device, descriptor_set_layout, - &uniform_buffers, &descriptor_pool, - swapchain.images.len(), - &self.texture_image_view, &self.sampler)?; - - // Notice that we reused the command pool. - let command_buffers = init_command_buffers( - device, &swapchain.extent, &framebuffers, &render_pass, - &pipeline_layout, &pipeline, &self.vertex_buffer, - &self.index_buffer, self.index_count, &descriptor_sets, - &self.primary_command_pool)?; - self.concurrency.image_fences.resize(swapchain.images.len(), vk::Fence::null()); @@ -294,15 +233,9 @@ impl WindowDressing { self.depth_image_memory = depth_image_memory; self.depth_image_view = depth_image_view; self.depth_format = depth_format; - self.render_pass = render_pass; - self.pipeline = pipeline; - self.pipeline_layout = pipeline_layout; - self.framebuffers = framebuffers; self.uniform_buffers = uniform_buffers; self.uniform_buffer_memory = uniform_buffer_memory; self.descriptor_pool = descriptor_pool; - self.descriptor_sets = descriptor_sets; - self.command_buffers = command_buffers; Ok(()) } @@ -314,12 +247,6 @@ impl WindowDressing { pub fn destroy(mut self, device: &Device) { self.destroy_replaceable(device); - unsafe { device.destroy_buffer(self.vertex_buffer, None) }; - unsafe { device.free_memory(self.vertex_buffer_memory, None) }; - - unsafe { device.destroy_buffer(self.index_buffer, None) }; - unsafe { device.free_memory(self.index_buffer_memory, None) }; - unsafe { device.destroy_image(self.texture_image, None) }; unsafe { device.free_memory(self.texture_image_memory, None) }; unsafe { device.destroy_image_view(self.texture_image_view, None) }; @@ -346,20 +273,6 @@ impl WindowDressing { #[allow(unsafe_code)] fn destroy_replaceable(&mut self, device: &Device) { - for framebuffer in &self.framebuffers { - unsafe { device.destroy_framebuffer(*framebuffer, None) }; - } - - // Notice that we free the buffers in the pool, but do not destroy the - // pool itself. Notice also that we only do this for the primary command - // pool, because that's the only one where we've kept track of the - // buffers. We promise ourselves to free buffers in the transient pool - // immediately after using them. - unsafe { - device.free_command_buffers(self.primary_command_pool, - &self.command_buffers) - }; - // While the descriptor pool is also a pool, it has a preallocated size // which will be different next time. So, we destroy it all the way. unsafe { device.destroy_descriptor_pool(self.descriptor_pool, None) }; @@ -374,9 +287,6 @@ impl WindowDressing { unsafe { device.free_memory(*memory, None) }; } - unsafe { device.destroy_pipeline(self.pipeline, None) }; - unsafe { device.destroy_pipeline_layout(self.pipeline_layout, None) }; - unsafe { device.destroy_render_pass(self.render_pass, None) }; unsafe { device.destroy_image(self.color_image, None) }; unsafe { device.free_memory(self.color_image_memory, None) }; unsafe { device.destroy_image_view(self.color_image_view, None) }; @@ -393,6 +303,167 @@ impl WindowDressing { } +impl RenderState { + pub fn new(permanent: &PermanentGraphicsState, + for_reinit: &GraphicsStateForReinit, + window_dressing: &WindowDressing) + -> Result + { + let device = &permanent.device; + let instance = &permanent.instance; + let graphics_queue = &permanent.graphics_queue; + let physical_device = &for_reinit.physical_device; + let sample_count = for_reinit.sample_count; + let descriptor_set_layout = &for_reinit.descriptor_set_layout; + let primary_command_pool = &window_dressing.primary_command_pool; + let transient_command_pool = &window_dressing.transient_command_pool; + let swapchain = &window_dressing.swapchain; + let depth_format = &window_dressing.depth_format; + let color_image_view = &window_dressing.color_image_view; + let depth_image_view = &window_dressing.depth_image_view; + let texture_image_view = &window_dressing.texture_image_view; + let uniform_buffers = &window_dressing.uniform_buffers; + let descriptor_pool = &window_dressing.descriptor_pool; + let sampler = &window_dressing.sampler; + + let render_pass = init_render_pass(device, sample_count, + &swapchain.format, &depth_format)?; + + let (pipeline_layout, pipeline) + = init_pipeline(device, descriptor_set_layout, &swapchain.extent, + sample_count, &render_pass)?; + + let framebuffers = init_framebuffers( + device, &swapchain.extent, &swapchain.image_views, + &color_image_view, &depth_image_view, &render_pass)?; + + let (vertices, indices) = load_model()?; + let index_count = indices.len(); + + let command_buffers = init_command_buffers(device, &framebuffers, + primary_command_pool)?; + + let (vertex_buffer, vertex_buffer_memory) + = init_vertex_buffer(vertices, instance, physical_device, device, + graphics_queue, &transient_command_pool)?; + let (index_buffer, index_buffer_memory) + = init_index_buffer(indices, instance, physical_device, device, + graphics_queue, &transient_command_pool)?; + + let descriptor_sets + = init_descriptor_sets(device, descriptor_set_layout, + &uniform_buffers, &descriptor_pool, + swapchain.images.len(), + &texture_image_view, &sampler)?; + + Ok(RenderState { + render_pass, + pipeline, + pipeline_layout, + vertex_buffer, + vertex_buffer_memory, + index_buffer, + index_buffer_memory, + index_count, + framebuffers, + command_buffers, + descriptor_sets, + }) + } + + // This relies on its caller to have already waited for the device to be + // idle. + pub fn reinit(&mut self, permanent: &PermanentGraphicsState, + for_reinit: &GraphicsStateForReinit, + window_dressing: &WindowDressing) + -> Result<()> + { + let device = &permanent.device; + let sample_count = for_reinit.sample_count; + let descriptor_set_layout = &for_reinit.descriptor_set_layout; + let primary_command_pool = &window_dressing.primary_command_pool; + let swapchain = &window_dressing.swapchain; + let depth_format = &window_dressing.depth_format; + let color_image_view = &window_dressing.color_image_view; + let depth_image_view = &window_dressing.depth_image_view; + let texture_image_view = &window_dressing.texture_image_view; + let uniform_buffers = &window_dressing.uniform_buffers; + let descriptor_pool = &window_dressing.descriptor_pool; + let sampler = &window_dressing.sampler; + + self.destroy_replaceable(device, primary_command_pool); + + let render_pass = init_render_pass(device, sample_count, + &swapchain.format, &depth_format)?; + + let (pipeline_layout, pipeline) + = init_pipeline(device, descriptor_set_layout, &swapchain.extent, + sample_count, &render_pass)?; + + let framebuffers = init_framebuffers( + device, &swapchain.extent, &swapchain.image_views, + &color_image_view, &depth_image_view, &render_pass)?; + + // Notice that we reused the command pool. + let command_buffers = init_command_buffers(device, &framebuffers, + primary_command_pool)?; + + let descriptor_sets + = init_descriptor_sets(device, descriptor_set_layout, + &uniform_buffers, &descriptor_pool, + swapchain.images.len(), + texture_image_view, sampler)?; + + self.render_pass = render_pass; + self.pipeline = pipeline; + self.pipeline_layout = pipeline_layout; + self.framebuffers = framebuffers; + self.command_buffers = command_buffers; + self.descriptor_sets = descriptor_sets; + + Ok(()) + } + + // This relies on its caller to have already waited for the device to be + // idle. + #[allow(unsafe_code)] + pub fn destroy(mut self, device: &Device, + window_dressing: &WindowDressing) + { + self.destroy_replaceable(device, &window_dressing.primary_command_pool); + + unsafe { device.destroy_buffer(self.vertex_buffer, None) }; + unsafe { device.free_memory(self.vertex_buffer_memory, None) }; + + unsafe { device.destroy_buffer(self.index_buffer, None) }; + unsafe { device.free_memory(self.index_buffer_memory, None) }; + } + + #[allow(unsafe_code)] + fn destroy_replaceable(&mut self, device: &Device, + primary_command_pool: &vk::CommandPool) + { + for framebuffer in &self.framebuffers { + unsafe { device.destroy_framebuffer(*framebuffer, None) }; + } + + // Notice that we free the buffers in the pool, but do not destroy the + // pool itself. Notice also that we only do this for the primary command + // pool, because that's the only one where we've kept track of the + // buffers. We promise ourselves to free buffers in the transient pool + // immediately after using them. + unsafe { + device.free_command_buffers(*primary_command_pool, + &self.command_buffers) + }; + + unsafe { device.destroy_pipeline(self.pipeline, None) }; + unsafe { device.destroy_pipeline_layout(self.pipeline_layout, None) }; + unsafe { device.destroy_render_pass(self.render_pass, None) }; + } +} + + #[allow(unsafe_code)] fn init_swapchain(window: &Window, instance: &Instance, surface: &vk::SurfaceKHR, @@ -1041,15 +1112,7 @@ fn init_command_pools(device: &Device, indices: &QueueFamilyIndices) #[allow(unsafe_code)] fn init_command_buffers(device: &Device, - extent: &vk::Extent2D, framebuffers: &Vec, - render_pass: &vk::RenderPass, - pipeline_layout: &vk::PipelineLayout, - pipeline: &vk::Pipeline, - vertex_buffer: &vk::Buffer, - index_buffer: &vk::Buffer, - index_count: usize, - descriptor_sets: &Vec, command_pool: &vk::CommandPool) -> Result> { diff --git a/src/main.rs b/src/main.rs index 8a42147..10b203a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -4,7 +4,7 @@ use crate::graphics_permanent::{ PermanentGraphicsState, GraphicsStateForReinit }; use crate::graphics_window_dressing::{ - WindowDressing, N_SIMULTANEOUS_FRAMES + WindowDressing, RenderState, N_SIMULTANEOUS_FRAMES }; use crate::linear_algebra::{ Vec3, Mat4, Transformation }; use crate::shader_data::UniformBlock; @@ -34,6 +34,7 @@ struct Surreality { permanent: RefCell>, for_reinit: RefCell>, window_dressing: RefCell>, + render_state: RefCell>, is_minimized: bool, is_reinit_queued: bool, frame_index: usize, @@ -50,6 +51,7 @@ impl Surreality { permanent: RefCell::new(None), for_reinit: RefCell::new(None), window_dressing: RefCell::new(None), + render_state: RefCell::new(None), is_minimized: false, is_reinit_queued: false, frame_index: 0, @@ -66,9 +68,13 @@ impl Surreality { = PermanentGraphicsState::new(event_loop)?; if enable_swapchain.0 { - *self.window_dressing.get_mut() - = Some(WindowDressing::new(&permanent, &for_reinit, - enable_anisotropy)?); + let window_dressing = WindowDressing::new(&permanent, &for_reinit, + enable_anisotropy)?; + let render_state = RenderState::new(&permanent, &for_reinit, + &window_dressing)?; + + *self.window_dressing.get_mut() = Some(window_dressing); + *self.render_state.get_mut() = Some(render_state); } *self.permanent.get_mut() = Some(permanent); @@ -82,8 +88,10 @@ impl Surreality { && let Some(for_reinit) = self.for_reinit.borrow().as_ref() && let Some(window_dressing) = self.window_dressing.borrow_mut().as_mut() + && let Some(render_state) = self.render_state.borrow_mut().as_mut() { window_dressing.reinit(permanent, for_reinit)?; + render_state.reinit(permanent, for_reinit, &window_dressing)?; } Ok(()) @@ -109,6 +117,7 @@ impl Surreality { if let Some(permanent) = self.permanent.borrow().as_ref() && let Some(window_dressing) = self.window_dressing.borrow_mut().as_mut() + && let Some(render_state) = self.render_state.borrow_mut().as_mut() && window_id == permanent.window.id() { let device = &permanent.device; @@ -141,16 +150,9 @@ impl Surreality { let time = self.simulation_start.elapsed().as_secs_f32(); - let command_buffer = window_dressing.command_buffers[image_index]; - let framebuffer = window_dressing.framebuffers[image_index]; - let descriptor_set = window_dressing.descriptor_sets[image_index]; - - crate::graphics_scene::generate_scene_commands( - &command_buffer, time, device, &window_dressing.swapchain.extent, - &framebuffer, &window_dressing.render_pass, - &window_dressing.pipeline_layout, &window_dressing.pipeline, - &window_dressing.vertex_buffer, &window_dressing.index_buffer, - window_dressing.index_count, &descriptor_set)?; + let command_buffer = crate::graphics_scene::generate_scene_commands( + image_index, time, &render_state, device, + &window_dressing.swapchain.extent)?; render_uniforms(time, device, &window_dressing.uniform_buffer_memory[image_index], @@ -160,7 +162,7 @@ impl Surreality { let second_semaphores = [*rendering_finished_semaphore]; let wait_stages = [vk::PipelineStageFlags::COLOR_ATTACHMENT_OUTPUT]; - let command_buffers = [command_buffer]; + let command_buffers = [*command_buffer]; let submit_info = vk::SubmitInfo::builder() .wait_semaphores(&first_semaphores) .wait_dst_stage_mask(&wait_stages) @@ -213,6 +215,10 @@ impl Drop for Surreality { unsafe { permanent.device.device_wait_idle() }.unwrap(); if let Some(window_dressing) = self.window_dressing.replace(None) { + if let Some(render_state) = self.render_state.replace(None) { + render_state.destroy(&permanent.device, &window_dressing); + } + window_dressing.destroy(&permanent.device); } -- cgit 1.4.1