chore(workspace): enforce missing_docs and resolve all linting violations
This commit is contained in:
parent
6d4435f856
commit
34f1a9d989
|
|
@ -1,3 +1,8 @@
|
||||||
|
//! Main entry point for the Project Catalyst client.
|
||||||
|
//!
|
||||||
|
//! This crate handles window creation, input processing, and drives the
|
||||||
|
//! renderer to display the game world.
|
||||||
|
|
||||||
use anyhow::{Context, Result};
|
use anyhow::{Context, Result};
|
||||||
use raw_window_handle::{HasDisplayHandle, HasWindowHandle};
|
use raw_window_handle::{HasDisplayHandle, HasWindowHandle};
|
||||||
use tracing::info;
|
use tracing::info;
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
//! Logic for selecting physical devices and creating logical devices.
|
||||||
|
|
||||||
use crate::error::RendererError;
|
use crate::error::RendererError;
|
||||||
use ash::{Device, Instance, khr, vk};
|
use ash::{Device, Instance, khr, vk};
|
||||||
|
|
||||||
|
|
@ -63,7 +65,8 @@ pub fn find_graphics_queue_family(
|
||||||
let props = unsafe { instance.get_physical_device_queue_family_properties(physical_device) };
|
let props = unsafe { instance.get_physical_device_queue_family_properties(physical_device) };
|
||||||
|
|
||||||
for (index, prop) in props.iter().enumerate() {
|
for (index, prop) in props.iter().enumerate() {
|
||||||
let index = index as u32;
|
#[allow(clippy::expect_used)]
|
||||||
|
let index = u32::try_from(index).expect("Queue family index exceeds u32 range");
|
||||||
let graphics = prop.queue_flags.contains(vk::QueueFlags::GRAPHICS);
|
let graphics = prop.queue_flags.contains(vk::QueueFlags::GRAPHICS);
|
||||||
let present = unsafe {
|
let present = unsafe {
|
||||||
surface_loader.get_physical_device_surface_support(physical_device, index, surface)?
|
surface_loader.get_physical_device_surface_support(physical_device, index, surface)?
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,9 @@
|
||||||
|
//! Error types for the renderer crate.
|
||||||
|
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
#[derive(Debug, Error)]
|
#[derive(Debug, Error)]
|
||||||
|
/// Enumerates all possible errors that can occur during rendering operations.
|
||||||
pub enum RendererError {
|
pub enum RendererError {
|
||||||
/// The Vulkan library could not be loaded from the system.
|
/// The Vulkan library could not be loaded from the system.
|
||||||
#[error("Failed to load Vulkan library")]
|
#[error("Failed to load Vulkan library")]
|
||||||
|
|
@ -14,4 +17,10 @@ pub enum RendererError {
|
||||||
/// An error occurred during GPU memory allocation.
|
/// An error occurred during GPU memory allocation.
|
||||||
#[error("GPU allocation error")]
|
#[error("GPU allocation error")]
|
||||||
AllocationError(#[from] gpu_allocator::AllocationError),
|
AllocationError(#[from] gpu_allocator::AllocationError),
|
||||||
|
/// An I/O error occurred (e.g. reading a shader).
|
||||||
|
#[error("I/O error")]
|
||||||
|
IoError(#[from] std::io::Error),
|
||||||
|
/// An invalid string was encountered.
|
||||||
|
#[error("Invalid string")]
|
||||||
|
InvalidString,
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,10 @@
|
||||||
|
#![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;
|
mod device;
|
||||||
pub mod error;
|
pub mod error;
|
||||||
mod instance;
|
mod instance;
|
||||||
|
|
@ -8,6 +15,7 @@ mod surface;
|
||||||
mod swapchain;
|
mod swapchain;
|
||||||
mod sync;
|
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;
|
pub const MAX_FRAMES_IN_FLIGHT: usize = 3;
|
||||||
|
|
||||||
use ash::{Entry, vk};
|
use ash::{Entry, vk};
|
||||||
|
|
@ -22,6 +30,11 @@ impl Renderer {
|
||||||
///
|
///
|
||||||
/// This function loads the Vulkan library, creates an instance, selects a GPU,
|
/// This function loads the Vulkan library, creates an instance, selects a GPU,
|
||||||
/// and initializes a logical device with a graphics queue.
|
/// 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(
|
pub fn new(
|
||||||
display_handle: RawDisplayHandle,
|
display_handle: RawDisplayHandle,
|
||||||
window_handle: RawWindowHandle,
|
window_handle: RawWindowHandle,
|
||||||
|
|
@ -78,10 +91,11 @@ impl Renderer {
|
||||||
let command_pool = unsafe { device.create_command_pool(&pool_create_info, None)? };
|
let command_pool = unsafe { device.create_command_pool(&pool_create_info, None)? };
|
||||||
|
|
||||||
// 9. Command Buffers
|
// 9. Command Buffers
|
||||||
|
#[allow(clippy::expect_used)]
|
||||||
let alloc_info = vk::CommandBufferAllocateInfo::default()
|
let alloc_info = vk::CommandBufferAllocateInfo::default()
|
||||||
.command_pool(command_pool)
|
.command_pool(command_pool)
|
||||||
.level(vk::CommandBufferLevel::PRIMARY)
|
.level(vk::CommandBufferLevel::PRIMARY)
|
||||||
.command_buffer_count(MAX_FRAMES_IN_FLIGHT as u32);
|
.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)? };
|
let command_buffers = unsafe { device.allocate_command_buffers(&alloc_info)? };
|
||||||
|
|
||||||
|
|
@ -90,81 +104,16 @@ impl Renderer {
|
||||||
sync::create_sync_primitives(&device, MAX_FRAMES_IN_FLIGHT, swapchain_images.len())?;
|
sync::create_sync_primitives(&device, MAX_FRAMES_IN_FLIGHT, swapchain_images.len())?;
|
||||||
|
|
||||||
// 11. GPU Memory Allocator
|
// 11. GPU Memory Allocator
|
||||||
// The allocator handles the complexity of sub-allocating memory blocks from the GPU.
|
let mut allocator = create_allocator(&instance, &device, physical_device)?;
|
||||||
let allocator_create_info = gpu_allocator::vulkan::AllocatorCreateDesc {
|
|
||||||
instance: instance.clone(),
|
|
||||||
device: device.clone(),
|
|
||||||
physical_device,
|
|
||||||
debug_settings: Default::default(),
|
|
||||||
buffer_device_address: false,
|
|
||||||
allocation_sizes: Default::default(),
|
|
||||||
};
|
|
||||||
|
|
||||||
let allocator = gpu_allocator::vulkan::Allocator::new(&allocator_create_info)
|
|
||||||
.expect("Failed to create Vulkan allocator");
|
|
||||||
|
|
||||||
// 12. Graphics Pipeline Configuration
|
// 12. Graphics Pipeline Configuration
|
||||||
// The pipeline defines the fixed-function and programmable state for rendering voxel geometry.
|
let pipeline_layout = pipeline::create_pipeline_layout(&device)?;
|
||||||
let pipeline_layout = pipeline::create_pipeline_layout(&device);
|
|
||||||
let graphics_pipeline =
|
let graphics_pipeline =
|
||||||
pipeline::create_graphics_pipeline(&device, pipeline_layout, swapchain_format);
|
pipeline::create_graphics_pipeline(&device, pipeline_layout, swapchain_format)?;
|
||||||
|
|
||||||
// 13. Vertex Buffer Initialization
|
// 13. Vertex Buffer Initialization
|
||||||
// A simple triangle is defined in normalized device coordinates and moved to GPU memory.
|
let (vertex_buffer, vertex_allocation) =
|
||||||
let vertices = [
|
create_vertex_buffer(&device, &mut allocator)?;
|
||||||
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: [1.0, 1.0],
|
|
||||||
},
|
|
||||||
mesh::Vertex {
|
|
||||||
position: [-0.5, 0.5, 0.0],
|
|
||||||
tex_coord: [0.0, 1.0],
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
// Create the buffer handle and query its memory requirements.
|
|
||||||
let buffer_info = vk::BufferCreateInfo::default()
|
|
||||||
.size(std::mem::size_of_val(&vertices) as 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 mut allocator = allocator;
|
|
||||||
|
|
||||||
// Allocate memory that is visible to the CPU for data transfer.
|
|
||||||
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,
|
|
||||||
})?;
|
|
||||||
|
|
||||||
// Bind the allocated memory to the buffer handle and copy the vertex data.
|
|
||||||
unsafe {
|
|
||||||
device.bind_buffer_memory(
|
|
||||||
vertex_buffer,
|
|
||||||
vertex_allocation.memory(),
|
|
||||||
vertex_allocation.offset(),
|
|
||||||
)?;
|
|
||||||
|
|
||||||
let ptr = vertex_allocation
|
|
||||||
.mapped_ptr()
|
|
||||||
.expect("Failed to map vertex buffer memory")
|
|
||||||
.as_ptr();
|
|
||||||
|
|
||||||
std::ptr::copy_nonoverlapping(
|
|
||||||
vertices.as_ptr() as *const u8,
|
|
||||||
ptr as *mut u8,
|
|
||||||
std::mem::size_of_val(&vertices),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
_entry: entry,
|
_entry: entry,
|
||||||
|
|
@ -195,3 +144,82 @@ impl Renderer {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 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 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::<u8>(),
|
||||||
|
ptr.cast::<u8>(),
|
||||||
|
std::mem::size_of_val(&vertices),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok((vertex_buffer, vertex_allocation))
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
//! Vertex data structures and layout descriptions.
|
||||||
|
|
||||||
use bytemuck::{Pod, Zeroable};
|
use bytemuck::{Pod, Zeroable};
|
||||||
|
|
||||||
/// Represents a single vertex in 3D space with position and texture coordinates.
|
/// Represents a single vertex in 3D space with position and texture coordinates.
|
||||||
|
|
@ -18,16 +20,18 @@ impl Vertex {
|
||||||
///
|
///
|
||||||
/// This defines the 'stride' (distance between vertices) and specifies that
|
/// This defines the 'stride' (distance between vertices) and specifies that
|
||||||
/// data is read per-vertex rather than per-instance.
|
/// data is read per-vertex rather than per-instance.
|
||||||
|
#[allow(clippy::expect_used)]
|
||||||
pub fn get_binding_description() -> ash::vk::VertexInputBindingDescription {
|
pub fn get_binding_description() -> ash::vk::VertexInputBindingDescription {
|
||||||
ash::vk::VertexInputBindingDescription::default()
|
ash::vk::VertexInputBindingDescription::default()
|
||||||
.binding(0)
|
.binding(0)
|
||||||
.stride(std::mem::size_of::<Self>() as u32)
|
.stride(u32::try_from(std::mem::size_of::<Self>()).expect("Vertex size exceeds u32 range"))
|
||||||
.input_rate(ash::vk::VertexInputRate::VERTEX)
|
.input_rate(ash::vk::VertexInputRate::VERTEX)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Describes the layout of individual fields (attributes) within a single vertex.
|
/// Describes the layout of individual fields (attributes) within a single vertex.
|
||||||
///
|
///
|
||||||
/// These 'locations' must match the `layout(location = X)` qualifiers in the vertex shader.
|
/// These 'locations' must match the `layout(location = X)` qualifiers in the vertex shader.
|
||||||
|
#[allow(clippy::expect_used)]
|
||||||
pub fn get_attribute_descriptions() -> [ash::vk::VertexInputAttributeDescription; 2] {
|
pub fn get_attribute_descriptions() -> [ash::vk::VertexInputAttributeDescription; 2] {
|
||||||
[
|
[
|
||||||
// Location 0: position (vec3 -> R32G32B32_SFLOAT)
|
// Location 0: position (vec3 -> R32G32B32_SFLOAT)
|
||||||
|
|
@ -41,7 +45,7 @@ impl Vertex {
|
||||||
.binding(0)
|
.binding(0)
|
||||||
.location(1)
|
.location(1)
|
||||||
.format(ash::vk::Format::R32G32_SFLOAT)
|
.format(ash::vk::Format::R32G32_SFLOAT)
|
||||||
.offset(std::mem::size_of::<[f32; 3]>() as u32),
|
.offset(u32::try_from(std::mem::size_of::<[f32; 3]>()).expect("Vertex offset exceeds u32 range")),
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,6 @@
|
||||||
|
//! Graphics pipeline creation and shader management.
|
||||||
|
|
||||||
|
use crate::error::RendererError;
|
||||||
use crate::mesh::Vertex;
|
use crate::mesh::Vertex;
|
||||||
use ash::{Device, vk};
|
use ash::{Device, vk};
|
||||||
use std::io::Cursor;
|
use std::io::Cursor;
|
||||||
|
|
@ -6,41 +9,38 @@ use std::io::Cursor;
|
||||||
///
|
///
|
||||||
/// Vulkan expects shader code to be 32-bit aligned; `ash::util::read_spv` is
|
/// Vulkan expects shader code to be 32-bit aligned; `ash::util::read_spv` is
|
||||||
/// used to correctly interpret the raw bytes as a slice of `u32`.
|
/// used to correctly interpret the raw bytes as a slice of `u32`.
|
||||||
pub fn create_shader_module(device: &Device, bytes: &[u8]) -> vk::ShaderModule {
|
pub fn create_shader_module(
|
||||||
|
device: &Device,
|
||||||
|
bytes: &[u8],
|
||||||
|
) -> Result<vk::ShaderModule, RendererError> {
|
||||||
let mut cursor = Cursor::new(bytes);
|
let mut cursor = Cursor::new(bytes);
|
||||||
|
|
||||||
let code = ash::util::read_spv(&mut cursor)
|
let code = ash::util::read_spv(&mut cursor)?;
|
||||||
.expect("Failed to read SPIR-V binary; check if the file is valid");
|
|
||||||
|
|
||||||
let create_info = vk::ShaderModuleCreateInfo::default().code(&code);
|
let create_info = vk::ShaderModuleCreateInfo::default().code(&code);
|
||||||
|
|
||||||
unsafe {
|
let module = unsafe { device.create_shader_module(&create_info, None)? };
|
||||||
device
|
Ok(module)
|
||||||
.create_shader_module(&create_info, None)
|
|
||||||
.expect("Failed to create Vulkan shader module")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Defines the 'interface' of the pipeline (what data we can pass to the shaders).
|
/// Defines the 'interface' of the pipeline (what data we can pass to the shaders).
|
||||||
///
|
///
|
||||||
/// This layout defines any push constants or descriptor sets (textures/UBOs)
|
/// This layout defines any push constants or descriptor sets (textures/UBOs)
|
||||||
/// accessed by the shaders during execution.
|
/// accessed by the shaders during execution.
|
||||||
pub fn create_pipeline_layout(device: &Device) -> vk::PipelineLayout {
|
pub fn create_pipeline_layout(device: &Device) -> Result<vk::PipelineLayout, RendererError> {
|
||||||
// A single push constant range is defined for the Model-View-Projection matrix.
|
// A single push constant range is defined for the Model-View-Projection matrix.
|
||||||
// This allows the matrix to be updated for every draw call with high efficiency.
|
// This allows the matrix to be updated for every draw call with high efficiency.
|
||||||
|
#[allow(clippy::expect_used)]
|
||||||
let push_constant_range = vk::PushConstantRange::default()
|
let push_constant_range = vk::PushConstantRange::default()
|
||||||
.stage_flags(vk::ShaderStageFlags::VERTEX)
|
.stage_flags(vk::ShaderStageFlags::VERTEX)
|
||||||
.offset(0)
|
.offset(0)
|
||||||
.size(std::mem::size_of::<glam::Mat4>() as u32);
|
.size(u32::try_from(std::mem::size_of::<glam::Mat4>()).expect("Mat4 size exceeds u32 range"));
|
||||||
|
|
||||||
let layout_create_info = vk::PipelineLayoutCreateInfo::default()
|
let layout_create_info = vk::PipelineLayoutCreateInfo::default()
|
||||||
.push_constant_ranges(std::slice::from_ref(&push_constant_range));
|
.push_constant_ranges(std::slice::from_ref(&push_constant_range));
|
||||||
|
|
||||||
unsafe {
|
let layout = unsafe { device.create_pipeline_layout(&layout_create_info, None)? };
|
||||||
device
|
Ok(layout)
|
||||||
.create_pipeline_layout(&layout_create_info, None)
|
|
||||||
.expect("Failed to create pipeline layout")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Creates a Graphics Pipeline for voxel rendering using Vulkan 1.3 Dynamic Rendering.
|
/// Creates a Graphics Pipeline for voxel rendering using Vulkan 1.3 Dynamic Rendering.
|
||||||
|
|
@ -51,16 +51,10 @@ pub fn create_graphics_pipeline(
|
||||||
device: &Device,
|
device: &Device,
|
||||||
layout: vk::PipelineLayout,
|
layout: vk::PipelineLayout,
|
||||||
color_format: vk::Format,
|
color_format: vk::Format,
|
||||||
) -> vk::Pipeline {
|
) -> Result<vk::Pipeline, RendererError> {
|
||||||
// 1. Load and compile shader modules
|
// 1. Load and compile shader modules
|
||||||
// Using include_bytes! embeds the shaders directly into the engine binary.
|
let (vert_module, frag_module) = load_shader_modules(device)?;
|
||||||
let vert_bytes = include_bytes!("../../../assets/shaders/cube.vert.spv");
|
let entry_point = std::ffi::CString::new("main").map_err(|_| RendererError::InvalidString)?;
|
||||||
let frag_bytes = include_bytes!("../../../assets/shaders/cube.frag.spv");
|
|
||||||
|
|
||||||
let vert_module = create_shader_module(device, vert_bytes);
|
|
||||||
let frag_module = create_shader_module(device, frag_bytes);
|
|
||||||
|
|
||||||
let entry_point = std::ffi::CString::new("main").unwrap();
|
|
||||||
|
|
||||||
let shader_stages = [
|
let shader_stages = [
|
||||||
vk::PipelineShaderStageCreateInfo::default()
|
vk::PipelineShaderStageCreateInfo::default()
|
||||||
|
|
@ -73,60 +67,51 @@ pub fn create_graphics_pipeline(
|
||||||
.name(&entry_point),
|
.name(&entry_point),
|
||||||
];
|
];
|
||||||
|
|
||||||
// 2. Configure Vertex Input
|
// 2. Configure Fixed-Function States
|
||||||
let binding_descriptions = [Vertex::get_binding_description()];
|
let binding_descriptions = [Vertex::get_binding_description()];
|
||||||
let attribute_descriptions = Vertex::get_attribute_descriptions();
|
let attribute_descriptions = Vertex::get_attribute_descriptions();
|
||||||
|
|
||||||
let vertex_input_info = vk::PipelineVertexInputStateCreateInfo::default()
|
let vertex_input_info = vk::PipelineVertexInputStateCreateInfo::default()
|
||||||
.vertex_binding_descriptions(&binding_descriptions)
|
.vertex_binding_descriptions(&binding_descriptions)
|
||||||
.vertex_attribute_descriptions(&attribute_descriptions);
|
.vertex_attribute_descriptions(&attribute_descriptions);
|
||||||
|
|
||||||
// 3. Configure Input Assembly (Drawing mode)
|
|
||||||
let input_assembly = vk::PipelineInputAssemblyStateCreateInfo::default()
|
let input_assembly = vk::PipelineInputAssemblyStateCreateInfo::default()
|
||||||
.topology(vk::PrimitiveTopology::TRIANGLE_LIST)
|
.topology(vk::PrimitiveTopology::TRIANGLE_LIST)
|
||||||
.primitive_restart_enable(false);
|
.primitive_restart_enable(false);
|
||||||
|
|
||||||
// 4. Viewport and Scissor (Static declarations, dynamic values set at runtime)
|
|
||||||
let viewport_state = vk::PipelineViewportStateCreateInfo::default()
|
let viewport_state = vk::PipelineViewportStateCreateInfo::default()
|
||||||
.viewport_count(1)
|
.viewport_count(1)
|
||||||
.scissor_count(1);
|
.scissor_count(1);
|
||||||
|
|
||||||
// 5. Configure Rasterizer (Triangles to Pixels)
|
|
||||||
let rasterizer = vk::PipelineRasterizationStateCreateInfo::default()
|
let rasterizer = vk::PipelineRasterizationStateCreateInfo::default()
|
||||||
.depth_clamp_enable(false)
|
.depth_clamp_enable(false)
|
||||||
.rasterizer_discard_enable(false)
|
.rasterizer_discard_enable(false)
|
||||||
.polygon_mode(vk::PolygonMode::FILL)
|
.polygon_mode(vk::PolygonMode::FILL)
|
||||||
.line_width(1.0)
|
.line_width(1.0)
|
||||||
.cull_mode(vk::CullModeFlags::BACK) // Back-face culling for performance
|
.cull_mode(vk::CullModeFlags::BACK)
|
||||||
.front_face(vk::FrontFace::COUNTER_CLOCKWISE)
|
.front_face(vk::FrontFace::COUNTER_CLOCKWISE)
|
||||||
.depth_bias_enable(false);
|
.depth_bias_enable(false);
|
||||||
|
|
||||||
// 6. Configure Multisampling (Anti-aliasing)
|
|
||||||
let multisampling = vk::PipelineMultisampleStateCreateInfo::default()
|
let multisampling = vk::PipelineMultisampleStateCreateInfo::default()
|
||||||
.sample_shading_enable(false)
|
.sample_shading_enable(false)
|
||||||
.rasterization_samples(vk::SampleCountFlags::TYPE_1);
|
.rasterization_samples(vk::SampleCountFlags::TYPE_1);
|
||||||
|
|
||||||
// 7. Configure Color Blending
|
|
||||||
let color_blend_attachment = vk::PipelineColorBlendAttachmentState::default()
|
let color_blend_attachment = vk::PipelineColorBlendAttachmentState::default()
|
||||||
.color_write_mask(vk::ColorComponentFlags::RGBA)
|
.color_write_mask(vk::ColorComponentFlags::RGBA)
|
||||||
.blend_enable(false); // Transparency is not required for the initial implementation
|
.blend_enable(false);
|
||||||
|
|
||||||
let color_blending = vk::PipelineColorBlendStateCreateInfo::default()
|
let color_blending = vk::PipelineColorBlendStateCreateInfo::default()
|
||||||
.logic_op_enable(false)
|
.logic_op_enable(false)
|
||||||
.attachments(std::slice::from_ref(&color_blend_attachment));
|
.attachments(std::slice::from_ref(&color_blend_attachment));
|
||||||
|
|
||||||
// 8. Define Dynamic States
|
|
||||||
// This allows the window to be resized without recreating the entire pipeline.
|
|
||||||
let dynamic_states = [vk::DynamicState::VIEWPORT, vk::DynamicState::SCISSOR];
|
let dynamic_states = [vk::DynamicState::VIEWPORT, vk::DynamicState::SCISSOR];
|
||||||
let dynamic_state_info =
|
let dynamic_state_info =
|
||||||
vk::PipelineDynamicStateCreateInfo::default().dynamic_states(&dynamic_states);
|
vk::PipelineDynamicStateCreateInfo::default().dynamic_states(&dynamic_states);
|
||||||
|
|
||||||
// 9. Configure Dynamic Rendering (Vulkan 1.3)
|
|
||||||
let color_formats = [color_format];
|
let color_formats = [color_format];
|
||||||
let mut rendering_info =
|
let mut rendering_info =
|
||||||
vk::PipelineRenderingCreateInfo::default().color_attachment_formats(&color_formats);
|
vk::PipelineRenderingCreateInfo::default().color_attachment_formats(&color_formats);
|
||||||
|
|
||||||
// 10. Finalize Pipeline Creation
|
// 3. Finalize Pipeline Creation
|
||||||
let pipeline_info = vk::GraphicsPipelineCreateInfo::default()
|
let pipeline_info = vk::GraphicsPipelineCreateInfo::default()
|
||||||
.push_next(&mut rendering_info)
|
.push_next(&mut rendering_info)
|
||||||
.stages(&shader_stages)
|
.stages(&shader_stages)
|
||||||
|
|
@ -142,8 +127,8 @@ pub fn create_graphics_pipeline(
|
||||||
let pipeline = unsafe {
|
let pipeline = unsafe {
|
||||||
device
|
device
|
||||||
.create_graphics_pipelines(vk::PipelineCache::null(), &[pipeline_info], None)
|
.create_graphics_pipelines(vk::PipelineCache::null(), &[pipeline_info], None)
|
||||||
.expect("Failed to create graphics pipeline")[0]
|
.map_err(|(_, e)| e)?
|
||||||
};
|
}[0];
|
||||||
|
|
||||||
// Cleanup temporary shader modules (they are baked into the pipeline now)
|
// Cleanup temporary shader modules (they are baked into the pipeline now)
|
||||||
unsafe {
|
unsafe {
|
||||||
|
|
@ -151,5 +136,16 @@ pub fn create_graphics_pipeline(
|
||||||
device.destroy_shader_module(frag_module, None);
|
device.destroy_shader_module(frag_module, None);
|
||||||
}
|
}
|
||||||
|
|
||||||
pipeline
|
Ok(pipeline)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Loads the vertex and fragment shader modules from embedded bytes.
|
||||||
|
fn load_shader_modules(device: &Device) -> Result<(vk::ShaderModule, vk::ShaderModule), RendererError> {
|
||||||
|
let vert_bytes = include_bytes!("../../../assets/shaders/cube.vert.spv");
|
||||||
|
let frag_bytes = include_bytes!("../../../assets/shaders/cube.frag.spv");
|
||||||
|
|
||||||
|
let vert_module = create_shader_module(device, vert_bytes)?;
|
||||||
|
let frag_module = create_shader_module(device, frag_bytes)?;
|
||||||
|
|
||||||
|
Ok((vert_module, frag_module))
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -14,12 +14,14 @@ pub struct Renderer {
|
||||||
/// The debug messenger for validation layer output.
|
/// The debug messenger for validation layer output.
|
||||||
pub(crate) debug_messenger: vk::DebugUtilsMessengerEXT,
|
pub(crate) debug_messenger: vk::DebugUtilsMessengerEXT,
|
||||||
/// Handle to the selected physical device (GPU).
|
/// Handle to the selected physical device (GPU).
|
||||||
|
#[allow(dead_code)]
|
||||||
pub(crate) physical_device: vk::PhysicalDevice,
|
pub(crate) physical_device: vk::PhysicalDevice,
|
||||||
/// The logical Vulkan device.
|
/// The logical Vulkan device.
|
||||||
pub(crate) device: Device,
|
pub(crate) device: Device,
|
||||||
/// The queue used for graphics operations.
|
/// The queue used for graphics operations.
|
||||||
pub(crate) graphics_queue: vk::Queue,
|
pub(crate) graphics_queue: vk::Queue,
|
||||||
/// Index of the graphics queue family.
|
/// Index of the graphics queue family.
|
||||||
|
#[allow(dead_code)]
|
||||||
pub(crate) graphics_queue_index: u32,
|
pub(crate) graphics_queue_index: u32,
|
||||||
/// Surface extension loader.
|
/// Surface extension loader.
|
||||||
pub(crate) surface_loader: khr::surface::Instance,
|
pub(crate) surface_loader: khr::surface::Instance,
|
||||||
|
|
@ -32,6 +34,7 @@ pub struct Renderer {
|
||||||
/// Images acquired from the swapchain.
|
/// Images acquired from the swapchain.
|
||||||
pub(crate) swapchain_images: Vec<vk::Image>,
|
pub(crate) swapchain_images: Vec<vk::Image>,
|
||||||
/// The pixel format of the swapchain images.
|
/// The pixel format of the swapchain images.
|
||||||
|
#[allow(dead_code)]
|
||||||
pub(crate) swapchain_format: vk::Format,
|
pub(crate) swapchain_format: vk::Format,
|
||||||
/// The dimensions of the swapchain images.
|
/// The dimensions of the swapchain images.
|
||||||
pub(crate) swapchain_extent: vk::Extent2D,
|
pub(crate) swapchain_extent: vk::Extent2D,
|
||||||
|
|
@ -53,7 +56,7 @@ pub struct Renderer {
|
||||||
pub(crate) vertex_allocation: Allocation,
|
pub(crate) vertex_allocation: Allocation,
|
||||||
/// Synchronization primitives for frame-by-frame execution.
|
/// Synchronization primitives for frame-by-frame execution.
|
||||||
pub(crate) sync: SyncPrimitives,
|
pub(crate) sync: SyncPrimitives,
|
||||||
/// Index of the current frame being processed (0 to crate::MAX_FRAMES_IN_FLIGHT - 1).
|
/// Index of the current frame being processed (0 to `crate::MAX_FRAMES_IN_FLIGHT` - 1).
|
||||||
pub(crate) current_frame: usize,
|
pub(crate) current_frame: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -204,16 +207,19 @@ impl Drop for Renderer {
|
||||||
self.device
|
self.device
|
||||||
.destroy_pipeline_layout(self.pipeline_layout, None);
|
.destroy_pipeline_layout(self.pipeline_layout, None);
|
||||||
|
|
||||||
self.allocator
|
if let Err(e) = self
|
||||||
.free(std::ptr::read(&self.vertex_allocation))
|
.allocator
|
||||||
.expect(" Failed to free vertex buffer allocation");
|
.free(std::ptr::read(&raw const self.vertex_allocation))
|
||||||
|
{
|
||||||
|
tracing::error!("Failed to free vertex buffer allocation: {e}");
|
||||||
|
}
|
||||||
|
|
||||||
self.device.destroy_buffer(self.vertex_buffer, None);
|
self.device.destroy_buffer(self.vertex_buffer, None);
|
||||||
|
|
||||||
self.device.destroy_command_pool(self.command_pool, None);
|
self.device.destroy_command_pool(self.command_pool, None);
|
||||||
|
|
||||||
// Use the safe cleanup function from sync module
|
// Use the safe cleanup function from sync module
|
||||||
let sync = std::ptr::read(&self.sync);
|
let sync = std::ptr::read(&raw const self.sync);
|
||||||
crate::sync::destroy_sync_primitives(&self.device, sync);
|
crate::sync::destroy_sync_primitives(&self.device, sync);
|
||||||
|
|
||||||
// Destroy the swapchain
|
// Destroy the swapchain
|
||||||
|
|
|
||||||
|
|
@ -39,9 +39,7 @@ pub fn create_swapchain(
|
||||||
|
|
||||||
let present_mode = vk::PresentModeKHR::FIFO;
|
let present_mode = vk::PresentModeKHR::FIFO;
|
||||||
|
|
||||||
let extent = if surface_capabilities.current_extent.width != u32::MAX {
|
let extent = if surface_capabilities.current_extent.width == u32::MAX {
|
||||||
surface_capabilities.current_extent
|
|
||||||
} else {
|
|
||||||
vk::Extent2D {
|
vk::Extent2D {
|
||||||
width: width.clamp(
|
width: width.clamp(
|
||||||
surface_capabilities.min_image_extent.width,
|
surface_capabilities.min_image_extent.width,
|
||||||
|
|
@ -52,6 +50,8 @@ pub fn create_swapchain(
|
||||||
surface_capabilities.max_image_extent.height,
|
surface_capabilities.max_image_extent.height,
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
surface_capabilities.current_extent
|
||||||
};
|
};
|
||||||
|
|
||||||
let image_count = if surface_capabilities.max_image_count > 0
|
let image_count = if surface_capabilities.max_image_count > 0
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,11 @@
|
||||||
|
//! Lua scripting and modding support for Project Catalyst.
|
||||||
|
//!
|
||||||
|
//! This crate handles the integration with Lua (via `mlua`) and provides
|
||||||
|
//! the API surface for both base game content and third-party mods.
|
||||||
|
|
||||||
|
/// Adds two numbers together.
|
||||||
|
///
|
||||||
|
/// This is a placeholder function for the initial crate setup.
|
||||||
pub fn add(left: u64, right: u64) -> u64 {
|
pub fn add(left: u64, right: u64) -> u64 {
|
||||||
left + right
|
left + right
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,8 @@
|
||||||
|
//! Dedicated server for Project Catalyst.
|
||||||
|
//!
|
||||||
|
//! The server handles the authoritative game simulation, including world
|
||||||
|
//! management, physics, and combat.
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
println!("Hello, world!");
|
println!("Hello, world!");
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,11 @@
|
||||||
|
//! Shared types and logic for Project Catalyst.
|
||||||
|
//!
|
||||||
|
//! This crate contains data structures and constants that are used by both
|
||||||
|
//! the client and the server.
|
||||||
|
|
||||||
|
/// Adds two numbers together.
|
||||||
|
///
|
||||||
|
/// This is a placeholder function for the initial crate setup.
|
||||||
pub fn add(left: u64, right: u64) -> u64 {
|
pub fn add(left: u64, right: u64) -> u64 {
|
||||||
left + right
|
left + right
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue