diff --git a/Cargo.lock b/Cargo.lock index cab6339..001bfcc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -549,6 +549,72 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "96a6ac251f4a2aca6b3f91340350eab87ae57c3f127ffeb585e92bd336717991" +[[package]] +name = "darling" +version = "0.20.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc7f46116c46ff9ab3eb1597a45688b6715c6e628b5c133e288e709a29bcb4ee" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.20.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d00b9596d185e565c2207a0b01f8bd1a135483d02d9b7b0a54b11da8d53412e" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn", +] + +[[package]] +name = "darling_macro" +version = "0.20.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" +dependencies = [ + "darling_core", + "quote", + "syn", +] + +[[package]] +name = "derive_builder" +version = "0.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "507dfb09ea8b7fa618fcf76e953f4f5e192547945816d5358edffe39f6f94947" +dependencies = [ + "derive_builder_macro", +] + +[[package]] +name = "derive_builder_core" +version = "0.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d5bcf7b024d6835cfb3d473887cd966994907effbe9227e8c8219824d06c4e8" +dependencies = [ + "darling", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "derive_builder_macro" +version = "0.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab63b0e2bf4d5928aff72e83a7dace85d7bba5fe12dcc3c5a572d78caffd3f3c" +dependencies = [ + "derive_builder_core", + "syn", +] + [[package]] name = "dispatch" version = "0.2.0" @@ -664,9 +730,11 @@ dependencies = [ "egui", "egui-winit", "gfx_hal", + "glam", "raw-window-handle", "renderer", "resource_manager", + "shared", "thiserror 2.0.12", "tracing", "tracing-subscriber", @@ -748,6 +816,12 @@ dependencies = [ "miniz_oxide 0.8.5", ] +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + [[package]] name = "foreign-types" version = "0.5.0" @@ -811,6 +885,7 @@ version = "0.1.0" dependencies = [ "ash", "ash-window", + "gpu-allocator", "thiserror 2.0.12", "tracing", "winit", @@ -990,6 +1065,12 @@ dependencies = [ "syn", ] +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + [[package]] name = "idna" version = "1.0.3" @@ -1199,6 +1280,15 @@ dependencies = [ "libc", ] +[[package]] +name = "memoffset" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a" +dependencies = [ + "autocfg", +] + [[package]] name = "miniz_oxide" version = "0.7.4" @@ -1758,6 +1848,7 @@ dependencies = [ "gpu-allocator", "resource_manager", "shaderc", + "shared", "thiserror 2.0.12", "tracing", "walkdir", @@ -1910,6 +2001,17 @@ dependencies = [ "lazy_static", ] +[[package]] +name = "shared" +version = "0.1.0" +dependencies = [ + "ash", + "bytemuck", + "derive_builder", + "glam", + "memoffset", +] + [[package]] name = "shlex" version = "1.3.0" diff --git a/Cargo.toml b/Cargo.toml index 454df20..539c227 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,7 +5,7 @@ members = [ "crates/engine", "crates/gfx_hal", "crates/renderer", - "crates/resource_manager", + "crates/resource_manager", "crates/shared", ] [workspace.dependencies] diff --git a/crates/engine/Cargo.toml b/crates/engine/Cargo.toml index 8f13de8..2fd8816 100644 --- a/crates/engine/Cargo.toml +++ b/crates/engine/Cargo.toml @@ -13,9 +13,12 @@ tracing-subscriber.workspace = true winit.workspace = true raw-window-handle.workspace = true thiserror.workspace = true +glam.workspace = true gfx_hal = { path = "../gfx_hal" } renderer = { path = "../renderer" } resource_manager = { path = "../resource_manager" } +shared = { path = "../shared" } + clap = { version = "4.5.34", features = ["derive"] } egui-winit = "0.31.1" diff --git a/crates/engine/src/main.rs b/crates/engine/src/main.rs index b32b286..4bd277a 100644 --- a/crates/engine/src/main.rs +++ b/crates/engine/src/main.rs @@ -8,15 +8,17 @@ use std::{ use ash::vk; use clap::Parser; -use egui::{Context, ViewportId}; +use egui::{Context, Slider, ViewportId}; use egui_winit::State; use gfx_hal::{ device::Device, error::GfxHalError, instance::Instance, instance::InstanceConfig, physical_device::PhysicalDevice, queue::Queue, surface::Surface, }; +use glam::Vec3; use raw_window_handle::HasDisplayHandle; use renderer::{Renderer, RendererError}; -use resource_manager::{ResourceManager, ResourceManagerError}; +use resource_manager::{Geometry, ResourceManager, ResourceManagerError}; +use shared::{CameraInfo, Vertex}; use tracing::{debug, error, info, warn}; use tracing_subscriber::{filter, layer::SubscriberExt, util::SubscriberInitExt, Layer}; use winit::{ @@ -25,6 +27,7 @@ use winit::{ event_loop::{ActiveEventLoop, EventLoop}, window::Window, }; + // --- Configuration --- const APP_NAME: &str = "BeginDisregard"; const ENGINE_NAME: &str = "Engine"; @@ -69,18 +72,33 @@ struct Application { frame_count: u32, last_fps_update_time: Instant, last_frame_time: Instant, + current_fps: f64, } #[derive(Default)] -struct EditorUI {} +struct EditorUI { + camera_info: CameraInfo, +} impl EditorUI { fn title() -> String { "engine".to_string() } - fn build_ui(&mut self, ctx: &egui::Context) { - egui::Window::new(Self::title()).show(ctx, |ui| ui.label(Self::title())); + fn build_ui(&mut self, ctx: &egui::Context, current_fps: f64) { + egui::Window::new(Self::title()).show(ctx, |ui| { + ui.label(format!("FPS - {:.2}", current_fps)); + + ui.separator(); + + egui::Grid::new("main_grid") + .spacing([40.0, 4.0]) + .striped(true) + .show(ui, |ui| { + ui.label("FOV"); + ui.add(Slider::new(&mut self.camera_info.camera_fov, 10.0..=120.0)); + }); + }); } } @@ -241,14 +259,61 @@ impl Application { // Get specific queues (assuming graphics and present are the same for simplicity) let graphics_queue = device.get_graphics_queue(); - let queue_associated_device_handle = graphics_queue.device().raw().handle(); // --- 4. Resource Manager --- let resource_manager = Arc::new(ResourceManager::new(instance.clone(), device.clone())?); info!("Resource Manager initialized."); - let renderer_device_handle_to_pass = device.raw().handle(); - let renderer_queue_device_handle_to_pass = graphics_queue.device().raw().handle(); + let vertices = vec![ + // Define 8 vertices for a cube with positions and colors + // Make sure winding order is correct (e.g., counter-clockwise for front faces) + Vertex { + pos: Vec3::new(-0.5, -0.5, 0.5).into(), + color: Vec3::new(1.0, 0.0, 0.0).into(), + }, // Front bottom left 0 + Vertex { + pos: Vec3::new(0.5, -0.5, 0.5).into(), + color: Vec3::new(0.0, 1.0, 0.0).into(), + }, // Front bottom right 1 + Vertex { + pos: Vec3::new(0.5, 0.5, 0.5).into(), + color: Vec3::new(0.0, 0.0, 1.0).into(), + }, // Front top right 2 + Vertex { + pos: Vec3::new(-0.5, 0.5, 0.5).into(), + color: Vec3::new(1.0, 1.0, 0.0).into(), + }, // Front top left 3 + // ... add back face vertices (4-7) ... + Vertex { + pos: Vec3::new(-0.5, -0.5, -0.5).into(), + color: Vec3::new(1.0, 0.0, 1.0).into(), + }, // Back bottom left 4 + Vertex { + pos: Vec3::new(0.5, -0.5, -0.5).into(), + color: Vec3::new(0.0, 1.0, 1.0).into(), + }, // Back bottom right 5 + Vertex { + pos: Vec3::new(0.5, 0.5, -0.5).into(), + color: Vec3::new(0.5, 0.5, 0.5).into(), + }, // Back top right 6 + Vertex { + pos: Vec3::new(-0.5, 0.5, -0.5).into(), + color: Vec3::new(1.0, 1.0, 1.0).into(), + }, // Back top left 7 + ]; + + let indices = vec![ + // Define 12 triangles (36 indices) for the cube faces + // Front face + 0, 1, 2, 2, 3, 0, // Right face + 1, 5, 6, 6, 2, 1, // Back face + 5, 4, 7, 7, 6, 5, // Left face + 4, 0, 3, 3, 7, 4, // Top face + 3, 2, 6, 6, 7, 3, // Bottom face + 4, 5, 1, 1, 0, 4, + ]; + + let cube_geometry = Geometry::new(resource_manager.clone(), &vertices, &indices)?; // --- 5. Renderer --- let initial_size = window.inner_size(); @@ -258,6 +323,7 @@ impl Application { graphics_queue.clone(), surface.clone(), resource_manager.clone(), + vec![cube_geometry], initial_size.width, initial_size.height, )?; @@ -288,6 +354,7 @@ impl Application { egui_ctx, egui_app, frame_count: 0, + current_fps: 0., last_fps_update_time: Instant::now(), last_frame_time: Instant::now(), }) @@ -332,6 +399,7 @@ impl Application { if elapsed_sice_last_update >= Duration::from_secs(1) { let fps = self.frame_count as f64 / elapsed_sice_last_update.as_secs_f64(); + self.current_fps = fps; let new_title = format!("{} - {} - {:.0} FPS", ENGINE_NAME, APP_NAME, fps); self.window.set_title(&new_title); @@ -349,7 +417,7 @@ impl Application { pixels_per_point, .. } = self.egui_ctx.run(raw_input, |ctx| { - self.egui_app.build_ui(ctx); + self.egui_app.build_ui(ctx, self.current_fps); }); self.renderer.update_textures(textures_delta).unwrap(); @@ -360,10 +428,11 @@ impl Application { let clipped_primitives = self.egui_ctx.tessellate(shapes, pixels_per_point); // --- Render Frame --- - match self - .renderer - .render_frame(pixels_per_point, &clipped_primitives) - { + match self.renderer.render_frame( + pixels_per_point, + &clipped_primitives, + self.egui_app.camera_info, + ) { Ok(_) => { self.window.request_redraw(); } @@ -499,7 +568,7 @@ fn main() -> Result<(), Box> { .with_ansi(true) .with_file(false) .with_line_number(false) - .with_filter(filter::LevelFilter::DEBUG); + .with_filter(filter::LevelFilter::INFO); let registry = tracing_subscriber::registry().with(fmt_layer); diff --git a/crates/gfx_hal/Cargo.toml b/crates/gfx_hal/Cargo.toml index acd0197..de36a5c 100644 --- a/crates/gfx_hal/Cargo.toml +++ b/crates/gfx_hal/Cargo.toml @@ -9,3 +9,4 @@ ash-window.workspace = true thiserror.workspace = true tracing.workspace = true winit.workspace = true +gpu-allocator.workspace = true diff --git a/crates/gfx_hal/src/error.rs b/crates/gfx_hal/src/error.rs index 1dec5bc..efe7c8e 100644 --- a/crates/gfx_hal/src/error.rs +++ b/crates/gfx_hal/src/error.rs @@ -63,6 +63,10 @@ pub enum GfxHalError { /// Placeholder for other specific errors. #[error("An unexpected error occurred: {0}")] Other(String), + + /// Size for Buffer is invalid. + #[error("Buffer size is invalid.")] + BufferSizeInvalid, } pub type Result = std::result::Result; diff --git a/crates/gfx_hal/src/lib.rs b/crates/gfx_hal/src/lib.rs index e83ecc2..3c1feeb 100644 --- a/crates/gfx_hal/src/lib.rs +++ b/crates/gfx_hal/src/lib.rs @@ -6,3 +6,12 @@ pub mod queue; pub mod surface; pub mod swapchain; pub mod sync; + +pub use device::*; +pub use error::*; +pub use instance::*; +pub use physical_device::*; +pub use queue::*; +pub use surface::*; +pub use swapchain::*; +pub use sync::*; diff --git a/crates/gfx_hal/src/queue.rs b/crates/gfx_hal/src/queue.rs index 7cbe40d..e0952b7 100644 --- a/crates/gfx_hal/src/queue.rs +++ b/crates/gfx_hal/src/queue.rs @@ -65,18 +65,6 @@ impl Queue { submits: &[vk::SubmitInfo], signal_fence: Option<&Fence>, ) -> Result<()> { - debug_assert!( - self.device.raw().handle() == submit_device_raw.handle(), - "Queue::submit called with an ash::Device from a different logical VkDevice than the queue belongs to!" - ); - // Optional: Check fence device consistency - if let Some(fence) = signal_fence { - debug_assert!( - fence.device().raw().handle() == submit_device_raw.handle(), - "Fence passed to Queue::submit belongs to a different logical device than submit_device_raw!" - ); - } - let fence_handle = signal_fence.map_or(vk::Fence::null(), |f| f.handle()); // Keep the lock for thread-safety on the VkQueue object itself diff --git a/crates/gfx_hal/src/sync.rs b/crates/gfx_hal/src/sync.rs index 0f684aa..0d20fa9 100644 --- a/crates/gfx_hal/src/sync.rs +++ b/crates/gfx_hal/src/sync.rs @@ -10,6 +10,7 @@ use crate::{ /// Wraps a `vk::Fence`, used for CPU-GPU synchronization. /// /// Owns the `vk::Fence` handle. +#[derive(Clone)] pub struct Fence { device: Arc, fence: vk::Fence, diff --git a/crates/renderer/Cargo.toml b/crates/renderer/Cargo.toml index fb324c5..9da1bc2 100644 --- a/crates/renderer/Cargo.toml +++ b/crates/renderer/Cargo.toml @@ -16,6 +16,7 @@ winit.workspace = true gfx_hal = { path = "../gfx_hal" } resource_manager = { path = "../resource_manager" } +shared = { path = "../shared" } [build-dependencies] shaderc = "0.9.1" diff --git a/crates/renderer/build.rs b/crates/renderer/build.rs index 16afb10..a24d5a1 100644 --- a/crates/renderer/build.rs +++ b/crates/renderer/build.rs @@ -16,6 +16,8 @@ fn main() -> Result<()> { let out_dir = PathBuf::from(env::var("OUT_DIR")?).join("shaders"); // Put shaders in a subdirectory for clarity fs::create_dir_all(&out_dir).context("Failed to create shader output directory")?; + println!("cargo:rerun-if-changed=build.rs"); + let compiler = Compiler::new().context("Failed to create shader compiler")?; let mut options = CompileOptions::new().context("Failed to create compile options")?; @@ -49,6 +51,7 @@ fn main() -> Result<()> { .filter(|e| e.file_type().is_file()) // Only process files { + println!("cargo:rerun-if-changed={:?}", entry.path()); let in_path = entry.path(); // Determine shader kind from extension diff --git a/crates/renderer/src/lib.rs b/crates/renderer/src/lib.rs index 33ff0a2..521cc52 100644 --- a/crates/renderer/src/lib.rs +++ b/crates/renderer/src/lib.rs @@ -1,6 +1,8 @@ use std::{ - ffi::CStr, + ffi::{c_void, CStr}, + mem, sync::{Arc, Mutex}, + time::Instant, }; use ash::vk; @@ -10,14 +12,15 @@ use gfx_hal::{ device::Device, error::GfxHalError, queue::Queue, surface::Surface, swapchain::Swapchain, swapchain::SwapchainConfig, sync::Fence, sync::Semaphore, }; -use gpu_allocator::{vulkan::Allocator, MemoryLocation}; -use resource_manager::{ImageHandle, ResourceManager, ResourceManagerError}; +use glam::{Mat4, Vec3}; +use gpu_allocator::{ + vulkan::{Allocation, AllocationCreateDesc, Allocator}, + MemoryLocation, +}; +use resource_manager::{Geometry, ImageHandle, ResourceManager, ResourceManagerError}; +use shared::{CameraInfo, UniformBufferObject}; use thiserror::Error; use tracing::{debug, error, info, warn}; -// Assuming winit is used by the app - -// Re-export ash for convenience if needed elsewhere -pub use ash; const MAX_FRAMES_IN_FLIGHT: usize = 2; @@ -55,6 +58,14 @@ pub enum RendererError { ImageInfoUnavailable, #[error("Failed to get allocator from resource manager")] AllocatorUnavailable, // Added based on egui requirement + #[error("Allocator Error: {0}")] + AllocatorError(#[from] gpu_allocator::AllocationError), +} + +impl From> for RendererError { + fn from(_: std::sync::PoisonError) -> Self { + Self::AllocatorUnavailable + } } struct FrameData { @@ -64,6 +75,12 @@ struct FrameData { render_finished_semaphore: Semaphore, textures_to_free: Option>, in_flight_fence: Fence, + + descriptor_set: vk::DescriptorSet, + uniform_buffer_object: UniformBufferObject, + uniform_buffer: vk::Buffer, + uniform_buffer_allocation: Allocation, + uniform_buffer_mapped_ptr: *mut c_void, } struct SwapchainSupportDetails { @@ -84,14 +101,19 @@ pub struct Renderer { swapchain_format: vk::SurfaceFormatKHR, swapchain_extent: vk::Extent2D, + scene: Vec, + + descriptor_set_layout: vk::DescriptorSetLayout, + descriptor_pool: vk::DescriptorPool, + egui_renderer: EguiRenderer, depth_image_handle: ImageHandle, depth_image_view: vk::ImageView, // Store the view directly depth_format: vk::Format, - triangle_pipeline_layout: vk::PipelineLayout, - triangle_pipeline: vk::Pipeline, + model_pipeline_layout: vk::PipelineLayout, + model_pipeline: vk::Pipeline, frames_data: Vec, current_frame: usize, @@ -100,6 +122,8 @@ pub struct Renderer { window_resized: bool, current_width: u32, current_height: u32, + + start_time: Instant, } impl Renderer { @@ -109,6 +133,7 @@ impl Renderer { graphics_queue: Arc, surface: Arc, resource_manager: Arc, + scene: Vec, initial_width: u32, initial_height: u32, ) -> Result { @@ -128,10 +153,26 @@ impl Renderer { let (depth_image_handle, depth_image_view) = Self::create_depth_resources(&device, &resource_manager, extent, depth_format)?; - let (triangle_pipeline_layout, triangle_pipeline) = - Self::create_triangle_pipeline(&device, format.format, depth_format)?; + let (descriptor_set_layout, descriptor_pool) = + Self::create_descriptor_sets_resources(&device)?; - let frames_data = Self::create_frame_data(&device)?; + let (model_pipeline_layout, model_pipeline) = Self::create_model_pipeline( + &device, + format.format, + depth_format, + descriptor_set_layout, + )?; + + let start_time = Instant::now(); + + let frames_data = Self::create_frame_data( + &device, + &resource_manager, + descriptor_pool, + descriptor_set_layout, + swapchain.extent(), + start_time, + )?; info!("Renderer initialized successfully."); @@ -144,6 +185,7 @@ impl Renderer { }, Options { srgb_framebuffer: true, + in_flight_frames: MAX_FRAMES_IN_FLIGHT, ..Default::default() }, )?; @@ -159,16 +201,21 @@ impl Renderer { swapchain_image_views: image_views, swapchain_format: format, swapchain_extent: extent, + descriptor_set_layout, + descriptor_pool, depth_image_handle, depth_image_view, depth_format, - triangle_pipeline_layout, - triangle_pipeline, + model_pipeline_layout, + model_pipeline, frames_data, + scene, current_frame: 0, window_resized: false, current_width: initial_width, current_height: initial_height, + + start_time, }) } @@ -208,6 +255,7 @@ impl Renderer { &mut self, pixels_per_point: f32, clipped_primitives: &[ClippedPrimitive], + camera_info: CameraInfo, ) -> Result<(), RendererError> { // --- Handle Resize --- if self.window_resized { @@ -229,6 +277,7 @@ impl Renderer { .swapchain .as_ref() .ok_or(RendererError::SwapchainAcquisitionFailed)?; + let (image_index, suboptimal) = unsafe { // Need unsafe block for acquire_next_image swapchain_ref.acquire_next_image( @@ -250,7 +299,6 @@ impl Renderer { frame_data.in_flight_fence.reset()?; if let Some(textures) = frame_data.textures_to_free.take() { - tracing::debug!("Freeing EGUI Textures"); self.egui_renderer.free_textures(&textures)?; } @@ -266,6 +314,15 @@ impl Renderer { let cmd_begin_info = vk::CommandBufferBeginInfo::default() .flags(vk::CommandBufferUsageFlags::ONE_TIME_SUBMIT); + // -- Update uniform buffer -- + self.update_uniform_buffer(camera_info)?; + + let frame_data = &mut self.frames_data[self.current_frame]; + let swapchain_ref = self + .swapchain + .as_ref() + .ok_or(RendererError::SwapchainAcquisitionFailed)?; + unsafe { // Need unsafe for Vulkan commands self.device @@ -373,10 +430,21 @@ impl Renderer { self.device.raw().cmd_bind_pipeline( command_buffer, vk::PipelineBindPoint::GRAPHICS, - self.triangle_pipeline, + self.model_pipeline, ); - // Draw 3 vertices, 1 instance, 0 first vertex, 0 first instance - self.device.raw().cmd_draw(command_buffer, 3, 1, 0, 0); + + self.device.raw().cmd_bind_descriptor_sets( + command_buffer, + vk::PipelineBindPoint::GRAPHICS, + self.model_pipeline_layout, + 0, + &[frame_data.descriptor_set], + &[], + ); + } + + for g in &self.scene { + g.draw(self.device.raw(), command_buffer)?; } tracing::trace!("Rendering EGUI"); @@ -682,33 +750,12 @@ impl Renderer { } // --- Helper: Create Triangle Pipeline --- - fn create_triangle_pipeline( + fn create_model_pipeline( device: &Arc, color_format: vk::Format, depth_format: vk::Format, + descriptor_set_layout: vk::DescriptorSetLayout, ) -> Result<(vk::PipelineLayout, vk::Pipeline), RendererError> { - // --- Shaders (Hardcoded example) --- - // Vertex Shader (GLSL) - outputs clip space position based on vertex index - /* - #version 450 - vec2 positions[3] = vec2[]( - vec2(0.0, -0.5), - vec2(0.5, 0.5), - vec2(-0.5, 0.5) - ); - void main() { - gl_Position = vec4(positions[gl_VertexIndex], 0.0, 1.0); - } - */ - // Fragment Shader (GLSL) - outputs solid orange - /* - #version 450 - layout(location = 0) out vec4 outColor; - void main() { - outColor = vec4(1.0, 0.5, 0.0, 1.0); // Orange - } - */ - // Load compiled SPIR-V (replace with actual loading) let vert_shader_code = include_bytes!(concat!(env!("OUT_DIR"), "/shaders/vert.glsl.spv")); // Placeholder path let frag_shader_code = include_bytes!(concat!(env!("OUT_DIR"), "/shaders/frag.glsl.spv")); // Placeholder path @@ -730,8 +777,13 @@ impl Renderer { let shader_stages = [vert_stage_info, frag_stage_info]; + let binding_description = shared::Vertex::get_binding_decription(); + let attribute_descriptions = shared::Vertex::get_attribute_descriptions(); + // --- Fixed Function State --- - let vertex_input_info = vk::PipelineVertexInputStateCreateInfo::default(); // No vertex buffers/attributes + let vertex_input_info = vk::PipelineVertexInputStateCreateInfo::default() + .vertex_binding_descriptions(std::slice::from_ref(&binding_description)) + .vertex_attribute_descriptions(&attribute_descriptions); let input_assembly = vk::PipelineInputAssemblyStateCreateInfo::default() .topology(vk::PrimitiveTopology::TRIANGLE_LIST) @@ -774,7 +826,8 @@ impl Renderer { vk::PipelineDynamicStateCreateInfo::default().dynamic_states(&dynamic_states); // --- Pipeline Layout --- - let layout_info = vk::PipelineLayoutCreateInfo::default(); // No descriptors/push constants + let layout_info = vk::PipelineLayoutCreateInfo::default() + .set_layouts(std::slice::from_ref(&descriptor_set_layout)); // No descriptors/push constants let pipeline_layout = unsafe { device .raw() @@ -872,7 +925,14 @@ impl Renderer { } // --- Helper: Create Frame Sync Objects & Command Resources --- - fn create_frame_data(device: &Arc) -> Result, RendererError> { + fn create_frame_data( + device: &Arc, + resource_manager: &Arc, + descriptor_pool: vk::DescriptorPool, + descriptor_set_layout: vk::DescriptorSetLayout, + swapchain_extent: vk::Extent2D, + instant: Instant, + ) -> Result, RendererError> { let mut frames_data = Vec::with_capacity(MAX_FRAMES_IN_FLIGHT); for _ in 0..MAX_FRAMES_IN_FLIGHT { let image_available_semaphore = Semaphore::new(device.clone())?; @@ -905,6 +965,19 @@ impl Renderer { .map_err(RendererError::CommandBufferAllocation)?[0] }; + tracing::info!("Allocated frame_data command_buffer: {:?}", command_buffer); + + let descriptor_set = + Self::create_descriptor_set(device, descriptor_set_layout, descriptor_pool)?; + + let (uniform_buffer, uniform_buffer_allocation, uniform_buffer_mapped_ptr) = + Self::create_uniform_buffer(device, resource_manager)?; + + Self::update_descriptor_set(device.clone(), descriptor_set, uniform_buffer); + + let uniform_buffer_object = + calculate_ubo(CameraInfo::default(), swapchain_extent, instant); + frames_data.push(FrameData { textures_to_free: None, command_pool, @@ -912,6 +985,11 @@ impl Renderer { image_available_semaphore, render_finished_semaphore, in_flight_fence, + descriptor_set, + uniform_buffer, + uniform_buffer_allocation, + uniform_buffer_mapped_ptr, + uniform_buffer_object, }); } Ok(frames_data) @@ -936,22 +1014,20 @@ impl Renderer { } fn choose_swapchain_format(available_formats: &[vk::SurfaceFormatKHR]) -> vk::SurfaceFormatKHR { - available_formats + *available_formats .iter() .find(|format| { format.format == vk::Format::B8G8R8A8_SRGB // Prefer SRGB && format.color_space == vk::ColorSpaceKHR::SRGB_NONLINEAR }) - .unwrap_or(&available_formats[0]) // Fallback to first available - .clone() + .unwrap_or(&available_formats[0]) } fn choose_swapchain_present_mode(available_modes: &[vk::PresentModeKHR]) -> vk::PresentModeKHR { - available_modes + *available_modes .iter() .find(|&&mode| mode == vk::PresentModeKHR::MAILBOX) // Prefer Mailbox (low latency) - .unwrap_or(&vk::PresentModeKHR::FIFO) // Guaranteed fallback - .clone() + .unwrap_or(&vk::PresentModeKHR::FIFO) } fn choose_swapchain_extent( @@ -1004,6 +1080,166 @@ impl Renderer { vk::Result::ERROR_FORMAT_NOT_SUPPORTED, )) // Or custom error } + + fn create_descriptor_sets_resources( + device: &Arc, + ) -> Result<(vk::DescriptorSetLayout, vk::DescriptorPool), RendererError> { + let ubo_layout_binding = vk::DescriptorSetLayoutBinding::default() + .binding(0) + .descriptor_type(vk::DescriptorType::UNIFORM_BUFFER) + .descriptor_count(1) + .stage_flags(vk::ShaderStageFlags::VERTEX); + + let layout_info = vk::DescriptorSetLayoutCreateInfo::default() + .bindings(std::slice::from_ref(&ubo_layout_binding)); + + let descriptor_set_layout = unsafe { + device + .raw() + .create_descriptor_set_layout(&layout_info, None)? + }; + + let pool_size = vk::DescriptorPoolSize { + ty: vk::DescriptorType::UNIFORM_BUFFER, + descriptor_count: MAX_FRAMES_IN_FLIGHT as u32, + }; + + let pool_info = vk::DescriptorPoolCreateInfo::default() + .pool_sizes(std::slice::from_ref(&pool_size)) + .max_sets(MAX_FRAMES_IN_FLIGHT as u32); + + let descriptor_pool = unsafe { device.raw().create_descriptor_pool(&pool_info, None)? }; + + Ok((descriptor_set_layout, descriptor_pool)) + } + + fn create_descriptor_set( + device: &Arc, + descriptor_set_layout: vk::DescriptorSetLayout, + descriptor_pool: vk::DescriptorPool, + ) -> Result { + let layouts = vec![descriptor_set_layout; 1]; + let alloc_info = vk::DescriptorSetAllocateInfo::default() + .descriptor_pool(descriptor_pool) + .set_layouts(&layouts); + + let descriptor_set = unsafe { device.raw().allocate_descriptor_sets(&alloc_info)? }[0]; + + Ok(descriptor_set) + } + + fn create_uniform_buffer( + device: &Arc, + resource_manager: &Arc, + ) -> Result<(vk::Buffer, Allocation, *mut std::ffi::c_void), RendererError> { + let buffer_size = mem::size_of::() as vk::DeviceSize; + + let buffer_info = vk::BufferCreateInfo::default() + .size(buffer_size) + .usage(vk::BufferUsageFlags::UNIFORM_BUFFER) + .sharing_mode(vk::SharingMode::EXCLUSIVE); + + let allocation = resource_manager + .allocator() + .lock()? + .allocate(&AllocationCreateDesc { + name: "Uniform Buffer", + requirements: unsafe { + { + let temp_buffer = device.raw().create_buffer(&buffer_info, None)?; + let req = device.raw().get_buffer_memory_requirements(temp_buffer); + + device.raw().destroy_buffer(temp_buffer, None); + req + } + }, + location: MemoryLocation::CpuToGpu, + linear: true, + allocation_scheme: gpu_allocator::vulkan::AllocationScheme::GpuAllocatorManaged, + })?; + + let buffer = unsafe { device.raw().create_buffer(&buffer_info, None)? }; + tracing::info!("Created uniform buffer {:?}", buffer); + + unsafe { + device + .raw() + .bind_buffer_memory(buffer, allocation.memory(), allocation.offset())?; + } + + let mapped_ptr = allocation + .mapped_ptr() + .ok_or_else(|| { + error!("Failed to get mapped pointer for CPU->GPU uniform buffer"); + ResourceManagerError::Other("Failed to map uniform buffer".to_string()) + })? + .as_ptr(); + + Ok((buffer, allocation, mapped_ptr)) + } + + fn update_descriptor_set( + device: Arc, + descriptor_set: vk::DescriptorSet, + buffer: vk::Buffer, + ) { + let buffer_info = vk::DescriptorBufferInfo::default() + .buffer(buffer) + .offset(0) + .range(mem::size_of::() as vk::DeviceSize); + + let descriptor_write = vk::WriteDescriptorSet::default() + .dst_set(descriptor_set) + .dst_binding(0) + .dst_array_element(0) + .descriptor_type(vk::DescriptorType::UNIFORM_BUFFER) + .buffer_info(std::slice::from_ref(&buffer_info)); + + unsafe { + device + .raw() + .update_descriptor_sets(std::slice::from_ref(&descriptor_write), &[]); + } + } + + fn update_uniform_buffer(&mut self, camera_info: CameraInfo) -> Result<(), RendererError> { + let frame_data = &mut self.frames_data[self.current_frame]; + + let ubo = calculate_ubo(camera_info, self.swapchain_extent, self.start_time); + + if frame_data.uniform_buffer_object != ubo { + let ptr = frame_data.uniform_buffer_mapped_ptr; + unsafe { + let aligned_ptr = ptr as *mut UniformBufferObject; + aligned_ptr.write(ubo); + } + } + + Ok(()) + } +} + +fn calculate_ubo( + camera_info: CameraInfo, + swapchain_extent: vk::Extent2D, + start: Instant, +) -> UniformBufferObject { + let time = start.elapsed(); + + let model = Mat4::from_rotation_y(time.as_secs_f32()); + + let view = Mat4::look_at_rh(camera_info.camera_pos, camera_info.camera_target, Vec3::Y); + + let mut proj = Mat4::perspective_rh( + camera_info.camera_fov.to_radians(), + swapchain_extent.width as f32 / swapchain_extent.height as f32, + 0.1, + 1000.0, + ); + + proj.y_axis.y *= -1.0; + + UniformBufferObject { model, view, proj } } // --- Drop Implementation --- @@ -1027,16 +1263,39 @@ impl Drop for Renderer { unsafe { self.device .raw() - .destroy_pipeline(self.triangle_pipeline, None); + .destroy_pipeline(self.model_pipeline, None); self.device .raw() - .destroy_pipeline_layout(self.triangle_pipeline_layout, None); + .destroy_pipeline_layout(self.model_pipeline_layout, None); + } + + unsafe { + self.device + .raw() + .destroy_descriptor_pool(self.descriptor_pool, None); + self.device + .raw() + .destroy_descriptor_set_layout(self.descriptor_set_layout, None); } // Destroy frame data (fences, semaphores, command pools) // Fences/Semaphores are handled by gfx_hal::Drop // Command buffers are freed with the pool for frame_data in self.frames_data.drain(..) { + unsafe { + self.device + .raw() + .destroy_buffer(frame_data.uniform_buffer, None); + + let mut allocator = self + .allocator + .lock() + .expect("Allocator Mutex to not be poisoned."); + allocator + .free(frame_data.uniform_buffer_allocation) + .expect("Allocator to be able to free an allocation"); + } + unsafe { self.device .raw() diff --git a/crates/resource_manager/src/geo.rs b/crates/resource_manager/src/geo.rs new file mode 100644 index 0000000..6a33e3a --- /dev/null +++ b/crates/resource_manager/src/geo.rs @@ -0,0 +1,174 @@ +use std::sync::Arc; + +use ash::vk::{self, IndexType}; +use gpu_allocator::MemoryLocation; +use tracing::{debug, trace}; + +use crate::{BufferHandle, ResourceManager, ResourceManagerError, Result}; + +// Helper to safely get a byte slice from structured data +unsafe fn as_byte_slice(data: &[T]) -> &[u8] { + std::slice::from_raw_parts(data.as_ptr() as *const u8, std::mem::size_of_val(data)) +} + +/// Represents geometry data (verticies and indicies) stored in GPU buffers managed by +/// ResourceManager. Handles automatic cleanup via a `Drop` implementation. +pub struct Geometry { + resource_manager: Arc, + pub vertex_buffer: BufferHandle, + pub index_buffer: BufferHandle, + pub index_count: u32, +} + +impl Geometry { + /// Creates new GPU buffers for the given vetex and index data using `ResourceManager`. + /// + /// # Arguments + /// + /// * `resource_manager` - An Arc reference to the ResourceManager. + /// * `vertices` - A slice of vertex data. + /// * `indices` - A slice of index data (u32) + /// + /// # Errors + /// + /// Returns a new `ResourceManagerError` if buffer creation or data upload fails. + pub fn new( + resource_manager: Arc, + vertices: &[V], + indicies: &[u32], + ) -> Result { + trace!( + "Creating Geometry: {} vertices, {} indicies", + vertices.len(), + indicies.len() + ); + + if vertices.is_empty() || indicies.is_empty() { + return Err(ResourceManagerError::Other( + "Cannot create Geometry with empty vertices or indicies.".to_string(), + )); + } + + let vertex_buffer = resource_manager.create_buffer_init( + vk::BufferUsageFlags::VERTEX_BUFFER, + MemoryLocation::GpuOnly, + unsafe { as_byte_slice(vertices) }, + )?; + trace!("Vertex buffer created: handle={:?}", vertex_buffer); + + let index_buffer = resource_manager.create_buffer_init( + vk::BufferUsageFlags::INDEX_BUFFER, + MemoryLocation::GpuOnly, + unsafe { as_byte_slice(indicies) }, + )?; + trace!("Index buffer created: handle={:?}", index_buffer); + + let index_count = indicies.len() as u32; + + debug!( + "Geometry created successfully: VB={:?}, IB={:?}, Indices={}", + vertex_buffer, index_buffer, index_count + ); + + Ok(Self { + resource_manager, + vertex_buffer, + index_buffer, + index_count, + // vertex_count, + }) + } + + /// Binds the vertex and index buffers for drawing. + /// + /// # Arguments + /// + /// * `device` - Raw `ash::Device` handle. + /// * `command_buffer` - The command buffer to record binding commands into. + /// + /// # Errors + /// + /// Returns `ResourceManagerError` if buffer info cannot be retrieved. + pub fn bind(&self, device: &ash::Device, command_buffer: vk::CommandBuffer) -> Result<()> { + trace!( + "Binding geometry: VB={:?}, IB={:?}", + self.vertex_buffer, + self.index_buffer + ); + // Get buffer info (locks resource manager map briefly) + let vb_info = self.resource_manager.get_buffer_info(self.vertex_buffer)?; + let ib_info = self.resource_manager.get_buffer_info(self.index_buffer)?; + + let vk_vertex_buffers = [vb_info.buffer]; + let offsets = [0_u64]; // Use vk::DeviceSize (u64) + + unsafe { + device.cmd_bind_vertex_buffers( + command_buffer, + 0, // binding = 0 + &vk_vertex_buffers, + &offsets, + ); + device.cmd_bind_index_buffer( + command_buffer, + ib_info.buffer, + 0, // offset = 0 + vk::IndexType::UINT32, + ); + } + Ok(()) + } + + /// Binds the geometry buffers and issues an indexed draw command. + /// + /// # Arguments + /// + /// * `device` - Raw `ash::Device` handle. + /// * `command_buffer` - The command buffer to record commands into. + /// + /// # Errors + /// + /// Returns `ResourceManagerError` if binding fails. + pub fn draw(&self, device: &ash::Device, command_buffer: vk::CommandBuffer) -> Result<()> { + self.bind(device, command_buffer)?; // Bind first + trace!("Drawing geometry: {} indices", self.index_count); + unsafe { + device.cmd_draw_indexed( + command_buffer, + self.index_count, // Use stored index count + 1, // instance_count + 0, // first_index + 0, // vertex_offset + 0, // first_instance + ); + } + Ok(()) + } +} + +impl Drop for Geometry { + fn drop(&mut self) { + debug!( + "Dropping Geometry: VB={:?}, IB={:?}", + self.vertex_buffer, self.index_buffer + ); + // Request destruction from the resource manager. + // Ignore errors during drop, but log them. + if let Err(e) = self.resource_manager.destroy_buffer(self.vertex_buffer) { + tracing::error!( + "Failed to destroy vertex buffer {:?} during Geometry drop: {}", + self.vertex_buffer, + e + ); + } + + if let Err(e) = self.resource_manager.destroy_buffer(self.index_buffer) { + tracing::error!( + "Failed to destroy index buffer {:?} during Geometry drop: {}", + self.index_buffer, + e + ); + } + // The Arc reference count decreases automatically. + } +} diff --git a/crates/resource_manager/src/lib.rs b/crates/resource_manager/src/lib.rs index add8a42..e6bd0e9 100644 --- a/crates/resource_manager/src/lib.rs +++ b/crates/resource_manager/src/lib.rs @@ -1,4 +1,5 @@ mod error; +mod geo; use std::{ collections::HashMap, @@ -10,10 +11,12 @@ use std::{ }; use ash::vk; -use gfx_hal::{device::Device, instance::Instance, queue::Queue}; +use gfx_hal::{device::Device, instance::Instance, queue::Queue, Fence}; use tracing::{debug, error, trace, warn}; pub use error::{ResourceManagerError, Result}; +pub use geo::Geometry; + use gpu_allocator::{ vulkan::{Allocation, AllocationCreateDesc, Allocator, AllocatorCreateDesc}, MemoryLocation, @@ -125,7 +128,7 @@ impl Drop for InternalImageInfo { struct TransferSetup { command_pool: vk::CommandPool, queue: Arc, - fence: vk::Fence, + fence: Fence, } pub struct ResourceManager { @@ -135,7 +138,7 @@ pub struct ResourceManager { buffers: Mutex>, images: Mutex>, next_id: AtomicU64, - transfer_setup: Mutex>, + transfer_setup: Arc>, } impl ResourceManager { @@ -152,6 +155,28 @@ impl ResourceManager { })?; debug!("GPU Allocator created."); + let queue_family_index = device + .transfer_queue_family_index() + .or(device.compute_queue_family_index()) // Try compute as fallback + .unwrap_or(device.graphics_queue_family_index()); // Graphics as last resort + + let queue = device.get_queue(queue_family_index, 0)?; + + // Create command pool for transfer commands + let pool_info = vk::CommandPoolCreateInfo::default() + .flags(vk::CommandPoolCreateFlags::TRANSIENT) // Hint that buffers are short-lived + .queue_family_index(queue_family_index); + let command_pool = unsafe { device.raw().create_command_pool(&pool_info, None)? }; + + // Create a fence for waiting + let fence = Fence::new(device.clone(), false)?; + + let new_setup = TransferSetup { + command_pool, + queue, + fence, + }; + Ok(Self { _instance: instance, device, @@ -159,7 +184,7 @@ impl ResourceManager { buffers: Mutex::new(HashMap::new()), images: Mutex::new(HashMap::new()), next_id: AtomicU64::new(1), - transfer_setup: Mutex::new(None), + transfer_setup: Arc::new(Mutex::new(new_setup)), }) } @@ -168,114 +193,59 @@ impl ResourceManager { self.allocator.clone() } - /// Gets or initializes the TransferSetup resources. - fn get_transfer_setup(&self) -> Result { - let mut setup_guard = self.transfer_setup.lock()?; - - if let Some(setup) = setup_guard.as_ref() { - // Simple check: Reset fence before reusing - unsafe { self.device.raw().reset_fences(&[setup.fence])? }; - return Ok(TransferSetup { - // Return a copy/clone - command_pool: setup.command_pool, - queue: setup.queue.clone(), - fence: setup.fence, - }); - } - - debug!("Initializing TransferSetup..."); - // Find a queue that supports transfer (prefer dedicated, fallback to graphics) - let queue_family_index = self - .device - .transfer_queue_family_index() - .or(self.device.compute_queue_family_index()) // Try compute as fallback - .unwrap_or(self.device.graphics_queue_family_index()); // Graphics as last resort - - let queue = self.device.get_queue(queue_family_index, 0)?; - - // Create command pool for transfer commands - let pool_info = vk::CommandPoolCreateInfo::default() - .flags(vk::CommandPoolCreateFlags::TRANSIENT) // Hint that buffers are short-lived - .queue_family_index(queue_family_index); - let command_pool = unsafe { self.device.raw().create_command_pool(&pool_info, None)? }; - - // Create a fence for waiting - let fence_info = vk::FenceCreateInfo::default(); - let fence = unsafe { self.device.raw().create_fence(&fence_info, None)? }; - - let new_setup = TransferSetup { - command_pool, - queue, - fence, - }; - *setup_guard = Some(new_setup); // Store it - debug!("TransferSetup initialized."); - - // Return a new copy for use - Ok(TransferSetup { - command_pool: setup_guard.as_ref().unwrap().command_pool, - queue: setup_guard.as_ref().unwrap().queue.clone(), - fence: setup_guard.as_ref().unwrap().fence, - }) - } - - /// Helper to allocate, begin, end, submit, and wait for a single command buffer. + /// Helper to allocate, begin, end, submit, and wait for a single command buffer + /// using the provided TransferSetup. unsafe fn submit_commands_and_wait( &self, - transfer_setup: &TransferSetup, + transfer_setup: &TransferSetup, // Use the cloned setup record_fn: F, ) -> Result<()> where - F: FnOnce(vk::CommandBuffer) -> Result<()>, + F: FnOnce(vk::CommandBuffer) -> Result<()>, // Closure records commands { - let device = self.device.raw(); + let device_raw = self.device.raw(); // Get raw ash::Device // Allocate command buffer let alloc_info = vk::CommandBufferAllocateInfo::default() .command_pool(transfer_setup.command_pool) .level(vk::CommandBufferLevel::PRIMARY) .command_buffer_count(1); - let command_buffer = device.allocate_command_buffers(&alloc_info)?[0]; + let command_buffer = device_raw.allocate_command_buffers(&alloc_info)?[0]; + tracing::info!("Allocated command_buffer: {:?}", command_buffer); + trace!("Allocated temporary command buffer for transfer."); // Begin recording let begin_info = vk::CommandBufferBeginInfo::default() .flags(vk::CommandBufferUsageFlags::ONE_TIME_SUBMIT); - device.begin_command_buffer(command_buffer, &begin_info)?; + device_raw.begin_command_buffer(command_buffer, &begin_info)?; - // Record user commands + // --- Record user commands --- let record_result = record_fn(command_buffer); - - // End recording (even if user function failed, to allow cleanup) - device.end_command_buffer(command_buffer)?; + // --- End Recording --- + // Always end buffer, even if recording failed, to allow cleanup + device_raw.end_command_buffer(command_buffer)?; // Check user function result *after* ending buffer record_result?; + trace!("Transfer commands recorded."); - let binding = [command_buffer]; - // Submit - let submits = [vk::SubmitInfo::default().command_buffers(&binding)]; - // Use the transfer queue and fence + // Submit to the transfer queue + let submits = + [vk::SubmitInfo::default().command_buffers(std::slice::from_ref(&command_buffer))]; + // Use the queue from the TransferSetup. Assuming Queue::submit handles locking. transfer_setup .queue - .submit(self.device.raw(), &submits, None)?; // Submit without fence initially + .submit(device_raw, &submits, Some(&transfer_setup.fence))?; // Submit WITH fence + trace!("Transfer command buffer submitted."); - // Wait for completion using a separate wait call - // This avoids holding the queue's internal submit lock during the wait. - let fences = [transfer_setup.fence]; - match device.wait_for_fences(&fences, true, u64::MAX) { - Ok(_) => {} - Err(vk::Result::TIMEOUT) => { - // Should not happen with u64::MAX - warn!("Transfer fence wait timed out unexpectedly."); - return Err(ResourceManagerError::TransferFailed( - "Fence wait timeout".to_string(), - )); - } - Err(e) => return Err(e.into()), - } + // Wait for completion using the fence + transfer_setup.fence.wait(None)?; - // Free command buffer - device.free_command_buffers(transfer_setup.command_pool, &[command_buffer]); + // Free command buffer *after* successful wait + device_raw.free_command_buffers(transfer_setup.command_pool, &[command_buffer]); + trace!("Temporary command buffer freed."); + + transfer_setup.fence.reset()?; Ok(()) } @@ -387,7 +357,7 @@ impl ResourceManager { let dest_handle = self.create_buffer(size, final_usage, location)?; // 4. Record and submit transfer command - let transfer_setup = self.get_transfer_setup()?; + let transfer_setup = self.transfer_setup.lock()?; let dest_info = self.get_buffer_info(dest_handle)?; // Get info for vk::Buffer handle let staging_info_for_copy = self.get_buffer_info(staging_handle)?; // Get info again @@ -589,22 +559,18 @@ impl Drop for ResourceManager { debug!("Clearing {} image entries...", images_map.len()); images_map.clear(); - // Destroy transfer setup resources - let mut setup_guard = self + let setup = self .transfer_setup .lock() .expect("mutex to not be poisoned"); - if let Some(setup) = setup_guard.take() { - // take() removes it from the Option - debug!("Destroying TransferSetup resources..."); - unsafe { - self.device.raw().destroy_fence(setup.fence, None); - self.device - .raw() - .destroy_command_pool(setup.command_pool, None); - } - debug!("TransferSetup resources destroyed."); + + debug!("Destroying TransferSetup resources..."); + unsafe { + self.device + .raw() + .destroy_command_pool(setup.command_pool, None); } + debug!("TransferSetup resources destroyed."); // The Allocator is wrapped in an Arc>, so its Drop will be handled // when the last Arc reference (including those held by Internal*Info) is dropped. diff --git a/crates/shared/Cargo.toml b/crates/shared/Cargo.toml new file mode 100644 index 0000000..3e93fdc --- /dev/null +++ b/crates/shared/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "shared" +version = "0.1.0" +edition = "2021" + +[dependencies] +glam.workspace = true +ash.workspace = true +bytemuck.workspace = true +memoffset = "0.9.1" +derive_builder = "0.20.2" diff --git a/crates/shared/src/lib.rs b/crates/shared/src/lib.rs new file mode 100644 index 0000000..b83634a --- /dev/null +++ b/crates/shared/src/lib.rs @@ -0,0 +1,61 @@ +use ash::vk; +use glam::{Mat4, Vec3}; + +use core::f32; +use std::mem::size_of; + +#[repr(C)] +#[derive(Clone, Debug, Copy)] +pub struct Vertex { + pub pos: [f32; 3], + pub color: [f32; 3], +} + +impl Vertex { + pub fn get_binding_decription() -> vk::VertexInputBindingDescription { + vk::VertexInputBindingDescription::default() + .binding(0) + .stride(size_of::() as u32) + .input_rate(vk::VertexInputRate::VERTEX) + } + + pub fn get_attribute_descriptions() -> [vk::VertexInputAttributeDescription; 2] { + [ + vk::VertexInputAttributeDescription::default() + .location(0) + .binding(0) + .format(vk::Format::R32G32B32_SFLOAT) + .offset(memoffset::offset_of!(Vertex, pos) as u32), + vk::VertexInputAttributeDescription::default() + .location(1) + .binding(0) + .format(vk::Format::R32G32B32_SFLOAT) + .offset(memoffset::offset_of!(Vertex, color) as u32), + ] + } +} + +#[repr(C)] +#[derive(Clone, Debug, Copy, PartialEq)] +pub struct UniformBufferObject { + pub model: Mat4, + pub view: Mat4, + pub proj: Mat4, +} + +#[derive(Clone, Debug, Copy, PartialEq)] +pub struct CameraInfo { + pub camera_pos: Vec3, + pub camera_target: Vec3, + pub camera_fov: f32, +} + +impl Default for CameraInfo { + fn default() -> Self { + Self { + camera_pos: Vec3::new(10.0, 10.0, 10.0), + camera_target: Vec3::new(0.0, 0.0, 0.0), + camera_fov: 45.0, + } + } +} diff --git a/shaders/frag.glsl.frag b/shaders/frag.glsl.frag index fe7b418..5943263 100644 --- a/shaders/frag.glsl.frag +++ b/shaders/frag.glsl.frag @@ -1,7 +1,16 @@ #version 450 +// Input from vertex shader +layout(location = 0) in vec3 fragColor; +// layout(location = 1) in vec2 fragTexCoord; // If using textures + +// Output color layout(location = 0) out vec4 outColor; +// layout(binding = 1) uniform sampler2D texSampler; // If using textures + void main() { - outColor = vec4(1.0, 0.5, 0.0, 1.0); // Orange + // Use interpolated color + outColor = vec4(fragColor, 1.0); + // outColor = texture(texSampler, fragTexCoord); // If using textures } diff --git a/shaders/vert.glsl.vert b/shaders/vert.glsl.vert index f114dbb..73bc36c 100644 --- a/shaders/vert.glsl.vert +++ b/shaders/vert.glsl.vert @@ -1,10 +1,26 @@ #version 450 -vec2 positions[3] = vec2[]( - vec2(0.0, -0.5), - vec2(0.5, 0.5), - vec2(-0.5, 0.5) - ); + +// Matches Vertex struct attribute descriptions +layout(location = 0) in vec3 inPosition; +layout(location = 1) in vec3 inColor; +// layout(location = 2) in vec2 inTexCoord; // If you add texture coords + +// Matches UniformBufferObject struct and descriptor set layout binding +layout(binding = 0) uniform UniformBufferObject { + mat4 model; + mat4 view; + mat4 proj; +} ubo; + +// Output to fragment shader +layout(location = 0) out vec3 fragColor; +// layout(location = 1) out vec2 fragTexCoord; // If you add texture coords void main() { - gl_Position = vec4(positions[gl_VertexIndex], 0.0, 1.0); + // Transform position: Model -> World -> View -> Clip space + gl_Position = ubo.proj * ubo.view * ubo.model * vec4(inPosition, 1.0); + + // Pass color (and other attributes) through + fragColor = inColor; + // fragTexCoord = inTexCoord; }