diff options
| -rw-r--r-- | adapters/fparkan-render-vulkan/src/ffi.rs | 683 | ||||
| -rw-r--r-- | adapters/fparkan-render-vulkan/src/ffi/resources.rs | 699 | ||||
| -rw-r--r-- | xtask/src/main.rs | 1 |
3 files changed, 706 insertions, 677 deletions
diff --git a/adapters/fparkan-render-vulkan/src/ffi.rs b/adapters/fparkan-render-vulkan/src/ffi.rs index 7159204..b7ec403 100644 --- a/adapters/fparkan-render-vulkan/src/ffi.rs +++ b/adapters/fparkan-render-vulkan/src/ffi.rs @@ -28,6 +28,7 @@ //! This crate is the declared low-level Vulkan boundary. mod instance; +mod resources; mod runtime; mod surface; mod validation; @@ -40,6 +41,11 @@ pub use self::instance::{ }; #[cfg(test)] use self::instance::{cstring_vec, ensure_instance_extensions_available}; +use self::resources::{ + color_subresource_range, create_command_pool, create_frame_sync, create_swapchain_resources, + create_triangle_index_buffer, create_triangle_vertex_buffer, destroy_allocated_buffer, + destroy_swapchain_resources, VulkanAllocatedBuffer, VulkanFrameSync, VulkanSwapchainResources, +}; pub use self::runtime::{ create_vulkan_logical_device_probe, create_vulkan_swapchain_probe, create_vulkan_swapchain_probe_for_extent, probe_vulkan_runtime_capabilities, @@ -560,35 +566,6 @@ impl std::fmt::Display for VulkanSmokeRendererError { impl std::error::Error for VulkanSmokeRendererError {} -struct VulkanAllocatedBuffer { - buffer: vk::Buffer, - memory: vk::DeviceMemory, -} - -struct VulkanSwapchainResources { - image_views: Vec<vk::ImageView>, - render_pass: vk::RenderPass, - pipeline_layout: vk::PipelineLayout, - pipeline: vk::Pipeline, - framebuffers: Vec<vk::Framebuffer>, - 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, - fence: vk::Fence, -} - /// Live Stage 0 Vulkan triangle renderer used by the smoke app. pub struct VulkanSmokeRenderer { instance: Option<VulkanInstanceProbe>, @@ -1204,654 +1181,6 @@ impl Drop for VulkanSmokeRenderer { } } -fn create_command_pool( - device: &VulkanLogicalDeviceProbe, -) -> Result<vk::CommandPool, VulkanSmokeRendererError> { - let create_info = vk::CommandPoolCreateInfo::default() - .queue_family_index(device.report.graphics_queue_family) - .flags(vk::CommandPoolCreateFlags::RESET_COMMAND_BUFFER); - // SAFETY: The queue-family index belongs to this live logical device. - unsafe { device.device().create_command_pool(&create_info, None) }.map_err(|error| { - VulkanSmokeRendererError::VulkanOperation { - context: "vkCreateCommandPool", - result: error, - } - }) -} - -fn create_triangle_vertex_buffer( - instance: &VulkanInstanceProbe, - device: &VulkanLogicalDeviceProbe, -) -> Result<VulkanAllocatedBuffer, VulkanSmokeRendererError> { - let vertices: [[f32; 5]; 3] = [ - [0.0, -0.55, 1.0, 0.2, 0.2], - [0.55, 0.55, 0.2, 1.0, 0.2], - [-0.55, 0.55, 0.2, 0.4, 1.0], - ]; - let mut bytes = Vec::with_capacity(vertices.len() * 5 * std::mem::size_of::<f32>()); - for vertex in vertices { - for value in vertex { - bytes.extend_from_slice(&value.to_ne_bytes()); - } - } - create_host_visible_buffer( - instance, - device, - &bytes, - vk::BufferUsageFlags::VERTEX_BUFFER, - "triangle vertex buffer", - ) -} - -fn create_triangle_index_buffer( - instance: &VulkanInstanceProbe, - device: &VulkanLogicalDeviceProbe, -) -> Result<VulkanAllocatedBuffer, VulkanSmokeRendererError> { - let indices = [0_u16, 1_u16, 2_u16]; - let mut bytes = Vec::with_capacity(indices.len() * std::mem::size_of::<u16>()); - for index in indices { - bytes.extend_from_slice(&index.to_ne_bytes()); - } - create_host_visible_buffer( - instance, - device, - &bytes, - vk::BufferUsageFlags::INDEX_BUFFER, - "triangle index buffer", - ) -} - -fn create_host_visible_buffer( - instance: &VulkanInstanceProbe, - device: &VulkanLogicalDeviceProbe, - bytes: &[u8], - usage: vk::BufferUsageFlags, - context: &'static str, -) -> Result<VulkanAllocatedBuffer, VulkanSmokeRendererError> { - let create_info = vk::BufferCreateInfo::default() - .size(bytes.len().try_into().unwrap_or(u64::MAX)) - .usage(usage) - .sharing_mode(vk::SharingMode::EXCLUSIVE); - // SAFETY: The create info is stack-owned and references no external memory. - let buffer = unsafe { device.device().create_buffer(&create_info, None) }.map_err(|error| { - VulkanSmokeRendererError::VulkanOperation { - context, - result: error, - } - })?; - // SAFETY: The buffer belongs to this device and is queried immediately after creation. - let requirements = unsafe { device.device().get_buffer_memory_requirements(buffer) }; - let Some(memory_type_index) = find_memory_type( - instance, - device.physical_device(), - requirements.memory_type_bits, - vk::MemoryPropertyFlags::HOST_VISIBLE | vk::MemoryPropertyFlags::HOST_COHERENT, - ) 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: error, - } - })?; - // 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: error, - } - })?; - // SAFETY: The allocation is HOST_VISIBLE, mapped for the full buffer size and unmapped before return. - let mapped = unsafe { - device - .device() - .map_memory(memory, 0, requirements.size, vk::MemoryMapFlags::empty()) - } - .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: error, - } - })?; - // SAFETY: The mapped pointer is valid for `bytes.len()` bytes and non-overlapping with the source slice. - unsafe { - std::ptr::copy_nonoverlapping(bytes.as_ptr(), mapped.cast::<u8>(), bytes.len()); - device.device().unmap_memory(memory); - } - Ok(VulkanAllocatedBuffer { buffer, memory }) -} - -fn find_memory_type( - instance: &VulkanInstanceProbe, - physical_device: vk::PhysicalDevice, - memory_type_bits: u32, - required_properties: vk::MemoryPropertyFlags, -) -> Option<u32> { - // SAFETY: Physical-device memory properties are queried from a live instance-owned physical device. - let memory_properties = unsafe { - instance - .instance - .get_physical_device_memory_properties(physical_device) - }; - memory_properties - .memory_types - .iter() - .enumerate() - .find_map(|(index, memory_type)| { - let supported = (memory_type_bits & (1_u32 << index)) != 0; - let has_properties = memory_type.property_flags.contains(required_properties); - (supported && has_properties).then(|| index.try_into().unwrap_or(u32::MAX)) - }) -} - -#[allow(clippy::too_many_lines)] -fn create_swapchain_resources( - device: &VulkanLogicalDeviceProbe, - swapchain: &VulkanSwapchainProbe, - command_pool: vk::CommandPool, - _vertex_buffer: &VulkanAllocatedBuffer, - _index_buffer: &VulkanAllocatedBuffer, - _reuse_command_pool: bool, -) -> Result<VulkanSwapchainResources, VulkanSmokeRendererError> { - // SAFETY: The swapchain is live and owned by this renderer for the duration of the query. - let images = unsafe { - swapchain - .loader() - .get_swapchain_images(swapchain.swapchain()) - } - .map_err(|error| VulkanSmokeRendererError::VulkanOperation { - context: "vkGetSwapchainImagesKHR", - result: error, - })?; - 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, - ) { - 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, - 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: partial.image_views, - render_pass, - pipeline_layout, - pipeline, - framebuffers: partial.framebuffers, - command_buffers: partial.command_buffers, - }) -} - -fn create_image_view( - device: &VulkanLogicalDeviceProbe, - image: vk::Image, - format: i32, -) -> Result<vk::ImageView, VulkanSmokeRendererError> { - let create_info = vk::ImageViewCreateInfo::default() - .image(image) - .view_type(vk::ImageViewType::TYPE_2D) - .format(vk::Format::from_raw(format)) - .subresource_range(color_subresource_range()); - // SAFETY: The image comes from the live swapchain and the subresource range covers its color aspect. - unsafe { device.device().create_image_view(&create_info, None) }.map_err(|error| { - VulkanSmokeRendererError::VulkanOperation { - context: "vkCreateImageView", - result: error, - } - }) -} - -fn create_render_pass( - device: &VulkanLogicalDeviceProbe, - format: i32, -) -> Result<vk::RenderPass, VulkanSmokeRendererError> { - let color_attachment = vk::AttachmentDescription::default() - .format(vk::Format::from_raw(format)) - .samples(vk::SampleCountFlags::TYPE_1) - .load_op(vk::AttachmentLoadOp::CLEAR) - .store_op(vk::AttachmentStoreOp::STORE) - .initial_layout(vk::ImageLayout::COLOR_ATTACHMENT_OPTIMAL) - .final_layout(vk::ImageLayout::COLOR_ATTACHMENT_OPTIMAL); - let color_attachment_ref = vk::AttachmentReference::default() - .attachment(0) - .layout(vk::ImageLayout::COLOR_ATTACHMENT_OPTIMAL); - let color_attachments = [color_attachment_ref]; - let subpass = vk::SubpassDescription::default() - .pipeline_bind_point(vk::PipelineBindPoint::GRAPHICS) - .color_attachments(&color_attachments); - let dependency = vk::SubpassDependency::default() - .src_subpass(vk::SUBPASS_EXTERNAL) - .dst_subpass(0) - .src_stage_mask(vk::PipelineStageFlags::COLOR_ATTACHMENT_OUTPUT) - .dst_stage_mask(vk::PipelineStageFlags::COLOR_ATTACHMENT_OUTPUT) - .dst_access_mask(vk::AccessFlags::COLOR_ATTACHMENT_WRITE); - let attachments = [color_attachment]; - let subpasses = [subpass]; - let dependencies = [dependency]; - let create_info = vk::RenderPassCreateInfo::default() - .attachments(&attachments) - .subpasses(&subpasses) - .dependencies(&dependencies); - // SAFETY: The render-pass create info only references stack-owned descriptors. - unsafe { device.device().create_render_pass(&create_info, None) }.map_err(|error| { - VulkanSmokeRendererError::VulkanOperation { - context: "vkCreateRenderPass", - result: error, - } - }) -} - -fn create_pipeline_layout( - device: &VulkanLogicalDeviceProbe, -) -> Result<vk::PipelineLayout, VulkanSmokeRendererError> { - let create_info = vk::PipelineLayoutCreateInfo::default(); - // SAFETY: The pipeline layout contains no descriptor sets or push constants. - unsafe { device.device().create_pipeline_layout(&create_info, None) }.map_err(|error| { - VulkanSmokeRendererError::VulkanOperation { - context: "vkCreatePipelineLayout", - result: error, - } - }) -} - -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, - pipeline_layout: vk::PipelineLayout, - extent: (u32, u32), -) -> Result<vk::Pipeline, VulkanSmokeRendererError> { - let entry_point = c"main"; - let vertex_module = create_shader_module(device, TRIANGLE_VERTEX_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) - .module(vertex_module) - .name(entry_point), - vk::PipelineShaderStageCreateInfo::default() - .stage(vk::ShaderStageFlags::FRAGMENT) - .module(fragment_module) - .name(entry_point), - ]; - let binding_descriptions = [vk::VertexInputBindingDescription { - binding: 0, - stride: 20, - input_rate: vk::VertexInputRate::VERTEX, - }]; - let attribute_descriptions = [ - vk::VertexInputAttributeDescription { - location: 0, - binding: 0, - format: vk::Format::R32G32_SFLOAT, - offset: 0, - }, - vk::VertexInputAttributeDescription { - location: 1, - binding: 0, - format: vk::Format::R32G32B32_SFLOAT, - offset: 8, - }, - ]; - let vertex_input_state = vk::PipelineVertexInputStateCreateInfo::default() - .vertex_binding_descriptions(&binding_descriptions) - .vertex_attribute_descriptions(&attribute_descriptions); - let input_assembly_state = vk::PipelineInputAssemblyStateCreateInfo::default() - .topology(vk::PrimitiveTopology::TRIANGLE_LIST); - let viewports = [vk::Viewport { - x: 0.0, - y: 0.0, - width: extent_component_to_f32(extent.0), - height: extent_component_to_f32(extent.1), - min_depth: 0.0, - max_depth: 1.0, - }]; - let scissors = [vk::Rect2D { - offset: vk::Offset2D { x: 0, y: 0 }, - extent: vk::Extent2D { - width: extent.0, - height: extent.1, - }, - }]; - let viewport_state = vk::PipelineViewportStateCreateInfo::default() - .viewports(&viewports) - .scissors(&scissors); - let rasterization_state = vk::PipelineRasterizationStateCreateInfo::default() - .polygon_mode(vk::PolygonMode::FILL) - .cull_mode(vk::CullModeFlags::BACK) - .front_face(vk::FrontFace::CLOCKWISE) - .line_width(1.0); - let multisample_state = vk::PipelineMultisampleStateCreateInfo::default() - .rasterization_samples(vk::SampleCountFlags::TYPE_1); - let color_blend_attachment = [vk::PipelineColorBlendAttachmentState::default() - .color_write_mask( - vk::ColorComponentFlags::R - | vk::ColorComponentFlags::G - | vk::ColorComponentFlags::B - | vk::ColorComponentFlags::A, - )]; - let color_blend_state = - vk::PipelineColorBlendStateCreateInfo::default().attachments(&color_blend_attachment); - let create_info = [vk::GraphicsPipelineCreateInfo::default() - .stages(&stage_create_infos) - .vertex_input_state(&vertex_input_state) - .input_assembly_state(&input_assembly_state) - .viewport_state(&viewport_state) - .rasterization_state(&rasterization_state) - .multisample_state(&multisample_state) - .color_blend_state(&color_blend_state) - .layout(pipeline_layout) - .render_pass(render_pass) - .subpass(0)]; - // SAFETY: The pipeline creation references live shader modules and stack-owned fixed-function descriptors. - let pipeline_result = unsafe { - device - .device() - .create_graphics_pipelines(vk::PipelineCache::null(), &create_info, None) - }; - // 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: error, - })?[0]; - Ok(pipeline) -} - -fn create_shader_module( - device: &VulkanLogicalDeviceProbe, - words: &[u32], -) -> Result<vk::ShaderModule, VulkanSmokeRendererError> { - let create_info = vk::ShaderModuleCreateInfo::default().code(words); - // SAFETY: SPIR-V words are immutable and valid for the duration of the call. - unsafe { device.device().create_shader_module(&create_info, None) }.map_err(|error| { - VulkanSmokeRendererError::VulkanOperation { - context: "vkCreateShaderModule", - result: error, - } - }) -} - -fn create_framebuffer( - device: &VulkanLogicalDeviceProbe, - render_pass: vk::RenderPass, - image_view: vk::ImageView, - extent: (u32, u32), -) -> Result<vk::Framebuffer, VulkanSmokeRendererError> { - let attachments = [image_view]; - let create_info = vk::FramebufferCreateInfo::default() - .render_pass(render_pass) - .attachments(&attachments) - .width(extent.0) - .height(extent.1) - .layers(1); - // SAFETY: The framebuffer attachments and render pass remain live for the duration of the call. - unsafe { device.device().create_framebuffer(&create_info, None) }.map_err(|error| { - VulkanSmokeRendererError::VulkanOperation { - context: "vkCreateFramebuffer", - result: error, - } - }) -} - -fn allocate_command_buffers( - device: &VulkanLogicalDeviceProbe, - command_pool: vk::CommandPool, - count: u32, -) -> Result<Vec<vk::CommandBuffer>, VulkanSmokeRendererError> { - let allocate_info = vk::CommandBufferAllocateInfo::default() - .command_pool(command_pool) - .level(vk::CommandBufferLevel::PRIMARY) - .command_buffer_count(count); - // SAFETY: Command buffers are allocated from a live resettable pool owned by this device. - unsafe { device.device().allocate_command_buffers(&allocate_info) }.map_err(|error| { - VulkanSmokeRendererError::VulkanOperation { - context: "vkAllocateCommandBuffers", - result: error, - } - }) -} - -fn create_frame_sync( - device: &VulkanLogicalDeviceProbe, -) -> Result<Vec<VulkanFrameSync>, VulkanSmokeRendererError> { - let semaphore_info = vk::SemaphoreCreateInfo::default(); - let fence_info = vk::FenceCreateInfo::default().flags(vk::FenceCreateFlags::SIGNALED); - let mut sync = Vec::with_capacity(2); - for _ in 0..2 { - // SAFETY: The sync objects belong to this live logical device and are destroyed at teardown. - let image_available = unsafe { device.device().create_semaphore(&semaphore_info, None) } - .map_err(|error| VulkanSmokeRendererError::VulkanOperation { - context: "vkCreateSemaphore(image_available)", - result: 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: error, - }); - } - }; - let fence = - // SAFETY: The fence belongs to this live logical device and is destroyed at teardown. - 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: error, - }); - } - }; - sync.push(VulkanFrameSync { - image_available, - render_finished, - fence, - }); - } - Ok(sync) -} - -fn destroy_swapchain_resources( - device: &VulkanLogicalDeviceProbe, - command_pool: vk::CommandPool, - resources: VulkanSwapchainResources, -) { - // SAFETY: All swapchain-dependent objects belong to this device and are destroyed once. - unsafe { - device - .device() - .free_command_buffers(command_pool, &resources.command_buffers); - for framebuffer in resources.framebuffers { - device.device().destroy_framebuffer(framebuffer, None); - } - device.device().destroy_pipeline(resources.pipeline, None); - device - .device() - .destroy_pipeline_layout(resources.pipeline_layout, None); - device - .device() - .destroy_render_pass(resources.render_pass, None); - for image_view in resources.image_views { - device.device().destroy_image_view(image_view, None); - } - } -} - -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) - .base_mip_level(0) - .level_count(1) - .base_array_layer(0) - .layer_count(1) -} - #[cfg(test)] mod tests { use super::*; diff --git a/adapters/fparkan-render-vulkan/src/ffi/resources.rs b/adapters/fparkan-render-vulkan/src/ffi/resources.rs new file mode 100644 index 0000000..6bfe459 --- /dev/null +++ b/adapters/fparkan-render-vulkan/src/ffi/resources.rs @@ -0,0 +1,699 @@ +#![allow(unsafe_code)] + +use ash::vk; + +use super::{ + VulkanInstanceProbe, VulkanLogicalDeviceProbe, VulkanSmokeRendererError, VulkanSwapchainProbe, + TRIANGLE_FRAGMENT_SHADER_WORDS, TRIANGLE_VERTEX_SHADER_WORDS, +}; + +pub(super) struct VulkanAllocatedBuffer { + pub(super) buffer: vk::Buffer, + pub(super) memory: vk::DeviceMemory, +} + +pub(super) struct VulkanSwapchainResources { + pub(super) image_views: Vec<vk::ImageView>, + pub(super) render_pass: vk::RenderPass, + pub(super) pipeline_layout: vk::PipelineLayout, + pub(super) pipeline: vk::Pipeline, + pub(super) framebuffers: Vec<vk::Framebuffer>, + pub(super) 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>, +} + +pub(super) struct VulkanFrameSync { + pub(super) image_available: vk::Semaphore, + pub(super) render_finished: vk::Semaphore, + pub(super) fence: vk::Fence, +} + +pub(super) fn create_command_pool( + device: &VulkanLogicalDeviceProbe, +) -> Result<vk::CommandPool, VulkanSmokeRendererError> { + let create_info = vk::CommandPoolCreateInfo::default() + .queue_family_index(device.report.graphics_queue_family) + .flags(vk::CommandPoolCreateFlags::RESET_COMMAND_BUFFER); + // SAFETY: The queue-family index belongs to this live logical device. + unsafe { device.device().create_command_pool(&create_info, None) }.map_err(|error| { + VulkanSmokeRendererError::VulkanOperation { + context: "vkCreateCommandPool", + result: error, + } + }) +} + +pub(super) fn create_triangle_vertex_buffer( + instance: &VulkanInstanceProbe, + device: &VulkanLogicalDeviceProbe, +) -> Result<VulkanAllocatedBuffer, VulkanSmokeRendererError> { + let vertices: [[f32; 5]; 3] = [ + [0.0, -0.55, 1.0, 0.2, 0.2], + [0.55, 0.55, 0.2, 1.0, 0.2], + [-0.55, 0.55, 0.2, 0.4, 1.0], + ]; + let mut bytes = Vec::with_capacity(vertices.len() * 5 * std::mem::size_of::<f32>()); + for vertex in vertices { + for value in vertex { + bytes.extend_from_slice(&value.to_ne_bytes()); + } + } + create_host_visible_buffer( + instance, + device, + &bytes, + vk::BufferUsageFlags::VERTEX_BUFFER, + "triangle vertex buffer", + ) +} + +pub(super) fn create_triangle_index_buffer( + instance: &VulkanInstanceProbe, + device: &VulkanLogicalDeviceProbe, +) -> Result<VulkanAllocatedBuffer, VulkanSmokeRendererError> { + let indices = [0_u16, 1_u16, 2_u16]; + let mut bytes = Vec::with_capacity(indices.len() * std::mem::size_of::<u16>()); + for index in indices { + bytes.extend_from_slice(&index.to_ne_bytes()); + } + create_host_visible_buffer( + instance, + device, + &bytes, + vk::BufferUsageFlags::INDEX_BUFFER, + "triangle index buffer", + ) +} + +fn create_host_visible_buffer( + instance: &VulkanInstanceProbe, + device: &VulkanLogicalDeviceProbe, + bytes: &[u8], + usage: vk::BufferUsageFlags, + context: &'static str, +) -> Result<VulkanAllocatedBuffer, VulkanSmokeRendererError> { + let create_info = vk::BufferCreateInfo::default() + .size(bytes.len().try_into().unwrap_or(u64::MAX)) + .usage(usage) + .sharing_mode(vk::SharingMode::EXCLUSIVE); + // SAFETY: The create info is stack-owned and references no external memory. + let buffer = unsafe { device.device().create_buffer(&create_info, None) }.map_err(|error| { + VulkanSmokeRendererError::VulkanOperation { + context, + result: error, + } + })?; + // SAFETY: The buffer belongs to this device and is queried immediately after creation. + let requirements = unsafe { device.device().get_buffer_memory_requirements(buffer) }; + let Some(memory_type_index) = find_memory_type( + instance, + device.physical_device(), + requirements.memory_type_bits, + vk::MemoryPropertyFlags::HOST_VISIBLE | vk::MemoryPropertyFlags::HOST_COHERENT, + ) 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: The allocation request matches the queried memory requirements for this buffer. + 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: error, + } + })?; + // SAFETY: The allocation satisfies the queried buffer memory requirements for this device. + unsafe { device.device().bind_buffer_memory(buffer, memory, 0) }.map_err(|error| { + // SAFETY: The buffer and allocation were created above on 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: error, + } + })?; + // SAFETY: The mapping range is within the host-visible allocation bound to the buffer. + let mapped = unsafe { + device + .device() + .map_memory(memory, 0, requirements.size, vk::MemoryMapFlags::empty()) + } + .map_err(|error| { + // SAFETY: The buffer and allocation were created above on 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: error, + } + })?; + // SAFETY: The destination points to the mapped allocation and the source slice lives for the copy. + unsafe { + std::ptr::copy_nonoverlapping(bytes.as_ptr(), mapped.cast::<u8>(), bytes.len()); + device.device().unmap_memory(memory); + } + Ok(VulkanAllocatedBuffer { buffer, memory }) +} + +fn find_memory_type( + instance: &VulkanInstanceProbe, + physical_device: vk::PhysicalDevice, + type_bits: u32, + required: vk::MemoryPropertyFlags, +) -> Option<u32> { + let properties = + // SAFETY: The physical device was selected from this live instance and queried by value. + unsafe { + instance + .instance + .get_physical_device_memory_properties(physical_device) + }; + let count = usize::try_from(properties.memory_type_count).unwrap_or(0); + properties.memory_types[..count] + .iter() + .enumerate() + .find_map(|(index, memory_type)| { + let index_u32 = u32::try_from(index).ok()?; + let supported = (type_bits & (1_u32 << index_u32)) != 0; + (supported && memory_type.property_flags.contains(required)).then_some(index_u32) + }) +} + +pub(super) fn create_swapchain_resources( + device: &VulkanLogicalDeviceProbe, + swapchain: &VulkanSwapchainProbe, + command_pool: vk::CommandPool, + vertex_buffer: &VulkanAllocatedBuffer, + index_buffer: &VulkanAllocatedBuffer, + reuse_command_pool: bool, +) -> Result<VulkanSwapchainResources, VulkanSmokeRendererError> { + // SAFETY: The swapchain is live and owned by this renderer for the duration of the query. + let images = unsafe { + swapchain + .loader() + .get_swapchain_images(swapchain.swapchain()) + } + .map_err(|error| VulkanSmokeRendererError::VulkanOperation { + context: "vkGetSwapchainImagesKHR", + result: error, + })?; + 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.iter().copied() { + 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, + ) { + 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.iter().copied() { + 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); + } + } + } + if reuse_command_pool { + // SAFETY: All command buffers allocated from the live pool are freed before reallocating them. + unsafe { + device + .device() + .reset_command_pool(command_pool, vk::CommandPoolResetFlags::empty()) + } + .map_err(|error| VulkanSmokeRendererError::VulkanOperation { + context: "vkResetCommandPool", + result: error, + })?; + } + let command_buffers = match allocate_command_buffers( + device, + command_pool, + u32::try_from(images.len()).unwrap_or(u32::MAX), + ) { + Ok(command_buffers) => command_buffers, + Err(error) => { + destroy_partial_swapchain_resources(device, command_pool, partial); + return Err(error); + } + }; + partial.command_buffers = command_buffers; + let _ = (vertex_buffer, index_buffer); + Ok(VulkanSwapchainResources { + image_views: partial.image_views, + render_pass, + pipeline_layout, + pipeline, + framebuffers: partial.framebuffers, + command_buffers: partial.command_buffers, + }) +} + +fn create_image_view( + device: &VulkanLogicalDeviceProbe, + image: vk::Image, + format: i32, +) -> Result<vk::ImageView, VulkanSmokeRendererError> { + let create_info = vk::ImageViewCreateInfo::default() + .image(image) + .view_type(vk::ImageViewType::TYPE_2D) + .format(vk::Format::from_raw(format)) + .subresource_range(color_subresource_range()); + // SAFETY: The image comes from the live swapchain and the subresource range covers its color aspect. + unsafe { device.device().create_image_view(&create_info, None) }.map_err(|error| { + VulkanSmokeRendererError::VulkanOperation { + context: "vkCreateImageView", + result: error, + } + }) +} + +fn create_render_pass( + device: &VulkanLogicalDeviceProbe, + format: i32, +) -> Result<vk::RenderPass, VulkanSmokeRendererError> { + let color_attachment = vk::AttachmentDescription::default() + .format(vk::Format::from_raw(format)) + .samples(vk::SampleCountFlags::TYPE_1) + .load_op(vk::AttachmentLoadOp::CLEAR) + .store_op(vk::AttachmentStoreOp::STORE) + .initial_layout(vk::ImageLayout::COLOR_ATTACHMENT_OPTIMAL) + .final_layout(vk::ImageLayout::COLOR_ATTACHMENT_OPTIMAL); + let color_attachment_ref = vk::AttachmentReference::default() + .attachment(0) + .layout(vk::ImageLayout::COLOR_ATTACHMENT_OPTIMAL); + let color_attachments = [color_attachment_ref]; + let subpass = vk::SubpassDescription::default() + .pipeline_bind_point(vk::PipelineBindPoint::GRAPHICS) + .color_attachments(&color_attachments); + let dependency = vk::SubpassDependency::default() + .src_subpass(vk::SUBPASS_EXTERNAL) + .dst_subpass(0) + .src_stage_mask(vk::PipelineStageFlags::COLOR_ATTACHMENT_OUTPUT) + .dst_stage_mask(vk::PipelineStageFlags::COLOR_ATTACHMENT_OUTPUT) + .dst_access_mask(vk::AccessFlags::COLOR_ATTACHMENT_WRITE); + let attachments = [color_attachment]; + let subpasses = [subpass]; + let dependencies = [dependency]; + let create_info = vk::RenderPassCreateInfo::default() + .attachments(&attachments) + .subpasses(&subpasses) + .dependencies(&dependencies); + // SAFETY: The render-pass create info only references stack-owned descriptors. + unsafe { device.device().create_render_pass(&create_info, None) }.map_err(|error| { + VulkanSmokeRendererError::VulkanOperation { + context: "vkCreateRenderPass", + result: error, + } + }) +} + +fn create_pipeline_layout( + device: &VulkanLogicalDeviceProbe, +) -> Result<vk::PipelineLayout, VulkanSmokeRendererError> { + let create_info = vk::PipelineLayoutCreateInfo::default(); + // SAFETY: The pipeline layout contains no descriptor sets or push constants. + unsafe { device.device().create_pipeline_layout(&create_info, None) }.map_err(|error| { + VulkanSmokeRendererError::VulkanOperation { + context: "vkCreatePipelineLayout", + result: error, + } + }) +} + +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, + pipeline_layout: vk::PipelineLayout, + extent: (u32, u32), +) -> Result<vk::Pipeline, VulkanSmokeRendererError> { + let entry_point = c"main"; + let vertex_module = create_shader_module(device, TRIANGLE_VERTEX_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) + .module(vertex_module) + .name(entry_point), + vk::PipelineShaderStageCreateInfo::default() + .stage(vk::ShaderStageFlags::FRAGMENT) + .module(fragment_module) + .name(entry_point), + ]; + let binding_descriptions = [vk::VertexInputBindingDescription { + binding: 0, + stride: 20, + input_rate: vk::VertexInputRate::VERTEX, + }]; + let attribute_descriptions = [ + vk::VertexInputAttributeDescription { + location: 0, + binding: 0, + format: vk::Format::R32G32_SFLOAT, + offset: 0, + }, + vk::VertexInputAttributeDescription { + location: 1, + binding: 0, + format: vk::Format::R32G32B32_SFLOAT, + offset: 8, + }, + ]; + let vertex_input_state = vk::PipelineVertexInputStateCreateInfo::default() + .vertex_binding_descriptions(&binding_descriptions) + .vertex_attribute_descriptions(&attribute_descriptions); + let input_assembly_state = vk::PipelineInputAssemblyStateCreateInfo::default() + .topology(vk::PrimitiveTopology::TRIANGLE_LIST); + let viewports = [vk::Viewport { + x: 0.0, + y: 0.0, + width: extent_component_to_f32(extent.0), + height: extent_component_to_f32(extent.1), + min_depth: 0.0, + max_depth: 1.0, + }]; + let scissors = [vk::Rect2D { + offset: vk::Offset2D { x: 0, y: 0 }, + extent: vk::Extent2D { + width: extent.0, + height: extent.1, + }, + }]; + let viewport_state = vk::PipelineViewportStateCreateInfo::default() + .viewports(&viewports) + .scissors(&scissors); + let rasterization_state = vk::PipelineRasterizationStateCreateInfo::default() + .polygon_mode(vk::PolygonMode::FILL) + .cull_mode(vk::CullModeFlags::BACK) + .front_face(vk::FrontFace::CLOCKWISE) + .line_width(1.0); + let multisample_state = vk::PipelineMultisampleStateCreateInfo::default() + .rasterization_samples(vk::SampleCountFlags::TYPE_1); + let color_blend_attachment = [vk::PipelineColorBlendAttachmentState::default() + .color_write_mask( + vk::ColorComponentFlags::R + | vk::ColorComponentFlags::G + | vk::ColorComponentFlags::B + | vk::ColorComponentFlags::A, + )]; + let color_blend_state = + vk::PipelineColorBlendStateCreateInfo::default().attachments(&color_blend_attachment); + let create_info = [vk::GraphicsPipelineCreateInfo::default() + .stages(&stage_create_infos) + .vertex_input_state(&vertex_input_state) + .input_assembly_state(&input_assembly_state) + .viewport_state(&viewport_state) + .rasterization_state(&rasterization_state) + .multisample_state(&multisample_state) + .color_blend_state(&color_blend_state) + .layout(pipeline_layout) + .render_pass(render_pass) + .subpass(0)]; + // SAFETY: The pipeline creation references live shader modules and stack-owned fixed-function descriptors. + let pipeline_result = unsafe { + device + .device() + .create_graphics_pipelines(vk::PipelineCache::null(), &create_info, None) + }; + // 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: error, + })?[0]; + Ok(pipeline) +} + +fn create_shader_module( + device: &VulkanLogicalDeviceProbe, + words: &[u32], +) -> Result<vk::ShaderModule, VulkanSmokeRendererError> { + let create_info = vk::ShaderModuleCreateInfo::default().code(words); + // SAFETY: SPIR-V words are immutable and valid for the duration of the call. + unsafe { device.device().create_shader_module(&create_info, None) }.map_err(|error| { + VulkanSmokeRendererError::VulkanOperation { + context: "vkCreateShaderModule", + result: error, + } + }) +} + +fn create_framebuffer( + device: &VulkanLogicalDeviceProbe, + render_pass: vk::RenderPass, + image_view: vk::ImageView, + extent: (u32, u32), +) -> Result<vk::Framebuffer, VulkanSmokeRendererError> { + let attachments = [image_view]; + let create_info = vk::FramebufferCreateInfo::default() + .render_pass(render_pass) + .attachments(&attachments) + .width(extent.0) + .height(extent.1) + .layers(1); + // SAFETY: The framebuffer attachments and render pass remain live for the duration of the call. + unsafe { device.device().create_framebuffer(&create_info, None) }.map_err(|error| { + VulkanSmokeRendererError::VulkanOperation { + context: "vkCreateFramebuffer", + result: error, + } + }) +} + +fn allocate_command_buffers( + device: &VulkanLogicalDeviceProbe, + command_pool: vk::CommandPool, + count: u32, +) -> Result<Vec<vk::CommandBuffer>, VulkanSmokeRendererError> { + let allocate_info = vk::CommandBufferAllocateInfo::default() + .command_pool(command_pool) + .level(vk::CommandBufferLevel::PRIMARY) + .command_buffer_count(count); + // SAFETY: Command buffers are allocated from a live resettable pool owned by this device. + unsafe { device.device().allocate_command_buffers(&allocate_info) }.map_err(|error| { + VulkanSmokeRendererError::VulkanOperation { + context: "vkAllocateCommandBuffers", + result: error, + } + }) +} + +pub(super) fn create_frame_sync( + device: &VulkanLogicalDeviceProbe, +) -> Result<Vec<VulkanFrameSync>, VulkanSmokeRendererError> { + let semaphore_info = vk::SemaphoreCreateInfo::default(); + let fence_info = vk::FenceCreateInfo::default().flags(vk::FenceCreateFlags::SIGNALED); + let mut sync = Vec::with_capacity(2); + for _ in 0..2 { + // SAFETY: The sync objects belong to this live logical device and are destroyed at teardown. + let image_available = unsafe { device.device().create_semaphore(&semaphore_info, None) } + .map_err(|error| VulkanSmokeRendererError::VulkanOperation { + context: "vkCreateSemaphore(image_available)", + result: error, + })?; + let render_finished = + 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: error, + }); + } + }; + let fence = 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: error, + }); + } + }; + sync.push(VulkanFrameSync { + image_available, + render_finished, + fence, + }); + } + Ok(sync) +} + +pub(super) fn destroy_swapchain_resources( + device: &VulkanLogicalDeviceProbe, + command_pool: vk::CommandPool, + resources: VulkanSwapchainResources, +) { + // SAFETY: All swapchain-dependent objects belong to this device and are destroyed once. + unsafe { + device + .device() + .free_command_buffers(command_pool, &resources.command_buffers); + for framebuffer in resources.framebuffers { + device.device().destroy_framebuffer(framebuffer, None); + } + device.device().destroy_pipeline(resources.pipeline, None); + device + .device() + .destroy_pipeline_layout(resources.pipeline_layout, None); + device + .device() + .destroy_render_pass(resources.render_pass, None); + for image_view in resources.image_views { + device.device().destroy_image_view(image_view, None); + } + } +} + +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); + } + } +} + +pub(super) 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); + } +} + +pub(super) fn color_subresource_range() -> vk::ImageSubresourceRange { + vk::ImageSubresourceRange::default() + .aspect_mask(vk::ImageAspectFlags::COLOR) + .base_mip_level(0) + .level_count(1) + .base_array_layer(0) + .layer_count(1) +} diff --git a/xtask/src/main.rs b/xtask/src/main.rs index fb85af5..498e260 100644 --- a/xtask/src/main.rs +++ b/xtask/src/main.rs @@ -1242,6 +1242,7 @@ fn has_safety_comment(line: &str) -> bool { const AUDITED_UNSAFE_SOURCE_FILES: &[&str] = &[ "adapters/fparkan-render-vulkan/src/ffi.rs", "adapters/fparkan-render-vulkan/src/ffi/instance.rs", + "adapters/fparkan-render-vulkan/src/ffi/resources.rs", "adapters/fparkan-render-vulkan/src/ffi/runtime.rs", "adapters/fparkan-render-vulkan/src/ffi/surface.rs", "adapters/fparkan-render-vulkan/src/ffi/validation.rs", |
