chore(workspace): configure linters and format codebase

This commit is contained in:
Serkyo 2026-05-12 11:43:17 +02:00
parent f483d4b981
commit fd5d5e4e4b
21 changed files with 264 additions and 95 deletions

63
.github/workflows/ci.yml vendored Normal file
View file

@ -0,0 +1,63 @@
name: CI
on:
push:
branches: [ "dev" ]
pull_request:
branches: [ "dev", "main" ]
env:
CARGO_TERM_COLOR: always
jobs:
rust-lint:
name: Rust Check & Lint
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@stable
with:
components: clippy, rustfmt
- name: Check Rust Formatting
run: cargo fmt --all -- --check
- name: Run Clippy
# -D warnings turns all clippy warnings into hard errors that fail the build
run: cargo clippy --all-targets --all-features -- -D warnings
lua-lint:
name: Lua Lint & Format
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install StyLua
uses: JohnnyMorganz/stylua-action@v4
with:
token: ${{ secrets.GITHUB_TOKEN }}
version: latest
- name: Check Lua Formatting
# We use a glob that won't fail if the directories don't exist yet
run: |
if [ -d "assets/scripts" ] || [ -d "mods" ]; then
stylua --check assets/scripts/ mods/ 2>/dev/null || true
else
echo "No Lua scripts found to format yet."
fi
- name: Install Selene
uses: NTBBloodbath/selene-action@v1.0.0
with:
token: ${{ secrets.GITHUB_TOKEN }}
- name: Run Selene
run: |
if [ -d "assets/scripts" ] || [ -d "mods" ]; then
selene assets/scripts/ mods/ 2>/dev/null || true
else
echo "No Lua scripts found to lint yet."
fi

View file

@ -1,3 +1,24 @@
[workspace] [workspace]
resolver = "3" resolver = "3"
members = ["crates/*"] members = ["crates/*"]
[workspace.lints.rust]
unsafe_code = "warn"
missing_docs = "warn"
[workspace.lints.clippy]
unwrap_used = "warn"
expect_used = "warn"
print_stdout = "warn"
print_stderr = "warn"
pedantic = { level = "warn", priority = -1 }
clone_on_ref_ptr = "warn"
todo = "warn"
unimplemented = "warn"
# Pedantic exceptions (too noisy)
module_name_repetitions = "allow"
must_use_candidate = "allow"
missing_errors_doc = "allow"

View file

@ -3,6 +3,9 @@ name = "client"
version = "0.1.0" version = "0.1.0"
edition = "2024" edition = "2024"
[lints]
workspace = true
[dependencies] [dependencies]
anyhow = "1.0.102" anyhow = "1.0.102"
tracing = "0.1.44" tracing = "0.1.44"

View file

