aboutsummaryrefslogtreecommitdiff
path: root/adapters/fparkan-render-vulkan/src/ffi.rs
diff options
context:
space:
mode:
Diffstat (limited to 'adapters/fparkan-render-vulkan/src/ffi.rs')
-rw-r--r--adapters/fparkan-render-vulkan/src/ffi.rs601
1 files changed, 2 insertions, 599 deletions
diff --git a/adapters/fparkan-render-vulkan/src/ffi.rs b/adapters/fparkan-render-vulkan/src/ffi.rs
index b7ec403..c5025a5 100644
--- a/adapters/fparkan-render-vulkan/src/ffi.rs
+++ b/adapters/fparkan-render-vulkan/src/ffi.rs
@@ -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::*;