synvael/crates/renderer/src/renderer.rs

208 lines
8.2 KiB
Rust

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<ash::ext::debug_utils::Instance>,
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<vk::Image>,
pub(crate) swapchain_format: vk::Format,
pub(crate) swapchain_extent: vk::Extent2D,
pub(crate) swapchain_image_views: Vec<vk::ImageView>,
pub(crate) command_pool: vk::CommandPool,
pub(crate) command_buffers: Vec<vk::CommandBuffer>,
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);
}
}
}