fix(vulkan-smoke): clean up partial runtime resources

This commit is contained in:
2026-06-25 04:29:08 +04:00
parent c8876d65eb
commit 53715d0d9c
+216 -45
View File
@@ -976,6 +976,15 @@ struct VulkanSwapchainResources {
command_buffers: Vec<vk::CommandBuffer>, 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 { struct VulkanFrameSync {
image_available: vk::Semaphore, image_available: vk::Semaphore,
render_finished: vk::Semaphore, render_finished: vk::Semaphore,
@@ -1041,8 +1050,23 @@ impl VulkanSmokeRenderer {
) )
.map_err(VulkanSmokeRendererError::Swapchain)?; .map_err(VulkanSmokeRendererError::Swapchain)?;
let command_pool = create_command_pool(&device)?; let command_pool = create_command_pool(&device)?;
let vertex_buffer = Some(create_triangle_vertex_buffer(&instance, &device)?); let vertex_buffer = match create_triangle_vertex_buffer(&instance, &device) {
let index_buffer = Some(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) };
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 { let mut renderer = Self {
instance: Some(instance), instance: Some(instance),
validation, validation,
@@ -1051,8 +1075,8 @@ impl VulkanSmokeRenderer {
swapchain: Some(swapchain), swapchain: Some(swapchain),
command_pool, command_pool,
swapchain_resources: None, swapchain_resources: None,
vertex_buffer, vertex_buffer: Some(vertex_buffer),
index_buffer, index_buffer: Some(index_buffer),
frame_sync: Vec::new(), frame_sync: Vec::new(),
images_in_flight: Vec::new(), images_in_flight: Vec::new(),
current_frame: 0, 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. // SAFETY: The buffer belongs to this device and is queried immediately after creation.
let requirements = unsafe { device.device().get_buffer_memory_requirements(buffer) }; 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, instance,
device.physical_device, device.physical_device,
requirements.memory_type_bits, requirements.memory_type_bits,
vk::MemoryPropertyFlags::HOST_VISIBLE | vk::MemoryPropertyFlags::HOST_COHERENT, vk::MemoryPropertyFlags::HOST_VISIBLE | vk::MemoryPropertyFlags::HOST_COHERENT,
) ) else {
.ok_or(VulkanSmokeRendererError::MissingMemoryType { context })?; // 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() let allocate_info = vk::MemoryAllocateInfo::default()
.allocation_size(requirements.size) .allocation_size(requirements.size)
.memory_type_index(memory_type_index); .memory_type_index(memory_type_index);
let memory = let memory =
// SAFETY: Allocation uses a memory type index selected from the physical-device requirements above. // 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| { 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 { VulkanSmokeRendererError::VulkanOperation {
context, context,
result: format!("{error:?}"), result: format!("{error:?}"),
@@ -1700,6 +1729,11 @@ fn create_host_visible_buffer(
})?; })?;
// SAFETY: The buffer and allocation belong to the same live logical device. // SAFETY: The buffer and allocation belong to the same live logical device.
unsafe { device.device().bind_buffer_memory(buffer, memory, 0) }.map_err(|error| { 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 { VulkanSmokeRendererError::VulkanOperation {
context, context,
result: format!("{error:?}"), result: format!("{error:?}"),
@@ -1711,9 +1745,16 @@ fn create_host_visible_buffer(
.device() .device()
.map_memory(memory, 0, requirements.size, vk::MemoryMapFlags::empty()) .map_memory(memory, 0, requirements.size, vk::MemoryMapFlags::empty())
} }
.map_err(|error| VulkanSmokeRendererError::VulkanOperation { .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, context,
result: format!("{error:?}"), result: format!("{error:?}"),
}
})?; })?;
// SAFETY: The mapped pointer is valid for `bytes.len()` bytes and non-overlapping with the source slice. // SAFETY: The mapped pointer is valid for `bytes.len()` bytes and non-overlapping with the source slice.
unsafe { unsafe {
@@ -1746,6 +1787,7 @@ fn find_memory_type(
}) })
} }
#[allow(clippy::too_many_lines)]
fn create_swapchain_resources( fn create_swapchain_resources(
device: &VulkanLogicalDeviceProbe, device: &VulkanLogicalDeviceProbe,
swapchain: &VulkanSwapchainProbe, swapchain: &VulkanSwapchainProbe,
@@ -1764,41 +1806,84 @@ fn create_swapchain_resources(
context: "vkGetSwapchainImagesKHR", context: "vkGetSwapchainImagesKHR",
result: format!("{error:?}"), result: format!("{error:?}"),
})?; })?;
let image_views = images let mut partial = PartialSwapchainResources {
.iter() image_views: Vec::with_capacity(images.len()),
.map(|image| create_image_view(device, *image, swapchain.report.plan.format.format)) render_pass: None,
.collect::<Result<Vec<_>, _>>()?; pipeline_layout: None,
let render_pass = create_render_pass(device, swapchain.report.plan.format.format)?; pipeline: None,
let pipeline_layout = create_pipeline_layout(device)?; framebuffers: Vec::with_capacity(images.len()),
let pipeline = create_graphics_pipeline( 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, device,
render_pass, render_pass,
pipeline_layout, pipeline_layout,
swapchain.report.plan.extent, swapchain.report.plan.extent,
)?; ) {
let framebuffers = image_views Ok(pipeline) => pipeline,
.iter() Err(error) => {
.map(|image_view| { destroy_partial_swapchain_resources(device, command_pool, partial);
create_framebuffer( return Err(error);
}
};
partial.pipeline = Some(pipeline);
for image_view in &partial.image_views {
match create_framebuffer(
device, device,
render_pass, render_pass,
*image_view, *image_view,
swapchain.report.plan.extent, swapchain.report.plan.extent,
) ) {
}) Ok(framebuffer) => partial.framebuffers.push(framebuffer),
.collect::<Result<Vec<_>, _>>()?; Err(error) => {
let command_buffers = allocate_command_buffers( destroy_partial_swapchain_resources(device, command_pool, partial);
return Err(error);
}
}
}
partial.command_buffers = match allocate_command_buffers(
device, device,
command_pool, 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 { Ok(VulkanSwapchainResources {
image_views, image_views: partial.image_views,
render_pass, render_pass,
pipeline_layout, pipeline_layout,
pipeline, pipeline,
framebuffers, framebuffers: partial.framebuffers,
command_buffers, 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) u16::try_from(value).map_or(f32::from(u16::MAX), f32::from)
} }
#[allow(clippy::too_many_lines)]
fn create_graphics_pipeline( fn create_graphics_pipeline(
device: &VulkanLogicalDeviceProbe, device: &VulkanLogicalDeviceProbe,
render_pass: vk::RenderPass, render_pass: vk::RenderPass,
@@ -1886,7 +1972,14 @@ fn create_graphics_pipeline(
) -> Result<vk::Pipeline, VulkanSmokeRendererError> { ) -> Result<vk::Pipeline, VulkanSmokeRendererError> {
let entry_point = c"main"; let entry_point = c"main";
let vertex_module = create_shader_module(device, TRIANGLE_VERTEX_SHADER_WORDS)?; 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 = [ let stage_create_infos = [
vk::PipelineShaderStageCreateInfo::default() vk::PipelineShaderStageCreateInfo::default()
.stage(vk::ShaderStageFlags::VERTEX) .stage(vk::ShaderStageFlags::VERTEX)
@@ -1967,20 +2060,21 @@ fn create_graphics_pipeline(
.render_pass(render_pass) .render_pass(render_pass)
.subpass(0)]; .subpass(0)];
// SAFETY: The pipeline creation references live shader modules and stack-owned fixed-function descriptors. // SAFETY: The pipeline creation references live shader modules and stack-owned fixed-function descriptors.
let pipeline = unsafe { let pipeline_result = unsafe {
device device
.device() .device()
.create_graphics_pipelines(vk::PipelineCache::null(), &create_info, None) .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. // SAFETY: Shader modules are no longer needed after pipeline creation completes.
unsafe { unsafe {
device.device().destroy_shader_module(vertex_module, None); device.device().destroy_shader_module(vertex_module, None);
device.device().destroy_shader_module(fragment_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) Ok(pipeline)
} }
@@ -2051,20 +2145,37 @@ fn create_frame_sync(
context: "vkCreateSemaphore(image_available)", context: "vkCreateSemaphore(image_available)",
result: format!("{error:?}"), result: format!("{error:?}"),
})?; })?;
let render_finished =
// SAFETY: The sync objects belong to this live logical device and are destroyed at teardown. // 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) } match unsafe { device.device().create_semaphore(&semaphore_info, None) } {
.map_err(|error| VulkanSmokeRendererError::VulkanOperation { 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)", context: "vkCreateSemaphore(render_finished)",
result: format!("{error:?}"), result: format!("{error:?}"),
})?; });
}
};
let fence = let fence =
// SAFETY: The fence belongs to this live logical device and is destroyed at teardown. // 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| { match unsafe { device.device().create_fence(&fence_info, None) } {
VulkanSmokeRendererError::VulkanOperation { 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", context: "vkCreateFence",
result: format!("{error:?}"), result: format!("{error:?}"),
});
} }
})?; };
sync.push(VulkanFrameSync { sync.push(VulkanFrameSync {
image_available, image_available,
render_finished, 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 { fn color_subresource_range() -> vk::ImageSubresourceRange {
vk::ImageSubresourceRange::default() vk::ImageSubresourceRange::default()
.aspect_mask(vk::ImageAspectFlags::COLOR) .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. // 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| { let images = match unsafe { loader.get_swapchain_images(swapchain) } {
VulkanSwapchainProbeError::ImagesFailed { 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:?}"), result: format!("{error:?}"),
});
} }
})?; };
Ok(VulkanSwapchainProbe { Ok(VulkanSwapchainProbe {
loader, loader,
swapchain, swapchain,