refactor(vulkan-ffi): extract smoke renderer module
This commit is contained in:
@@ -30,6 +30,7 @@
|
||||
mod instance;
|
||||
mod resources;
|
||||
mod runtime;
|
||||
mod smoke;
|
||||
mod surface;
|
||||
mod validation;
|
||||
|
||||
@@ -60,9 +61,7 @@ pub use self::surface::{
|
||||
VulkanSurfacePlan, VulkanSurfaceProbe,
|
||||
};
|
||||
use self::validation::{create_validation_messenger, VulkanValidationMessenger};
|
||||
use crate::shader_manifest::{
|
||||
triangle_shader_manifest, validate_shader_manifest, VulkanShaderManifestError,
|
||||
};
|
||||
use crate::shader_manifest::VulkanShaderManifestError;
|
||||
use ash::vk;
|
||||
use fparkan_platform::NativeWindowHandles;
|
||||
/// Minimum Vulkan API version accepted by the Stage 0 backend.
|
||||
@@ -585,602 +584,6 @@ pub struct VulkanSmokeRenderer {
|
||||
report: VulkanSmokeRendererReport,
|
||||
}
|
||||
|
||||
impl VulkanSmokeRenderer {
|
||||
/// Creates a live Vulkan smoke renderer bound to a live native window.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns [`VulkanSmokeRendererError`] when Vulkan bootstrap, pipeline creation,
|
||||
/// memory allocation, or synchronization resource creation fails.
|
||||
pub fn new(
|
||||
create_info: &VulkanSmokeRendererCreateInfo,
|
||||
) -> Result<Self, VulkanSmokeRendererError> {
|
||||
let shader_manifest = validate_shader_manifest(&triangle_shader_manifest())
|
||||
.map_err(VulkanSmokeRendererError::ShaderManifest)?;
|
||||
let surface_plan = plan_vulkan_surface(Some(create_info.native_handles))
|
||||
.map_err(VulkanSmokeRendererError::Surface)?;
|
||||
let mut instance_config = VulkanInstanceConfig::smoke(&create_info.application_name);
|
||||
instance_config
|
||||
.required_extensions
|
||||
.clone_from(&surface_plan.required_instance_extensions);
|
||||
instance_config.enable_validation = create_info.enable_validation;
|
||||
let instance = create_vulkan_instance_probe(&instance_config)
|
||||
.map_err(VulkanSmokeRendererError::Instance)?;
|
||||
let validation = if create_info.enable_validation {
|
||||
Some(create_validation_messenger(&instance)?)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let surface = create_vulkan_surface_probe(&instance, Some(create_info.native_handles))
|
||||
.map_err(VulkanSmokeRendererError::Surface)?;
|
||||
let device =
|
||||
create_vulkan_logical_device_probe(&instance, &surface, create_info.drawable_extent)
|
||||
.map_err(VulkanSmokeRendererError::LogicalDevice)?;
|
||||
let swapchain = create_vulkan_swapchain_probe_for_extent(
|
||||
&instance,
|
||||
&surface,
|
||||
&device,
|
||||
create_info.drawable_extent,
|
||||
vk::SwapchainKHR::null(),
|
||||
)
|
||||
.map_err(VulkanSmokeRendererError::Swapchain)?;
|
||||
let command_pool = create_command_pool(&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,
|
||||
surface: Some(surface),
|
||||
device: Some(device),
|
||||
swapchain: Some(swapchain),
|
||||
command_pool,
|
||||
swapchain_resources: None,
|
||||
vertex_buffer: Some(vertex_buffer),
|
||||
index_buffer: Some(index_buffer),
|
||||
frame_sync: Vec::new(),
|
||||
images_in_flight: Vec::new(),
|
||||
current_frame: 0,
|
||||
pending_extent: None,
|
||||
swapchain_recreate_count: 0,
|
||||
report: VulkanSmokeRendererReport {
|
||||
shader_manifest_hash: shader_manifest.manifest_hash.clone(),
|
||||
portability_enumeration: instance_config.enable_portability_enumeration,
|
||||
device_name: String::new(),
|
||||
graphics_queue_family: 0,
|
||||
present_queue_family: 0,
|
||||
enabled_extension_count: 0,
|
||||
swapchain_extent: (0, 0),
|
||||
swapchain_image_count: 0,
|
||||
},
|
||||
};
|
||||
renderer.rebuild_swapchain_resources(false)?;
|
||||
let device_ref = renderer.device_ref()?;
|
||||
let swapchain_ref = renderer.swapchain_ref()?;
|
||||
renderer.report = VulkanSmokeRendererReport {
|
||||
shader_manifest_hash: shader_manifest.manifest_hash,
|
||||
portability_enumeration: renderer
|
||||
.instance
|
||||
.as_ref()
|
||||
.is_some_and(|instance| instance.report.create_flags != 0),
|
||||
device_name: device_ref.report.device_name.clone(),
|
||||
graphics_queue_family: device_ref.report.graphics_queue_family,
|
||||
present_queue_family: device_ref.report.present_queue_family,
|
||||
enabled_extension_count: device_ref
|
||||
.report
|
||||
.enabled_extensions
|
||||
.len()
|
||||
.try_into()
|
||||
.unwrap_or(u32::MAX),
|
||||
swapchain_extent: swapchain_ref.report.plan.extent,
|
||||
swapchain_image_count: swapchain_ref.report.image_count,
|
||||
};
|
||||
Ok(renderer)
|
||||
}
|
||||
|
||||
/// Returns the current bootstrap report.
|
||||
#[must_use]
|
||||
pub const fn report(&self) -> &VulkanSmokeRendererReport {
|
||||
&self.report
|
||||
}
|
||||
|
||||
/// Returns measured validation counters and VUIDs.
|
||||
#[must_use]
|
||||
pub fn validation_report(&self) -> VulkanValidationReport {
|
||||
self.validation.as_ref().map_or(
|
||||
VulkanValidationReport {
|
||||
warning_count: 0,
|
||||
error_count: 0,
|
||||
vuids: Vec::new(),
|
||||
},
|
||||
VulkanValidationMessenger::report,
|
||||
)
|
||||
}
|
||||
|
||||
/// Returns the measured swapchain recreation count.
|
||||
#[must_use]
|
||||
pub const fn swapchain_recreate_count(&self) -> u32 {
|
||||
self.swapchain_recreate_count
|
||||
}
|
||||
|
||||
/// Requests swapchain recreation for a new drawable extent.
|
||||
pub fn request_resize(&mut self, extent: (u32, u32)) {
|
||||
self.pending_extent = Some(extent);
|
||||
}
|
||||
|
||||
fn device_ref(&self) -> Result<&VulkanLogicalDeviceProbe, VulkanSmokeRendererError> {
|
||||
self.device
|
||||
.as_ref()
|
||||
.ok_or(VulkanSmokeRendererError::InvariantViolation {
|
||||
context: "logical device",
|
||||
})
|
||||
}
|
||||
|
||||
fn swapchain_ref(&self) -> Result<&VulkanSwapchainProbe, VulkanSmokeRendererError> {
|
||||
self.swapchain
|
||||
.as_ref()
|
||||
.ok_or(VulkanSmokeRendererError::InvariantViolation {
|
||||
context: "swapchain",
|
||||
})
|
||||
}
|
||||
|
||||
fn instance_ref(&self) -> Result<&VulkanInstanceProbe, VulkanSmokeRendererError> {
|
||||
self.instance
|
||||
.as_ref()
|
||||
.ok_or(VulkanSmokeRendererError::InvariantViolation {
|
||||
context: "instance",
|
||||
})
|
||||
}
|
||||
|
||||
fn surface_ref(&self) -> Result<&VulkanSurfaceProbe, VulkanSmokeRendererError> {
|
||||
self.surface
|
||||
.as_ref()
|
||||
.ok_or(VulkanSmokeRendererError::InvariantViolation { context: "surface" })
|
||||
}
|
||||
|
||||
fn resources_ref(&self) -> Result<&VulkanSwapchainResources, VulkanSmokeRendererError> {
|
||||
self.swapchain_resources
|
||||
.as_ref()
|
||||
.ok_or(VulkanSmokeRendererError::InvariantViolation {
|
||||
context: "swapchain resources",
|
||||
})
|
||||
}
|
||||
|
||||
fn vertex_buffer_ref(&self) -> Result<&VulkanAllocatedBuffer, VulkanSmokeRendererError> {
|
||||
self.vertex_buffer
|
||||
.as_ref()
|
||||
.ok_or(VulkanSmokeRendererError::InvariantViolation {
|
||||
context: "vertex buffer",
|
||||
})
|
||||
}
|
||||
|
||||
fn index_buffer_ref(&self) -> Result<&VulkanAllocatedBuffer, VulkanSmokeRendererError> {
|
||||
self.index_buffer
|
||||
.as_ref()
|
||||
.ok_or(VulkanSmokeRendererError::InvariantViolation {
|
||||
context: "index buffer",
|
||||
})
|
||||
}
|
||||
|
||||
/// Draws and presents one indexed-triangle frame.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns [`VulkanSmokeRendererError`] when synchronization, command recording,
|
||||
/// submission, or presentation fails.
|
||||
#[allow(clippy::too_many_lines)]
|
||||
pub fn draw_frame(&mut self) -> Result<VulkanSmokeFrameOutcome, VulkanSmokeRendererError> {
|
||||
if let Some(extent) = self.pending_extent.take() {
|
||||
if extent.0 == 0 || extent.1 == 0 {
|
||||
self.pending_extent = Some(extent);
|
||||
return Ok(VulkanSmokeFrameOutcome::ZeroExtent);
|
||||
}
|
||||
self.recreate_swapchain(extent)?;
|
||||
return Ok(VulkanSmokeFrameOutcome::Recreated);
|
||||
}
|
||||
|
||||
let sync = &self.frame_sync[self.current_frame];
|
||||
let image_available = sync.image_available;
|
||||
let render_finished = sync.render_finished;
|
||||
let in_flight_fence = sync.fence;
|
||||
// SAFETY: The fence belongs to this live logical device and is waited from one thread.
|
||||
unsafe {
|
||||
self.device_ref()?
|
||||
.device()
|
||||
.wait_for_fences(&[in_flight_fence], true, 1_000_000_000)
|
||||
}
|
||||
.map_err(|error| VulkanSmokeRendererError::VulkanOperation {
|
||||
context: "vkWaitForFences",
|
||||
result: error,
|
||||
})?;
|
||||
// SAFETY: The swapchain, semaphore and fence inputs are live for the duration of the acquire call.
|
||||
let acquire = unsafe {
|
||||
self.swapchain_ref()?.loader().acquire_next_image(
|
||||
self.swapchain_ref()?.swapchain(),
|
||||
1_000_000_000,
|
||||
image_available,
|
||||
vk::Fence::null(),
|
||||
)
|
||||
};
|
||||
let (image_index, acquire_suboptimal) = match acquire {
|
||||
Ok(result) => result,
|
||||
Err(vk::Result::ERROR_OUT_OF_DATE_KHR) => {
|
||||
self.recreate_swapchain(self.report.swapchain_extent)?;
|
||||
return Ok(VulkanSmokeFrameOutcome::Recreated);
|
||||
}
|
||||
Err(error) => {
|
||||
return Err(VulkanSmokeRendererError::VulkanOperation {
|
||||
context: "vkAcquireNextImageKHR",
|
||||
result: error,
|
||||
});
|
||||
}
|
||||
};
|
||||
let image_index_usize = usize::try_from(image_index).unwrap_or(0);
|
||||
let image_fence = self.images_in_flight[image_index_usize];
|
||||
if image_fence != vk::Fence::null() {
|
||||
// SAFETY: The fence belongs to this renderer and can be waited independently.
|
||||
unsafe {
|
||||
self.device_ref()?
|
||||
.device()
|
||||
.wait_for_fences(&[image_fence], true, 1_000_000_000)
|
||||
}
|
||||
.map_err(|error| VulkanSmokeRendererError::VulkanOperation {
|
||||
context: "vkWaitForFences(image)",
|
||||
result: error,
|
||||
})?;
|
||||
}
|
||||
self.images_in_flight[image_index_usize] = in_flight_fence;
|
||||
// SAFETY: The fence belongs to this frame context and is not in use after the wait above.
|
||||
unsafe { self.device_ref()?.device().reset_fences(&[in_flight_fence]) }.map_err(
|
||||
|error| VulkanSmokeRendererError::VulkanOperation {
|
||||
context: "vkResetFences",
|
||||
result: error,
|
||||
},
|
||||
)?;
|
||||
|
||||
self.record_command_buffer(image_index_usize)?;
|
||||
let wait_semaphores = [image_available];
|
||||
let wait_stages = [vk::PipelineStageFlags::COLOR_ATTACHMENT_OUTPUT];
|
||||
let command_buffers = [self.resources_ref()?.command_buffers[image_index_usize]];
|
||||
let signal_semaphores = [render_finished];
|
||||
let submit_info = [vk::SubmitInfo::default()
|
||||
.wait_semaphores(&wait_semaphores)
|
||||
.wait_dst_stage_mask(&wait_stages)
|
||||
.command_buffers(&command_buffers)
|
||||
.signal_semaphores(&signal_semaphores)];
|
||||
// SAFETY: Submission references live queue, sync objects and recorded command buffer.
|
||||
unsafe {
|
||||
self.device_ref()?.device().queue_submit(
|
||||
self.device_ref()?.graphics_queue(),
|
||||
&submit_info,
|
||||
in_flight_fence,
|
||||
)
|
||||
}
|
||||
.map_err(|error| VulkanSmokeRendererError::VulkanOperation {
|
||||
context: "vkQueueSubmit",
|
||||
result: error,
|
||||
})?;
|
||||
|
||||
let present_wait = [render_finished];
|
||||
let swapchains = [self.swapchain_ref()?.swapchain()];
|
||||
let image_indices = [image_index];
|
||||
let present_info = vk::PresentInfoKHR::default()
|
||||
.wait_semaphores(&present_wait)
|
||||
.swapchains(&swapchains)
|
||||
.image_indices(&image_indices);
|
||||
// SAFETY: Presentation uses the rendered image index and a semaphore signaled by queue submission.
|
||||
let present_suboptimal = match unsafe {
|
||||
self.swapchain_ref()?
|
||||
.loader()
|
||||
.queue_present(self.device_ref()?.present_queue(), &present_info)
|
||||
} {
|
||||
Ok(suboptimal) => suboptimal,
|
||||
Err(vk::Result::ERROR_OUT_OF_DATE_KHR) => {
|
||||
self.recreate_swapchain(self.report.swapchain_extent)?;
|
||||
return Ok(VulkanSmokeFrameOutcome::Recreated);
|
||||
}
|
||||
Err(error) => {
|
||||
return Err(VulkanSmokeRendererError::VulkanOperation {
|
||||
context: "vkQueuePresentKHR",
|
||||
result: error,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
self.current_frame = (self.current_frame + 1) % self.frame_sync.len().max(1);
|
||||
if acquire_suboptimal || present_suboptimal {
|
||||
self.recreate_swapchain(self.report.swapchain_extent)?;
|
||||
Ok(VulkanSmokeFrameOutcome::Recreated)
|
||||
} else {
|
||||
Ok(VulkanSmokeFrameOutcome::Presented)
|
||||
}
|
||||
}
|
||||
|
||||
fn recreate_swapchain(&mut self, extent: (u32, u32)) -> Result<(), VulkanSmokeRendererError> {
|
||||
let device = self.device_ref()?;
|
||||
// SAFETY: The logical device remains live and idling at swapchain recreation boundaries.
|
||||
unsafe { device.device().device_wait_idle() }.map_err(|error| {
|
||||
VulkanSmokeRendererError::VulkanOperation {
|
||||
context: "vkDeviceWaitIdle",
|
||||
result: error,
|
||||
}
|
||||
})?;
|
||||
self.pending_extent = None;
|
||||
self.rebuild_swapchain(extent)?;
|
||||
self.swapchain_recreate_count = self.swapchain_recreate_count.saturating_add(1);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn rebuild_swapchain(&mut self, extent: (u32, u32)) -> Result<(), VulkanSmokeRendererError> {
|
||||
self.destroy_swapchain_resources();
|
||||
let instance = self.instance_ref()?;
|
||||
let surface = self.surface_ref()?;
|
||||
let device = self.device_ref()?;
|
||||
let old_swapchain = self
|
||||
.swapchain
|
||||
.as_ref()
|
||||
.map_or(vk::SwapchainKHR::null(), VulkanSwapchainProbe::swapchain);
|
||||
let new_swapchain = create_vulkan_swapchain_probe_for_extent(
|
||||
instance,
|
||||
surface,
|
||||
device,
|
||||
extent,
|
||||
old_swapchain,
|
||||
)
|
||||
.map_err(VulkanSmokeRendererError::Swapchain)?;
|
||||
self.swapchain = Some(new_swapchain);
|
||||
self.rebuild_swapchain_resources(true)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn rebuild_swapchain_resources(
|
||||
&mut self,
|
||||
reuse_command_pool: bool,
|
||||
) -> Result<(), VulkanSmokeRendererError> {
|
||||
let resources = {
|
||||
let device = self.device_ref()?;
|
||||
let swapchain = self.swapchain_ref()?;
|
||||
create_swapchain_resources(
|
||||
device,
|
||||
swapchain,
|
||||
self.command_pool,
|
||||
self.vertex_buffer_ref()?,
|
||||
self.index_buffer_ref()?,
|
||||
reuse_command_pool,
|
||||
)?
|
||||
};
|
||||
let frame_sync = {
|
||||
let device = self.device_ref()?;
|
||||
create_frame_sync(device)?
|
||||
};
|
||||
let swapchain_extent = self.swapchain_ref()?.report.plan.extent;
|
||||
let swapchain_image_count = self.swapchain_ref()?.report.image_count;
|
||||
self.images_in_flight = vec![vk::Fence::null(); resources.image_views.len()];
|
||||
self.frame_sync = frame_sync;
|
||||
self.report.swapchain_extent = swapchain_extent;
|
||||
self.report.swapchain_image_count = swapchain_image_count;
|
||||
self.swapchain_resources = Some(resources);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_lines)]
|
||||
fn record_command_buffer(
|
||||
&mut self,
|
||||
image_index: usize,
|
||||
) -> Result<(), VulkanSmokeRendererError> {
|
||||
let device = self.device_ref()?;
|
||||
let swapchain = self.swapchain_ref()?;
|
||||
let resources = self.resources_ref()?;
|
||||
let command_buffer = resources.command_buffers[image_index];
|
||||
// SAFETY: The command buffer belongs to the resettable pool owned by this renderer.
|
||||
unsafe {
|
||||
device
|
||||
.device()
|
||||
.reset_command_buffer(command_buffer, vk::CommandBufferResetFlags::empty())
|
||||
}
|
||||
.map_err(|error| VulkanSmokeRendererError::VulkanOperation {
|
||||
context: "vkResetCommandBuffer",
|
||||
result: error,
|
||||
})?;
|
||||
let begin_info = vk::CommandBufferBeginInfo::default();
|
||||
// SAFETY: The command buffer is in the initial state after reset and recorded on one thread.
|
||||
unsafe {
|
||||
device
|
||||
.device()
|
||||
.begin_command_buffer(command_buffer, &begin_info)
|
||||
}
|
||||
.map_err(|error| VulkanSmokeRendererError::VulkanOperation {
|
||||
context: "vkBeginCommandBuffer",
|
||||
result: error,
|
||||
})?;
|
||||
|
||||
let pre_barrier = vk::ImageMemoryBarrier::default()
|
||||
.old_layout(vk::ImageLayout::PRESENT_SRC_KHR)
|
||||
.new_layout(vk::ImageLayout::COLOR_ATTACHMENT_OPTIMAL)
|
||||
.src_queue_family_index(vk::QUEUE_FAMILY_IGNORED)
|
||||
.dst_queue_family_index(vk::QUEUE_FAMILY_IGNORED)
|
||||
.subresource_range(color_subresource_range())
|
||||
.src_access_mask(vk::AccessFlags::empty())
|
||||
.dst_access_mask(vk::AccessFlags::COLOR_ATTACHMENT_WRITE);
|
||||
// SAFETY: The swapchain is live and queried only to resolve the current image handles.
|
||||
let swapchain_images = unsafe {
|
||||
swapchain
|
||||
.loader()
|
||||
.get_swapchain_images(swapchain.swapchain())
|
||||
}
|
||||
.map_err(|error| VulkanSmokeRendererError::VulkanOperation {
|
||||
context: "vkGetSwapchainImagesKHR",
|
||||
result: error,
|
||||
})?;
|
||||
let pre_barrier = pre_barrier.image(swapchain_images[image_index]);
|
||||
// SAFETY: The barriers operate on the acquired swapchain image owned by this command buffer submission.
|
||||
unsafe {
|
||||
device.device().cmd_pipeline_barrier(
|
||||
command_buffer,
|
||||
vk::PipelineStageFlags::COLOR_ATTACHMENT_OUTPUT,
|
||||
vk::PipelineStageFlags::COLOR_ATTACHMENT_OUTPUT,
|
||||
vk::DependencyFlags::empty(),
|
||||
&[],
|
||||
&[],
|
||||
&[pre_barrier],
|
||||
);
|
||||
}
|
||||
|
||||
let clear_values = [vk::ClearValue {
|
||||
color: vk::ClearColorValue {
|
||||
float32: [0.05, 0.08, 0.11, 1.0],
|
||||
},
|
||||
}];
|
||||
let render_area = vk::Rect2D {
|
||||
offset: vk::Offset2D { x: 0, y: 0 },
|
||||
extent: vk::Extent2D {
|
||||
width: swapchain.report.plan.extent.0,
|
||||
height: swapchain.report.plan.extent.1,
|
||||
},
|
||||
};
|
||||
let render_pass_info = vk::RenderPassBeginInfo::default()
|
||||
.render_pass(resources.render_pass)
|
||||
.framebuffer(resources.framebuffers[image_index])
|
||||
.render_area(render_area)
|
||||
.clear_values(&clear_values);
|
||||
// SAFETY: All commands target live frame resources owned by this renderer.
|
||||
unsafe {
|
||||
device.device().cmd_begin_render_pass(
|
||||
command_buffer,
|
||||
&render_pass_info,
|
||||
vk::SubpassContents::INLINE,
|
||||
);
|
||||
device.device().cmd_bind_pipeline(
|
||||
command_buffer,
|
||||
vk::PipelineBindPoint::GRAPHICS,
|
||||
resources.pipeline,
|
||||
);
|
||||
let vertex_buffers = [self.vertex_buffer_ref()?.buffer];
|
||||
let offsets = [0_u64];
|
||||
device
|
||||
.device()
|
||||
.cmd_bind_vertex_buffers(command_buffer, 0, &vertex_buffers, &offsets);
|
||||
device.device().cmd_bind_index_buffer(
|
||||
command_buffer,
|
||||
self.index_buffer_ref()?.buffer,
|
||||
0,
|
||||
vk::IndexType::UINT16,
|
||||
);
|
||||
device
|
||||
.device()
|
||||
.cmd_draw_indexed(command_buffer, 3, 1, 0, 0, 0);
|
||||
device.device().cmd_end_render_pass(command_buffer);
|
||||
}
|
||||
|
||||
let post_barrier = vk::ImageMemoryBarrier::default()
|
||||
.old_layout(vk::ImageLayout::COLOR_ATTACHMENT_OPTIMAL)
|
||||
.new_layout(vk::ImageLayout::PRESENT_SRC_KHR)
|
||||
.src_queue_family_index(vk::QUEUE_FAMILY_IGNORED)
|
||||
.dst_queue_family_index(vk::QUEUE_FAMILY_IGNORED)
|
||||
.image(swapchain_images[image_index])
|
||||
.subresource_range(color_subresource_range())
|
||||
.src_access_mask(vk::AccessFlags::COLOR_ATTACHMENT_WRITE)
|
||||
.dst_access_mask(vk::AccessFlags::empty());
|
||||
// SAFETY: The post-render barrier transitions the same live swapchain image into present layout.
|
||||
unsafe {
|
||||
device.device().cmd_pipeline_barrier(
|
||||
command_buffer,
|
||||
vk::PipelineStageFlags::COLOR_ATTACHMENT_OUTPUT,
|
||||
vk::PipelineStageFlags::BOTTOM_OF_PIPE,
|
||||
vk::DependencyFlags::empty(),
|
||||
&[],
|
||||
&[],
|
||||
&[post_barrier],
|
||||
);
|
||||
device.device().end_command_buffer(command_buffer)
|
||||
}
|
||||
.map_err(|error| VulkanSmokeRendererError::VulkanOperation {
|
||||
context: "vkEndCommandBuffer",
|
||||
result: error,
|
||||
})?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn destroy_swapchain_resources(&mut self) {
|
||||
let Some(device) = self.device.as_ref() else {
|
||||
return;
|
||||
};
|
||||
for sync in self.frame_sync.drain(..) {
|
||||
// SAFETY: These sync objects belong to this device and are destroyed once.
|
||||
unsafe {
|
||||
device
|
||||
.device()
|
||||
.destroy_semaphore(sync.image_available, None);
|
||||
device
|
||||
.device()
|
||||
.destroy_semaphore(sync.render_finished, None);
|
||||
device.device().destroy_fence(sync.fence, None);
|
||||
}
|
||||
}
|
||||
if let Some(resources) = self.swapchain_resources.take() {
|
||||
destroy_swapchain_resources(device, self.command_pool, resources);
|
||||
}
|
||||
self.images_in_flight.clear();
|
||||
self.current_frame = 0;
|
||||
}
|
||||
|
||||
fn teardown(&mut self) {
|
||||
if let Some(device) = self.device.as_ref() {
|
||||
// SAFETY: The logical device remains live until teardown finishes and idling prevents in-flight work from touching swapchain, buffers, sync objects or the command pool after destruction starts.
|
||||
let _ = unsafe { device.device().device_wait_idle() };
|
||||
}
|
||||
self.destroy_swapchain_resources();
|
||||
if let Some(device) = self.device.as_ref() {
|
||||
if let Some(buffer) = self.index_buffer.take() {
|
||||
// SAFETY: Buffer and memory belong to this device and are destroyed once after the device has been idled and frame work has been torn down.
|
||||
unsafe {
|
||||
device.device().destroy_buffer(buffer.buffer, None);
|
||||
device.device().free_memory(buffer.memory, None);
|
||||
}
|
||||
}
|
||||
if let Some(buffer) = self.vertex_buffer.take() {
|
||||
// SAFETY: Buffer and memory belong to this device and are destroyed once after the device has been idled and frame work has been torn down.
|
||||
unsafe {
|
||||
device.device().destroy_buffer(buffer.buffer, None);
|
||||
device.device().free_memory(buffer.memory, None);
|
||||
}
|
||||
}
|
||||
// SAFETY: The command pool belongs to this device and is destroyed once after the device is idle and all command buffers allocated from it were freed above.
|
||||
unsafe {
|
||||
device
|
||||
.device()
|
||||
.destroy_command_pool(self.command_pool, None);
|
||||
};
|
||||
}
|
||||
// Drop child Vulkan owners explicitly before their parents instead of relying on field order.
|
||||
self.swapchain.take();
|
||||
self.device.take();
|
||||
self.surface.take();
|
||||
self.validation.take();
|
||||
self.instance.take();
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for VulkanSmokeRenderer {
|
||||
fn drop(&mut self) {
|
||||
self.teardown();
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
Reference in New Issue
Block a user