diff options
| author | Valentin Popov <valentin@popov.link> | 2026-06-25 03:29:08 +0300 |
|---|---|---|
| committer | Valentin Popov <valentin@popov.link> | 2026-06-25 10:45:33 +0300 |
| commit | 53715d0d9c581fd213770b1554237d2db1f11d4a (patch) | |
| tree | 62c33394a98706f2a123034d23fe892995c977e0 | |
| parent | c8876d65eb4e6ba183987db6aaae7d00669d45b4 (diff) | |
| download | fparkan-53715d0d9c581fd213770b1554237d2db1f11d4a.tar.xz fparkan-53715d0d9c581fd213770b1554237d2db1f11d4a.zip | |
fix(vulkan-smoke): clean up partial runtime resources
| -rw-r--r-- | adapters/fparkan-render-vulkan/src/lib.rs | 281 |
1 files changed, 226 insertions, 55 deletions
diff --git a/adapters/fparkan-render-vulkan/src/lib.rs b/adapters/fparkan-render-vulkan/src/lib.rs index 3970acf..9e66c1e 100644 --- a/adapters/fparkan-render-vulkan/src/lib.rs +++ b/adapters/fparkan-render-vulkan/src/lib.rs @@ -976,6 +976,15 @@ struct VulkanSwapchainResources { command_buffers: Vec<vk::CommandBuffer>, } +struct PartialSwapchainResources { + image_views: Vec<vk::ImageView>, + render_pass: Option<vk::RenderPass>, + pipeline_layout: Option<vk::PipelineLayout>, + pipeline: Option<vk::Pipeline>, + framebuffers: Vec<vk::Framebuffer>, + command_buffers: Vec<vk::CommandBuffer>, +} + struct VulkanFrameSync { image_available: vk::Semaphore, render_finished: vk::Semaphore, @@ -1041,8 +1050,23 @@ impl VulkanSmokeRenderer { ) .map_err(VulkanSmokeRendererError::Swapchain)?; let command_pool = create_command_pool(&device)?; - let vertex_buffer = Some(create_triangle_vertex_buffer(&instance, &device)?); - let index_buffer = Some(create_triangle_index_buffer(&instance, &device)?); + let vertex_buffer = match create_triangle_vertex_buffer(&instance, &device) { + Ok(buffer) => buffer, + Err(error) => { + // SAFETY: The command pool belongs to this live logical device and is destroyed on setup failure. + unsafe { device.device().destroy_command_pool(command_pool, None) }; + return Err(error); + } + }; + let index_buffer = match create_triangle_index_buffer(&instance, &device) { + Ok(buffer) => buffer, + Err(error) => { + // SAFETY: The command pool belongs to this live logical device and is destroyed on setup failure. + unsafe { device.device().destroy_command_pool(command_pool, None) }; + destroy_allocated_buffer(&device, &vertex_buffer); + return Err(error); + } + }; let mut renderer = Self { instance: Some(instance), validation, @@ -1051,8 +1075,8 @@ impl VulkanSmokeRenderer { swapchain: Some(swapchain), command_pool, swapchain_resources: None, - vertex_buffer, - index_buffer, + vertex_buffer: Some(vertex_buffer), + index_buffer: Some(index_buffer), frame_sync: Vec::new(), images_in_flight: Vec::new(), current_frame: 0, @@ -1680,19 +1704,24 @@ fn create_host_visible_buffer( })?; // SAFETY: The buffer belongs to this device and is queried immediately after creation. let requirements = unsafe { device.device().get_buffer_memory_requirements(buffer) }; - let memory_type_index = find_memory_type( + let Some(memory_type_index) = find_memory_type( instance, device.physical_device, requirements.memory_type_bits, vk::MemoryPropertyFlags::HOST_VISIBLE | vk::MemoryPropertyFlags::HOST_COHERENT, - ) - .ok_or(VulkanSmokeRendererError::MissingMemoryType { context })?; + ) else { + // SAFETY: The buffer was created above on this logical device and is destroyed on setup failure. + unsafe { device.device().destroy_buffer(buffer, None) }; + return Err(VulkanSmokeRendererError::MissingMemoryType { context }); + }; let allocate_info = vk::MemoryAllocateInfo::default() .allocation_size(requirements.size) .memory_type_index(memory_type_index); let memory = // SAFETY: Allocation uses a memory type index selected from the physical-device requirements above. unsafe { device.device().allocate_memory(&allocate_info, None) }.map_err(|error| { + // SAFETY: The buffer was created above on this logical device and is destroyed on setup failure. + unsafe { device.device().destroy_buffer(buffer, None) }; VulkanSmokeRendererError::VulkanOperation { context, result: format!("{error:?}"), @@ -1700,6 +1729,11 @@ fn create_host_visible_buffer( })?; // SAFETY: The buffer and allocation belong to the same live logical device. unsafe { device.device().bind_buffer_memory(buffer, memory, 0) }.map_err(|error| { + // SAFETY: The buffer and allocation belong to this logical device and are destroyed on setup failure. + unsafe { + device.device().destroy_buffer(buffer, None); + device.device().free_memory(memory, None); + } VulkanSmokeRendererError::VulkanOperation { context, result: format!("{error:?}"), @@ -1711,9 +1745,16 @@ fn create_host_visible_buffer( .device() .map_memory(memory, 0, requirements.size, vk::MemoryMapFlags::empty()) } - .map_err(|error| VulkanSmokeRendererError::VulkanOperation { - context, - result: format!("{error:?}"), + .map_err(|error| { + // SAFETY: The buffer and allocation belong to this logical device and are destroyed on setup failure. + unsafe { + device.device().destroy_buffer(buffer, None); + device.device().free_memory(memory, None); + } + VulkanSmokeRendererError::VulkanOperation { + context, + result: format!("{error:?}"), + } })?; // SAFETY: The mapped pointer is valid for `bytes.len()` bytes and non-overlapping with the source slice. unsafe { @@ -1746,6 +1787,7 @@ fn find_memory_type( }) } +#[allow(clippy::too_many_lines)] fn create_swapchain_resources( device: &VulkanLogicalDeviceProbe, swapchain: &VulkanSwapchainProbe, @@ -1764,41 +1806,84 @@ fn create_swapchain_resources( context: "vkGetSwapchainImagesKHR", result: format!("{error:?}"), })?; - let image_views = images - .iter() - .map(|image| create_image_view(device, *image, swapchain.report.plan.format.format)) - .collect::<Result<Vec<_>, _>>()?; - let render_pass = create_render_pass(device, swapchain.report.plan.format.format)?; - let pipeline_layout = create_pipeline_layout(device)?; - let pipeline = create_graphics_pipeline( + let mut partial = PartialSwapchainResources { + image_views: Vec::with_capacity(images.len()), + render_pass: None, + pipeline_layout: None, + pipeline: None, + framebuffers: Vec::with_capacity(images.len()), + command_buffers: Vec::new(), + }; + for image in &images { + match create_image_view(device, *image, swapchain.report.plan.format.format) { + Ok(image_view) => partial.image_views.push(image_view), + Err(error) => { + destroy_partial_swapchain_resources(device, command_pool, partial); + return Err(error); + } + } + } + let render_pass = match create_render_pass(device, swapchain.report.plan.format.format) { + Ok(render_pass) => render_pass, + Err(error) => { + destroy_partial_swapchain_resources(device, command_pool, partial); + return Err(error); + } + }; + partial.render_pass = Some(render_pass); + let pipeline_layout = match create_pipeline_layout(device) { + Ok(pipeline_layout) => pipeline_layout, + Err(error) => { + destroy_partial_swapchain_resources(device, command_pool, partial); + return Err(error); + } + }; + partial.pipeline_layout = Some(pipeline_layout); + let pipeline = match create_graphics_pipeline( device, render_pass, pipeline_layout, swapchain.report.plan.extent, - )?; - let framebuffers = image_views - .iter() - .map(|image_view| { - create_framebuffer( - device, - render_pass, - *image_view, - swapchain.report.plan.extent, - ) - }) - .collect::<Result<Vec<_>, _>>()?; - let command_buffers = allocate_command_buffers( + ) { + Ok(pipeline) => pipeline, + Err(error) => { + destroy_partial_swapchain_resources(device, command_pool, partial); + return Err(error); + } + }; + partial.pipeline = Some(pipeline); + for image_view in &partial.image_views { + match create_framebuffer( + device, + render_pass, + *image_view, + swapchain.report.plan.extent, + ) { + Ok(framebuffer) => partial.framebuffers.push(framebuffer), + Err(error) => { + destroy_partial_swapchain_resources(device, command_pool, partial); + return Err(error); + } + } + } + partial.command_buffers = match allocate_command_buffers( device, command_pool, - image_views.len().try_into().unwrap_or(u32::MAX), - )?; + partial.image_views.len().try_into().unwrap_or(u32::MAX), + ) { + Ok(command_buffers) => command_buffers, + Err(error) => { + destroy_partial_swapchain_resources(device, command_pool, partial); + return Err(error); + } + }; Ok(VulkanSwapchainResources { - image_views, + image_views: partial.image_views, render_pass, pipeline_layout, pipeline, - framebuffers, - command_buffers, + framebuffers: partial.framebuffers, + command_buffers: partial.command_buffers, }) } @@ -1878,6 +1963,7 @@ fn extent_component_to_f32(value: u32) -> f32 { u16::try_from(value).map_or(f32::from(u16::MAX), f32::from) } +#[allow(clippy::too_many_lines)] fn create_graphics_pipeline( device: &VulkanLogicalDeviceProbe, render_pass: vk::RenderPass, @@ -1886,7 +1972,14 @@ fn create_graphics_pipeline( ) -> Result<vk::Pipeline, VulkanSmokeRendererError> { let entry_point = c"main"; let vertex_module = create_shader_module(device, TRIANGLE_VERTEX_SHADER_WORDS)?; - let fragment_module = create_shader_module(device, TRIANGLE_FRAGMENT_SHADER_WORDS)?; + let fragment_module = match create_shader_module(device, TRIANGLE_FRAGMENT_SHADER_WORDS) { + Ok(fragment_module) => fragment_module, + Err(error) => { + // SAFETY: The vertex shader module was created above on this logical device and is destroyed on setup failure. + unsafe { device.device().destroy_shader_module(vertex_module, None) }; + return Err(error); + } + }; let stage_create_infos = [ vk::PipelineShaderStageCreateInfo::default() .stage(vk::ShaderStageFlags::VERTEX) @@ -1967,20 +2060,21 @@ fn create_graphics_pipeline( .render_pass(render_pass) .subpass(0)]; // SAFETY: The pipeline creation references live shader modules and stack-owned fixed-function descriptors. - let pipeline = unsafe { + let pipeline_result = unsafe { device .device() .create_graphics_pipelines(vk::PipelineCache::null(), &create_info, None) - } - .map_err(|(_, error)| VulkanSmokeRendererError::VulkanOperation { - context: "vkCreateGraphicsPipelines", - result: format!("{error:?}"), - })?[0]; + }; // SAFETY: Shader modules are no longer needed after pipeline creation completes. unsafe { device.device().destroy_shader_module(vertex_module, None); device.device().destroy_shader_module(fragment_module, None); } + let pipeline = + pipeline_result.map_err(|(_, error)| VulkanSmokeRendererError::VulkanOperation { + context: "vkCreateGraphicsPipelines", + result: format!("{error:?}"), + })?[0]; Ok(pipeline) } @@ -2051,20 +2145,37 @@ fn create_frame_sync( context: "vkCreateSemaphore(image_available)", result: format!("{error:?}"), })?; - // SAFETY: The sync objects belong to this live logical device and are destroyed at teardown. - let render_finished = unsafe { device.device().create_semaphore(&semaphore_info, None) } - .map_err(|error| VulkanSmokeRendererError::VulkanOperation { - context: "vkCreateSemaphore(render_finished)", - result: format!("{error:?}"), - })?; + let render_finished = + // SAFETY: The sync objects belong to this live logical device and are destroyed at teardown. + match unsafe { device.device().create_semaphore(&semaphore_info, None) } { + Ok(render_finished) => render_finished, + Err(error) => { + destroy_frame_sync_objects(device, &sync); + // SAFETY: The semaphore was created above on this logical device and is destroyed on setup failure. + unsafe { device.device().destroy_semaphore(image_available, None) }; + return Err(VulkanSmokeRendererError::VulkanOperation { + context: "vkCreateSemaphore(render_finished)", + result: format!("{error:?}"), + }); + } + }; let fence = // SAFETY: The fence belongs to this live logical device and is destroyed at teardown. - unsafe { device.device().create_fence(&fence_info, None) }.map_err(|error| { - VulkanSmokeRendererError::VulkanOperation { + match unsafe { device.device().create_fence(&fence_info, None) } { + Ok(fence) => fence, + Err(error) => { + destroy_frame_sync_objects(device, &sync); + // SAFETY: These semaphores were created above on this logical device and are destroyed on setup failure. + unsafe { + device.device().destroy_semaphore(image_available, None); + device.device().destroy_semaphore(render_finished, None); + } + return Err(VulkanSmokeRendererError::VulkanOperation { context: "vkCreateFence", result: format!("{error:?}"), + }); } - })?; + }; sync.push(VulkanFrameSync { image_available, render_finished, @@ -2100,6 +2211,61 @@ fn destroy_swapchain_resources( } } +fn destroy_partial_swapchain_resources( + device: &VulkanLogicalDeviceProbe, + command_pool: vk::CommandPool, + resources: PartialSwapchainResources, +) { + // SAFETY: All handles in this partial resource set were created on this live logical device and are destroyed once. + unsafe { + if !resources.command_buffers.is_empty() { + device + .device() + .free_command_buffers(command_pool, &resources.command_buffers); + } + for framebuffer in resources.framebuffers { + device.device().destroy_framebuffer(framebuffer, None); + } + if let Some(pipeline) = resources.pipeline { + device.device().destroy_pipeline(pipeline, None); + } + if let Some(pipeline_layout) = resources.pipeline_layout { + device + .device() + .destroy_pipeline_layout(pipeline_layout, None); + } + if let Some(render_pass) = resources.render_pass { + device.device().destroy_render_pass(render_pass, None); + } + for image_view in resources.image_views { + device.device().destroy_image_view(image_view, None); + } + } +} + +fn destroy_frame_sync_objects(device: &VulkanLogicalDeviceProbe, sync: &[VulkanFrameSync]) { + for frame_sync in sync { + // SAFETY: These sync objects belong to this live logical device and are destroyed once during teardown. + unsafe { + device + .device() + .destroy_semaphore(frame_sync.image_available, None); + device + .device() + .destroy_semaphore(frame_sync.render_finished, None); + device.device().destroy_fence(frame_sync.fence, None); + } + } +} + +fn destroy_allocated_buffer(device: &VulkanLogicalDeviceProbe, buffer: &VulkanAllocatedBuffer) { + // SAFETY: The buffer and allocation belong to this live logical device and are destroyed once during teardown. + unsafe { + device.device().destroy_buffer(buffer.buffer, None); + device.device().free_memory(buffer.memory, None); + } +} + fn color_subresource_range() -> vk::ImageSubresourceRange { vk::ImageSubresourceRange::default() .aspect_mask(vk::ImageAspectFlags::COLOR) @@ -2537,11 +2703,16 @@ pub fn create_vulkan_swapchain_probe_for_extent( } })?; // SAFETY: The swapchain was created above and the returned image handles are owned by it. - let images = unsafe { loader.get_swapchain_images(swapchain) }.map_err(|error| { - VulkanSwapchainProbeError::ImagesFailed { - result: format!("{error:?}"), + let images = match unsafe { loader.get_swapchain_images(swapchain) } { + Ok(images) => images, + Err(error) => { + // SAFETY: The swapchain was created above on this loader/device pair and is destroyed on setup failure. + unsafe { loader.destroy_swapchain(swapchain, None) }; + return Err(VulkanSwapchainProbeError::ImagesFailed { + result: format!("{error:?}"), + }); } - })?; + }; Ok(VulkanSwapchainProbe { loader, swapchain, |
