#![allow(unsafe_code)] //! Voxel rendering engine using Vulkan 1.3 and dynamic rendering. //! //! This crate provides the core `Renderer` structure and associated types //! for handling GPU resources and drawing operations. mod device; pub mod error; mod instance; mod mesh; mod pipeline; mod renderer; mod surface; mod swapchain; mod sync; /// The maximum number of frames that can be processed by the GPU and CPU simultaneously. pub const MAX_FRAMES_IN_FLIGHT: usize = 3; use ash::{Entry, vk}; use raw_window_handle::{RawDisplayHandle, RawWindowHandle}; use std::ffi::c_char; pub use error::RendererError; pub use renderer::Renderer; impl Renderer { /// Initializes the Vulkan renderer. /// /// This function loads the Vulkan library, creates an instance, selects a GPU, /// and initializes a logical device with a graphics queue. /// /// # Panics /// /// Panics if `MAX_FRAMES_IN_FLIGHT` or vertex data sizes exceed `u32`/`u64` limits. #[allow(clippy::expect_used)] pub fn new( display_handle: RawDisplayHandle, window_handle: RawWindowHandle, width: u32, height: u32, required_extensions: &[*const c_char], ) -> Result { let entry = unsafe { Entry::load() }?; // 1. Instance and Debug Messenger let (instance, debug_utils, debug_messenger) = instance::create_instance(&entry, required_extensions)?; // 2. Surface let (surface_loader, surface) = surface::create_surface(&entry, &instance, display_handle, window_handle)?; // 3. Physical Device (GPU) let physical_device = device::pick_physical_device(&instance, &surface_loader, surface)?; // 4. Graphics Queue Index let graphics_queue_index = device::find_graphics_queue_family( &instance, physical_device, &surface_loader, surface, )?; // 5. Logical Device and Queue let (device, graphics_queue) = device::create_logical_device(&instance, physical_device, graphics_queue_index)?; // 6. Swapchain let (swapchain_loader, swapchain, swapchain_images, swapchain_format, swapchain_extent) = swapchain::create_swapchain( &instance, physical_device, &device, &surface_loader, surface, width, height, )?; // 7. Image Views let image_views = swapchain::create_image_views(&device, &swapchain_images, swapchain_format)?; // 8. Command Pool let pool_create_info = vk::CommandPoolCreateInfo::default() .queue_family_index(graphics_queue_index) .flags(vk::CommandPoolCreateFlags::RESET_COMMAND_BUFFER); let command_pool = unsafe { device.create_command_pool(&pool_create_info, None)? }; // 9. Command Buffers #[allow(clippy::expect_used)] let alloc_info = vk::CommandBufferAllocateInfo::default() .command_pool(command_pool) .level(vk::CommandBufferLevel::PRIMARY) .command_buffer_count(u32::try_from(MAX_FRAMES_IN_FLIGHT).expect("MAX_FRAMES_IN_FLIGHT exceeds u32")); let command_buffers = unsafe { device.allocate_command_buffers(&alloc_info)? }; // 10. Synchronization Primitives let sync = sync::create_sync_primitives(&device, MAX_FRAMES_IN_FLIGHT, swapchain_images.len())?; // 11. GPU Memory Allocator let mut allocator = create_allocator(&instance, &device, physical_device)?; // 12. Graphics Pipeline Configuration let pipeline_layout = pipeline::create_pipeline_layout(&device)?; let graphics_pipeline = pipeline::create_graphics_pipeline(&device, pipeline_layout, swapchain_format)?; // 13. Vertex Buffer Initialization let (vertex_buffer, vertex_allocation) = create_vertex_buffer(&device, &mut allocator)?; Ok(Self { _entry: entry, instance, debug_utils, debug_messenger, physical_device, device, graphics_queue, graphics_queue_index, surface_loader, surface, swapchain_loader, swapchain, swapchain_images, swapchain_format, swapchain_extent, swapchain_image_views: image_views, command_pool, command_buffers, allocator, pipeline_layout, graphics_pipeline, vertex_buffer, vertex_allocation, sync, current_frame: 0, }) } } /// Creates a GPU memory allocator. fn create_allocator( instance: &ash::Instance, device: &ash::Device, physical_device: vk::PhysicalDevice, ) -> Result { let allocator_create_info = gpu_allocator::vulkan::AllocatorCreateDesc { instance: instance.clone(), device: device.clone(), physical_device, debug_settings: gpu_allocator::AllocatorDebugSettings::default(), buffer_device_address: false, allocation_sizes: gpu_allocator::AllocationSizes::default(), }; let allocator = gpu_allocator::vulkan::Allocator::new(&allocator_create_info) .map_err(RendererError::AllocationError)?; Ok(allocator) } /// Creates a vertex buffer and populates it with initial triangle data. #[allow(clippy::expect_used)] fn create_vertex_buffer( device: &ash::Device, allocator: &mut gpu_allocator::vulkan::Allocator, ) -> Result<(vk::Buffer, gpu_allocator::vulkan::Allocation), RendererError> { let vertices = [ mesh::Vertex { position: [0.0, -0.5, 0.0], tex_coord: [0.5, 0.0], }, mesh::Vertex { position: [-0.5, 0.5, 0.0], tex_coord: [0.0, 1.0], }, mesh::Vertex { position: [0.5, 0.5, 0.0], tex_coord: [1.0, 1.0], }, ]; let buffer_info = vk::BufferCreateInfo::default() .size(u64::try_from(std::mem::size_of_val(&vertices)).expect("Vertices size exceeds u64")) .usage(vk::BufferUsageFlags::VERTEX_BUFFER); let vertex_buffer = unsafe { device.create_buffer(&buffer_info, None)? }; let requirements = unsafe { device.get_buffer_memory_requirements(vertex_buffer) }; let vertex_allocation = allocator.allocate(&gpu_allocator::vulkan::AllocationCreateDesc { name: "Vertex Buffer", requirements, location: gpu_allocator::MemoryLocation::CpuToGpu, linear: true, allocation_scheme: gpu_allocator::vulkan::AllocationScheme::GpuAllocatorManaged, })?; unsafe { device.bind_buffer_memory( vertex_buffer, vertex_allocation.memory(), vertex_allocation.offset(), )?; let ptr = vertex_allocation .mapped_ptr() .ok_or(RendererError::NoSuitableGpu)? // Should have a better error but for now .as_ptr(); std::ptr::copy_nonoverlapping( vertices.as_ptr().cast::(), ptr.cast::(), std::mem::size_of_val(&vertices), ); } Ok((vertex_buffer, vertex_allocation)) }