use ash::{Device, Instance, khr, vk}; use crate::error::RendererError; use crate::sync::SyncPrimitives; pub const MAX_FRAMES_IN_FLIGHT: usize = 2; /// The core renderer structure holding the Vulkan resources. pub struct Renderer { pub(crate) _entry: ash::Entry, pub(crate) instance: Instance, pub(crate) debug_utils: Option, pub(crate) debug_messenger: vk::DebugUtilsMessengerEXT, pub(crate) physical_device: vk::PhysicalDevice, pub(crate) device: Device, pub(crate) graphics_queue: vk::Queue, pub(crate) graphics_queue_index: u32, pub(crate) surface_loader: khr::surface::Instance, pub(crate) surface: vk::SurfaceKHR, pub(crate) swapchain_loader: khr::swapchain::Device, pub(crate) swapchain: vk::SwapchainKHR, pub(crate) swapchain_images: Vec, pub(crate) swapchain_format: vk::Format, pub(crate) swapchain_extent: vk::Extent2D, pub(crate) swapchain_image_views: Vec, pub(crate) command_pool: vk::CommandPool, pub(crate) command_buffers: Vec, pub(crate) sync: SyncPrimitives, pub(crate) current_frame: usize, } impl Renderer { /// Renders a single frame. pub fn draw_frame(&mut self) -> Result<(), RendererError> { let in_flight_fence = self.sync.in_flight[self.current_frame]; let image_available_semaphore = self.sync.image_available[self.current_frame]; let cmd = self.command_buffers[self.current_frame]; // 1. Wait for the current frame's GPU work to finish unsafe { self.device.wait_for_fences(&[in_flight_fence], true, u64::MAX)?; self.device.reset_fences(&[in_flight_fence])?; } // 2. Acquire an image from the swapchain let (image_index, _is_suboptimal) = unsafe { self.swapchain_loader.acquire_next_image( self.swapchain, u64::MAX, image_available_semaphore, vk::Fence::null(), )? }; // If the acquired image is still being used by a previous frame, wait for it let image_fence = self.sync.images_in_flight[image_index as usize]; if image_fence != vk::Fence::null() { unsafe { self.device.wait_for_fences(&[image_fence], true, u64::MAX)? }; } // Mark the image as being in use by the current frame's fence self.sync.images_in_flight[image_index as usize] = in_flight_fence; // Use the semaphore tied to this specific swapchain image for rendering completion let render_finished_semaphore = self.sync.render_finished[image_index as usize]; // 3. Reset and begin recording the command buffer unsafe { self.device.reset_command_buffer(cmd, vk::CommandBufferResetFlags::empty())?; let begin_info = vk::CommandBufferBeginInfo::default() .flags(vk::CommandBufferUsageFlags::ONE_TIME_SUBMIT); self.device.begin_command_buffer(cmd, &begin_info)?; } let image = self.swapchain_images[image_index as usize]; let view = self.swapchain_image_views[image_index as usize]; // 4. Transition the swapchain image to a layout suitable for drawing let range = vk::ImageSubresourceRange { aspect_mask: vk::ImageAspectFlags::COLOR, base_mip_level: 0, level_count: 1, base_array_layer: 0, layer_count: 1, }; let barrier_to_draw = vk::ImageMemoryBarrier2::default() .image(image) .subresource_range(range) .src_stage_mask(vk::PipelineStageFlags2::COLOR_ATTACHMENT_OUTPUT) .src_access_mask(vk::AccessFlags2::empty()) .dst_stage_mask(vk::PipelineStageFlags2::COLOR_ATTACHMENT_OUTPUT) .dst_access_mask(vk::AccessFlags2::COLOR_ATTACHMENT_WRITE) .old_layout(vk::ImageLayout::UNDEFINED) .new_layout(vk::ImageLayout::COLOR_ATTACHMENT_OPTIMAL); let dependency_info = vk::DependencyInfo::default() .image_memory_barriers(std::slice::from_ref(&barrier_to_draw)); unsafe { self.device.cmd_pipeline_barrier2(cmd, &dependency_info) }; // 5. Begin Dynamic Rendering with a clear color let color_attachment = vk::RenderingAttachmentInfo::default() .image_view(view) .image_layout(vk::ImageLayout::COLOR_ATTACHMENT_OPTIMAL) .load_op(vk::AttachmentLoadOp::CLEAR) .store_op(vk::AttachmentStoreOp::STORE) .clear_value(vk::ClearValue { color: vk::ClearColorValue { float32: [0.1, 0.2, 0.4, 1.0], // Project Catalyst Blue }, }); let rendering_info = vk::RenderingInfo::default() .render_area(vk::Rect2D { offset: vk::Offset2D { x: 0, y: 0 }, extent: self.swapchain_extent, }) .layer_count(1) .color_attachments(std::slice::from_ref(&color_attachment)); unsafe { self.device.cmd_begin_rendering(cmd, &rendering_info); // Future draw calls will go here self.device.cmd_end_rendering(cmd); } // 6. Transition the image back to Present layout let barrier_to_present = vk::ImageMemoryBarrier2::default() .image(image) .subresource_range(range) .src_stage_mask(vk::PipelineStageFlags2::COLOR_ATTACHMENT_OUTPUT) .src_access_mask(vk::AccessFlags2::COLOR_ATTACHMENT_WRITE) .dst_stage_mask(vk::PipelineStageFlags2::BOTTOM_OF_PIPE) .dst_access_mask(vk::AccessFlags2::empty()) .old_layout(vk::ImageLayout::COLOR_ATTACHMENT_OPTIMAL) .new_layout(vk::ImageLayout::PRESENT_SRC_KHR); let dependency_info = vk::DependencyInfo::default() .image_memory_barriers(std::slice::from_ref(&barrier_to_present)); unsafe { self.device.cmd_pipeline_barrier2(cmd, &dependency_info); self.device.end_command_buffer(cmd)?; } // 7. Submit the work to the GPU let submit_info = vk::SubmitInfo::default() .wait_semaphores(std::slice::from_ref(&image_available_semaphore)) .wait_dst_stage_mask(&[vk::PipelineStageFlags::COLOR_ATTACHMENT_OUTPUT]) .command_buffers(std::slice::from_ref(&cmd)) .signal_semaphores(std::slice::from_ref(&render_finished_semaphore)); unsafe { self.device.queue_submit(self.graphics_queue, &[submit_info], in_flight_fence)?; } // 8. Present the result to the screen let present_info = vk::PresentInfoKHR::default() .wait_semaphores(std::slice::from_ref(&render_finished_semaphore)) .swapchains(std::slice::from_ref(&self.swapchain)) .image_indices(std::slice::from_ref(&image_index)); unsafe { self.swapchain_loader.queue_present(self.graphics_queue, &present_info)?; } // Advance the frame index for the next call self.current_frame = (self.current_frame + 1) % MAX_FRAMES_IN_FLIGHT; Ok(()) } } impl Drop for Renderer { fn drop(&mut self) { unsafe { let _ = self.device.device_wait_idle(); self.device.destroy_command_pool(self.command_pool, None); // Use the safe cleanup function from sync module let sync = std::ptr::read(&self.sync); crate::sync::destroy_sync_primitives(&self.device, sync); // Destroy the swapchain self.swapchain_loader.destroy_swapchain(self.swapchain, None); // Destroy image views for &view in &self.swapchain_image_views { self.device.destroy_image_view(view, None); } // Destroy the logical device self.device.destroy_device(None); // Destroy the surface self.surface_loader.destroy_surface(self.surface, None); // Destroy the debug messenger if we created one if let Some(utils) = &self.debug_utils { utils.destroy_debug_utils_messenger(self.debug_messenger, None); } // Destroy the instance self.instance.destroy_instance(None); } } }