359 lines
11 KiB
Rust
359 lines
11 KiB
Rust
#![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;
|
|
pub 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 gpu_allocator::vulkan::{Allocation, Allocator};
|
|
use raw_window_handle::{RawDisplayHandle, RawWindowHandle};
|
|
use std::ffi::c_char;
|
|
|
|
pub use error::RendererError;
|
|
pub use renderer::Renderer;
|
|
|
|
use crate::mesh::Vertex;
|
|
|
|
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<Self, RendererError> {
|
|
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)?;
|
|
|
|
let (vertex_buffer, vertex_allocation, index_buffer, index_allocation) =
|
|
create_geometry(&device, &mut allocator)?;
|
|
|
|
let index_count = 36;
|
|
|
|
let (depth_image, depth_allocation, depth_image_view) =
|
|
create_depth_resources(&device, &mut allocator, swapchain_extent)?;
|
|
|
|
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,
|
|
index_buffer,
|
|
index_allocation,
|
|
index_count,
|
|
depth_image,
|
|
depth_allocation,
|
|
depth_image_view,
|
|
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<gpu_allocator::vulkan::Allocator, RendererError> {
|
|
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 the 3D geometry buffers (vertex and index) for a cube.
|
|
fn create_geometry(
|
|
device: &ash::Device,
|
|
allocator: &mut Allocator,
|
|
) -> Result<(vk::Buffer, Allocation, vk::Buffer, Allocation), RendererError> {
|
|
let vertices = [
|
|
// Front face
|
|
Vertex {
|
|
position: [-0.5, -0.5, 0.5],
|
|
color: [1.0, 0.0, 0.0],
|
|
},
|
|
Vertex {
|
|
position: [0.5, -0.5, 0.5],
|
|
color: [0.0, 1.0, 0.0],
|
|
},
|
|
Vertex {
|
|
position: [0.5, 0.5, 0.5],
|
|
color: [0.0, 0.0, 1.0],
|
|
},
|
|
Vertex {
|
|
position: [-0.5, 0.5, 0.5],
|
|
color: [1.0, 1.0, 1.0],
|
|
},
|
|
// Back face
|
|
Vertex {
|
|
position: [-0.5, -0.5, -0.5],
|
|
color: [1.0, 0.0, 0.0],
|
|
},
|
|
Vertex {
|
|
position: [0.5, -0.5, -0.5],
|
|
color: [0.0, 1.0, 0.0],
|
|
},
|
|
Vertex {
|
|
position: [0.5, 0.5, -0.5],
|
|
color: [0.0, 0.0, 1.0],
|
|
},
|
|
Vertex {
|
|
position: [-0.5, 0.5, -0.5],
|
|
color: [1.0, 1.0, 1.0],
|
|
},
|
|
];
|
|
let indices: [u32; 36] = [
|
|
0, 1, 2, 2, 3, 0, // front
|
|
1, 5, 6, 6, 2, 1, // right
|
|
7, 6, 5, 5, 4, 7, // back
|
|
4, 0, 3, 3, 7, 4, // left
|
|
4, 5, 1, 1, 0, 4, // bottom
|
|
3, 2, 6, 6, 7, 3, // top
|
|
];
|
|
|
|
let (vertex_buffer, vertex_allocation) = create_gpu_buffer(
|
|
device,
|
|
allocator,
|
|
bytemuck::cast_slice(&vertices),
|
|
vk::BufferUsageFlags::VERTEX_BUFFER,
|
|
"Vertex Buffer",
|
|
)?;
|
|
|
|
let (index_buffer, index_allocation) = create_gpu_buffer(
|
|
device,
|
|
allocator,
|
|
bytemuck::cast_slice(&indices),
|
|
vk::BufferUsageFlags::INDEX_BUFFER,
|
|
"Index Buffer",
|
|
)?;
|
|
|
|
Ok((
|
|
vertex_buffer,
|
|
vertex_allocation,
|
|
index_buffer,
|
|
index_allocation,
|
|
))
|
|
}
|
|
|
|
/// Creates the depth buffer resources (image, memory, and view).
|
|
fn create_depth_resources(
|
|
device: &ash::Device,
|
|
allocator: &mut Allocator,
|
|
extent: vk::Extent2D,
|
|
) -> Result<(vk::Image, Allocation, vk::ImageView), RendererError> {
|
|
let depth_format = vk::Format::D32_SFLOAT;
|
|
|
|
let image_create_info = vk::ImageCreateInfo::default()
|
|
.image_type(vk::ImageType::TYPE_2D)
|
|
.format(depth_format)
|
|
.extent(vk::Extent3D {
|
|
width: extent.width,
|
|
height: extent.height,
|
|
depth: 1,
|
|
})
|
|
.mip_levels(1)
|
|
.array_layers(1)
|
|
.samples(vk::SampleCountFlags::TYPE_1)
|
|
.tiling(vk::ImageTiling::OPTIMAL)
|
|
.usage(vk::ImageUsageFlags::DEPTH_STENCIL_ATTACHMENT)
|
|
.sharing_mode(vk::SharingMode::EXCLUSIVE)
|
|
.initial_layout(vk::ImageLayout::UNDEFINED);
|
|
|
|
let depth_image = unsafe { device.create_image(&image_create_info, None)? };
|
|
|
|
let requirements = unsafe { device.get_image_memory_requirements(depth_image) };
|
|
|
|
let depth_allocation = allocator.allocate(&gpu_allocator::vulkan::AllocationCreateDesc {
|
|
name: "Depth Image",
|
|
requirements,
|
|
location: gpu_allocator::MemoryLocation::GpuOnly,
|
|
linear: false,
|
|
allocation_scheme: gpu_allocator::vulkan::AllocationScheme::GpuAllocatorManaged,
|
|
})?;
|
|
|
|
unsafe {
|
|
device.bind_image_memory(
|
|
depth_image,
|
|
depth_allocation.memory(),
|
|
depth_allocation.offset(),
|
|
)?;
|
|
}
|
|
|
|
let view_create_info = vk::ImageViewCreateInfo::default()
|
|
.image(depth_image)
|
|
.view_type(vk::ImageViewType::TYPE_2D)
|
|
.format(depth_format)
|
|
.subresource_range(vk::ImageSubresourceRange {
|
|
aspect_mask: vk::ImageAspectFlags::DEPTH,
|
|
base_mip_level: 0,
|
|
level_count: 1,
|
|
base_array_layer: 0,
|
|
layer_count: 1,
|
|
});
|
|
|
|
let depth_image_view = unsafe { device.create_image_view(&view_create_info, None)? };
|
|
|
|
Ok((depth_image, depth_allocation, depth_image_view))
|
|
}
|
|
|
|
/// Helper function to create and populate a GPU buffer.
|
|
fn create_gpu_buffer(
|
|
device: &ash::Device,
|
|
allocator: &mut Allocator,
|
|
data: &[u8],
|
|
usage: vk::BufferUsageFlags,
|
|
name: &str,
|
|
) -> Result<(vk::Buffer, Allocation), RendererError> {
|
|
let size = data.len() as u64;
|
|
|
|
let buffer_info = vk::BufferCreateInfo::default()
|
|
.size(size)
|
|
.usage(usage)
|
|
.sharing_mode(vk::SharingMode::EXCLUSIVE);
|
|
|
|
let buffer = unsafe { device.create_buffer(&buffer_info, None)? };
|
|
|
|
let requirements = unsafe { device.get_buffer_memory_requirements(buffer) };
|
|
let allocation = allocator.allocate(&gpu_allocator::vulkan::AllocationCreateDesc {
|
|
name,
|
|
requirements,
|
|
location: gpu_allocator::MemoryLocation::CpuToGpu,
|
|
linear: true,
|
|
allocation_scheme: gpu_allocator::vulkan::AllocationScheme::GpuAllocatorManaged,
|
|
})?;
|
|
|
|
unsafe {
|
|
device.bind_buffer_memory(buffer, allocation.memory(), allocation.offset())?;
|
|
}
|
|
|
|
let ptr = allocation
|
|
.mapped_ptr()
|
|
.ok_or(RendererError::NoSuitableGpu)?
|
|
.as_ptr();
|
|
|
|
unsafe {
|
|
std::ptr::copy_nonoverlapping(data.as_ptr(), ptr.cast(), data.len());
|
|
}
|
|
|
|
Ok((buffer, allocation))
|
|
}
|