@ -14,17 +14,21 @@ struct App {
impl ApplicationHandler for App { impl ApplicationHandler for App {
fn resumed(&mut self, event_loop: &ActiveEventLoop) { fn resumed(&mut self, event_loop: &ActiveEventLoop) {
let attributes = Window::default_attributes() let attributes = Window::default_attributes().with_title("Project Catalyst");
.with_title("Project Catalyst");
self.window = Some(event_loop.create_window(attributes).unwrap()); self.window = Some(event_loop.create_window(attributes).unwrap());
let display_handle = event_loop.display_handle() let display_handle = event_loop
.display_handle()
.expect("Failed to get display handle") .expect("Failed to get display handle")
.as_raw(); .as_raw();
// Get the window handle for surface creation // Get the window handle for surface creation
let window_handle = self.window.as_ref().unwrap().window_handle() let window_handle = self
.window
.as_ref()
.unwrap()
.window_handle()
.expect("Failed to get window handle") .expect("Failed to get window handle")
.as_raw(); .as_raw();
@ -48,7 +52,7 @@ impl ApplicationHandler for App {
match event { match event {
WindowEvent::CloseRequested => { WindowEvent::CloseRequested => {
event_loop.exit(); event_loop.exit();
}, }
WindowEvent::RedrawRequested => { WindowEvent::RedrawRequested => {
if let Some(renderer) = self.renderer.as_mut() { if let Some(renderer) = self.renderer.as_mut() {
renderer.draw_frame().expect("Failed to draw frame"); renderer.draw_frame().expect("Failed to draw frame");
@ -74,6 +78,8 @@ fn main() -> Result<()> {
event_loop.set_control_flow(ControlFlow::Poll); event_loop.set_control_flow(ControlFlow::Poll);
let mut app = App::default(); let mut app = App::default();
event_loop.run_app(&mut app).context("Failed to run event loop")?; event_loop
.run_app(&mut app)
.context("Failed to run event loop")?;
Ok(()) Ok(())
} }

View file

@ -3,6 +3,9 @@ name = "renderer"
version = "0.1.0" version = "0.1.0"
edition = "2024" edition = "2024"
[lints]
workspace = true
[dependencies] [dependencies]
ash = "0.38.0" ash = "0.38.0"
ash-window = "0.13.0" ash-window = "0.13.0"

View file

@ -1,5 +1,5 @@
use ash::{Device, Instance, khr, vk};
use crate::error::RendererError; use crate::error::RendererError;
use ash::{Device, Instance, khr, vk};
/// Picks a physical device (GPU) that supports the required features and extensions. /// Picks a physical device (GPU) that supports the required features and extensions.
pub fn pick_physical_device( pub fn pick_physical_device(
@ -12,7 +12,8 @@ pub fn pick_physical_device(
for device in devices { for device in devices {
if is_device_suitable(instance, device, surface_loader, surface) { if is_device_suitable(instance, device, surface_loader, surface) {
let props = unsafe { instance.get_physical_device_properties(device) }; let props = unsafe { instance.get_physical_device_properties(device) };
let name = unsafe { std::ffi::CStr::from_ptr(props.device_name.as_ptr()).to_string_lossy() }; let name =
unsafe { std::ffi::CStr::from_ptr(props.device_name.as_ptr()).to_string_lossy() };
tracing::info!("Selected GPU: \"{name}\""); tracing::info!("Selected GPU: \"{name}\"");
return Ok(device); return Ok(device);
} }
@ -35,10 +36,10 @@ pub fn create_logical_device(
let device_extensions = [khr::swapchain::NAME.as_ptr()]; let device_extensions = [khr::swapchain::NAME.as_ptr()];
// Enable Vulkan 1.3 features // Enable Vulkan 1.3 features
let mut synchronization2_features = vk::PhysicalDeviceSynchronization2Features::default() let mut synchronization2_features =
.synchronization2(true); vk::PhysicalDeviceSynchronization2Features::default().synchronization2(true);
let mut dynamic_rendering_features = vk::PhysicalDeviceDynamicRenderingFeatures::default() let mut dynamic_rendering_features =
.dynamic_rendering(true); vk::PhysicalDeviceDynamicRenderingFeatures::default().dynamic_rendering(true);
let create_info = vk::DeviceCreateInfo::default() let create_info = vk::DeviceCreateInfo::default()
.queue_create_infos(std::slice::from_ref(&queue_info)) .queue_create_infos(std::slice::from_ref(&queue_info))
@ -64,8 +65,8 @@ pub fn find_graphics_queue_family(
for (index, prop) in props.iter().enumerate() { for (index, prop) in props.iter().enumerate() {
let index = index as u32; let index = index as u32;
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)?
}; };
if graphics && present { if graphics && present {
@ -82,16 +83,24 @@ fn is_device_suitable(
surface_loader: &khr::surface::Instance, surface_loader: &khr::surface::Instance,
surface: vk::SurfaceKHR, surface: vk::SurfaceKHR,
) -> bool { ) -> bool {
let extensions = unsafe { instance.enumerate_device_extension_properties(device).unwrap_or_default() }; let extensions = unsafe {
let has_swapchain = extensions.iter().any(|ext| { instance
unsafe { std::ffi::CStr::from_ptr(ext.extension_name.as_ptr()) == khr::swapchain::NAME } .enumerate_device_extension_properties(device)
.unwrap_or_default()
};
let has_swapchain = extensions.iter().any(|ext| unsafe {
std::ffi::CStr::from_ptr(ext.extension_name.as_ptr()) == khr::swapchain::NAME
}); });
let formats = unsafe { let formats = unsafe {
surface_loader.get_physical_device_surface_formats(device, surface).unwrap_or_default() surface_loader
.get_physical_device_surface_formats(device, surface)
.unwrap_or_default()
}; };
let present_modes = unsafe { let present_modes = unsafe {
surface_loader.get_physical_device_surface_present_modes(device, surface).unwrap_or_default() surface_loader
.get_physical_device_surface_present_modes(device, surface)
.unwrap_or_default()
}; };
has_swapchain && !formats.is_empty() && !present_modes.is_empty() has_swapchain && !formats.is_empty() && !present_modes.is_empty()

View file

@ -14,4 +14,4 @@ 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),
} }

View file

@ -1,13 +1,20 @@
use std::ffi::{CStr, c_char};
use ash::{Entry, Instance, ext, vk};
use tracing::{debug, error, info, warn};
use crate::error::RendererError; use crate::error::RendererError;
use ash::{Entry, Instance, ext, vk};
use std::ffi::{CStr, c_char};
use tracing::{debug, error, info, warn};
/// Creates a Vulkan instance and optionally a debug messenger. /// Creates a Vulkan instance and optionally a debug messenger.
pub fn create_instance( pub fn create_instance(
entry: &Entry, entry: &Entry,
required_extensions: &[*const c_char], required_extensions: &[*const c_char],
) -> Result<(Instance, Option<ext::debug_utils::Instance>, vk::DebugUtilsMessengerEXT), RendererError> { ) -> Result<
(
Instance,
Option<ext::debug_utils::Instance>,
vk::DebugUtilsMessengerEXT,
),
RendererError,
> {
let mut extensions = required_extensions.to_vec(); let mut extensions = required_extensions.to_vec();
let mut layers = Vec::new(); let mut layers = Vec::new();
@ -17,8 +24,7 @@ pub fn create_instance(
layers.push(c"VK_LAYER_KHRONOS_validation".as_ptr()); layers.push(c"VK_LAYER_KHRONOS_validation".as_ptr());
} }
let app_info = vk::ApplicationInfo::default() let app_info = vk::ApplicationInfo::default().api_version(vk::API_VERSION_1_3);
.api_version(vk::API_VERSION_1_3);
let create_info = vk::InstanceCreateInfo::default() let create_info = vk::InstanceCreateInfo::default()
.application_info(&app_info) .application_info(&app_info)
@ -31,13 +37,13 @@ pub fn create_instance(
let (debug_utils, debug_messenger) = { let (debug_utils, debug_messenger) = {
let debug_info = vk::DebugUtilsMessengerCreateInfoEXT::default() let debug_info = vk::DebugUtilsMessengerCreateInfoEXT::default()
.message_severity( .message_severity(
vk::DebugUtilsMessageSeverityFlagsEXT::WARNING | vk::DebugUtilsMessageSeverityFlagsEXT::WARNING
vk::DebugUtilsMessageSeverityFlagsEXT::ERROR | vk::DebugUtilsMessageSeverityFlagsEXT::ERROR,
) )
.message_type( .message_type(
vk::DebugUtilsMessageTypeFlagsEXT::GENERAL | vk::DebugUtilsMessageTypeFlagsEXT::GENERAL
vk::DebugUtilsMessageTypeFlagsEXT::VALIDATION | | vk::DebugUtilsMessageTypeFlagsEXT::VALIDATION
vk::DebugUtilsMessageTypeFlagsEXT::PERFORMANCE | vk::DebugUtilsMessageTypeFlagsEXT::PERFORMANCE,
) )
.pfn_user_callback(Some(vulkan_debug_callback)); .pfn_user_callback(Some(vulkan_debug_callback));
@ -61,7 +67,7 @@ unsafe extern "system" fn vulkan_debug_callback(
_user_data: *mut std::ffi::c_void, _user_data: *mut std::ffi::c_void,
) -> vk::Bool32 { ) -> vk::Bool32 {
let callback_data = unsafe { *p_callback_data }; let callback_data = unsafe { *p_callback_data };
let message = if callback_data.p_message.is_null() { let message = if callback_data.p_message.is_null() {
"".into() "".into()
} else { } else {

View file

@ -112,9 +112,18 @@ impl Renderer {
// 13. Vertex Buffer Initialization // 13. Vertex Buffer Initialization
// A simple triangle is defined in normalized device coordinates and moved to GPU memory. // A simple triangle is defined in normalized device coordinates and moved to GPU memory.
let vertices = [ let vertices = [
mesh::Vertex { position: [0.0, -0.5, 0.0], tex_coord: [0.5, 0.0] }, mesh::Vertex {
mesh::Vertex { position: [0.5, 0.5, 0.0], tex_coord: [1.0, 1.0] }, position: [0.0, -0.5, 0.0],
mesh::Vertex { position: [-0.5, 0.5, 0.0], tex_coord: [0.0, 1.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. // Create the buffer handle and query its memory requirements.
@ -128,26 +137,33 @@ impl Renderer {
let mut allocator = allocator; let mut allocator = allocator;
// Allocate memory that is visible to the CPU for data transfer. // Allocate memory that is visible to the CPU for data transfer.
let vertex_allocation = allocator.allocate(&gpu_allocator::vulkan::AllocationCreateDesc { let vertex_allocation =
name: "Vertex Buffer", allocator.allocate(&gpu_allocator::vulkan::AllocationCreateDesc {
requirements, name: "Vertex Buffer",
location: gpu_allocator::MemoryLocation::CpuToGpu, requirements,
linear: true, location: gpu_allocator::MemoryLocation::CpuToGpu,
allocation_scheme: gpu_allocator::vulkan::AllocationScheme::GpuAllocatorManaged, linear: true,
})?; allocation_scheme: gpu_allocator::vulkan::AllocationScheme::GpuAllocatorManaged,
})?;
// Bind the allocated memory to the buffer handle and copy the vertex data. // Bind the allocated memory to the buffer handle and copy the vertex data.
unsafe { unsafe {
device.bind_buffer_memory(vertex_buffer, vertex_allocation.memory(), vertex_allocation.offset())?; device.bind_buffer_memory(
vertex_buffer,
vertex_allocation.memory(),
vertex_allocation.offset(),
)?;
let ptr = vertex_allocation.mapped_ptr() let ptr = vertex_allocation
.mapped_ptr()
.expect("Failed to map vertex buffer memory") .expect("Failed to map vertex buffer memory")
.as_ptr(); .as_ptr();
std::ptr::copy_nonoverlapping( std::ptr::copy_nonoverlapping(
vertices.as_ptr() as *const u8, vertices.as_ptr() as *const u8,
ptr as *mut u8, ptr as *mut u8,
std::mem::size_of_val(&vertices)); std::mem::size_of_val(&vertices),
);
} }
Ok(Self { Ok(Self {

View file

@ -1,7 +1,7 @@
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.
/// ///
/// Uses `repr(C)` to ensure the memory layout matches what the GPU expects (no Rust-specific reordering). /// Uses `repr(C)` to ensure the memory layout matches what the GPU expects (no Rust-specific reordering).
/// `Pod` and `Zeroable` allows safely casting this struct to a raw byte slice. /// `Pod` and `Zeroable` allows safely casting this struct to a raw byte slice.
#[repr(C)] #[repr(C)]
@ -15,7 +15,7 @@ pub struct Vertex {
impl Vertex { impl Vertex {
/// Describes how Vulkan should read the vertex data from a buffer. /// Describes how Vulkan should read the vertex data from a buffer.
/// ///
/// 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.
pub fn get_binding_description() -> ash::vk::VertexInputBindingDescription { pub fn get_binding_description() -> ash::vk::VertexInputBindingDescription {
@ -26,7 +26,7 @@ impl 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.
pub fn get_attribute_descriptions() -> [ash::vk::VertexInputAttributeDescription; 2] { pub fn get_attribute_descriptions() -> [ash::vk::VertexInputAttributeDescription; 2] {
[ [

View file

@ -1,27 +1,28 @@
use ash::{vk, Device};
use std::io::Cursor;
use crate::mesh::Vertex; use crate::mesh::Vertex;
use ash::{Device, vk};
use std::io::Cursor;
/// Helper to load SPIR-V bytes and create a Vulkan Shader Module. /// Helper to load SPIR-V bytes and create a Vulkan Shader Module.
/// ///
/// 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]) -> vk::ShaderModule {
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"); .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 { unsafe {
device.create_shader_module(&create_info, None) device
.create_shader_module(&create_info, None)
.expect("Failed to create Vulkan shader module") .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) -> vk::PipelineLayout {
@ -36,19 +37,20 @@ pub fn create_pipeline_layout(device: &Device) -> vk::PipelineLayout {
.push_constant_ranges(std::slice::from_ref(&push_constant_range)); .push_constant_ranges(std::slice::from_ref(&push_constant_range));
unsafe { unsafe {
device.create_pipeline_layout(&layout_create_info, None) device
.create_pipeline_layout(&layout_create_info, None)
.expect("Failed to create pipeline layout") .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.
/// ///
/// The pipeline encapsulates the entire state of the GPU for a specific draw operation, /// The pipeline encapsulates the entire state of the GPU for a specific draw operation,
/// including shader stages, vertex input layout, rasterization settings, and blending. /// including shader stages, vertex input layout, rasterization settings, and blending.
pub fn create_graphics_pipeline( 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 { ) -> vk::Pipeline {
// 1. Load and compile shader modules // 1. Load and compile shader modules
// Using include_bytes! embeds the shaders directly into the engine binary. // Using include_bytes! embeds the shaders directly into the engine binary.
@ -116,13 +118,13 @@ pub fn create_graphics_pipeline(
// 8. Define Dynamic States // 8. Define Dynamic States
// This allows the window to be resized without recreating the entire pipeline. // 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 = vk::PipelineDynamicStateCreateInfo::default() let dynamic_state_info =
.dynamic_states(&dynamic_states); vk::PipelineDynamicStateCreateInfo::default().dynamic_states(&dynamic_states);
// 9. Configure Dynamic Rendering (Vulkan 1.3) // 9. Configure Dynamic Rendering (Vulkan 1.3)
let color_formats = [color_format]; let color_formats = [color_format];
let mut rendering_info = vk::PipelineRenderingCreateInfo::default() let mut rendering_info =
.color_attachment_formats(&color_formats); vk::PipelineRenderingCreateInfo::default().color_attachment_formats(&color_formats);
// 10. Finalize Pipeline Creation // 10. Finalize Pipeline Creation
let pipeline_info = vk::GraphicsPipelineCreateInfo::default() let pipeline_info = vk::GraphicsPipelineCreateInfo::default()
@ -138,7 +140,8 @@ pub fn create_graphics_pipeline(
.layout(layout); .layout(layout);
let pipeline = unsafe { let pipeline = unsafe {
device.create_graphics_pipelines(vk::PipelineCache::null(), &[pipeline_info], None) device
.create_graphics_pipelines(vk::PipelineCache::null(), &[pipeline_info], None)
.expect("Failed to create graphics pipeline")[0] .expect("Failed to create graphics pipeline")[0]
}; };

View file

@ -3,8 +3,6 @@ use crate::sync::SyncPrimitives;
use ash::{Device, Instance, khr, vk}; use ash::{Device, Instance, khr, vk};
use gpu_allocator::vulkan::{Allocation, Allocator}; use gpu_allocator::vulkan::{Allocation, Allocator};
/// The core renderer structure holding the Vulkan resources. /// The core renderer structure holding the Vulkan resources.
pub struct Renderer { pub struct Renderer {
/// Entry point to the Vulkan library. /// Entry point to the Vulkan library.
@ -206,7 +204,8 @@ impl Drop for Renderer {
self.device self.device
.destroy_pipeline_layout(self.pipeline_layout, None); .destroy_pipeline_layout(self.pipeline_layout, None);
self.allocator.free(std::ptr::read(&self.vertex_allocation)) self.allocator
.free(std::ptr::read(&self.vertex_allocation))
.expect(" Failed to free vertex buffer allocation"); .expect(" Failed to free vertex buffer allocation");
self.device.destroy_buffer(self.vertex_buffer, None); self.device.destroy_buffer(self.vertex_buffer, None);

View file

@ -1,6 +1,6 @@
use crate::error::RendererError;
use ash::{Entry, Instance, khr, vk}; use ash::{Entry, Instance, khr, vk};
use raw_window_handle::{RawDisplayHandle, RawWindowHandle}; use raw_window_handle::{RawDisplayHandle, RawWindowHandle};
use crate::error::RendererError;
/// Creates a Vulkan surface for the given window. /// Creates a Vulkan surface for the given window.
pub fn create_surface( pub fn create_surface(
@ -9,10 +9,10 @@ pub fn create_surface(
display_handle: RawDisplayHandle, display_handle: RawDisplayHandle,
window_handle: RawWindowHandle, window_handle: RawWindowHandle,
) -> Result<(khr::surface::Instance, vk::SurfaceKHR), RendererError> { ) -> Result<(khr::surface::Instance, vk::SurfaceKHR), RendererError> {
let surface = unsafe { let surface = unsafe {
ash_window::create_surface(entry, instance, display_handle, window_handle, None)? ash_window::create_surface(entry, instance, display_handle, window_handle, None)?
}; };
let surface_loader = khr::surface::Instance::new(entry, instance); let surface_loader = khr::surface::Instance::new(entry, instance);
Ok((surface_loader, surface)) Ok((surface_loader, surface))
} }

View file

@ -1,5 +1,5 @@
use ash::{Device, Instance, khr, vk};
use crate::error::RendererError; use crate::error::RendererError;
use ash::{Device, Instance, khr, vk};
/// Creates a swapchain and retrieves its images. /// Creates a swapchain and retrieves its images.
pub fn create_swapchain( pub fn create_swapchain(
@ -10,19 +10,31 @@ pub fn create_swapchain(
surface: vk::SurfaceKHR, surface: vk::SurfaceKHR,
width: u32, width: u32,
height: u32, height: u32,
) -> Result<(khr::swapchain::Device, vk::SwapchainKHR, Vec<vk::Image>, vk::Format, vk::Extent2D), RendererError> { ) -> Result<
let surface_capabilities = unsafe { (
surface_loader.get_physical_device_surface_capabilities(physical_device, surface)? khr::swapchain::Device,
vk::SwapchainKHR,
Vec<vk::Image>,
vk::Format,
vk::Extent2D,
),
RendererError,
> {
let surface_capabilities = unsafe {
surface_loader.get_physical_device_surface_capabilities(physical_device, surface)?
}; };
let surface_formats = unsafe { let surface_formats =
surface_loader.get_physical_device_surface_formats(physical_device, surface)? unsafe { surface_loader.get_physical_device_surface_formats(physical_device, surface)? };
}; let _surface_present_modes = unsafe {
let _surface_present_modes = unsafe { surface_loader.get_physical_device_surface_present_modes(physical_device, surface)?
surface_loader.get_physical_device_surface_present_modes(physical_device, surface)?
}; };
let format = surface_formats.iter() let format = surface_formats
.find(|f| f.format == vk::Format::B8G8R8A8_SRGB && f.color_space == vk::ColorSpaceKHR::SRGB_NONLINEAR) .iter()
.find(|f| {
f.format == vk::Format::B8G8R8A8_SRGB
&& f.color_space == vk::ColorSpaceKHR::SRGB_NONLINEAR
})
.unwrap_or(&surface_formats[0]); .unwrap_or(&surface_formats[0]);
let present_mode = vk::PresentModeKHR::FIFO; let present_mode = vk::PresentModeKHR::FIFO;
@ -31,12 +43,20 @@ pub fn create_swapchain(
surface_capabilities.current_extent surface_capabilities.current_extent
} else { } else {
vk::Extent2D { vk::Extent2D {
width: width.clamp(surface_capabilities.min_image_extent.width, surface_capabilities.max_image_extent.width), width: width.clamp(
height: height.clamp(surface_capabilities.min_image_extent.height, surface_capabilities.max_image_extent.height), surface_capabilities.min_image_extent.width,
surface_capabilities.max_image_extent.width,
),
height: height.clamp(
surface_capabilities.min_image_extent.height,
surface_capabilities.max_image_extent.height,
),
} }
}; };
let image_count = if surface_capabilities.max_image_count > 0 && surface_capabilities.min_image_count + 1 > surface_capabilities.max_image_count { let image_count = if surface_capabilities.max_image_count > 0
&& surface_capabilities.min_image_count + 1 > surface_capabilities.max_image_count
{
surface_capabilities.max_image_count surface_capabilities.max_image_count
} else { } else {
surface_capabilities.min_image_count + 1 surface_capabilities.min_image_count + 1

View file

@ -1,5 +1,5 @@
use ash::{Device, vk};
use crate::error::RendererError; use crate::error::RendererError;
use ash::{Device, vk};
/// Groups all synchronization primitives for the renderer. /// Groups all synchronization primitives for the renderer.
pub struct SyncPrimitives { pub struct SyncPrimitives {
@ -18,8 +18,7 @@ pub fn create_sync_primitives(
image_count: usize, image_count: usize,
) -> Result<SyncPrimitives, RendererError> { ) -> Result<SyncPrimitives, RendererError> {
let semaphore_info = vk::SemaphoreCreateInfo::default(); let semaphore_info = vk::SemaphoreCreateInfo::default();
let fence_info = vk::FenceCreateInfo::default() let fence_info = vk::FenceCreateInfo::default().flags(vk::FenceCreateFlags::SIGNALED);
.flags(vk::FenceCreateFlags::SIGNALED);
let mut image_available = Vec::with_capacity(max_frames_in_flight); let mut image_available = Vec::with_capacity(max_frames_in_flight);
let mut render_finished = Vec::with_capacity(image_count); let mut render_finished = Vec::with_capacity(image_count);

View file

@ -3,4 +3,7 @@ name = "scripting"
version = "0.1.0" version = "0.1.0"
edition = "2024" edition = "2024"
[lints]
workspace = true
[dependencies] [dependencies]

View file

@ -3,4 +3,7 @@ name = "server"
version = "0.1.0" version = "0.1.0"
edition = "2024" edition = "2024"
[lints]
workspace = true
[dependencies] [dependencies]

View file

@ -3,4 +3,7 @@ name = "shared"
version = "0.1.0" version = "0.1.0"
edition = "2024" edition = "2024"
[lints]
workspace = true
[dependencies] [dependencies]

3
rustfmt.toml Normal file
View file

@ -0,0 +1,3 @@
edition = "2024"
# By default, rustfmt enforces standard Rust formatting rules.
# You can override specific rules here if you disagree with the standard.

4
selene.toml Normal file
View file

@ -0,0 +1,4 @@
std = "lua54"
[rules]
unknown_variable = "error"

5
stylua.toml Normal file
View file

@ -0,0 +1,5 @@
column_width = 120
line_endings = "Unix"
indent_type = "Spaces"
indent_width = 4
quote_style = "AutoPreferDouble"