From f71f0d8e10d677bf852a846e40368e1e997dd9f8 Mon Sep 17 00:00:00 2001 From: zack Date: Sun, 30 Mar 2025 16:27:35 -0400 Subject: [PATCH 1/7] feat: switch away from `parking_lot::Mutex` Switching away from `parking_lot::Mutex` as `std::sync::Mutex` performs better in all scenarios on both of our targets (linux/windows). --- Cargo.lock | 3 --- Cargo.toml | 1 - crates/gfx_hal/Cargo.toml | 1 - crates/gfx_hal/src/device.rs | 21 ++++++++++------ crates/gfx_hal/src/error.rs | 10 ++++++++ crates/gfx_hal/src/queue.rs | 3 +-- crates/renderer/Cargo.toml | 1 - crates/renderer/src/lib.rs | 24 ++++++++++++++++-- crates/resource_manager/Cargo.toml | 1 - crates/resource_manager/src/lib.rs | 39 +++++++++++++++--------------- 10 files changed, 66 insertions(+), 38 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 7297667..40e668d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -613,7 +613,6 @@ version = "0.1.0" dependencies = [ "ash", "ash-window", - "parking_lot", "thiserror 2.0.12", "tracing", "winit", @@ -1308,7 +1307,6 @@ dependencies = [ "gfx_hal", "glam", "gpu-allocator", - "parking_lot", "resource_manager", "shaderc", "thiserror 2.0.12", @@ -1324,7 +1322,6 @@ dependencies = [ "ash", "gfx_hal", "gpu-allocator", - "parking_lot", "thiserror 2.0.12", "tracing", ] diff --git a/Cargo.toml b/Cargo.toml index ed32bcf..454df20 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -27,7 +27,6 @@ egui = "0.31" bytemuck = { version = "1.21.0", features = ["derive"] } tracing = "0.1" tracing-subscriber = { version = "0.3", features = ["json"] } -parking_lot = "0.12.3" thiserror = "2.0.12" diff --git a/crates/gfx_hal/Cargo.toml b/crates/gfx_hal/Cargo.toml index caa74b1..acd0197 100644 --- a/crates/gfx_hal/Cargo.toml +++ b/crates/gfx_hal/Cargo.toml @@ -9,4 +9,3 @@ ash-window.workspace = true thiserror.workspace = true tracing.workspace = true winit.workspace = true -parking_lot.workspace = true diff --git a/crates/gfx_hal/src/device.rs b/crates/gfx_hal/src/device.rs index a26e36b..0d0c619 100644 --- a/crates/gfx_hal/src/device.rs +++ b/crates/gfx_hal/src/device.rs @@ -1,9 +1,10 @@ use ash::vk; -use parking_lot::Mutex; use std::collections::HashSet; use std::ffi::CStr; -use std::sync::Weak; -use std::{collections::HashMap, sync::Arc}; +use std::{ + collections::HashMap, + sync::{Arc, Mutex}, +}; use crate::error::{GfxHalError, Result}; use crate::instance::Instance; @@ -146,7 +147,7 @@ impl Device { // Lock the mutex and insert the created queues into the map within the Arc { // Scope for the mutex guard - let mut queues_map_guard = device_arc.queues.lock(); + let mut queues_map_guard = device_arc.queues.lock()?; *queues_map_guard = queues_to_insert; // Replace the empty map with the populated one tracing::debug!( "Device Arc populated with {} queues (Stage 2).", @@ -185,15 +186,21 @@ impl Device { /// Gets a wrapped queue handle. /// Currently only supports queue index 0 for each family. - pub fn get_queue(&self, family_index: u32, queue_index: u32) -> Option> { + pub fn get_queue(&self, family_index: u32, queue_index: u32) -> Result> { if queue_index != 0 { tracing::warn!("get_queue currently only supports queue_index 0"); - return None; + return Err(GfxHalError::MissingQueueFamily( + "get_queue only supports queue_index 0".to_string(), + )); } + self.queues - .lock() + .lock()? .get(&(family_index, queue_index)) .cloned() + .ok_or(GfxHalError::MissingQueueFamily( + "could not get queue family".to_string(), + )) } /// Gets the primary graphics queue (family index from `graphics_queue_family_index`, queue index 0). diff --git a/crates/gfx_hal/src/error.rs b/crates/gfx_hal/src/error.rs index 0e3e8d6..1dec5bc 100644 --- a/crates/gfx_hal/src/error.rs +++ b/crates/gfx_hal/src/error.rs @@ -56,9 +56,19 @@ pub enum GfxHalError { #[error("Error loading the ash entry.")] AshEntryError(#[from] ash::LoadingError), + /// Poisoned Mutex + #[error("Error from poisoned mutex: {0}")] + MutexPoisoned(String), + /// Placeholder for other specific errors. #[error("An unexpected error occurred: {0}")] Other(String), } pub type Result = std::result::Result; + +impl From> for GfxHalError { + fn from(e: std::sync::PoisonError) -> Self { + Self::MutexPoisoned(e.to_string()) + } +} diff --git a/crates/gfx_hal/src/queue.rs b/crates/gfx_hal/src/queue.rs index 8aa4513..7cbe40d 100644 --- a/crates/gfx_hal/src/queue.rs +++ b/crates/gfx_hal/src/queue.rs @@ -1,7 +1,6 @@ -use std::sync::Arc; +use std::sync::{Arc, Mutex}; use ash::{vk, Device as AshDevice}; -use parking_lot::Mutex; use crate::device::Device; use crate::error::Result; diff --git a/crates/renderer/Cargo.toml b/crates/renderer/Cargo.toml index 6f4bbee..fb324c5 100644 --- a/crates/renderer/Cargo.toml +++ b/crates/renderer/Cargo.toml @@ -13,7 +13,6 @@ gpu-allocator.workspace = true egui.workspace = true egui-ash-renderer.workspace = true winit.workspace = true -parking_lot.workspace = true gfx_hal = { path = "../gfx_hal" } resource_manager = { path = "../resource_manager" } diff --git a/crates/renderer/src/lib.rs b/crates/renderer/src/lib.rs index 279df1b..8e32ab0 100644 --- a/crates/renderer/src/lib.rs +++ b/crates/renderer/src/lib.rs @@ -1,12 +1,15 @@ -use std::{ffi::CStr, sync::Arc}; +use std::{ + ffi::CStr, + sync::{Arc, Mutex}, +}; use ash::vk; +use egui_ash_renderer::{DynamicRendering, Options, Renderer as EguiRenderer}; 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 parking_lot::Mutex; use resource_manager::{ImageHandle, ResourceManager, ResourceManagerError}; use thiserror::Error; use tracing::{debug, error, info, warn}; @@ -79,6 +82,8 @@ pub struct Renderer { swapchain_format: vk::SurfaceFormatKHR, swapchain_extent: vk::Extent2D, + egui_renderer: EguiRenderer, + depth_image_handle: ImageHandle, depth_image_view: vk::ImageView, // Store the view directly depth_format: vk::Format, @@ -128,10 +133,21 @@ impl Renderer { info!("Renderer initialized successfully."); + let egui_renderer = EguiRenderer::with_gpu_allocator( + resource_manager.allocator(), + device.raw().clone(), + DynamicRendering { + color_attachment_format: swapchain.format().format, + depth_attachment_format: Some(depth_format), + }, + Options::default(), + )?; + Ok(Self { device, graphics_queue, resource_manager, + egui_renderer, allocator, // Store the allocator Arc surface, swapchain: Some(swapchain), @@ -465,6 +481,10 @@ impl Renderer { self.depth_image_handle = new_depth_handle; self.depth_image_view = new_depth_view; + // 4. Update Egui Renderer (if necessary, depends on its implementation) + // It might need the new extent or recreate internal resources. + // Assuming it handles extent changes via update_screen_descriptor called earlier. + info!( "Swapchain recreated successfully ({}x{}).", new_extent.width, new_extent.height diff --git a/crates/resource_manager/Cargo.toml b/crates/resource_manager/Cargo.toml index 73c615a..f71fc5d 100644 --- a/crates/resource_manager/Cargo.toml +++ b/crates/resource_manager/Cargo.toml @@ -7,7 +7,6 @@ edition = "2021" ash.workspace = true gpu-allocator.workspace = true thiserror.workspace = true -parking_lot.workspace = true tracing.workspace = true gfx_hal = { path = "../gfx_hal" } diff --git a/crates/resource_manager/src/lib.rs b/crates/resource_manager/src/lib.rs index bec4110..add8a42 100644 --- a/crates/resource_manager/src/lib.rs +++ b/crates/resource_manager/src/lib.rs @@ -5,7 +5,7 @@ use std::{ hash::Hash, sync::{ atomic::{AtomicU64, Ordering}, - Arc, + Arc, Mutex, }, }; @@ -18,7 +18,6 @@ use gpu_allocator::{ vulkan::{Allocation, AllocationCreateDesc, Allocator, AllocatorCreateDesc}, MemoryLocation, }; -use parking_lot::Mutex; #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub struct BufferHandle(u64); @@ -63,7 +62,7 @@ impl Drop for InternalBufferInfo { fn drop(&mut self) { trace!("Dropping InternalBufferInfo for handle: {:?}", self.handle); if let Some(allocation) = self.allocation.take() { - let mut allc = self.allocator.lock(); + let mut allc = self.allocator.lock().expect("to acquire mutex lock"); if let Err(e) = allc.free(allocation) { error!( "Failed to free allocation for buffer handle {:?}, {}", @@ -102,7 +101,7 @@ impl Drop for InternalImageInfo { } // Then free memory if let Some(allocation) = self.allocation.take() { - let mut allocator = self.allocator.lock(); + let mut allocator = self.allocator.lock().expect("to acquire mutex lock"); if let Err(e) = allocator.free(allocation) { error!( "Failed to free allocation for image handle {:?}: {}", @@ -171,7 +170,7 @@ impl ResourceManager { /// Gets or initializes the TransferSetup resources. fn get_transfer_setup(&self) -> Result { - let mut setup_guard = self.transfer_setup.lock(); + let mut setup_guard = self.transfer_setup.lock()?; if let Some(setup) = setup_guard.as_ref() { // Simple check: Reset fence before reusing @@ -192,10 +191,7 @@ impl ResourceManager { .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) - .ok_or(ResourceManagerError::NoTransferQueue)?; + let queue = self.device.get_queue(queue_family_index, 0)?; // Create command pool for transfer commands let pool_info = vk::CommandPoolCreateInfo::default() @@ -306,7 +302,7 @@ impl ResourceManager { let requirements = unsafe { self.device.raw().get_buffer_memory_requirements(buffer) }; - let allocation = self.allocator.lock().allocate(&AllocationCreateDesc { + let allocation = self.allocator.lock()?.allocate(&AllocationCreateDesc { name: &format!("buffer_usage_{:?}_loc_{:?}", usage, location), requirements, location, @@ -342,7 +338,7 @@ impl ResourceManager { handle, }; - self.buffers.lock().insert(id, internal_info); + self.buffers.lock()?.insert(id, internal_info); debug!("Buffer created successfully: handle={:?}", handle); Ok(handle) } @@ -441,7 +437,7 @@ impl ResourceManager { let requirements = unsafe { self.device.raw().get_image_memory_requirements(image) }; - let allocation = self.allocator.lock().allocate(&AllocationCreateDesc { + let allocation = self.allocator.lock()?.allocate(&AllocationCreateDesc { name: &format!( "image_fmt_{:?}_usage_{:?}", create_info.format, create_info.usage @@ -491,7 +487,7 @@ impl ResourceManager { handle, }; - self.images.lock().insert(id, internal_info); + self.images.lock()?.insert(id, internal_info); debug!("Image created successfully: handle={:?}", handle); Ok(handle) } @@ -501,7 +497,7 @@ impl ResourceManager { /// Destroys a buffer and frees its memory. pub fn destroy_buffer(&self, handle: BufferHandle) -> Result<()> { debug!("Requesting destroy for buffer handle {:?}", handle); - let mut buffers_map = self.buffers.lock(); + let mut buffers_map = self.buffers.lock()?; // Remove the entry. The Drop impl of InternalBufferInfo handles the cleanup. if buffers_map.remove(&handle.0).is_some() { debug!("Buffer handle {:?} removed for destruction.", handle); @@ -518,7 +514,7 @@ impl ResourceManager { /// Destroys an image, its view, and frees its memory. pub fn destroy_image(&self, handle: ImageHandle) -> Result<()> { debug!("Requesting destroy for image handle {:?}", handle); - let mut images_map = self.images.lock(); + let mut images_map = self.images.lock()?; // Remove the entry. The Drop impl of InternalImageInfo handles the cleanup. if images_map.remove(&handle.0).is_some() { debug!("Image handle {:?} removed for destruction.", handle); @@ -534,7 +530,7 @@ impl ResourceManager { /// Gets non-owning information about a buffer. pub fn get_buffer_info(&self, handle: BufferHandle) -> Result { - let buffers_map = self.buffers.lock(); + let buffers_map = self.buffers.lock()?; buffers_map .get(&handle.0) .map(|internal| BufferInfo { @@ -549,7 +545,7 @@ impl ResourceManager { /// Gets non-owning information about an image. pub fn get_image_info(&self, handle: ImageHandle) -> Result { - let images_map = self.images.lock(); + let images_map = self.images.lock()?; images_map .get(&handle.0) .map(|internal| ImageInfo { @@ -586,15 +582,18 @@ impl Drop for ResourceManager { // Clear resource maps. This triggers the Drop impl for each Internal*Info, // which frees allocations and destroys Vulkan objects. - let mut buffers_map = self.buffers.lock(); + let mut buffers_map = self.buffers.lock().expect("mutex to not be poisoned"); debug!("Clearing {} buffer entries...", buffers_map.len()); buffers_map.clear(); - let mut images_map = self.images.lock(); + let mut images_map = self.images.lock().expect("mutex to not be poisoned"); debug!("Clearing {} image entries...", images_map.len()); images_map.clear(); // Destroy transfer setup resources - let mut setup_guard = self.transfer_setup.lock(); + let mut setup_guard = 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..."); From b1c164dc6a61236c3f8355e00b2df9e6b0f6b47f Mon Sep 17 00:00:00 2001 From: zack Date: Mon, 31 Mar 2025 14:54:56 -0400 Subject: [PATCH 2/7] wip: add init egui impl --- Cargo.lock | 724 +++++++++++++++++++++++++++++++++++-- crates/engine/Cargo.toml | 2 + crates/engine/src/main.rs | 74 +++- crates/renderer/src/lib.rs | 45 ++- 4 files changed, 790 insertions(+), 55 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 40e668d..cab6339 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -18,6 +18,27 @@ version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c71b1793ee61086797f5c80b6efa2b8ffa6d5dd703f118545808a7f2e27f7046" +[[package]] +name = "addr2line" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" + +[[package]] +name = "adler2" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" + [[package]] name = "ahash" version = "0.8.11" @@ -114,6 +135,24 @@ version = "1.0.97" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dcfed56ad506cb2c684a14971b8861fdc3baaaae314b9e5f9bb532cbe3ba7a4f" +[[package]] +name = "arboard" +version = "3.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df099ccb16cd014ff054ac1bf392c67feeef57164b05c42f037cd40f5d4357f4" +dependencies = [ + "clipboard-win", + "core-graphics", + "image", + "log", + "objc2 0.5.2", + "objc2-app-kit", + "objc2-foundation 0.2.2", + "parking_lot", + "windows-sys 0.48.0", + "x11rb", +] + [[package]] name = "arrayref" version = "0.3.9" @@ -164,6 +203,21 @@ version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" +[[package]] +name = "backtrace" +version = "0.3.71" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26b05800d2e817c8b3b4b54abd461726265fa9789ae34330622f2db9ee696f9d" +dependencies = [ + "addr2line", + "cc", + "cfg-if", + "libc", + "miniz_oxide 0.7.4", + "object", + "rustc-demangle", +] + [[package]] name = "bitflags" version = "1.3.2" @@ -188,7 +242,7 @@ version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2c132eebf10f5cad5289222520a4a058514204aed6d791f1cf4fe8088b82d15f" dependencies = [ - "objc2", + "objc2 0.5.2", ] [[package]] @@ -217,6 +271,12 @@ dependencies = [ "syn", ] +[[package]] +name = "byteorder-lite" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495" + [[package]] name = "bytes" version = "1.10.1" @@ -318,6 +378,15 @@ version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6" +[[package]] +name = "clipboard-win" +version = "5.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15efe7a882b08f34e38556b14f2fb3daa98769d06c7f0c1b076dfd0d983bc892" +dependencies = [ + "error-code", +] + [[package]] name = "cmake" version = "0.1.54" @@ -336,7 +405,7 @@ dependencies = [ "bitflags 1.3.2", "block", "cocoa-foundation", - "core-foundation", + "core-foundation 0.9.4", "core-graphics", "foreign-types", "libc", @@ -351,12 +420,39 @@ checksum = "8c6234cbb2e4c785b456c0644748b1ac416dd045799740356f8363dfe00c93f7" dependencies = [ "bitflags 1.3.2", "block", - "core-foundation", + "core-foundation 0.9.4", "core-graphics-types", "libc", "objc", ] +[[package]] +name = "color-eyre" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55146f5e46f237f7423d74111267d4597b59b0dad0ffaf7303bce9945d843ad5" +dependencies = [ + "backtrace", + "color-spantrace", + "eyre", + "indenter", + "once_cell", + "owo-colors", + "tracing-error", +] + +[[package]] +name = "color-spantrace" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd6be1b2a7e382e2b98b43b2adcca6bb0e465af0bdd38123873ae61eb17a72c2" +dependencies = [ + "once_cell", + "owo-colors", + "tracing-core", + "tracing-error", +] + [[package]] name = "colorchoice" version = "1.0.3" @@ -392,6 +488,16 @@ dependencies = [ "libc", ] +[[package]] +name = "core-foundation" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b55271e5c8c478ad3f38ad24ef34923091e0548492a266d19b3c0b4d82574c63" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "core-foundation-sys" version = "0.8.7" @@ -405,7 +511,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c07782be35f9e1140080c6b96f0d44b739e2278479f64e02fdab4e32dfd8b081" dependencies = [ "bitflags 1.3.2", - "core-foundation", + "core-foundation 0.9.4", "core-graphics-types", "foreign-types", "libc", @@ -418,10 +524,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "45390e6114f68f718cc7a830514a96f903cccd70d02a8f6d9f643ac4ba45afaf" dependencies = [ "bitflags 1.3.2", - "core-foundation", + "core-foundation 0.9.4", "libc", ] +[[package]] +name = "crc32fast" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" +dependencies = [ + "cfg-if", +] + [[package]] name = "crossbeam-utils" version = "0.8.21" @@ -440,6 +555,17 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bd0c93bb4b0c6d9b77f4435b0ae98c24d17f1c45b2ff844c6151a07256ca923b" +[[package]] +name = "displaydoc" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "dlib" version = "0.5.2" @@ -467,6 +593,7 @@ version = "0.31.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bc4feb366740ded31a004a0e4452fbf84e80ef432ecf8314c485210229672fd1" dependencies = [ + "bytemuck", "emath", ] @@ -480,6 +607,7 @@ dependencies = [ "bitflags 2.9.0", "emath", "epaint", + "log", "nohash-hasher", "profiling", ] @@ -497,11 +625,33 @@ dependencies = [ "thiserror 2.0.12", ] +[[package]] +name = "egui-winit" +version = "0.31.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d9dfbb78fe4eb9c3a39ad528b90ee5915c252e77bbab9d4ebc576541ab67e13" +dependencies = [ + "ahash", + "arboard", + "bytemuck", + "egui", + "log", + "profiling", + "raw-window-handle", + "smithay-clipboard", + "web-time", + "webbrowser", + "winit", +] + [[package]] name = "emath" version = "0.31.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9e4cadcff7a5353ba72b7fea76bf2122b5ebdbc68e8155aa56dfdea90083fe1b" +dependencies = [ + "bytemuck", +] [[package]] name = "engine" @@ -510,7 +660,9 @@ dependencies = [ "ash", "ash-window", "clap", + "color-eyre", "egui", + "egui-winit", "gfx_hal", "raw-window-handle", "renderer", @@ -529,9 +681,11 @@ checksum = "41fcc0f5a7c613afd2dee5e4b30c3e6acafb8ad6f0edb06068811f708a67c562" dependencies = [ "ab_glyph", "ahash", + "bytemuck", "ecolor", "emath", "epaint_default_fonts", + "log", "nohash-hasher", "parking_lot", "profiling", @@ -559,6 +713,41 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "error-code" +version = "3.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5d9305ccc6942a704f4335694ecd3de2ea531b114ac2d51f5f843750787a92f" + +[[package]] +name = "eyre" +version = "0.6.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cd915d99f24784cdc19fd37ef22b97e3ff0ae756c7e492e9fbfe897d61e2aec" +dependencies = [ + "indenter", + "once_cell", +] + +[[package]] +name = "fdeflate" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e6853b52649d4ac5c0bd02320cddc5ba956bdb407c4b75a2c6b75bf51500f8c" +dependencies = [ + "simd-adler32", +] + +[[package]] +name = "flate2" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11faaf5a5236997af9848be0bef4db95824b1d534ebc64d0f0c6cf3e67bd38dc" +dependencies = [ + "crc32fast", + "miniz_oxide 0.8.5", +] + [[package]] name = "foreign-types" version = "0.5.0" @@ -586,6 +775,15 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "aa9a19cbb55df58761df49b23516a86d432839add4af60fc256da840f66ed35b" +[[package]] +name = "form_urlencoded" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" +dependencies = [ + "percent-encoding", +] + [[package]] name = "gethostname" version = "0.4.3" @@ -618,6 +816,12 @@ dependencies = [ "winit", ] +[[package]] +name = "gimli" +version = "0.28.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" + [[package]] name = "glam" version = "0.22.0" @@ -659,6 +863,173 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fbf6a919d6cf397374f7dfeeea91d974c7c0a7221d0d0f4f20d859d329e53fcc" +[[package]] +name = "home" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589533453244b0995c858700322199b2becb13b627df2851f64a2775d024abcf" +dependencies = [ + "windows-sys 0.59.0", +] + +[[package]] +name = "icu_collections" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db2fa452206ebee18c4b5c2274dbf1de17008e874b4dc4f0aea9d01ca79e4526" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locid" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13acbb8371917fc971be86fc8057c41a64b521c184808a698c02acc242dbf637" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_locid_transform" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01d11ac35de8e40fdeda00d9e1e9d92525f3f9d887cdd7aa81d727596788b54e" +dependencies = [ + "displaydoc", + "icu_locid", + "icu_locid_transform_data", + "icu_provider", + "tinystr", + "zerovec", +] + +[[package]] +name = "icu_locid_transform_data" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7515e6d781098bf9f7205ab3fc7e9709d34554ae0b21ddbcb5febfa4bc7df11d" + +[[package]] +name = "icu_normalizer" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19ce3e0da2ec68599d193c93d088142efd7f9c5d6fc9b803774855747dc6a84f" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "utf16_iter", + "utf8_iter", + "write16", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c5e8338228bdc8ab83303f16b797e177953730f601a96c25d10cb3ab0daa0cb7" + +[[package]] +name = "icu_properties" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93d6020766cfc6302c15dbbc9c8778c37e62c14427cb7f6e601d849e092aeef5" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_locid_transform", + "icu_properties_data", + "icu_provider", + "tinystr", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85fb8799753b75aee8d2a21d7c14d9f38921b54b3dbda10f5a3c7a7b82dba5e2" + +[[package]] +name = "icu_provider" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ed421c8a8ef78d3e2dbc98a973be2f3770cb42b606e3ab18d6237c4dfde68d9" +dependencies = [ + "displaydoc", + "icu_locid", + "icu_provider_macros", + "stable_deref_trait", + "tinystr", + "writeable", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_provider_macros" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "idna" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e" +dependencies = [ + "idna_adapter", + "smallvec", + "utf8_iter", +] + +[[package]] +name = "idna_adapter" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "daca1df1c957320b2cf139ac61e7bd64fed304c5040df000a745aa1de3b4ef71" +dependencies = [ + "icu_normalizer", + "icu_properties", +] + +[[package]] +name = "image" +version = "0.25.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db35664ce6b9810857a38a906215e75a9c879f0696556a39f59c62829710251a" +dependencies = [ + "bytemuck", + "byteorder-lite", + "num-traits", + "png", + "tiff", +] + +[[package]] +name = "indenter" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683" + [[package]] name = "indexmap" version = "2.8.0" @@ -712,6 +1083,12 @@ dependencies = [ "libc", ] +[[package]] +name = "jpeg-decoder" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f5d4a7da358eff58addd2877a45865158f0d78c911d43a5784ceb7bbf52833b0" + [[package]] name = "js-sys" version = "0.3.77" @@ -776,6 +1153,12 @@ version = "0.4.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" +[[package]] +name = "litemap" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23fb14cb19457329c82206317a5663005a4d404783dc74f4252769b0d5f42856" + [[package]] name = "lock_api" version = "0.4.12" @@ -816,6 +1199,25 @@ dependencies = [ "libc", ] +[[package]] +name = "miniz_oxide" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8a240ddb74feaf34a79a7add65a741f3167852fba007066dcac1ca548d89c08" +dependencies = [ + "adler", +] + +[[package]] +name = "miniz_oxide" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e3e04debbb59698c15bacbb6d93584a8c0ca9cc3213cb423d31f760d8843ce5" +dependencies = [ + "adler2", + "simd-adler32", +] + [[package]] name = "ndk" version = "0.9.0" @@ -918,6 +1320,15 @@ dependencies = [ "objc2-encode", ] +[[package]] +name = "objc2" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3531f65190d9cff863b77a99857e74c314dd16bf56c538c4b57c7cbc3f3a6e59" +dependencies = [ + "objc2-encode", +] + [[package]] name = "objc2-app-kit" version = "0.2.2" @@ -927,10 +1338,10 @@ dependencies = [ "bitflags 2.9.0", "block2", "libc", - "objc2", + "objc2 0.5.2", "objc2-core-data", "objc2-core-image", - "objc2-foundation", + "objc2-foundation 0.2.2", "objc2-quartz-core", ] @@ -942,9 +1353,9 @@ checksum = "74dd3b56391c7a0596a295029734d3c1c5e7e510a4cb30245f8221ccea96b009" dependencies = [ "bitflags 2.9.0", "block2", - "objc2", + "objc2 0.5.2", "objc2-core-location", - "objc2-foundation", + "objc2-foundation 0.2.2", ] [[package]] @@ -954,8 +1365,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a5ff520e9c33812fd374d8deecef01d4a840e7b41862d849513de77e44aa4889" dependencies = [ "block2", - "objc2", - "objc2-foundation", + "objc2 0.5.2", + "objc2-foundation 0.2.2", ] [[package]] @@ -966,8 +1377,8 @@ checksum = "617fbf49e071c178c0b24c080767db52958f716d9eabdf0890523aeae54773ef" dependencies = [ "bitflags 2.9.0", "block2", - "objc2", - "objc2-foundation", + "objc2 0.5.2", + "objc2-foundation 0.2.2", ] [[package]] @@ -977,8 +1388,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "55260963a527c99f1819c4f8e3b47fe04f9650694ef348ffd2227e8196d34c80" dependencies = [ "block2", - "objc2", - "objc2-foundation", + "objc2 0.5.2", + "objc2-foundation 0.2.2", "objc2-metal", ] @@ -989,9 +1400,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "000cfee34e683244f284252ee206a27953279d370e309649dc3ee317b37e5781" dependencies = [ "block2", - "objc2", + "objc2 0.5.2", "objc2-contacts", - "objc2-foundation", + "objc2-foundation 0.2.2", ] [[package]] @@ -1010,7 +1421,17 @@ dependencies = [ "block2", "dispatch", "libc", - "objc2", + "objc2 0.5.2", +] + +[[package]] +name = "objc2-foundation" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a21c6c9014b82c39515db5b396f91645182611c97d24637cf56ac01e5f8d998" +dependencies = [ + "bitflags 2.9.0", + "objc2 0.6.0", ] [[package]] @@ -1020,9 +1441,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a1a1ae721c5e35be65f01a03b6d2ac13a54cb4fa70d8a5da293d7b0020261398" dependencies = [ "block2", - "objc2", + "objc2 0.5.2", "objc2-app-kit", - "objc2-foundation", + "objc2-foundation 0.2.2", ] [[package]] @@ -1033,8 +1454,8 @@ checksum = "dd0cba1276f6023976a406a14ffa85e1fdd19df6b0f737b063b95f6c8c7aadd6" dependencies = [ "bitflags 2.9.0", "block2", - "objc2", - "objc2-foundation", + "objc2 0.5.2", + "objc2-foundation 0.2.2", ] [[package]] @@ -1045,8 +1466,8 @@ checksum = "e42bee7bff906b14b167da2bac5efe6b6a07e6f7c0a21a7308d40c960242dc7a" dependencies = [ "bitflags 2.9.0", "block2", - "objc2", - "objc2-foundation", + "objc2 0.5.2", + "objc2-foundation 0.2.2", "objc2-metal", ] @@ -1056,8 +1477,8 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0a684efe3dec1b305badae1a28f6555f6ddd3bb2c2267896782858d5a78404dc" dependencies = [ - "objc2", - "objc2-foundation", + "objc2 0.5.2", + "objc2-foundation 0.2.2", ] [[package]] @@ -1068,12 +1489,12 @@ checksum = "b8bb46798b20cd6b91cbd113524c490f1686f4c4e8f49502431415f3512e2b6f" dependencies = [ "bitflags 2.9.0", "block2", - "objc2", + "objc2 0.5.2", "objc2-cloud-kit", "objc2-core-data", "objc2-core-image", "objc2-core-location", - "objc2-foundation", + "objc2-foundation 0.2.2", "objc2-link-presentation", "objc2-quartz-core", "objc2-symbols", @@ -1088,8 +1509,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "44fa5f9748dbfe1ca6c0b79ad20725a11eca7c2218bceb4b005cb1be26273bfe" dependencies = [ "block2", - "objc2", - "objc2-foundation", + "objc2 0.5.2", + "objc2-foundation 0.2.2", ] [[package]] @@ -1100,9 +1521,18 @@ checksum = "76cfcbf642358e8689af64cee815d139339f3ed8ad05103ed5eaf73db8d84cb3" dependencies = [ "bitflags 2.9.0", "block2", - "objc2", + "objc2 0.5.2", "objc2-core-location", - "objc2-foundation", + "objc2-foundation 0.2.2", +] + +[[package]] +name = "object" +version = "0.32.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6a622008b6e321afc04970976f62ee297fdbaa6f95318ca343e3eebb9648441" +dependencies = [ + "memchr", ] [[package]] @@ -1135,6 +1565,12 @@ dependencies = [ "ttf-parser", ] +[[package]] +name = "owo-colors" +version = "3.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1b04fb49957986fdce4d6ee7a65027d55d4b6d2265e5848bbb507b58ccfdb6f" + [[package]] name = "parking_lot" version = "0.12.3" @@ -1196,6 +1632,19 @@ version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" +[[package]] +name = "png" +version = "0.17.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82151a2fc869e011c153adc57cf2789ccb8d9906ce52c0b39a6b5697749d7526" +dependencies = [ + "bitflags 1.3.2", + "crc32fast", + "fdeflate", + "flate2", + "miniz_oxide 0.8.5", +] + [[package]] name = "polling" version = "3.7.4" @@ -1332,6 +1781,12 @@ version = "0.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6c20b6793b5c2fa6553b250154b78d6d0db37e72700ae35fad9387a46f487c97" +[[package]] +name = "rustc-demangle" +version = "0.1.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" + [[package]] name = "rustix" version = "0.38.44" @@ -1461,6 +1916,12 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" +[[package]] +name = "simd-adler32" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" + [[package]] name = "slab" version = "0.4.9" @@ -1501,6 +1962,17 @@ dependencies = [ "xkeysym", ] +[[package]] +name = "smithay-clipboard" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc8216eec463674a0e90f29e0ae41a4db573ec5b56b1c6c1c71615d249b6d846" +dependencies = [ + "libc", + "smithay-client-toolkit", + "wayland-backend", +] + [[package]] name = "smol_str" version = "0.2.2" @@ -1510,6 +1982,12 @@ dependencies = [ "serde", ] +[[package]] +name = "stable_deref_trait" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" + [[package]] name = "strict-num" version = "0.1.1" @@ -1533,6 +2011,17 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "synstructure" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "thiserror" version = "1.0.69" @@ -1583,6 +2072,17 @@ dependencies = [ "once_cell", ] +[[package]] +name = "tiff" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba1310fcea54c6a9a4fd1aad794ecc02c31682f6bfbecdf460bf19533eed1e3e" +dependencies = [ + "flate2", + "jpeg-decoder", + "weezl", +] + [[package]] name = "tiny-skia" version = "0.11.4" @@ -1608,6 +2108,16 @@ dependencies = [ "strict-num", ] +[[package]] +name = "tinystr" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9117f5d4db391c1cf6927e7bea3db74b9a1c1add8f7eda9ffd5364f40f57b82f" +dependencies = [ + "displaydoc", + "zerovec", +] + [[package]] name = "toml_datetime" version = "0.6.8" @@ -1657,6 +2167,16 @@ dependencies = [ "valuable", ] +[[package]] +name = "tracing-error" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b1581020d7a273442f5b45074a6a57d5757ad0a47dac0e9f0bd57b81936f3db" +dependencies = [ + "tracing", + "tracing-subscriber", +] + [[package]] name = "tracing-log" version = "0.2.0" @@ -1713,6 +2233,29 @@ version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" +[[package]] +name = "url" +version = "2.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", +] + +[[package]] +name = "utf16_iter" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8232dd3cdaed5356e0f716d285e4b40b932ac434100fe9b7e0e8e935b9e6246" + +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + [[package]] name = "utf8parse" version = "0.2.2" @@ -1947,6 +2490,29 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "webbrowser" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5df295f8451142f1856b1bd86a606dfe9587d439bc036e319c827700dbd555e" +dependencies = [ + "core-foundation 0.10.0", + "home", + "jni", + "log", + "ndk-context", + "objc2 0.6.0", + "objc2-foundation 0.3.0", + "url", + "web-sys", +] + +[[package]] +name = "weezl" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53a85b86a771b1c87058196170769dd264f66c0782acf1ae6cc51bfd64b39082" + [[package]] name = "winapi" version = "0.3.9" @@ -2051,6 +2617,15 @@ dependencies = [ "windows-targets 0.42.2", ] +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets 0.48.5", +] + [[package]] name = "windows-sys" version = "0.52.0" @@ -2262,7 +2837,7 @@ dependencies = [ "calloop", "cfg_aliases", "concurrent-queue", - "core-foundation", + "core-foundation 0.9.4", "core-graphics", "cursor-icon", "dpi", @@ -2270,9 +2845,9 @@ dependencies = [ "libc", "memmap2", "ndk", - "objc2", + "objc2 0.5.2", "objc2-app-kit", - "objc2-foundation", + "objc2-foundation 0.2.2", "objc2-ui-kit", "orbclient", "percent-encoding", @@ -2308,6 +2883,18 @@ dependencies = [ "memchr", ] +[[package]] +name = "write16" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1890f4022759daae28ed4fe62859b1236caebfc61ede2f63ed4e695f3f6d936" + +[[package]] +name = "writeable" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51" + [[package]] name = "x11-dl" version = "2.21.0" @@ -2365,6 +2952,30 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9cc00251562a284751c9973bace760d86c0276c471b4be569fe6b068ee97a56" +[[package]] +name = "yoke" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "120e6aef9aa629e3d4f52dc8cc43a015c7724194c97dfaf45180d2daf2b77f40" +dependencies = [ + "serde", + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + [[package]] name = "zerocopy" version = "0.7.35" @@ -2384,3 +2995,46 @@ dependencies = [ "quote", "syn", ] + +[[package]] +name = "zerofrom" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + +[[package]] +name = "zerovec" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa2b893d79df23bfb12d5461018d408ea19dfafe76c2c7ef6d4eba614f8ff079" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] diff --git a/crates/engine/Cargo.toml b/crates/engine/Cargo.toml index 84a72bc..8f13de8 100644 --- a/crates/engine/Cargo.toml +++ b/crates/engine/Cargo.toml @@ -7,6 +7,7 @@ edition = "2021" egui.workspace = true ash.workspace = true ash-window.workspace = true +color-eyre.workspace = true tracing.workspace = true tracing-subscriber.workspace = true winit.workspace = true @@ -17,3 +18,4 @@ gfx_hal = { path = "../gfx_hal" } renderer = { path = "../renderer" } resource_manager = { path = "../resource_manager" } 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 ff90adb..e5af844 100644 --- a/crates/engine/src/main.rs +++ b/crates/engine/src/main.rs @@ -8,6 +8,8 @@ use std::{ use ash::vk; use clap::Parser; +use egui::{Context, ViewportId}; +use egui_winit::State; use gfx_hal::{ device::Device, error::GfxHalError, instance::Instance, instance::InstanceConfig, physical_device::PhysicalDevice, queue::Queue, surface::Surface, @@ -57,6 +59,10 @@ struct Application { // Renderer renderer: Renderer, + egui_ctx: Context, + egui_winit: State, + egui_app: EditorUI, + // Windowing window: Arc, // Use Arc for potential multi-threading later @@ -65,6 +71,19 @@ struct Application { last_frame_time: Instant, } +#[derive(Default)] +struct EditorUI {} + +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())); + } +} + #[derive(Default)] struct ApplicationWrapper { app: Option, @@ -223,14 +242,6 @@ 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(); - info!( - "App: Queue is associated with Device handle: {:?}", - queue_associated_device_handle - ); - assert_eq!( - device_handle_at_creation, queue_associated_device_handle, - "Device handle mismatch immediately after queue creation!" - ); // --- 4. Resource Manager --- let resource_manager = Arc::new(ResourceManager::new(instance.clone(), device.clone())?); @@ -238,14 +249,6 @@ impl Application { let renderer_device_handle_to_pass = device.raw().handle(); let renderer_queue_device_handle_to_pass = graphics_queue.device().raw().handle(); - info!( - "App: Passing Device handle to Renderer: {:?}", - renderer_device_handle_to_pass - ); - info!( - "App: Passing Queue associated with Device handle: {:?}", - renderer_queue_device_handle_to_pass - ); // --- 5. Renderer --- let initial_size = window.inner_size(); @@ -258,6 +261,18 @@ impl Application { initial_size.width, initial_size.height, )?; + + let egui_ctx = Context::default(); + let egui_winit = State::new( + egui_ctx.clone(), + ViewportId::ROOT, + &window, + None, + None, + None, + ); + let egui_app = EditorUI::default(); + info!("Renderer initialized."); Ok(Self { @@ -269,6 +284,9 @@ impl Application { _resource_manager: resource_manager, renderer, window, + egui_winit, + egui_ctx, + egui_app, frame_count: 0, last_fps_update_time: Instant::now(), last_frame_time: Instant::now(), @@ -320,8 +338,29 @@ impl Application { self.last_fps_update_time = now; } + let raw_input = self.egui_winit.take_egui_input(&self.window); + + let egui::FullOutput { + platform_output, + textures_delta, + shapes, + pixels_per_point, + .. + } = self.egui_ctx.run(raw_input, |ctx| { + self.egui_app.build_ui(ctx); + }); + + self.egui_winit + .handle_platform_output(&self.window, platform_output); + + let clipped_primitives = self.egui_ctx.tessellate(shapes, pixels_per_point); + // --- Render Frame --- - match self.renderer.render_frame() { + match self.renderer.render_frame( + pixels_per_point, + textures_delta, + &clipped_primitives, + ) { Ok(_) => { self.window.request_redraw(); } @@ -450,6 +489,7 @@ struct Args { // --- Entry Point --- fn main() -> Result<(), Box> { + color_eyre::install()?; let args = Args::parse(); let fmt_layer = tracing_subscriber::fmt::layer() diff --git a/crates/renderer/src/lib.rs b/crates/renderer/src/lib.rs index 8e32ab0..44fc872 100644 --- a/crates/renderer/src/lib.rs +++ b/crates/renderer/src/lib.rs @@ -4,6 +4,7 @@ use std::{ }; use ash::vk; +use egui::{ClippedPrimitive, TextureId, TexturesDelta}; use egui_ash_renderer::{DynamicRendering, Options, Renderer as EguiRenderer}; use gfx_hal::{ device::Device, error::GfxHalError, queue::Queue, surface::Surface, swapchain::Swapchain, @@ -61,6 +62,7 @@ struct FrameData { command_buffer: vk::CommandBuffer, image_available_semaphore: Semaphore, render_finished_semaphore: Semaphore, + textures_to_free: Option>, in_flight_fence: Fence, } @@ -140,7 +142,10 @@ impl Renderer { color_attachment_format: swapchain.format().format, depth_attachment_format: Some(depth_format), }, - Options::default(), + Options { + srgb_framebuffer: true, + ..Default::default() + }, )?; Ok(Self { @@ -178,7 +183,12 @@ impl Renderer { } } - pub fn render_frame(&mut self) -> Result<(), RendererError> { + pub fn render_frame( + &mut self, + pixels_per_point: f32, + textures_delta: TexturesDelta, + clipped_primitives: &[ClippedPrimitive], + ) -> Result<(), RendererError> { // --- Handle Resize --- if self.window_resized { self.window_resized = false; @@ -190,7 +200,7 @@ impl Renderer { // --- Wait for Previous Frame --- let frame_index = self.current_frame; - let frame_data = &self.frames_data[frame_index]; + let frame_data = &mut self.frames_data[frame_index]; frame_data.in_flight_fence.wait(None)?; // Wait indefinitely @@ -219,6 +229,11 @@ impl Renderer { // --- Reset Fence (only after successful acquisition) --- 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)?; + } + // --- Record Command Buffer --- unsafe { // Need unsafe for Vulkan commands @@ -238,6 +253,29 @@ impl Renderer { .begin_command_buffer(command_buffer, &cmd_begin_info)?; } + if !textures_delta.free.is_empty() { + tracing::debug!("Setting textures to free"); + frame_data.textures_to_free = Some(textures_delta.free.clone()); + } + + if !textures_delta.set.is_empty() { + tracing::trace!("Setting EGUI textures"); + self.egui_renderer.set_textures( + self.device.get_graphics_queue().handle(), + frame_data.command_pool, + textures_delta.set.as_slice(), + )?; + } + + tracing::info!("Rendering EGUI"); + self.egui_renderer.cmd_draw( + command_buffer, + self.swapchain_extent, + pixels_per_point, + clipped_primitives, + )?; + tracing::debug!("Rendered EGUI"); + let current_swapchain_image = swapchain_ref.images()[image_index as usize]; let initial_layout_transition_barrier = vk::ImageMemoryBarrier::default() @@ -862,6 +900,7 @@ impl Renderer { }; frames_data.push(FrameData { + textures_to_free: None, command_pool, command_buffer, // Stays allocated, just reset/rerecorded image_available_semaphore, From 6c70f7bc2e18b519dc437e1e9069ae51e84adc14 Mon Sep 17 00:00:00 2001 From: zack Date: Tue, 1 Apr 2025 14:20:23 -0400 Subject: [PATCH 3/7] impl: add egui!! --- crates/engine/src/main.rs | 15 +++++++---- crates/renderer/src/lib.rs | 54 +++++++++++++++++++++----------------- 2 files changed, 40 insertions(+), 29 deletions(-) diff --git a/crates/engine/src/main.rs b/crates/engine/src/main.rs index e5af844..9f115d7 100644 --- a/crates/engine/src/main.rs +++ b/crates/engine/src/main.rs @@ -294,6 +294,8 @@ impl Application { } fn handle_event(&mut self, event: &WindowEvent, active_event_loop: &ActiveEventLoop) { + let _ = self.egui_winit.on_window_event(&self.window, event); + match event { WindowEvent::CloseRequested => { info!("Close requested. Exiting..."); @@ -340,6 +342,8 @@ impl Application { let raw_input = self.egui_winit.take_egui_input(&self.window); + tracing::info!("{:?}", raw_input); + let egui::FullOutput { platform_output, textures_delta, @@ -350,17 +354,18 @@ impl Application { self.egui_app.build_ui(ctx); }); + self.renderer.update_textures(textures_delta).unwrap(); + self.egui_winit .handle_platform_output(&self.window, platform_output); let clipped_primitives = self.egui_ctx.tessellate(shapes, pixels_per_point); // --- Render Frame --- - match self.renderer.render_frame( - pixels_per_point, - textures_delta, - &clipped_primitives, - ) { + match self + .renderer + .render_frame(pixels_per_point, &clipped_primitives) + { Ok(_) => { self.window.request_redraw(); } diff --git a/crates/renderer/src/lib.rs b/crates/renderer/src/lib.rs index 44fc872..33ff0a2 100644 --- a/crates/renderer/src/lib.rs +++ b/crates/renderer/src/lib.rs @@ -183,10 +183,30 @@ impl Renderer { } } + pub fn update_textures(&mut self, textures_delta: TexturesDelta) -> Result<(), RendererError> { + tracing::trace!("Updating EGUI textures!"); + + if !textures_delta.free.is_empty() { + self.frames_data[self.current_frame].textures_to_free = + Some(textures_delta.free.clone()); + } + + if !textures_delta.set.is_empty() { + self.egui_renderer + .set_textures( + self.device.get_graphics_queue().handle(), + self.frames_data[self.current_frame].command_pool, + textures_delta.set.as_slice(), + ) + .expect("Failed to update texture"); + } + + Ok(()) + } + pub fn render_frame( &mut self, pixels_per_point: f32, - textures_delta: TexturesDelta, clipped_primitives: &[ClippedPrimitive], ) -> Result<(), RendererError> { // --- Handle Resize --- @@ -253,29 +273,6 @@ impl Renderer { .begin_command_buffer(command_buffer, &cmd_begin_info)?; } - if !textures_delta.free.is_empty() { - tracing::debug!("Setting textures to free"); - frame_data.textures_to_free = Some(textures_delta.free.clone()); - } - - if !textures_delta.set.is_empty() { - tracing::trace!("Setting EGUI textures"); - self.egui_renderer.set_textures( - self.device.get_graphics_queue().handle(), - frame_data.command_pool, - textures_delta.set.as_slice(), - )?; - } - - tracing::info!("Rendering EGUI"); - self.egui_renderer.cmd_draw( - command_buffer, - self.swapchain_extent, - pixels_per_point, - clipped_primitives, - )?; - tracing::debug!("Rendered EGUI"); - let current_swapchain_image = swapchain_ref.images()[image_index as usize]; let initial_layout_transition_barrier = vk::ImageMemoryBarrier::default() @@ -382,6 +379,15 @@ impl Renderer { self.device.raw().cmd_draw(command_buffer, 3, 1, 0, 0); } + tracing::trace!("Rendering EGUI"); + self.egui_renderer.cmd_draw( + command_buffer, + self.swapchain_extent, + pixels_per_point, + clipped_primitives, + )?; + tracing::trace!("Rendered EGUI"); + // --- End Dynamic Rendering --- unsafe { // Need unsafe for Vulkan commands From dbf9544e80f29d9d9c3a961ad2058cb4fed4fc2e Mon Sep 17 00:00:00 2001 From: zack Date: Tue, 1 Apr 2025 14:20:54 -0400 Subject: [PATCH 4/7] fix: remove log spam --- crates/engine/src/main.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/crates/engine/src/main.rs b/crates/engine/src/main.rs index 9f115d7..b32b286 100644 --- a/crates/engine/src/main.rs +++ b/crates/engine/src/main.rs @@ -342,8 +342,6 @@ impl Application { let raw_input = self.egui_winit.take_egui_input(&self.window); - tracing::info!("{:?}", raw_input); - let egui::FullOutput { platform_output, textures_delta, From 70176bb86a399feed4d900c7f1665a504cbbbc55 Mon Sep 17 00:00:00 2001 From: zack Date: Tue, 1 Apr 2025 21:41:24 -0400 Subject: [PATCH 5/7] impl: 3D Rendering --- Cargo.lock | 102 ++++++++ Cargo.toml | 2 +- crates/engine/Cargo.toml | 3 + crates/engine/src/main.rs | 97 ++++++-- crates/gfx_hal/Cargo.toml | 1 + crates/gfx_hal/src/error.rs | 4 + crates/gfx_hal/src/lib.rs | 9 + crates/gfx_hal/src/queue.rs | 12 - crates/gfx_hal/src/sync.rs | 1 + crates/renderer/Cargo.toml | 1 + crates/renderer/build.rs | 3 + crates/renderer/src/lib.rs | 363 ++++++++++++++++++++++++----- crates/resource_manager/src/geo.rs | 174 ++++++++++++++ crates/resource_manager/src/lib.rs | 164 ++++++------- crates/shared/Cargo.toml | 11 + crates/shared/src/lib.rs | 61 +++++ shaders/frag.glsl.frag | 11 +- shaders/vert.glsl.vert | 28 ++- 18 files changed, 862 insertions(+), 185 deletions(-) create mode 100644 crates/resource_manager/src/geo.rs create mode 100644 crates/shared/Cargo.toml create mode 100644 crates/shared/src/lib.rs 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; } From 25013902252a06ebf92a30418c299f9587416a8d Mon Sep 17 00:00:00 2001 From: zack Date: Thu, 3 Apr 2025 17:33:35 -0400 Subject: [PATCH 6/7] fix: all clippy errors --- crates/engine/src/main.rs | 2 +- crates/gfx_hal/src/device.rs | 5 +-- crates/renderer/src/lib.rs | 54 +++++------------------------- crates/resource_manager/src/geo.rs | 2 +- crates/resource_manager/src/lib.rs | 34 +++++++++---------- 5 files changed, 30 insertions(+), 67 deletions(-) diff --git a/crates/engine/src/main.rs b/crates/engine/src/main.rs index 4bd277a..e011ef6 100644 --- a/crates/engine/src/main.rs +++ b/crates/engine/src/main.rs @@ -568,7 +568,7 @@ fn main() -> Result<(), Box> { .with_ansi(true) .with_file(false) .with_line_number(false) - .with_filter(filter::LevelFilter::INFO); + .with_filter(filter::LevelFilter::DEBUG); let registry = tracing_subscriber::registry().with(fmt_layer); diff --git a/crates/gfx_hal/src/device.rs b/crates/gfx_hal/src/device.rs index 0d0c619..71abd47 100644 --- a/crates/gfx_hal/src/device.rs +++ b/crates/gfx_hal/src/device.rs @@ -15,7 +15,7 @@ use crate::queue::Queue; /// /// Owns the `ash::Device` and provides access to device functions and queues. pub struct Device { - instance: Arc, + _instance: Arc, physical_device: vk::PhysicalDevice, device: ash::Device, queues: Mutex>>, @@ -33,6 +33,7 @@ impl Device { /// - `queue_family_indicies` must be valid indicies obtained from the `physical_device_handle`. /// - `required_extensions` must be supported by the `physical_device_handle`. /// - All feature structs passed must be supported by the `physical_device_handle`. + #[allow(clippy::too_many_arguments)] pub(crate) unsafe fn new( instance: Arc, physical_device_handle: vk::PhysicalDevice, @@ -113,7 +114,7 @@ impl Device { // --- 4. Create the Device struct in an Arc (Stage 1) --- // Initialize the queues map as empty for now. let device_arc = Arc::new(Device { - instance: instance.clone(), + _instance: instance.clone(), physical_device: physical_device_handle, device: ash_device, // Move the created ash::Device here queues: Mutex::new(HashMap::new()), // Start with empty map diff --git a/crates/renderer/src/lib.rs b/crates/renderer/src/lib.rs index 521cc52..5e32a9c 100644 --- a/crates/renderer/src/lib.rs +++ b/crates/renderer/src/lib.rs @@ -1,5 +1,5 @@ use std::{ - ffi::{c_void, CStr}, + ffi::c_void, mem, sync::{Arc, Mutex}, time::Instant, @@ -127,6 +127,7 @@ pub struct Renderer { } impl Renderer { + #[allow(clippy::too_many_arguments)] pub fn new( instance: Arc, // Needed for allocator device: Arc, @@ -510,12 +511,6 @@ impl Renderer { .command_buffers(&command_buffers) .signal_semaphores(&signal_semaphores); - // assert_eq!( - // self.graphics_queue.device().raw().handle(), // Device from Queue - // self.device.raw().handle(), // Device stored in Renderer - // "Device handle mismatch between Renderer and Graphics Queue!" - // ); - unsafe { // Need unsafe for queue submit self.graphics_queue.submit( @@ -593,10 +588,6 @@ impl Renderer { self.depth_image_handle = new_depth_handle; self.depth_image_view = new_depth_view; - // 4. Update Egui Renderer (if necessary, depends on its implementation) - // It might need the new extent or recreate internal resources. - // Assuming it handles extent changes via update_screen_descriptor called earlier. - info!( "Swapchain recreated successfully ({}x{}).", new_extent.width, new_extent.height @@ -607,7 +598,7 @@ impl Renderer { // --- Helper: Cleanup Swapchain Dependent Resources --- fn cleanup_swapchain_resources(&mut self) { debug!("Cleaning up swapchain resources..."); - // Destroy depth buffer view + unsafe { self.device .raw() @@ -616,9 +607,8 @@ impl Renderer { // Destroy depth buffer image via resource manager if let Err(e) = self.resource_manager.destroy_image(self.depth_image_handle) { error!("Failed to destroy depth image: {}", e); - // Continue cleanup even if this fails } - // Drop the old swapchain object (RAII in gfx_hal::Swapchain handles vkDestroySwapchainKHR) + self.swapchain = None; debug!("Swapchain resources cleaned up."); } @@ -665,38 +655,10 @@ impl Renderer { let swapchain = unsafe { Swapchain::new(device.clone(), surface.clone(), config, old_swapchain)? }; - // Create Image Views let image_views = swapchain .image_views() // Assuming Swapchain::new creates and stores these .to_vec(); // Clone the slice into a Vec - // If Swapchain::new doesn't create views, we need to do it here: - /* - let images = swapchain.images()?; // Assuming this method exists - let mut image_views = Vec::with_capacity(images.len()); - for &image in images.iter() { - let create_info = vk::ImageViewCreateInfo::default() - .image(image) - .view_type(vk::ImageViewType::TYPE_2D) - .format(surface_format.format) - .components(vk::ComponentMapping { - r: vk::ComponentSwizzle::IDENTITY, - g: vk::ComponentSwizzle::IDENTITY, - b: vk::ComponentSwizzle::IDENTITY, - a: vk::ComponentSwizzle::IDENTITY, - }) - .subresource_range(vk::ImageSubresourceRange { - aspect_mask: vk::ImageAspectFlags::COLOR, - base_mip_level: 0, - level_count: 1, - base_array_layer: 0, - layer_count: 1, - }); - let view = unsafe { device.raw().create_image_view(&create_info, None)? }; - image_views.push(view); - } - */ - Ok((swapchain, surface_format, extent, image_views)) } @@ -763,7 +725,7 @@ impl Renderer { let vert_module = Self::create_shader_module(device, vert_shader_code)?; let frag_module = Self::create_shader_module(device, frag_shader_code)?; - let main_function_name = CStr::from_bytes_with_nul(b"main\0").unwrap(); + let main_function_name = c"main"; let vert_stage_info = vk::PipelineShaderStageCreateInfo::default() .stage(vk::ShaderStageFlags::VERTEX) @@ -911,7 +873,7 @@ impl Renderer { // -------------------------------------------------------------------- // 3. Create the shader module - let create_info = vk::ShaderModuleCreateInfo::default().code(&code_slice_ref); // Pass the &[u32] slice + let create_info = vk::ShaderModuleCreateInfo::default().code(code_slice_ref); // Pass the &[u32] slice unsafe { device @@ -1026,7 +988,7 @@ impl Renderer { fn choose_swapchain_present_mode(available_modes: &[vk::PresentModeKHR]) -> vk::PresentModeKHR { *available_modes .iter() - .find(|&&mode| mode == vk::PresentModeKHR::MAILBOX) // Prefer Mailbox (low latency) + .find(|&&mode| mode == vk::PresentModeKHR::FIFO) // Prefer Mailbox (low latency) .unwrap_or(&vk::PresentModeKHR::FIFO) } @@ -1078,7 +1040,7 @@ impl Renderer { } Err(RendererError::Vulkan( vk::Result::ERROR_FORMAT_NOT_SUPPORTED, - )) // Or custom error + )) } fn create_descriptor_sets_resources( diff --git a/crates/resource_manager/src/geo.rs b/crates/resource_manager/src/geo.rs index 6a33e3a..3af2e71 100644 --- a/crates/resource_manager/src/geo.rs +++ b/crates/resource_manager/src/geo.rs @@ -1,6 +1,6 @@ use std::sync::Arc; -use ash::vk::{self, IndexType}; +use ash::vk; use gpu_allocator::MemoryLocation; use tracing::{debug, trace}; diff --git a/crates/resource_manager/src/lib.rs b/crates/resource_manager/src/lib.rs index e6bd0e9..518fc12 100644 --- a/crates/resource_manager/src/lib.rs +++ b/crates/resource_manager/src/lib.rs @@ -57,7 +57,6 @@ struct InternalBufferInfo { allocation: Option, // Option because it's taken in Drop size: vk::DeviceSize, usage: vk::BufferUsageFlags, - mapped_ptr: Option<*mut u8>, handle: BufferHandle, } @@ -135,8 +134,8 @@ pub struct ResourceManager { _instance: Arc, device: Arc, allocator: Arc>, - buffers: Mutex>, - images: Mutex>, + buffers: Arc>>, + images: Arc>>, next_id: AtomicU64, transfer_setup: Arc>, } @@ -181,8 +180,8 @@ impl ResourceManager { _instance: instance, device, allocator: Arc::new(Mutex::new(allocator)), - buffers: Mutex::new(HashMap::new()), - images: Mutex::new(HashMap::new()), + buffers: Arc::new(Mutex::new(HashMap::new())), + images: Arc::new(Mutex::new(HashMap::new())), next_id: AtomicU64::new(1), transfer_setup: Arc::new(Mutex::new(new_setup)), }) @@ -289,11 +288,6 @@ impl ResourceManager { } trace!("Buffer memory bound."); - let mapped_ptr = allocation.mapped_ptr().map(|p| p.as_ptr() as *mut u8); - if mapped_ptr.is_some() { - trace!("Buffer memory is mapped."); - } - let id = self.next_id.fetch_add(1, Ordering::Relaxed); let handle = BufferHandle(id); @@ -304,7 +298,6 @@ impl ResourceManager { allocation: Some(allocation), size, usage, - mapped_ptr, handle, }; @@ -503,12 +496,19 @@ impl ResourceManager { let buffers_map = self.buffers.lock()?; buffers_map .get(&handle.0) - .map(|internal| BufferInfo { - handle: internal.handle, - buffer: internal.buffer, - size: internal.size, - usage: internal.usage, - mapped_ptr: internal.mapped_ptr, + .map(|internal| { + let mapped_ptr = internal + .allocation + .as_ref() + .and_then(|a| a.mapped_ptr().map(|p| p.as_ptr() as *mut u8)); + + BufferInfo { + handle: internal.handle, + buffer: internal.buffer, + size: internal.size, + usage: internal.usage, + mapped_ptr, + } }) .ok_or(ResourceManagerError::HandleNotFound(handle.0)) } From 74a1be796f039feb8acbd28464e27e222c822b83 Mon Sep 17 00:00:00 2001 From: zack Date: Sat, 5 Apr 2025 20:54:38 -0400 Subject: [PATCH 7/7] feat: render and manage textures :3 --- Cargo.lock | 862 ++++++++++++++++++++-- Cargo.toml | 11 +- crates/engine/Cargo.toml | 2 + crates/engine/src/main.rs | 411 +++++++++-- crates/renderer/Cargo.toml | 1 + crates/renderer/src/lib.rs | 313 ++++++-- crates/resource_manager/Cargo.toml | 2 + crates/resource_manager/src/error.rs | 6 + crates/resource_manager/src/geo.rs | 1 + crates/resource_manager/src/lib.rs | 977 ++++++++++++++++++++++--- crates/resource_manager/src/texture.rs | 59 ++ crates/scene/Cargo.toml | 14 + crates/scene/src/error.rs | 16 + crates/scene/src/lib.rs | 411 +++++++++++ crates/shared/src/lib.rs | 17 +- crates/shared/src/material.rs | 0 flake.lock | 45 +- flake.nix | 3 +- rust-toolchain.toml | 7 +- shaders/frag.glsl.frag | 30 +- shaders/vert.glsl.vert | 40 +- 21 files changed, 2908 insertions(+), 320 deletions(-) create mode 100644 crates/resource_manager/src/texture.rs create mode 100644 crates/scene/Cargo.toml create mode 100644 crates/scene/src/error.rs create mode 100644 crates/scene/src/lib.rs create mode 100644 crates/shared/src/material.rs diff --git a/Cargo.lock b/Cargo.lock index 001bfcc..84d93d9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -18,6 +18,16 @@ version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c71b1793ee61086797f5c80b6efa2b8ffa6d5dd703f118545808a7f2e27f7046" +[[package]] +name = "accesskit" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3d3b8f9bae46a948369bc4a03e815d4ed6d616bd00de4051133a5019dc31c5a" +dependencies = [ + "enumn", + "serde", +] + [[package]] name = "addr2line" version = "0.21.0" @@ -46,12 +56,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" dependencies = [ "cfg-if", - "getrandom", + "getrandom 0.2.15", "once_cell", + "serde", "version_check", - "zerocopy", + "zerocopy 0.7.35", ] +[[package]] +name = "aligned-vec" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4aa90d7ce82d4be67b64039a3d588d38dbcc6736577de4a847025ce5b0c468d1" + [[package]] name = "android-activity" version = "0.6.0" @@ -136,23 +153,42 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dcfed56ad506cb2c684a14971b8861fdc3baaaae314b9e5f9bb532cbe3ba7a4f" [[package]] -name = "arboard" -version = "3.4.1" +name = "arbitrary" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df099ccb16cd014ff054ac1bf392c67feeef57164b05c42f037cd40f5d4357f4" +checksum = "dde20b3d026af13f561bdd0f15edf01fc734f0dafcedbaf42bba506a9517f223" + +[[package]] +name = "arboard" +version = "3.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1df21f715862ede32a0c525ce2ca4d52626bb0007f8c18b87a384503ac33e70" dependencies = [ "clipboard-win", - "core-graphics", "image", "log", - "objc2 0.5.2", - "objc2-app-kit", - "objc2-foundation 0.2.2", + "objc2 0.6.0", + "objc2-app-kit 0.3.0", + "objc2-core-foundation", + "objc2-core-graphics", + "objc2-foundation 0.3.0", "parking_lot", - "windows-sys 0.48.0", + "percent-encoding", + "windows-sys 0.59.0", "x11rb", ] +[[package]] +name = "arg_enum_proc_macro" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ae92a5119aa49cdbcf6b9f893fe4e1d98b04ccbf82ee0584ad948a44a734dea" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "arrayref" version = "0.3.9" @@ -203,6 +239,29 @@ version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" +[[package]] +name = "av1-grain" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6678909d8c5d46a42abcf571271e15fdbc0a225e3646cf23762cd415046c78bf" +dependencies = [ + "anyhow", + "arrayvec", + "log", + "nom", + "num-rational", + "v_frame", +] + +[[package]] +name = "avif-serialize" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "98922d6a4cfbcb08820c69d8eeccc05bb1f29bfa06b4f5b1dbfe9a868bd7608e" +dependencies = [ + "arrayvec", +] + [[package]] name = "backtrace" version = "0.3.71" @@ -218,6 +277,18 @@ dependencies = [ "rustc-demangle", ] +[[package]] +name = "base64" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" + +[[package]] +name = "bit_field" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc827186963e592360843fb5ba4b973e145841266c1357f7180c43526f2e5b61" + [[package]] name = "bitflags" version = "1.3.2" @@ -230,6 +301,12 @@ version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd" +[[package]] +name = "bitstream-io" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6099cdc01846bc367c4e7dd630dc5966dccf36b652fae7a74e17b640411a91b2" + [[package]] name = "block" version = "0.1.6" @@ -245,6 +322,12 @@ dependencies = [ "objc2 0.5.2", ] +[[package]] +name = "built" +version = "0.7.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56ed6191a7e78c36abdb16ab65341eefd73d64d303fffccdbb00d51e4205967b" + [[package]] name = "bumpalo" version = "3.17.0" @@ -262,15 +345,21 @@ dependencies = [ [[package]] name = "bytemuck_derive" -version = "1.9.2" +version = "1.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ff22c2722516255d1823ce3cc4bc0b154dbc9364be5c905d6baa6eccbbc8774" +checksum = "7ecc273b49b3205b83d648f0690daa588925572cc5063745bfe547fe7ec8e1a1" dependencies = [ "proc-macro2", "quote", "syn", ] +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + [[package]] name = "byteorder-lite" version = "0.1.0" @@ -326,6 +415,16 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" +[[package]] +name = "cfg-expr" +version = "0.15.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d067ad48b8650848b989a59a86c6c36a995d02d2bf778d45c3c5d57bc2718f02" +dependencies = [ + "smallvec", + "target-lexicon", +] + [[package]] name = "cfg-if" version = "1.0.0" @@ -340,9 +439,9 @@ checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" [[package]] name = "clap" -version = "4.5.34" +version = "4.5.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e958897981290da2a852763fe9cdb89cd36977a5d729023127095fa94d95e2ff" +checksum = "d8aa86934b44c19c50f87cc2790e19f54f7a67aedb64101c2e1a2e5ecfb73944" dependencies = [ "clap_builder", "clap_derive", @@ -350,9 +449,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.34" +version = "4.5.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83b0f35019843db2160b5bb19ae09b4e6411ac33fc6a712003c33e03090e2489" +checksum = "2414dbb2dd0695280da6ea9261e327479e9d37b0630f6b53ba2a11c60c679fd9" dependencies = [ "anstream", "anstyle", @@ -453,6 +552,12 @@ dependencies = [ "tracing-error", ] +[[package]] +name = "color_quant" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b" + [[package]] name = "colorchoice" version = "1.0.3" @@ -537,12 +642,37 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "crossbeam-deque" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51" +dependencies = [ + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" +dependencies = [ + "crossbeam-utils", +] + [[package]] name = "crossbeam-utils" version = "0.8.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" +[[package]] +name = "crunchy" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43da5946c66ffcc7745f48db692ffbb10a83bfe0afd96235c5c2a4fb23994929" + [[package]] name = "cursor-icon" version = "1.1.0" @@ -661,6 +791,7 @@ checksum = "bc4feb366740ded31a004a0e4452fbf84e80ef432ecf8314c485210229672fd1" dependencies = [ "bytemuck", "emath", + "serde", ] [[package]] @@ -669,6 +800,7 @@ version = "0.31.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "25dd34cec49ab55d85ebf70139cb1ccd29c977ef6b6ba4fe85489d6877ee9ef3" dependencies = [ + "accesskit", "ahash", "bitflags 2.9.0", "emath", @@ -676,6 +808,7 @@ dependencies = [ "log", "nohash-hasher", "profiling", + "serde", ] [[package]] @@ -710,6 +843,25 @@ dependencies = [ "winit", ] +[[package]] +name = "egui_tiles" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67756b63b283a65bd0534b0c2a5fb1a12a5768bb6383d422147cc93193d09cfc" +dependencies = [ + "ahash", + "egui", + "itertools 0.13.0", + "log", + "serde", +] + +[[package]] +name = "either" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" + [[package]] name = "emath" version = "0.31.1" @@ -717,6 +869,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9e4cadcff7a5353ba72b7fea76bf2122b5ebdbc68e8155aa56dfdea90083fe1b" dependencies = [ "bytemuck", + "serde", ] [[package]] @@ -729,11 +882,13 @@ dependencies = [ "color-eyre", "egui", "egui-winit", + "egui_tiles", "gfx_hal", "glam", "raw-window-handle", "renderer", "resource_manager", + "scene", "shared", "thiserror 2.0.12", "tracing", @@ -741,6 +896,17 @@ dependencies = [ "winit", ] +[[package]] +name = "enumn" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f9ed6b3789237c8a0c1c505af1c7eb2c560df6186f01b098c3a1064ea532f38" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "epaint" version = "0.31.1" @@ -757,6 +923,7 @@ dependencies = [ "nohash-hasher", "parking_lot", "profiling", + "serde", ] [[package]] @@ -787,6 +954,21 @@ version = "3.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a5d9305ccc6942a704f4335694ecd3de2ea531b114ac2d51f5f843750787a92f" +[[package]] +name = "exr" +version = "1.73.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f83197f59927b46c04a183a619b7c29df34e63e63c7869320862268c0ef687e0" +dependencies = [ + "bit_field", + "half", + "lebe", + "miniz_oxide 0.8.7", + "rayon-core", + "smallvec", + "zune-inflate", +] + [[package]] name = "eyre" version = "0.6.12" @@ -808,12 +990,12 @@ dependencies = [ [[package]] name = "flate2" -version = "1.1.0" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11faaf5a5236997af9848be0bef4db95824b1d534ebc64d0f0c6cf3e67bd38dc" +checksum = "7ced92e76e966ca2fd84c8f7aa01a4aea65b0eb6648d72f7c8f3e2764a67fece" dependencies = [ "crc32fast", - "miniz_oxide 0.8.5", + "miniz_oxide 0.8.7", ] [[package]] @@ -876,7 +1058,19 @@ checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" dependencies = [ "cfg-if", "libc", - "wasi", + "wasi 0.11.0+wasi-snapshot-preview1", +] + +[[package]] +name = "getrandom" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73fea8450eea4bac3940448fb7ae50d91f034f941199fcd9d909a5a07aa455f0" +dependencies = [ + "cfg-if", + "libc", + "r-efi", + "wasi 0.14.2+wasi-0.2.4", ] [[package]] @@ -891,6 +1085,16 @@ dependencies = [ "winit", ] +[[package]] +name = "gif" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fb2d69b19215e18bb912fa30f7ce15846e301408695e44e0ef719f1da9e19f2" +dependencies = [ + "color_quant", + "weezl", +] + [[package]] name = "gimli" version = "0.28.1" @@ -907,6 +1111,45 @@ dependencies = [ "num-traits", ] +[[package]] +name = "gltf" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3ce1918195723ce6ac74e80542c5a96a40c2b26162c1957a5cd70799b8cacf7" +dependencies = [ + "base64", + "byteorder", + "gltf-json", + "image", + "lazy_static", + "serde_json", + "urlencoding", +] + +[[package]] +name = "gltf-derive" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14070e711538afba5d6c807edb74bcb84e5dbb9211a3bf5dea0dfab5b24f4c51" +dependencies = [ + "inflections", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "gltf-json" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6176f9d60a7eab0a877e8e96548605dedbde9190a7ae1e80bbcc1c9af03ab14" +dependencies = [ + "gltf-derive", + "serde", + "serde_derive", + "serde_json", +] + [[package]] name = "gpu-allocator" version = "0.27.0" @@ -920,6 +1163,16 @@ dependencies = [ "windows", ] +[[package]] +name = "half" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7db2ff139bba50379da6aa0766b52fdcb62cb5b263009b09ed58ba604e14bbd1" +dependencies = [ + "cfg-if", + "crunchy", +] + [[package]] name = "hashbrown" version = "0.15.2" @@ -1100,11 +1353,37 @@ checksum = "db35664ce6b9810857a38a906215e75a9c879f0696556a39f59c62829710251a" dependencies = [ "bytemuck", "byteorder-lite", + "color_quant", + "exr", + "gif", + "image-webp", "num-traits", "png", + "qoi", + "ravif", + "rayon", + "rgb", "tiff", + "zune-core", + "zune-jpeg", ] +[[package]] +name = "image-webp" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b77d01e822461baa8409e156015a1d91735549f0f2c17691bd2d996bef238f7f" +dependencies = [ + "byteorder-lite", + "quick-error", +] + +[[package]] +name = "imgref" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0263a3d970d5c054ed9312c0057b4f3bde9c0b33836d3637361d4a9e6e7a408" + [[package]] name = "indenter" version = "0.3.3" @@ -1121,6 +1400,23 @@ dependencies = [ "hashbrown", ] +[[package]] +name = "inflections" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a257582fdcde896fd96463bf2d40eefea0580021c0712a0e2b028b60b47a837a" + +[[package]] +name = "interpolate_name" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c34819042dc3d3971c46c2190835914dfbe0c3c13f61449b2997f4e9722dfa60" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "is_terminal_polyfill" version = "1.70.1" @@ -1128,10 +1424,28 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" [[package]] -name = "itoa" -version = "1.0.14" +name = "itertools" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674" +checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" +dependencies = [ + "either", +] + +[[package]] +name = "itertools" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" [[package]] name = "jni" @@ -1157,10 +1471,11 @@ checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" [[package]] name = "jobserver" -version = "0.1.32" +version = "0.1.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48d1dbcbbeb6a7fec7e059840aa538bd62aaccf972c7346c4d9d2059312853d0" +checksum = "38f262f097c174adebe41eb73d66ae9c06b2844fb0da69969647bbddd9b0538a" dependencies = [ + "getrandom 0.3.2", "libc", ] @@ -1186,12 +1501,28 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" +[[package]] +name = "lebe" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03087c2bad5e1034e8cace5926dec053fb3790248370865f5117a7d0213354c8" + [[package]] name = "libc" version = "0.2.171" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c19937216e9d3aa9956d9bb8dfc0b0c8beb6058fc4f7a4dc4d850edf86a237d6" +[[package]] +name = "libfuzzer-sys" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf78f52d400cf2d84a3a973a78a592b4adc535739e0a5597a0da6f0c357adc75" +dependencies = [ + "arbitrary", + "cc", +] + [[package]] name = "libloading" version = "0.8.6" @@ -1256,6 +1587,15 @@ version = "0.4.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" +[[package]] +name = "loop9" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fae87c125b03c1d2c0150c90365d7d6bcc53fb73a9acaef207d2d065860f062" +dependencies = [ + "imgref", +] + [[package]] name = "malloc_buf" version = "0.0.6" @@ -1265,6 +1605,16 @@ dependencies = [ "libc", ] +[[package]] +name = "maybe-rayon" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ea1f30cedd69f0a2954655f7188c6a834246d2bcf1e315e2ac40c4b24dc9519" +dependencies = [ + "cfg-if", + "rayon", +] + [[package]] name = "memchr" version = "2.7.4" @@ -1289,6 +1639,12 @@ dependencies = [ "autocfg", ] +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + [[package]] name = "miniz_oxide" version = "0.7.4" @@ -1300,9 +1656,9 @@ dependencies = [ [[package]] name = "miniz_oxide" -version = "0.8.5" +version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e3e04debbb59698c15bacbb6d93584a8c0ca9cc3213cb423d31f760d8843ce5" +checksum = "ff70ce3e48ae43fa075863cef62e8b43b71a4f2382229920e0df362592919430" dependencies = [ "adler2", "simd-adler32", @@ -1338,12 +1694,34 @@ dependencies = [ "jni-sys", ] +[[package]] +name = "new_debug_unreachable" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086" + [[package]] name = "nohash-hasher" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2bf50223579dc7cdcfb3bfcacf7069ff68243f8c363f62ffa99cf000a6b9c451" +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + +[[package]] +name = "noop_proc_macro" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0676bb32a98c1a483ce53e500a81ad9c3d5b3f7c920c28c24e9cb0980d0b5bc8" + [[package]] name = "nu-ansi-term" version = "0.46.0" @@ -1354,6 +1732,47 @@ dependencies = [ "winapi", ] +[[package]] +name = "num-bigint" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" +dependencies = [ + "num-integer", + "num-traits", +] + +[[package]] +name = "num-derive" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-rational" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824" +dependencies = [ + "num-bigint", + "num-integer", + "num-traits", +] + [[package]] name = "num-traits" version = "0.2.19" @@ -1435,6 +1854,18 @@ dependencies = [ "objc2-quartz-core", ] +[[package]] +name = "objc2-app-kit" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5906f93257178e2f7ae069efb89fbd6ee94f0592740b5f8a1512ca498814d0fb" +dependencies = [ + "bitflags 2.9.0", + "objc2 0.6.0", + "objc2-core-graphics", + "objc2-foundation 0.3.0", +] + [[package]] name = "objc2-cloud-kit" version = "0.2.2" @@ -1471,6 +1902,28 @@ dependencies = [ "objc2-foundation 0.2.2", ] +[[package]] +name = "objc2-core-foundation" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "daeaf60f25471d26948a1c2f840e3f7d86f4109e3af4e8e4b5cd70c39690d925" +dependencies = [ + "bitflags 2.9.0", + "objc2 0.6.0", +] + +[[package]] +name = "objc2-core-graphics" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dca602628b65356b6513290a21a6405b4d4027b8b250f0b98dddbb28b7de02" +dependencies = [ + "bitflags 2.9.0", + "objc2 0.6.0", + "objc2-core-foundation", + "objc2-io-surface", +] + [[package]] name = "objc2-core-image" version = "0.2.2" @@ -1522,6 +1975,18 @@ checksum = "3a21c6c9014b82c39515db5b396f91645182611c97d24637cf56ac01e5f8d998" dependencies = [ "bitflags 2.9.0", "objc2 0.6.0", + "objc2-core-foundation", +] + +[[package]] +name = "objc2-io-surface" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "161a8b87e32610086e1a7a9e9ec39f84459db7b3a0881c1f16ca5a2605581c19" +dependencies = [ + "bitflags 2.9.0", + "objc2 0.6.0", + "objc2-core-foundation", ] [[package]] @@ -1532,7 +1997,7 @@ checksum = "a1a1ae721c5e35be65f01a03b6d2ac13a54cb4fa70d8a5da293d7b0020261398" dependencies = [ "block2", "objc2 0.5.2", - "objc2-app-kit", + "objc2-app-kit 0.2.2", "objc2-foundation 0.2.2", ] @@ -1627,9 +2092,9 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.21.2" +version = "1.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2806eaa3524762875e21c3dcd057bc4b7bfa01ce4da8d46be1cd43649e1cc6b" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" [[package]] name = "orbclient" @@ -1684,6 +2149,12 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + [[package]] name = "percent-encoding" version = "2.3.1" @@ -1732,7 +2203,7 @@ dependencies = [ "crc32fast", "fdeflate", "flate2", - "miniz_oxide 0.8.5", + "miniz_oxide 0.8.7", ] [[package]] @@ -1750,6 +2221,15 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "ppv-lite86" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +dependencies = [ + "zerocopy 0.8.24", +] + [[package]] name = "presser" version = "0.3.1" @@ -1779,12 +2259,40 @@ name = "profiling" version = "1.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "afbdc74edc00b6f6a218ca6a5364d6226a259d4b8ea1af4a0ea063f27e179f4d" +dependencies = [ + "profiling-procmacros", +] + +[[package]] +name = "profiling-procmacros" +version = "1.0.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a65f2e60fbf1063868558d69c6beacf412dc755f9fc020f514b7955fc914fe30" +dependencies = [ + "quote", + "syn", +] + +[[package]] +name = "qoi" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f6d64c71eb498fe9eae14ce4ec935c555749aef511cca85b5568910d6e48001" +dependencies = [ + "bytemuck", +] + +[[package]] +name = "quick-error" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3" [[package]] name = "quick-xml" -version = "0.37.3" +version = "0.37.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf763ab1c7a3aa408be466efc86efe35ed1bd3dd74173ed39d6b0d0a6f0ba148" +checksum = "a4ce8c88de324ff838700f36fb6ab86c96df0e3c4ab6ef3a9b2044465cce1369" dependencies = [ "memchr", ] @@ -1798,6 +2306,92 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "r-efi" +version = "5.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74765f6d916ee2faa39bc8e68e4f3ed8949b48cccdac59983d287a7cb71ce9c5" + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom 0.2.15", +] + +[[package]] +name = "rav1e" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd87ce80a7665b1cce111f8a16c1f3929f6547ce91ade6addf4ec86a8dda5ce9" +dependencies = [ + "arbitrary", + "arg_enum_proc_macro", + "arrayvec", + "av1-grain", + "bitstream-io", + "built", + "cfg-if", + "interpolate_name", + "itertools 0.12.1", + "libc", + "libfuzzer-sys", + "log", + "maybe-rayon", + "new_debug_unreachable", + "noop_proc_macro", + "num-derive", + "num-traits", + "once_cell", + "paste", + "profiling", + "rand", + "rand_chacha", + "simd_helpers", + "system-deps", + "thiserror 1.0.69", + "v_frame", + "wasm-bindgen", +] + +[[package]] +name = "ravif" +version = "0.11.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2413fd96bd0ea5cdeeb37eaf446a22e6ed7b981d792828721e74ded1980a45c6" +dependencies = [ + "avif-serialize", + "imgref", + "loop9", + "quick-error", + "rav1e", + "rayon", + "rgb", +] + [[package]] name = "raw-window-handle" version = "0.6.2" @@ -1816,6 +2410,26 @@ dependencies = [ "raw-window-handle", ] +[[package]] +name = "rayon" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa" +dependencies = [ + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2" +dependencies = [ + "crossbeam-deque", + "crossbeam-utils", +] + [[package]] name = "redox_syscall" version = "0.4.1" @@ -1847,6 +2461,7 @@ dependencies = [ "glam", "gpu-allocator", "resource_manager", + "scene", "shaderc", "shared", "thiserror 2.0.12", @@ -1861,11 +2476,19 @@ version = "0.1.0" dependencies = [ "ash", "gfx_hal", + "gltf", "gpu-allocator", + "image", "thiserror 2.0.12", "tracing", ] +[[package]] +name = "rgb" +version = "0.8.50" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57397d16646700483b67d2dd6511d79318f9d057fdbd21a4066aeac8b41d310a" + [[package]] name = "roxmltree" version = "0.20.0" @@ -1899,9 +2522,9 @@ checksum = "eded382c5f5f786b989652c49544c4877d9f015cc22e145a5ea8ea66c2921cd2" [[package]] name = "ryu" -version = "1.0.19" +version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ea1a2d0a644769cc99faa24c3ad26b379b786fe7c36fd3c546254801650e6dd" +checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" [[package]] name = "same-file" @@ -1912,6 +2535,19 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "scene" +version = "0.1.0" +dependencies = [ + "ash", + "glam", + "gltf", + "resource_manager", + "shared", + "thiserror 2.0.12", + "tracing", +] + [[package]] name = "scoped-tls" version = "1.0.1" @@ -1959,9 +2595,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.139" +version = "1.0.140" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44f86c3acccc9c65b153fe1b85a3be07fe5515274ec9f0653b4a0875731c72a6" +checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373" dependencies = [ "itoa", "memchr", @@ -1969,6 +2605,15 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_spanned" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87607cb1398ed59d48732e575a4c28a7a8ebf2454b964fe3f224f2afc07909e1" +dependencies = [ + "serde", +] + [[package]] name = "shaderc" version = "0.9.1" @@ -2024,6 +2669,15 @@ version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" +[[package]] +name = "simd_helpers" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95890f873bec569a0362c235787f3aca6e1e887302ba4840839bcc6459c42da6" +dependencies = [ + "quote", +] + [[package]] name = "slab" version = "0.4.9" @@ -2124,6 +2778,25 @@ dependencies = [ "syn", ] +[[package]] +name = "system-deps" +version = "6.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3e535eb8dded36d55ec13eddacd30dec501792ff23a0b1682c38601b8cf2349" +dependencies = [ + "cfg-expr", + "heck", + "pkg-config", + "toml", + "version-compare", +] + +[[package]] +name = "target-lexicon" +version = "0.12.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1" + [[package]] name = "thiserror" version = "1.0.69" @@ -2220,11 +2893,26 @@ dependencies = [ "zerovec", ] +[[package]] +name = "toml" +version = "0.8.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd87a5cdd6ffab733b2f74bc4fd7ee5fff6634124999ac278c35fc78c6120148" +dependencies = [ + "serde", + "serde_spanned", + "toml_datetime", + "toml_edit", +] + [[package]] name = "toml_datetime" version = "0.6.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41" +dependencies = [ + "serde", +] [[package]] name = "toml_edit" @@ -2233,6 +2921,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "17b4795ff5edd201c7cd6dca065ae59972ce77d1b80fa0a84d94950ece7d1474" dependencies = [ "indexmap", + "serde", + "serde_spanned", "toml_datetime", "winnow", ] @@ -2346,6 +3036,12 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "urlencoding" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da" + [[package]] name = "utf16_iter" version = "1.0.5" @@ -2364,12 +3060,29 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" +[[package]] +name = "v_frame" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6f32aaa24bacd11e488aa9ba66369c7cd514885742c9fe08cfe85884db3e92b" +dependencies = [ + "aligned-vec", + "num-traits", + "wasm-bindgen", +] + [[package]] name = "valuable" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" +[[package]] +name = "version-compare" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "852e951cb7832cb45cb1169900d19760cfa39b82bc0ea9c0e5a14ae88411c98b" + [[package]] name = "version_check" version = "0.9.5" @@ -2392,6 +3105,15 @@ version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" +[[package]] +name = "wasi" +version = "0.14.2+wasi-0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3" +dependencies = [ + "wit-bindgen-rt", +] + [[package]] name = "wasm-bindgen" version = "0.2.100" @@ -2719,15 +3441,6 @@ dependencies = [ "windows-targets 0.42.2", ] -[[package]] -name = "windows-sys" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" -dependencies = [ - "windows-targets 0.48.5", -] - [[package]] name = "windows-sys" version = "0.52.0" @@ -2948,7 +3661,7 @@ dependencies = [ "memmap2", "ndk", "objc2 0.5.2", - "objc2-app-kit", + "objc2-app-kit 0.2.2", "objc2-foundation 0.2.2", "objc2-ui-kit", "orbclient", @@ -2985,6 +3698,15 @@ dependencies = [ "memchr", ] +[[package]] +name = "wit-bindgen-rt" +version = "0.39.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" +dependencies = [ + "bitflags 2.9.0", +] + [[package]] name = "write16" version = "1.0.0" @@ -3084,7 +3806,16 @@ version = "0.7.35" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" dependencies = [ - "zerocopy-derive", + "zerocopy-derive 0.7.35", +] + +[[package]] +name = "zerocopy" +version = "0.8.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2586fea28e186957ef732a5f8b3be2da217d65c5969d4b1e17f973ebbe876879" +dependencies = [ + "zerocopy-derive 0.8.24", ] [[package]] @@ -3098,6 +3829,17 @@ dependencies = [ "syn", ] +[[package]] +name = "zerocopy-derive" +version = "0.8.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a996a8f63c5c4448cd959ac1bab0aaa3306ccfd060472f85943ee0750f0169be" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "zerofrom" version = "0.1.6" @@ -3140,3 +3882,27 @@ dependencies = [ "quote", "syn", ] + +[[package]] +name = "zune-core" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f423a2c17029964870cfaabb1f13dfab7d092a62a29a89264f4d36990ca414a" + +[[package]] +name = "zune-inflate" +version = "0.2.54" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73ab332fe2f6680068f3582b16a24f90ad7096d5d39b974d1c0aff0125116f02" +dependencies = [ + "simd-adler32", +] + +[[package]] +name = "zune-jpeg" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99a5bab8d7dedf81405c4bb1f2b83ea057643d9cb28778cea9eecddeedd2e028" +dependencies = [ + "zune-core", +] diff --git a/Cargo.toml b/Cargo.toml index 539c227..0cdd355 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,7 +5,9 @@ members = [ "crates/engine", "crates/gfx_hal", "crates/renderer", - "crates/resource_manager", "crates/shared", + "crates/resource_manager", + "crates/scene", + "crates/shared", ] [workspace.dependencies] @@ -24,10 +26,12 @@ egui-ash-renderer = { version = "0.8.0", features = [ "dynamic-rendering", ] } egui = "0.31" +egui_tiles = "0.12" bytemuck = { version = "1.21.0", features = ["derive"] } -tracing = "0.1" +tracing = { features = ["release_max_level_warn"], version = "0.1" } tracing-subscriber = { version = "0.3", features = ["json"] } thiserror = "2.0.12" +gltf = "1.4.1" # # Enable incremental by default in release mode. @@ -56,4 +60,7 @@ thiserror = "2.0.12" # # rustflags = ["-Zshare-generics=off"] # codegen-units = 1 +opt-level = 1 + +[profile.dev.package."*"] opt-level = 3 diff --git a/crates/engine/Cargo.toml b/crates/engine/Cargo.toml index 2fd8816..c111b04 100644 --- a/crates/engine/Cargo.toml +++ b/crates/engine/Cargo.toml @@ -5,6 +5,7 @@ edition = "2021" [dependencies] egui.workspace = true +egui_tiles.workspace = true ash.workspace = true ash-window.workspace = true color-eyre.workspace = true @@ -19,6 +20,7 @@ gfx_hal = { path = "../gfx_hal" } renderer = { path = "../renderer" } resource_manager = { path = "../resource_manager" } shared = { path = "../shared" } +scene = { path = "../scene" } 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 e011ef6..bcd9aa2 100644 --- a/crates/engine/src/main.rs +++ b/crates/engine/src/main.rs @@ -17,14 +17,16 @@ use gfx_hal::{ use glam::Vec3; use raw_window_handle::HasDisplayHandle; use renderer::{Renderer, RendererError}; -use resource_manager::{Geometry, ResourceManager, ResourceManagerError}; -use shared::{CameraInfo, Vertex}; +use resource_manager::{ResourceManager, ResourceManagerError}; +use scene::Scene; +use shared::CameraInfo; use tracing::{debug, error, info, warn}; use tracing_subscriber::{filter, layer::SubscriberExt, util::SubscriberInitExt, Layer}; use winit::{ application::ApplicationHandler, - event::WindowEvent, + event::{ElementState, KeyEvent, MouseButton, WindowEvent}, event_loop::{ActiveEventLoop, EventLoop}, + keyboard::{KeyCode, PhysicalKey}, window::Window, }; @@ -47,6 +49,8 @@ enum AppError { NoSuitableDevice, #[error("Failed to create CString: {0}")] NulError(#[from] std::ffi::NulError), + #[error("Scene Error: {0}")] + SceneError(#[from] scene::SceneError), } struct Application { @@ -66,6 +70,23 @@ struct Application { egui_winit: State, egui_app: EditorUI, + // --- Camera State --- + camera_info: CameraInfo, + camera_speed: f32, + camera_sensitivity: f32, + + // --- Input State --- + is_forward_pressed: bool, + is_backward_pressed: bool, + is_left_pressed: bool, + is_right_pressed: bool, + is_up_pressed: bool, // Optional: For flying up + is_down_pressed: bool, // Optional: For flying down + is_rmb_pressed: bool, // Right mouse button + last_mouse_pos: Option<(f64, f64)>, + mouse_delta: (f64, f64), + capture_mouse: bool, // Flag to indicate if mouse should control camera + // Windowing window: Arc, // Use Arc for potential multi-threading later @@ -76,17 +97,15 @@ struct Application { } #[derive(Default)] -struct EditorUI { - camera_info: CameraInfo, -} +struct EditorUI {} impl EditorUI { fn title() -> String { "engine".to_string() } - fn build_ui(&mut self, ctx: &egui::Context, current_fps: f64) { - egui::Window::new(Self::title()).show(ctx, |ui| { + fn build_ui(&mut self, ctx: &egui::Context, current_fps: f64, camera_info: &mut CameraInfo) { + egui::SidePanel::new(egui::panel::Side::Left, Self::title()).show(ctx, |ui| { ui.label(format!("FPS - {:.2}", current_fps)); ui.separator(); @@ -96,12 +115,78 @@ impl EditorUI { .striped(true) .show(ui, |ui| { ui.label("FOV"); - ui.add(Slider::new(&mut self.camera_info.camera_fov, 10.0..=120.0)); + // Modify the passed-in camera_info + ui.add(Slider::new(&mut camera_info.camera_fov, 10.0..=120.0)); + ui.end_row(); // Good practice in grids + + // You could add more camera controls here if needed + // e.g., sliders for position, target (though direct manipulation is better) + ui.label("Camera Pos"); + ui.label(format!( + "({:.1}, {:.1}, {:.1})", + camera_info.camera_pos.x, + camera_info.camera_pos.y, + camera_info.camera_pos.z + )); + ui.end_row(); + + ui.label("Camera Target"); + ui.label(format!( + "({:.1}, {:.1}, {:.1})", + camera_info.camera_target.x, + camera_info.camera_target.y, + camera_info.camera_target.z + )); + ui.end_row(); }); + + ui.separator(); + ui.label("Controls:"); + ui.label("RMB + Drag: Look"); + ui.label("WASD: Move"); + ui.label("Space: Up"); + ui.label("Shift: Down"); + ui.label("Hold RMB to activate controls."); }); + + // let mut tree = create_tree(); + // + // egui::panel::SidePanel::new(egui::panel::Side::Left, Id::new("main_panel")).show( + // ctx, + // |ui| { + // let mut behavior = TreeBehavior {}; + // tree.ui(&mut behavior, ui); + // }, + // ); } } +fn create_tree() -> egui_tiles::Tree { + let mut next_view_nr = 0; + let mut gen_pane = || { + let pane = EditorUI {}; + next_view_nr += 1; + pane + }; + + let mut tiles = egui_tiles::Tiles::default(); + + let mut tabs = vec![]; + tabs.push({ + let children = (0..7).map(|_| tiles.insert_pane(gen_pane())).collect(); + tiles.insert_horizontal_tile(children) + }); + tabs.push({ + let cells = (0..11).map(|_| tiles.insert_pane(gen_pane())).collect(); + tiles.insert_grid_tile(cells) + }); + tabs.push(tiles.insert_pane(gen_pane())); + + let root = tiles.insert_tab_tile(tabs); + + egui_tiles::Tree::new("my_tree", root, tiles) +} + #[derive(Default)] struct ApplicationWrapper { app: Option, @@ -264,56 +349,10 @@ impl Application { let resource_manager = Arc::new(ResourceManager::new(instance.clone(), device.clone())?); info!("Resource Manager initialized."); - 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)?; + let scene = Scene::from_gltf( + "./sponza/NewSponza_Main_glTF_003.gltf", + resource_manager.clone(), + )?; // --- 5. Renderer --- let initial_size = window.inner_size(); @@ -323,7 +362,7 @@ impl Application { graphics_queue.clone(), surface.clone(), resource_manager.clone(), - vec![cube_geometry], + scene, initial_size.width, initial_size.height, )?; @@ -341,6 +380,8 @@ impl Application { info!("Renderer initialized."); + let camera_info = CameraInfo::default(); // Get default camera settings + Ok(Self { _instance: instance, _physical_device: physical_device, @@ -353,6 +394,23 @@ impl Application { egui_winit, egui_ctx, egui_app, + + // --- Camera --- + camera_info, // Store the camera state here + camera_speed: 5.0, // Adjust as needed + camera_sensitivity: 0.002, // Adjust as needed + + // --- Input --- + is_forward_pressed: false, + is_backward_pressed: false, + is_left_pressed: false, + is_right_pressed: false, + is_up_pressed: false, + is_down_pressed: false, + is_rmb_pressed: false, + last_mouse_pos: None, + mouse_delta: (0.0, 0.0), + capture_mouse: false, // Start with mouse free frame_count: 0, current_fps: 0., last_fps_update_time: Instant::now(), @@ -361,7 +419,11 @@ impl Application { } fn handle_event(&mut self, event: &WindowEvent, active_event_loop: &ActiveEventLoop) { - let _ = self.egui_winit.on_window_event(&self.window, event); + // Let egui process the event first + let egui_consumed_event = self.egui_winit.on_window_event(&self.window, event); + + // Only process input for camera if egui didn't consume it AND we are capturing + let process_camera_input = !egui_consumed_event.consumed && self.capture_mouse; match event { WindowEvent::CloseRequested => { @@ -385,29 +447,127 @@ impl Application { .resize(new_inner_size.width, new_inner_size.height); } // Handle other inputs if not consumed by egui - WindowEvent::KeyboardInput { .. } - | WindowEvent::CursorMoved { .. } - | WindowEvent::MouseInput { .. } => {} + WindowEvent::MouseInput { state, button, .. } => { + if *button == MouseButton::Right { + let is_pressed = *state == ElementState::Pressed; + self.is_rmb_pressed = is_pressed; + + // Decide whether to capture/release mouse based on RMB + // Only capture if pressed *outside* an egui interactive area + if is_pressed && !self.egui_ctx.is_pointer_over_area() { + self.capture_mouse = true; + self.window + .set_cursor_grab(winit::window::CursorGrabMode::Confined) + .or_else(|_| { + self.window + .set_cursor_grab(winit::window::CursorGrabMode::Locked) + }) + .unwrap_or(()); + self.window.set_cursor_visible(false); + self.last_mouse_pos = None; // Reset last pos on capture start + } else if !is_pressed { + self.capture_mouse = false; + self.window + .set_cursor_grab(winit::window::CursorGrabMode::None) + .unwrap_or(()); + self.window.set_cursor_visible(true); + self.mouse_delta = (0.0, 0.0); // Stop camera movement + } + } + // Let egui handle its mouse clicks regardless of capture state + // (handled by on_window_event) + } + + WindowEvent::CursorMoved { position, .. } => { + let current_pos = (position.x, position.y); + if self.capture_mouse { + // Only calculate delta if capturing + if let Some(last_pos) = self.last_mouse_pos { + self.mouse_delta.0 += current_pos.0 - last_pos.0; + self.mouse_delta.1 += current_pos.1 - last_pos.1; + } + // Store position relative to window center might be more robust + // with set_cursor_position, but this works with grab/confine too. + self.last_mouse_pos = Some(current_pos); + } else { + // Still update egui's pointer position even if not capturing + // (handled by on_window_event) + self.last_mouse_pos = None; // Reset if not capturing + } + } + + // Use PhysicalKey for layout-independent keys + WindowEvent::KeyboardInput { + event: + KeyEvent { + physical_key, + state, + .. + }, + .. + } => { + // Let egui handle keyboard input first if it wants it + if egui_consumed_event.consumed { + return; + } + + let is_pressed = *state == ElementState::Pressed; + match physical_key { + PhysicalKey::Code(KeyCode::KeyW) | PhysicalKey::Code(KeyCode::ArrowUp) => { + self.is_forward_pressed = is_pressed; + } + PhysicalKey::Code(KeyCode::KeyS) | PhysicalKey::Code(KeyCode::ArrowDown) => { + self.is_backward_pressed = is_pressed; + } + PhysicalKey::Code(KeyCode::KeyA) | PhysicalKey::Code(KeyCode::ArrowLeft) => { + self.is_left_pressed = is_pressed; + } + PhysicalKey::Code(KeyCode::KeyD) | PhysicalKey::Code(KeyCode::ArrowRight) => { + self.is_right_pressed = is_pressed; + } + PhysicalKey::Code(KeyCode::Space) => { + self.is_up_pressed = is_pressed; + } + PhysicalKey::Code(KeyCode::ShiftLeft) + | PhysicalKey::Code(KeyCode::ShiftRight) => { + self.is_down_pressed = is_pressed; + } + // Optional: Escape to release mouse capture + PhysicalKey::Code(KeyCode::Escape) if is_pressed && self.capture_mouse => { + self.capture_mouse = false; + self.is_rmb_pressed = false; // Ensure RMB state is also reset + self.window + .set_cursor_grab(winit::window::CursorGrabMode::None) + .unwrap_or(()); + self.window.set_cursor_visible(true); + self.mouse_delta = (0.0, 0.0); + } + _ => {} + } + } WindowEvent::RedrawRequested => { let now = Instant::now(); - let _delta_time = now.duration_since(self.last_frame_time); + let delta_time = now.duration_since(self.last_frame_time).as_secs_f32(); self.last_frame_time = now; - let elapsed_sice_last_update = now.duration_since(self.last_fps_update_time); + // --- FPS Calculation --- + let elapsed_since_last_update = now.duration_since(self.last_fps_update_time); self.frame_count += 1; - - 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); + if elapsed_since_last_update >= Duration::from_secs(1) { + self.current_fps = + self.frame_count as f64 / elapsed_since_last_update.as_secs_f64(); + let new_title = format!( + "{} - {} - {:.0} FPS", + ENGINE_NAME, APP_NAME, self.current_fps + ); self.window.set_title(&new_title); - self.frame_count = 0; self.last_fps_update_time = now; } + self.update_camera(delta_time); // Call the new update function + let raw_input = self.egui_winit.take_egui_input(&self.window); let egui::FullOutput { @@ -417,7 +577,8 @@ impl Application { pixels_per_point, .. } = self.egui_ctx.run(raw_input, |ctx| { - self.egui_app.build_ui(ctx, self.current_fps); + self.egui_app + .build_ui(ctx, self.current_fps, &mut self.camera_info); }); self.renderer.update_textures(textures_delta).unwrap(); @@ -431,7 +592,7 @@ impl Application { match self.renderer.render_frame( pixels_per_point, &clipped_primitives, - self.egui_app.camera_info, + self.camera_info, ) { Ok(_) => { self.window.request_redraw(); @@ -454,6 +615,106 @@ impl Application { _ => {} } } + + // --- New Camera Update Function --- + fn update_camera(&mut self, dt: f32) { + if !self.capture_mouse + && self.mouse_delta == (0.0, 0.0) + && !self.is_forward_pressed + && !self.is_backward_pressed + && !self.is_left_pressed + && !self.is_right_pressed + && !self.is_up_pressed + && !self.is_down_pressed + { + return; // No input, no update needed + } + + let mut cam_pos = self.camera_info.camera_pos; + let mut cam_target = self.camera_info.camera_target; + let cam_up = self.camera_info.camera_up; // Usually Vec3::Y + + // --- Mouse Look (Rotation) --- + if self.capture_mouse && self.mouse_delta != (0.0, 0.0) { + let (delta_x, delta_y) = self.mouse_delta; + self.mouse_delta = (0.0, 0.0); // Consume the delta + + let sensitivity = self.camera_sensitivity; + let yaw_delta = delta_x as f32 * sensitivity; + let pitch_delta = delta_y as f32 * sensitivity; + + let forward_dir = (cam_target - cam_pos).normalize(); + let right_dir = forward_dir.cross(cam_up).normalize(); + // Recalculate up to prevent roll if needed, though cross product handles it here + let current_up = right_dir.cross(forward_dir).normalize(); + + // --- Pitch (Up/Down) --- + // Calculate new forward direction based on pitch rotation around right axis + let pitch_quat = glam::Quat::from_axis_angle(right_dir, -pitch_delta); // Negative for standard mouse look + let mut new_forward = pitch_quat * forward_dir; + + // Clamp pitch to avoid flipping over (e.g., +/- 89 degrees) + let max_pitch_angle = 89.0f32.to_radians(); + let current_pitch = new_forward.angle_between(cam_up) - 90.0f32.to_radians(); + if current_pitch.abs() > max_pitch_angle { + // Revert pitch if it exceeds limits + new_forward = forward_dir; // Keep previous forward if clamp needed + } + + // --- Yaw (Left/Right) --- + // Rotate the (potentially pitch-adjusted) forward direction and right vector around the global up axis (Y) + let yaw_quat = glam::Quat::from_axis_angle(Vec3::Y, -yaw_delta); // Negative for standard mouse look + new_forward = yaw_quat * new_forward; + + // Update target based on the new forward direction + cam_target = cam_pos + new_forward; + + // Update the camera's internal up vector based on yaw rotation as well + // This prevents weird tilting when looking straight up/down if up wasn't Vec3::Y + // self.camera_info.camera_up = yaw_quat * current_up; // Optional: only if up can change + } + + // --- Keyboard Movement --- + let forward_dir = (cam_target - cam_pos).normalize(); + // Use Vec3::Y for world-relative right/up movement, or calculate from forward/up + let right_dir = forward_dir.cross(Vec3::Y).normalize(); + // let up_dir = right_dir.cross(forward_dir).normalize(); // Camera's local up + let world_up_dir = Vec3::Y; // Use world up for space/shift + + let effective_speed = self.camera_speed * dt; + let mut move_delta = Vec3::ZERO; + + if self.is_forward_pressed { + move_delta += forward_dir; + } + if self.is_backward_pressed { + move_delta -= forward_dir; + } + if self.is_left_pressed { + move_delta -= right_dir; + } + if self.is_right_pressed { + move_delta += right_dir; + } + if self.is_up_pressed { + move_delta += world_up_dir; // Move along world Y + } + if self.is_down_pressed { + move_delta -= world_up_dir; // Move along world Y + } + + // Normalize move_delta if non-zero to ensure consistent speed diagonally + if move_delta != Vec3::ZERO { + let move_vec = move_delta.normalize() * effective_speed; + cam_pos += move_vec; + cam_target += move_vec; // Move target along with position + } + + // --- Apply Changes --- + self.camera_info.camera_pos = cam_pos; + self.camera_info.camera_target = cam_target; + // self.camera_info.camera_up remains Vec3::Y usually + } } // --- Helper Functions --- diff --git a/crates/renderer/Cargo.toml b/crates/renderer/Cargo.toml index 9da1bc2..c82ec34 100644 --- a/crates/renderer/Cargo.toml +++ b/crates/renderer/Cargo.toml @@ -17,6 +17,7 @@ winit.workspace = true gfx_hal = { path = "../gfx_hal" } resource_manager = { path = "../resource_manager" } shared = { path = "../shared" } +scene = { path = "../scene" } [build-dependencies] shaderc = "0.9.1" diff --git a/crates/renderer/src/lib.rs b/crates/renderer/src/lib.rs index 5e32a9c..fd69ff9 100644 --- a/crates/renderer/src/lib.rs +++ b/crates/renderer/src/lib.rs @@ -1,4 +1,5 @@ use std::{ + collections::HashMap, ffi::c_void, mem, sync::{Arc, Mutex}, @@ -17,12 +18,15 @@ use gpu_allocator::{ vulkan::{Allocation, AllocationCreateDesc, Allocator}, MemoryLocation, }; -use resource_manager::{Geometry, ImageHandle, ResourceManager, ResourceManagerError}; +use resource_manager::{ + ImageHandle, Material, ResourceManager, ResourceManagerError, SamplerHandle, Texture, +}; use shared::{CameraInfo, UniformBufferObject}; use thiserror::Error; use tracing::{debug, error, info, warn}; const MAX_FRAMES_IN_FLIGHT: usize = 2; +const MAX_MATERIALS: usize = 150; #[derive(Debug, Error)] pub enum RendererError { @@ -60,6 +64,9 @@ pub enum RendererError { AllocatorUnavailable, // Added based on egui requirement #[error("Allocator Error: {0}")] AllocatorError(#[from] gpu_allocator::AllocationError), + + #[error("Other Error: {0}")] + Other(String), } impl From> for RendererError { @@ -101,11 +108,13 @@ pub struct Renderer { swapchain_format: vk::SurfaceFormatKHR, swapchain_extent: vk::Extent2D, - scene: Vec, + scene: scene::Scene, descriptor_set_layout: vk::DescriptorSetLayout, descriptor_pool: vk::DescriptorPool, + material_descriptor_set_layout: vk::DescriptorSetLayout, + egui_renderer: EguiRenderer, depth_image_handle: ImageHandle, @@ -115,6 +124,11 @@ pub struct Renderer { model_pipeline_layout: vk::PipelineLayout, model_pipeline: vk::Pipeline, + material_descriptor_sets: HashMap, + + default_white_texture: Option>, + default_sampler: SamplerHandle, + frames_data: Vec, current_frame: usize, @@ -134,7 +148,7 @@ impl Renderer { graphics_queue: Arc, surface: Arc, resource_manager: Arc, - scene: Vec, + scene: scene::Scene, initial_width: u32, initial_height: u32, ) -> Result { @@ -154,14 +168,18 @@ impl Renderer { let (depth_image_handle, depth_image_view) = Self::create_depth_resources(&device, &resource_manager, extent, depth_format)?; - let (descriptor_set_layout, descriptor_pool) = - Self::create_descriptor_sets_resources(&device)?; + let descriptor_set_layout = Self::create_descriptor_set_layout(&device)?; + let material_descriptor_set_layout = Self::create_material_descriptor_set_layout(&device)?; + + let descriptor_set_layouts = [descriptor_set_layout, material_descriptor_set_layout]; + + let descriptor_pool = Self::create_descriptor_pool(&device)?; let (model_pipeline_layout, model_pipeline) = Self::create_model_pipeline( &device, format.format, depth_format, - descriptor_set_layout, + &descriptor_set_layouts, )?; let start_time = Instant::now(); @@ -170,9 +188,8 @@ impl Renderer { &device, &resource_manager, descriptor_pool, - descriptor_set_layout, + &descriptor_set_layouts, swapchain.extent(), - start_time, )?; info!("Renderer initialized successfully."); @@ -191,6 +208,13 @@ impl Renderer { }, )?; + let default_sampler = resource_manager.get_or_create_sampler(&Default::default())?; + + let default_white_texture = Some(Self::create_default_texture( + device.clone(), + resource_manager.clone(), + )); + Ok(Self { device, graphics_queue, @@ -204,11 +228,19 @@ impl Renderer { swapchain_extent: extent, descriptor_set_layout, descriptor_pool, + + material_descriptor_set_layout, depth_image_handle, depth_image_view, depth_format, model_pipeline_layout, model_pipeline, + + material_descriptor_sets: HashMap::new(), + + default_white_texture, + default_sampler, + frames_data, scene, current_frame: 0, @@ -220,6 +252,134 @@ impl Renderer { }) } + /// Gets or creates/updates a descriptor set for a given material. + fn get_or_create_material_set( + &mut self, + material: &Arc, // Use Arc directly if hashable, or use a unique ID + ) -> Result { + // Return generic error + + // Use a unique identifier for the material instance if Arc isn't directly hashable + // or if pointer comparison isn't reliable across runs/reloads. + // For simplicity here, we use the Arc's pointer address as a key. + // WARNING: This is only safe if the Arc instances are stable! + // A better key might be derived from material.name or a generated ID. + let material_key = Arc::as_ptr(material) as usize; + + if let Some(set) = self.material_descriptor_sets.get(&material_key) { + return Ok(*set); + } + + // --- Allocate Descriptor Set --- + let layouts = [self.material_descriptor_set_layout]; + let alloc_info = vk::DescriptorSetAllocateInfo::default() + .descriptor_pool(self.descriptor_pool) + .set_layouts(&layouts); + + let descriptor_set = unsafe { self.device.raw().allocate_descriptor_sets(&alloc_info)? }[0]; + + // --- Update Descriptor Set --- + let (image_handle, view_handle, sampler_handle) = match &material.base_color_texture { + Some(texture) => { + // Get the default view handle associated with the image + let img_info = self.resource_manager.get_image_info(texture.handle)?; + let view_h = img_info.default_view_handle.ok_or(RendererError::Other( + "Image missing default view handle".to_string(), + ))?; + // Use the sampler specified by the material, or the default + let sampler_h = material.base_color_sampler.unwrap_or(self.default_sampler); + (texture.handle, view_h, sampler_h) + } + None => { + // Use default white texture + let default_tex = + self.default_white_texture + .as_ref() + .ok_or(RendererError::Other( + "Default texture not created".to_string(), + ))?; + let img_info = self.resource_manager.get_image_info(default_tex.handle)?; + let view_h = img_info.default_view_handle.ok_or(RendererError::Other( + "Default image missing default view handle".to_string(), + ))?; + (default_tex.handle, view_h, self.default_sampler) + } + }; + + // Get the actual Vulkan handles + let image_view_info = self.resource_manager.get_image_view_info(view_handle)?; + let sampler_info = self.resource_manager.get_sampler_info(sampler_handle)?; + + let image_descriptor_info = vk::DescriptorImageInfo::default() + .image_layout(vk::ImageLayout::SHADER_READ_ONLY_OPTIMAL) // Expected layout for sampling + .image_view(image_view_info.view) // The vk::ImageView + .sampler(sampler_info.sampler); // The vk::Sampler + + let writes = [ + // Write for binding 0 (baseColorSampler) + vk::WriteDescriptorSet::default() + .dst_set(descriptor_set) + .dst_binding(0) + .dst_array_element(0) + .descriptor_type(vk::DescriptorType::COMBINED_IMAGE_SAMPLER) + .image_info(std::slice::from_ref(&image_descriptor_info)), + // Add writes for other bindings (normal map, etc.) here + ]; + + unsafe { + self.device.raw().update_descriptor_sets(&writes, &[]); // Update the set + } + + // Store in cache + self.material_descriptor_sets + .insert(material_key, descriptor_set); + Ok(descriptor_set) + } + + fn create_default_texture( + device: Arc, // Need device Arc for RM + resource_manager: Arc, + ) -> Arc { + let width = 1; + let height = 1; + let data = [255u8, 255, 255, 255]; // White RGBA + let format = vk::Format::R8G8B8A8_UNORM; // Or SRGB if preferred + + let create_info = vk::ImageCreateInfo::default() + .image_type(vk::ImageType::TYPE_2D) + .format(format) + .extent(vk::Extent3D { + width, + height, + depth: 1, + }) + .mip_levels(1) + .array_layers(1) + .samples(vk::SampleCountFlags::TYPE_1) + .tiling(vk::ImageTiling::OPTIMAL) + .usage(vk::ImageUsageFlags::SAMPLED | vk::ImageUsageFlags::TRANSFER_DST) + .initial_layout(vk::ImageLayout::UNDEFINED); + + let handle = resource_manager + .create_image_init( + &create_info, + gpu_allocator::MemoryLocation::GpuOnly, + vk::ImageAspectFlags::COLOR, + &data, + ) + .expect("Failed to create default white texture"); + + Arc::new(Texture { + handle, + format: vk::Format::R8G8B8A8_UNORM, + extent: vk::Extent3D { + width: 1, + height: 1, + depth: 1, + }, + }) + } + pub fn resize(&mut self, width: u32, height: u32) { if width > 0 && height > 0 { self.window_resized = true; @@ -425,9 +585,7 @@ impl Renderer { .cmd_set_scissor(command_buffer, 0, &[scissor]); } - // --- Draw Triangle --- unsafe { - // Need unsafe for Vulkan commands self.device.raw().cmd_bind_pipeline( command_buffer, vk::PipelineBindPoint::GRAPHICS, @@ -444,10 +602,48 @@ impl Renderer { ); } - for g in &self.scene { - g.draw(self.device.raw(), command_buffer)?; + let meshes = self.scene.meshes.clone(); + + for mesh in meshes { + let material_set = self.get_or_create_material_set(&mesh.material)?; + + unsafe { + self.device.raw().cmd_bind_descriptor_sets( + command_buffer, + vk::PipelineBindPoint::GRAPHICS, + self.model_pipeline_layout, + 1, + &[material_set], + &[], + ); + } + + let model_matrix_bytes = unsafe { + std::slice::from_raw_parts( + mesh.transform.as_ref().as_ptr() as *const u8, + std::mem::size_of::(), + ) + }; + + unsafe { + self.device.raw().cmd_push_constants( + command_buffer, + self.model_pipeline_layout, + vk::ShaderStageFlags::VERTEX, + 0, + model_matrix_bytes, + ); + } + + mesh.geometry.draw(self.device.raw(), command_buffer)?; } + let frame_data = &mut self.frames_data[self.current_frame]; + let swapchain_ref = self + .swapchain + .as_ref() + .ok_or(RendererError::SwapchainAcquisitionFailed)?; + tracing::trace!("Rendering EGUI"); self.egui_renderer.cmd_draw( command_buffer, @@ -716,7 +912,7 @@ impl Renderer { device: &Arc, color_format: vk::Format, depth_format: vk::Format, - descriptor_set_layout: vk::DescriptorSetLayout, + descriptor_set_layouts: &[vk::DescriptorSetLayout], ) -> Result<(vk::PipelineLayout, vk::Pipeline), RendererError> { // Load compiled SPIR-V (replace with actual loading) let vert_shader_code = include_bytes!(concat!(env!("OUT_DIR"), "/shaders/vert.glsl.spv")); // Placeholder path @@ -787,9 +983,15 @@ impl Renderer { let dynamic_state = vk::PipelineDynamicStateCreateInfo::default().dynamic_states(&dynamic_states); + let push_constant_range = vk::PushConstantRange::default() + .stage_flags(vk::ShaderStageFlags::VERTEX) + .offset(0) + .size(mem::size_of::() as u32); + // --- Pipeline Layout --- let layout_info = vk::PipelineLayoutCreateInfo::default() - .set_layouts(std::slice::from_ref(&descriptor_set_layout)); // No descriptors/push constants + .set_layouts(descriptor_set_layouts) + .push_constant_ranges(std::slice::from_ref(&push_constant_range)); let pipeline_layout = unsafe { device .raw() @@ -891,9 +1093,8 @@ impl Renderer { device: &Arc, resource_manager: &Arc, descriptor_pool: vk::DescriptorPool, - descriptor_set_layout: vk::DescriptorSetLayout, + descriptor_set_layouts: &[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 { @@ -930,15 +1131,14 @@ impl Renderer { tracing::info!("Allocated frame_data command_buffer: {:?}", command_buffer); let descriptor_set = - Self::create_descriptor_set(device, descriptor_set_layout, descriptor_pool)?; + Self::create_descriptor_set(device, descriptor_set_layouts, 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); + let uniform_buffer_object = calculate_ubo(CameraInfo::default(), swapchain_extent); frames_data.push(FrameData { textures_to_free: None, @@ -1043,9 +1243,37 @@ impl Renderer { )) } - fn create_descriptor_sets_resources( + fn create_material_descriptor_set_layout( device: &Arc, - ) -> Result<(vk::DescriptorSetLayout, vk::DescriptorPool), RendererError> { + ) -> Result { + let bindings = [ + // Binding 0: Combined Image Sampler (baseColorSampler) + vk::DescriptorSetLayoutBinding::default() + .binding(0) + .descriptor_type(vk::DescriptorType::COMBINED_IMAGE_SAMPLER) + .descriptor_count(1) + .stage_flags(vk::ShaderStageFlags::FRAGMENT), // Used in fragment shader + // Add more bindings here if needed (e.g., for normal map, metallic/roughness map) + // Binding 1: Uniform Buffer (Optional: for material factors) + // vk::DescriptorSetLayoutBinding::default() + // .binding(1) + // .descriptor_type(vk::DescriptorType::UNIFORM_BUFFER) + // .descriptor_count(1) + // .stage_flags(vk::ShaderStageFlags::FRAGMENT), + ]; + + let layout_info = vk::DescriptorSetLayoutCreateInfo::default().bindings(&bindings); + + Ok(unsafe { + device + .raw() + .create_descriptor_set_layout(&layout_info, None)? + }) + } + + fn create_descriptor_set_layout( + device: &Arc, + ) -> Result { let ubo_layout_binding = vk::DescriptorSetLayoutBinding::default() .binding(0) .descriptor_type(vk::DescriptorType::UNIFORM_BUFFER) @@ -1061,29 +1289,38 @@ impl Renderer { .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, - }; + Ok(descriptor_set_layout) + } + + fn create_descriptor_pool(device: &Arc) -> Result { + let pool_sizes = [ + vk::DescriptorPoolSize { + ty: vk::DescriptorType::UNIFORM_BUFFER, + descriptor_count: MAX_FRAMES_IN_FLIGHT as u32, + }, + vk::DescriptorPoolSize { + ty: vk::DescriptorType::COMBINED_IMAGE_SAMPLER, + descriptor_count: MAX_MATERIALS as u32, + }, + ]; let pool_info = vk::DescriptorPoolCreateInfo::default() - .pool_sizes(std::slice::from_ref(&pool_size)) - .max_sets(MAX_FRAMES_IN_FLIGHT as u32); + .pool_sizes(&pool_sizes) + .max_sets(MAX_FRAMES_IN_FLIGHT as u32 + MAX_MATERIALS as u32); let descriptor_pool = unsafe { device.raw().create_descriptor_pool(&pool_info, None)? }; - Ok((descriptor_set_layout, descriptor_pool)) + Ok(descriptor_pool) } fn create_descriptor_set( device: &Arc, - descriptor_set_layout: vk::DescriptorSetLayout, + descriptor_set_layouts: &[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); + .set_layouts(descriptor_set_layouts); let descriptor_set = unsafe { device.raw().allocate_descriptor_sets(&alloc_info)? }[0]; @@ -1167,7 +1404,7 @@ impl Renderer { 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); + let ubo = calculate_ubo(camera_info, self.swapchain_extent); if frame_data.uniform_buffer_object != ubo { let ptr = frame_data.uniform_buffer_mapped_ptr; @@ -1181,15 +1418,7 @@ impl Renderer { } } -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()); - +fn calculate_ubo(camera_info: CameraInfo, swapchain_extent: vk::Extent2D) -> UniformBufferObject { let view = Mat4::look_at_rh(camera_info.camera_pos, camera_info.camera_target, Vec3::Y); let mut proj = Mat4::perspective_rh( @@ -1201,7 +1430,7 @@ fn calculate_ubo( proj.y_axis.y *= -1.0; - UniformBufferObject { model, view, proj } + UniformBufferObject { view, proj } } // --- Drop Implementation --- diff --git a/crates/resource_manager/Cargo.toml b/crates/resource_manager/Cargo.toml index f71fc5d..bde4cc1 100644 --- a/crates/resource_manager/Cargo.toml +++ b/crates/resource_manager/Cargo.toml @@ -8,5 +8,7 @@ ash.workspace = true gpu-allocator.workspace = true thiserror.workspace = true tracing.workspace = true +gltf.workspace = true gfx_hal = { path = "../gfx_hal" } +image = { version = "0.25.6", features = ["rayon"] } diff --git a/crates/resource_manager/src/error.rs b/crates/resource_manager/src/error.rs index 669427d..96906d6 100644 --- a/crates/resource_manager/src/error.rs +++ b/crates/resource_manager/src/error.rs @@ -34,6 +34,12 @@ pub enum ResourceManagerError { #[error("Error occurred in GfxHal: {0}")] GfxHalError(#[from] gfx_hal::error::GfxHalError), + #[error("I/O Error occurred: {0}")] + Io(#[from] std::io::Error), + + #[error("Image Error occurred: {0}")] + ImageError(#[from] image::ImageError), + #[error("An unexpected error occurred: {0}")] Other(String), } diff --git a/crates/resource_manager/src/geo.rs b/crates/resource_manager/src/geo.rs index 3af2e71..c78c467 100644 --- a/crates/resource_manager/src/geo.rs +++ b/crates/resource_manager/src/geo.rs @@ -13,6 +13,7 @@ unsafe fn as_byte_slice(data: &[T]) -> &[u8] { /// Represents geometry data (verticies and indicies) stored in GPU buffers managed by /// ResourceManager. Handles automatic cleanup via a `Drop` implementation. +#[derive(Clone)] pub struct Geometry { resource_manager: Arc, pub vertex_buffer: BufferHandle, diff --git a/crates/resource_manager/src/lib.rs b/crates/resource_manager/src/lib.rs index 518fc12..4a3ec7a 100644 --- a/crates/resource_manager/src/lib.rs +++ b/crates/resource_manager/src/lib.rs @@ -1,9 +1,12 @@ mod error; mod geo; +mod texture; use std::{ collections::HashMap, + fs, hash::Hash, + path::{Path, PathBuf}, sync::{ atomic::{AtomicU64, Ordering}, Arc, Mutex, @@ -16,6 +19,7 @@ use tracing::{debug, error, trace, warn}; pub use error::{ResourceManagerError, Result}; pub use geo::Geometry; +pub use texture::{Material, SamplerDesc, Texture}; use gpu_allocator::{ vulkan::{Allocation, AllocationCreateDesc, Allocator, AllocatorCreateDesc}, @@ -28,6 +32,12 @@ pub struct BufferHandle(u64); #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub struct ImageHandle(u64); +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] // New Handle +pub struct ImageViewHandle(u64); + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub struct SamplerHandle(u64); + #[derive(Debug, Clone)] pub struct BufferInfo { pub handle: BufferHandle, @@ -40,14 +50,21 @@ pub struct BufferInfo { #[derive(Debug, Clone)] pub struct ImageInfo { pub handle: ImageHandle, - /// Non-owning handle. pub image: vk::Image, - /// Non-owning handle. - pub view: vk::ImageView, + // pub view: vk::ImageView, // Removed raw view + pub default_view_handle: Option, // Added handle to default view pub format: vk::Format, pub extent: vk::Extent3D, pub usage: vk::ImageUsageFlags, pub layout: vk::ImageLayout, + pub mapped_ptr: Option<*mut u8>, +} + +#[derive(Debug, Clone)] +pub struct SamplerInfo { + pub handle: SamplerHandle, + pub sampler: vk::Sampler, + pub desc: SamplerDesc, // Include desc if useful } struct InternalBufferInfo { @@ -82,11 +99,12 @@ impl Drop for InternalBufferInfo { } struct InternalImageInfo { - device: Arc, // Keep device alive for Drop - allocator: Arc>, // Needed for Drop + device: Arc, + allocator: Arc>, image: vk::Image, - view: vk::ImageView, - allocation: Option, // Option because it's taken in Drop + // view: vk::ImageView, // Removed raw view + default_view_handle: Option, // Added handle + allocation: Option, format: vk::Format, extent: vk::Extent3D, usage: vk::ImageUsageFlags, @@ -97,10 +115,7 @@ struct InternalImageInfo { impl Drop for InternalImageInfo { fn drop(&mut self) { trace!("Dropping InternalImageInfo for handle {:?}", self.handle); - // Destroy view first - unsafe { - self.device.raw().destroy_image_view(self.view, None); - } + // Then free memory if let Some(allocation) = self.allocation.take() { let mut allocator = self.allocator.lock().expect("to acquire mutex lock"); @@ -124,6 +139,51 @@ impl Drop for InternalImageInfo { } } +struct InternalSamplerInfo { + device: Arc, + sampler: vk::Sampler, + handle: SamplerHandle, + desc: SamplerDesc, +} + +struct InternalImageViewInfo { + device: Arc, // Keep device alive for Drop + view: vk::ImageView, + handle: ImageViewHandle, + // Optional: Store the ImageHandle this view belongs to for debugging/validation + // image_handle: ImageHandle, +} + +#[derive(Debug, Clone)] +pub struct ImageViewInfo { + pub handle: ImageViewHandle, + pub view: vk::ImageView, + // Could add format, subresource range etc. if needed frequently +} + +impl Drop for InternalImageViewInfo { + fn drop(&mut self) { + trace!( + "Dropping InternalImageViewInfo for handle {:?}", + self.handle + ); + unsafe { + self.device.raw().destroy_image_view(self.view, None); + } + trace!("Destroyed vk::ImageView for handle {:?}", self.handle); + } +} + +impl Drop for InternalSamplerInfo { + fn drop(&mut self) { + trace!("Dropping InternalSamplerInfo for handle {:?}", self.handle); + unsafe { + self.device.raw().destroy_sampler(self.sampler, None); + } + trace!("Destroyed vk::Sampler for handle {:?}", self.handle); + } +} + struct TransferSetup { command_pool: vk::CommandPool, queue: Arc, @@ -136,6 +196,10 @@ pub struct ResourceManager { allocator: Arc>, buffers: Arc>>, images: Arc>>, + image_views: Arc>>, + samplers: Arc>>, + sampler_cache_by_desc: Arc>>, + texture_cache_uri: Arc>>>, next_id: AtomicU64, transfer_setup: Arc>, } @@ -182,6 +246,10 @@ impl ResourceManager { allocator: Arc::new(Mutex::new(allocator)), buffers: Arc::new(Mutex::new(HashMap::new())), images: Arc::new(Mutex::new(HashMap::new())), + image_views: Arc::new(Mutex::new(HashMap::new())), // Initialize view map + samplers: Arc::new(Mutex::new(HashMap::new())), + sampler_cache_by_desc: Arc::new(Mutex::new(HashMap::new())), + texture_cache_uri: Arc::new(Mutex::new(HashMap::new())), next_id: AtomicU64::new(1), transfer_setup: Arc::new(Mutex::new(new_setup)), }) @@ -306,11 +374,11 @@ impl ResourceManager { Ok(handle) } - /// Creates a buffer, allocates memory, and uploads initial data using a staging buffer. + // Make sure create_buffer_init is correct and doesn't call itself pub fn create_buffer_init( &self, - usage: vk::BufferUsageFlags, // Usage for the *final* buffer - location: MemoryLocation, // Memory location for the *final* buffer + usage: vk::BufferUsageFlags, + location: MemoryLocation, data: &[u8], ) -> Result { let size = data.len() as vk::DeviceSize; @@ -324,14 +392,16 @@ impl ResourceManager { size, usage, location ); - // 1. Create Staging Buffer - let staging_usage = vk::BufferUsageFlags::TRANSFER_SRC; - let staging_location = MemoryLocation::CpuToGpu; // Mapped memory for upload - let staging_handle = self.create_buffer(size, staging_usage, staging_location)?; + // 1. Create Staging Buffer (CPU accessible) + let staging_handle = self.create_buffer( + // Call create_buffer, NOT create_buffer_init + size, + vk::BufferUsageFlags::TRANSFER_SRC, + MemoryLocation::CpuToGpu, + )?; // 2. Map & Copy data to staging buffer { - // Scope for buffer info and mapping pointer let staging_info = self.get_buffer_info(staging_handle)?; let mapping = staging_info .mapped_ptr @@ -339,42 +409,45 @@ impl ResourceManager { unsafe { std::ptr::copy_nonoverlapping(data.as_ptr(), mapping, data.len()); } - // If memory is not HOST_COHERENT, need to flush here: - // let mem_range = vk::MappedMemoryRange::builder().memory(...).offset(...).size(size); - // unsafe { self.device.raw().flush_mapped_memory_ranges(&[mem_range])? }; - trace!("Data copied to staging buffer."); - } // staging_info goes out of scope + // Optional: Flush + trace!("Data copied to staging buffer handle {:?}", staging_handle); + } // 3. Create Destination Buffer - let final_usage = usage | vk::BufferUsageFlags::TRANSFER_DST; // Add transfer dest usage - let dest_handle = self.create_buffer(size, final_usage, location)?; + let final_usage = usage | vk::BufferUsageFlags::TRANSFER_DST; + let dest_handle = self.create_buffer(size, final_usage, location)?; // Call create_buffer - // 4. Record and submit transfer command - 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 + // 4. Perform Copy via Command Buffer + { + let transfer_setup_locked = self.transfer_setup.lock()?; + let buffers_locked = self.buffers.lock()?; - trace!("Submitting buffer copy command..."); - unsafe { - self.submit_commands_and_wait(&transfer_setup, |cmd| { - let region = vk::BufferCopy::default() - .src_offset(0) - .dst_offset(0) - .size(size); - self.device.raw().cmd_copy_buffer( - cmd, - staging_info_for_copy.buffer, // Use raw handle from info struct - dest_info.buffer, // Use raw handle from info struct - &[region], - ); - Ok(()) // Return Ok inside the closure - })?; + let dest_internal = buffers_locked + .get(&dest_handle.0) + .ok_or(ResourceManagerError::HandleNotFound(dest_handle.0))?; + let staging_internal = buffers_locked + .get(&staging_handle.0) + .ok_or(ResourceManagerError::HandleNotFound(staging_handle.0))?; + + trace!("Submitting buffer copy command..."); + unsafe { + Self::submit_commands_and_wait(self, &transfer_setup_locked, |cmd| { + let region = vk::BufferCopy::default().size(size); + self.device.raw().cmd_copy_buffer( + cmd, + staging_internal.buffer, + dest_internal.buffer, + &[region], + ); + Ok(()) + })?; + } + trace!("Buffer copy command finished."); } - trace!("Buffer copy command finished."); // 5. Cleanup staging buffer - self.destroy_buffer(staging_handle)?; // This frees memory and destroys buffer - debug!("Staging buffer destroyed."); + self.destroy_buffer(staging_handle)?; + debug!("Staging buffer destroyed: handle={:?}", staging_handle); Ok(dest_handle) } @@ -397,9 +470,7 @@ impl ResourceManager { ); let image = unsafe { self.device.raw().create_image(create_info, None)? }; - let requirements = unsafe { self.device.raw().get_image_memory_requirements(image) }; - let allocation = self.allocator.lock()?.allocate(&AllocationCreateDesc { name: &format!( "image_fmt_{:?}_usage_{:?}", @@ -410,7 +481,6 @@ impl ResourceManager { linear: create_info.tiling == vk::ImageTiling::LINEAR, allocation_scheme: gpu_allocator::vulkan::AllocationScheme::GpuAllocatorManaged, })?; - unsafe { self.device .raw() @@ -418,22 +488,17 @@ impl ResourceManager { } trace!("Image memory bound."); - // Create a default image view - // TODO: Make view creation more flexible (allow different subresource ranges, types) - let view_info = vk::ImageViewCreateInfo::default() - .image(image) - .view_type(vk::ImageViewType::TYPE_2D) // Assuming 2D, adjust based on create_info - .format(create_info.format) - .subresource_range(vk::ImageSubresourceRange { - aspect_mask: aspect_flags, - base_mip_level: 0, - level_count: create_info.mip_levels, - base_array_layer: 0, - layer_count: create_info.array_layers, - }); - let view = unsafe { self.device.raw().create_image_view(&view_info, None)? }; - trace!("Default image view created."); + // 2. Create the *default* Image View using the new method + let default_view_create_info = Self::build_default_view_info(create_info, aspect_flags); + // Use internal helper to avoid locking images map again if called from create_image_view + let default_view_handle = + self.create_image_view_internal(image, &default_view_create_info)?; + trace!( + "Default image view created: handle={:?}", + default_view_handle + ); + // 3. Store InternalImageInfo let id = self.next_id.fetch_add(1, Ordering::Relaxed); let handle = ImageHandle(id); @@ -441,12 +506,12 @@ impl ResourceManager { device: self.device.clone(), allocator: self.allocator.clone(), image, - view, + default_view_handle: Some(default_view_handle), // Store handle allocation: Some(allocation), format: create_info.format, extent: create_info.extent, usage: create_info.usage, - layout: create_info.initial_layout, // Store initial layout + layout: create_info.initial_layout, handle, }; @@ -455,7 +520,658 @@ impl ResourceManager { Ok(handle) } - // TODO: Implement create_image_init (similar to create_buffer_init but uses vkCmdCopyBufferToImage and layout transitions) + fn build_default_view_info<'a>( + image_create_info: &vk::ImageCreateInfo, + aspect_flags: vk::ImageAspectFlags, + ) -> vk::ImageViewCreateInfo<'a> { + let view_type = match image_create_info.image_type { + vk::ImageType::TYPE_1D => { + if image_create_info.array_layers > 1 { + vk::ImageViewType::TYPE_1D_ARRAY + } else { + vk::ImageViewType::TYPE_1D + } + } + vk::ImageType::TYPE_3D => vk::ImageViewType::TYPE_3D, + _ => { + // TYPE_2D + if image_create_info + .flags + .contains(vk::ImageCreateFlags::CUBE_COMPATIBLE) + { + if image_create_info.array_layers > 6 { + vk::ImageViewType::CUBE_ARRAY + } else { + vk::ImageViewType::CUBE + } // Assumes 6 layers + } else if image_create_info.array_layers > 1 { + vk::ImageViewType::TYPE_2D_ARRAY + } else { + vk::ImageViewType::TYPE_2D + } + } + }; + + vk::ImageViewCreateInfo::default() + // .image(image) // Image is set by create_image_view_internal + .view_type(view_type) + .format(image_create_info.format) + .subresource_range( + vk::ImageSubresourceRange::default() + .aspect_mask(aspect_flags) + .base_mip_level(0) + .level_count(image_create_info.mip_levels) + .base_array_layer(0) + .layer_count(image_create_info.array_layers), + ) + // .components(...) // Default components usually fine + } + + /// Creates a new Vulkan ImageView for an existing Image. + /// The view's lifetime is managed by the ResourceManager. + pub fn create_image_view( + &self, + image_handle: ImageHandle, + view_create_info: &vk::ImageViewCreateInfo, // User provides desired view settings + ) -> Result { + trace!("Request to create image view for image {:?}", image_handle); + // 1. Get the vk::Image handle from the InternalImageInfo + let image_vk_handle = { + // Scope for images lock + let images_map = self.images.lock()?; + let internal_image_info = images_map + .get(&image_handle.0) + .ok_or(ResourceManagerError::HandleNotFound(image_handle.0))?; + internal_image_info.image // Copy the vk::Image handle + }; // Release images lock + + // 2. Call internal helper to create the view and manage it + self.create_image_view_internal(image_vk_handle, view_create_info) + } + + /// Internal helper to create and register an image view. + /// Takes the raw vk::Image to avoid re-locking the images map. + fn create_image_view_internal( + &self, + image: vk::Image, // The actual Vulkan image handle + view_create_info: &vk::ImageViewCreateInfo, + ) -> Result { + // Ensure the create info points to the correct image + let final_view_info = (*view_create_info).image(image); + + let view = unsafe { + self.device + .raw() + .create_image_view(&final_view_info, None)? + }; + trace!("vk::ImageView created."); + + let id = self.next_id.fetch_add(1, Ordering::Relaxed); + let handle = ImageViewHandle(id); + + let internal_view_info = InternalImageViewInfo { + device: self.device.clone(), + view, + handle, + // image_handle: image_handle, // Optional: Store originating image handle + }; + + // Add to the image_views map + self.image_views.lock()?.insert(id, internal_view_info); + debug!("Image view registered: handle={:?}", handle); + + Ok(handle) + } + + /// Creates an image, uploads data from a buffer, and transitions layout. + pub fn create_image_init( + &self, + create_info: &vk::ImageCreateInfo, // Must have usage TRANSFER_DST + location: MemoryLocation, // Usually GpuOnly for textures + aspect_flags: vk::ImageAspectFlags, + data: &[u8], + ) -> Result { + if data.is_empty() { + return Err(ResourceManagerError::Other( + "Cannot create image with empty data".to_string(), + )); + } + if !create_info + .usage + .contains(vk::ImageUsageFlags::TRANSFER_DST) + { + return Err(ResourceManagerError::Other( + "Image create info must include TRANSFER_DST usage for init".to_string(), + )); + } + // It's okay if initialLayout is not UNDEFINED, we override it for the creation step + // but the user might have specified it for other reasons. We just ensure the + // internal creation uses UNDEFINED. + // if create_info.initial_layout != vk::ImageLayout::UNDEFINED { + // warn!( + // "create_image_init expects initial_layout UNDEFINED, overriding." + // ); + // } + + let data_size = data.len() as vk::DeviceSize; + debug!( + "Creating image with init data: size={}, format={:?}, extent={:?}", + data_size, create_info.format, create_info.extent + ); + + // --- Corrected Flow --- + + // 1. Create Staging Buffer (CPU accessible for copy) + // Use create_buffer directly, then map and copy. + let staging_handle = self.create_buffer( + data_size, + vk::BufferUsageFlags::TRANSFER_SRC, + MemoryLocation::CpuToGpu, // Mapped memory for upload + )?; + // Map & Copy data to staging buffer + { + // Scope for buffer info and mapping pointer + let staging_info = self.get_buffer_info(staging_handle)?; // Lock buffers map + let mapping = staging_info + .mapped_ptr + .ok_or(ResourceManagerError::MappingFailed)?; + unsafe { + std::ptr::copy_nonoverlapping(data.as_ptr(), mapping, data.len()); + } + // Optional: Flush if memory is not HOST_COHERENT + // Check allocation properties if needed and flush + trace!("Data copied to staging buffer handle {:?}", staging_handle); + } // staging_info goes out of scope, unlocks buffers map + + // 2. Create Destination Image (ensure layout is UNDEFINED initially) + let mut final_create_info = *create_info; + final_create_info.initial_layout = vk::ImageLayout::UNDEFINED; // MUST start as undefined for transition + let image_handle = self.create_image(&final_create_info, location, aspect_flags)?; + + // 3. Perform Layout Transition (Undefined -> TransferDst) & Copy + { + let transfer_setup_locked = self.transfer_setup.lock()?; + let buffers_locked = self.buffers.lock()?; + let mut images_locked = self.images.lock()?; // Mut needed to update layout + + let image_internal = images_locked + .get_mut(&image_handle.0) // Get mut ref + .ok_or(ResourceManagerError::HandleNotFound(image_handle.0))?; + // Get the *correct* staging buffer info + let staging_internal = buffers_locked + .get(&staging_handle.0) // Use the handle created in step 1 + .ok_or(ResourceManagerError::HandleNotFound(staging_handle.0))?; + + let subresource_layers = vk::ImageSubresourceLayers::default() + .aspect_mask(aspect_flags) + .mip_level(0) + .base_array_layer(0) + .layer_count(create_info.array_layers); + + let copy_region = vk::BufferImageCopy::default() + .buffer_offset(0) + .buffer_row_length(0) // 0 means tightly packed + .buffer_image_height(0) // 0 means tightly packed + .image_subresource(subresource_layers) + .image_offset(vk::Offset3D::default()) + .image_extent(create_info.extent); + + trace!("Submitting image transition and copy command..."); + unsafe { + Self::submit_commands_and_wait(self, &transfer_setup_locked, |cmd| { + // Barrier 1: Undefined -> TransferDstOptimal + let (src_access, dst_access, src_stage, dst_stage) = Self::get_barrier_params( + vk::ImageLayout::UNDEFINED, + vk::ImageLayout::TRANSFER_DST_OPTIMAL, + ); + let barrier1 = vk::ImageMemoryBarrier::default() + .old_layout(vk::ImageLayout::UNDEFINED) + .new_layout(vk::ImageLayout::TRANSFER_DST_OPTIMAL) + .src_queue_family_index(vk::QUEUE_FAMILY_IGNORED) + .dst_queue_family_index(vk::QUEUE_FAMILY_IGNORED) + .image(image_internal.image) + .subresource_range( + vk::ImageSubresourceRange::default() + .aspect_mask(aspect_flags) + .base_mip_level(0) + .level_count(create_info.mip_levels) + .base_array_layer(0) + .layer_count(create_info.array_layers), + ) + .src_access_mask(src_access) + .dst_access_mask(dst_access); + + self.device.raw().cmd_pipeline_barrier( + cmd, + src_stage, + dst_stage, + vk::DependencyFlags::empty(), + &[], + &[], + &[barrier1], + ); + + // Copy Command (using correct staging buffer) + self.device.raw().cmd_copy_buffer_to_image( + cmd, + staging_internal.buffer, // Use buffer from staging_handle + image_internal.image, + vk::ImageLayout::TRANSFER_DST_OPTIMAL, // Layout during copy + &[copy_region], + ); + + // Barrier 2: TransferDstOptimal -> ShaderReadOnlyOptimal + let final_layout = vk::ImageLayout::SHADER_READ_ONLY_OPTIMAL; + let (src_access, dst_access, src_stage, dst_stage) = Self::get_barrier_params( + vk::ImageLayout::TRANSFER_DST_OPTIMAL, + final_layout, + ); + let barrier2 = vk::ImageMemoryBarrier::default() + .old_layout(vk::ImageLayout::TRANSFER_DST_OPTIMAL) + .new_layout(final_layout) + .src_queue_family_index(vk::QUEUE_FAMILY_IGNORED) + .dst_queue_family_index(vk::QUEUE_FAMILY_IGNORED) + .image(image_internal.image) + .subresource_range( + vk::ImageSubresourceRange::default() + .aspect_mask(aspect_flags) + .base_mip_level(0) + .level_count(create_info.mip_levels) + .base_array_layer(0) + .layer_count(create_info.array_layers), + ) + .src_access_mask(src_access) + .dst_access_mask(dst_access); + + self.device.raw().cmd_pipeline_barrier( + cmd, + src_stage, + dst_stage, + vk::DependencyFlags::empty(), + &[], + &[], + &[barrier2], + ); + + Ok(()) // Return Ok from the closure + })?; // End submit_commands_and_wait + } // Locks released (transfer_setup, buffers, images) + + drop(images_locked); + + let mut images_locked_update = self.images.lock()?; // Use different name to avoid confusion + if let Some(info) = images_locked_update.get_mut(&image_handle.0) { + info.layout = vk::ImageLayout::SHADER_READ_ONLY_OPTIMAL; // Update to final layout + trace!( + "Image {:?} layout updated internally to {:?}", + image_handle, + info.layout + ); + } else { + warn!( + "Image {:?} disappeared after creation during init!", + image_handle + ); + } + } // Scope for locks ends here + + // 4. Cleanup staging buffer (the one created in step 1) + self.destroy_buffer(staging_handle)?; // Destroy the correct handle + debug!( + "Staging buffer destroyed for image init: handle={:?}", + staging_handle + ); // Log handle + + tracing::info!( + "Image created and initialized successfully: handle={:?}", + image_handle + ); + Ok(image_handle) + } + + /// Gets non-owning information about a sampler. + pub fn get_sampler_info(&self, handle: SamplerHandle) -> Result { + let samplers_map = self + .samplers + .lock() + .map_err(|_| ResourceManagerError::Other("Sampler map mutex poisoned".to_string()))?; + samplers_map + .get(&handle.0) + .map(|internal| SamplerInfo { + handle: internal.handle, + sampler: internal.sampler, + desc: internal.desc.clone(), + }) + .ok_or(ResourceManagerError::HandleNotFound(handle.0)) + } + + /// Gets or creates a Vulkan sampler based on the description. + /// Uses caching to avoid creating duplicate samplers. + pub fn get_or_create_sampler(&self, desc: &SamplerDesc) -> Result { + let mut cache = self.sampler_cache_by_desc.lock()?; + if let Some(handle) = cache.get(desc) { + if self.samplers.lock()?.contains_key(&handle.0) { + trace!("Using cached sampler for desc: {:?}", desc); + return Ok(*handle); + } else { + warn!( + "Sampler handle {:?} found in cache but not main map. Removing from cache.", + handle + ); + cache.remove(desc); + } + } + + drop(cache); + + trace!("Creating a new sampler for desc: {:?}", desc); + let sampler_info = vk::SamplerCreateInfo::default() + .mag_filter(desc.mag_filter) + .min_filter(desc.min_filter) + .mipmap_mode(desc.mipmap_mode) + .address_mode_u(desc.address_mode_u) + .address_mode_v(desc.address_mode_v) + .address_mode_w(desc.address_mode_w) + .mip_lod_bias(0.0) + .anisotropy_enable(false) // TODO: Expose anisotropy in SamplerDesc? + .max_anisotropy(1.0) + .compare_enable(false) // TODO: Expose compare op? + .compare_op(vk::CompareOp::ALWAYS) + .min_lod(0.0) + .max_lod(vk::LOD_CLAMP_NONE) // TODO: Allow setting max LOD (e.g., for mipmapping) + .border_color(vk::BorderColor::INT_OPAQUE_BLACK) // TODO: Expose border color? + .unnormalized_coordinates(false); + + let vk_sampler = unsafe { self.device.raw().create_sampler(&sampler_info, None)? }; + + let id = self.next_id.fetch_add(1, Ordering::Relaxed); + let handle = SamplerHandle(id); + + let internal_info = InternalSamplerInfo { + device: self.device.clone(), + sampler: vk_sampler, + handle, + desc: desc.clone(), + }; + + self.samplers.lock()?.insert(id, internal_info); + self.sampler_cache_by_desc + .lock()? + .insert(desc.clone(), handle); + + debug!("Sampler created successfully: handle={:?}", handle); + Ok(handle) + } + + pub fn destroy_sampler(&self, handle: SamplerHandle) -> Result<()> { + debug!("Requesting destroy for sampler handle {:?}", handle); + let mut samplers_map = self.samplers.lock()?; + if let Some(internal_info) = samplers_map.remove(&handle.0) { + self.sampler_cache_by_desc + .lock()? + .remove(&internal_info.desc); + debug!("Sampler handle {:?} removed for destruction.", handle); + Ok(()) + } else { + warn!( + "Attempted to destroy non-existent sampler handle {:?}", + handle + ); + Err(ResourceManagerError::HandleNotFound(handle.0)) + } + } + + /// Loads a texture from a file path or embedded data using glTF info. + /// Handles image decoding, resource creation, upload and caching. + pub fn load_texture( + &self, + gltf_image: &gltf::Image, + gltf_source: &gltf::image::Source, + base_path: &Path, + buffers: &[gltf::buffer::Data], + usage: vk::ImageUsageFlags, + ) -> Result> { + let cache_key_path: Option = match gltf_source { + gltf::image::Source::View { view, mime_type } => todo!(), + gltf::image::Source::Uri { uri, mime_type } => { + let image_path = base_path.join(uri); + match fs::canonicalize(&image_path) { + Ok(canon_path) => Some(canon_path), + Err(e) => { + warn!( + "Failed to canonicalize image path {:?}: {} Skipping cache lookup", + image_path, e + ); + None + } + } + } + }; + + if let Some(ref path_key) = cache_key_path { + let uri_cache = self.texture_cache_uri.lock()?; + if let Some(cached_texture) = uri_cache.get(path_key) { + if self.images.lock()?.contains_key(&cached_texture.handle.0) { + trace!("Using cached texture (URI): {:?}", path_key); + return Ok(cached_texture.clone()); + } else { + warn!( + "Texture hadnle {:?} found in URI cache but not in main map. Will reload.", + cached_texture.handle + ); + } + } + } + + let (image_data, _format_hint) = match gltf_source { + gltf::image::Source::Uri { uri, mime_type } => { + let image_path = base_path.join(uri); + debug!("Loading texture from URI: {:?}", image_path); + let bytes = fs::read(&image_path).map_err(|e| { + error!("Failed to read image file {:?}: {}", image_path, e); + ResourceManagerError::Io(e) + })?; + (bytes, mime_type.map(|s| s.to_string())) + } + gltf::image::Source::View { view, mime_type } => { + debug!( + "Loading texture from buffer view: index={}, offset={}, length={}", + view.buffer().index(), + view.offset(), + view.length() + ); + let buffer_data = &buffers[view.buffer().index()]; + let start = view.offset(); + let end = start + view.length(); + if end > buffer_data.len() { + return Err(ResourceManagerError::Other(format!( + "Buffer view out of bounds for image: view index {}, buffer index {}", + view.index(), + view.buffer().index() + ))); + } + let bytes: Vec = buffer_data[start..end].to_vec(); // Clone data + (bytes, Some(mime_type.to_string())) // mime_type is required for View + } + }; + + let img = image::load_from_memory(&image_data)?; + + let rgba_image = img.to_rgba8(); + let (width, height) = rgba_image.dimensions(); + let raw_pixels = rgba_image.into_raw(); + + let vk_format = vk::Format::R8G8B8A8_SRGB; + + let extent = vk::Extent3D { + width, + height, + depth: 1, + }; + let image_create_info = vk::ImageCreateInfo::default() + .image_type(vk::ImageType::TYPE_2D) + .format(vk_format) + .extent(extent) + .mip_levels(1) // TODO: Add mipmap generation + .array_layers(1) + .samples(vk::SampleCountFlags::TYPE_1) + .tiling(vk::ImageTiling::OPTIMAL) // Textures should be optimal + .usage(usage | vk::ImageUsageFlags::TRANSFER_DST | vk::ImageUsageFlags::SAMPLED) // Ensure needed usages + .sharing_mode(vk::SharingMode::EXCLUSIVE) + .initial_layout(vk::ImageLayout::UNDEFINED); // create_image_init handles transition + + let image_handle = self.create_image_init( + &image_create_info, + MemoryLocation::GpuOnly, // Textures usually live on GPU + vk::ImageAspectFlags::COLOR, + &raw_pixels, + )?; + + let app_texture = Arc::new(Texture { + handle: image_handle, + format: vk_format, + extent, + }); + + if let Some(path_key) = cache_key_path { + self.texture_cache_uri + .lock()? + .insert(path_key, app_texture.clone()); + trace!("Texture added to URI cache."); + } + + Ok(app_texture) + } + + /// Transitions the layout of an image using a command buffer. + /// Updates the internal layout state. + pub fn transition_image_layout( + &self, + image_handle: ImageHandle, + new_layout: vk::ImageLayout, + ) -> Result<()> { + let transfer_setup_locked = self.transfer_setup.lock()?; + let mut images_locked = self.images.lock()?; + + let internal_info = images_locked + .get_mut(&image_handle.0) + .ok_or(ResourceManagerError::HandleNotFound(image_handle.0))?; + + let old_layout = internal_info.layout; + if old_layout == new_layout { + trace!( + "Image {:?} already in layout {:?}. Skipping transition.", + image_handle, + new_layout + ); + return Ok(()); + } + + trace!( + "Transitioning image {:?} layout {:?} -> {:?}", + image_handle, + old_layout, + new_layout + ); + + let aspect_mask = if new_layout == vk::ImageLayout::DEPTH_STENCIL_ATTACHMENT_OPTIMAL + || old_layout == vk::ImageLayout::DEPTH_STENCIL_ATTACHMENT_OPTIMAL + { + vk::ImageAspectFlags::DEPTH + } else { + vk::ImageAspectFlags::COLOR + }; + + let subresource_range = vk::ImageSubresourceRange::default() + .aspect_mask(aspect_mask) + .base_mip_level(0) + .level_count(1) + .base_array_layer(0) + .layer_count(1); + + unsafe { + Self::submit_commands_and_wait(self, &transfer_setup_locked, |cmd| { + let (src_access_mask, dst_access_mask, src_stage, dst_stage) = + Self::get_barrier_params(old_layout, new_layout); + + let barrier = vk::ImageMemoryBarrier::default() + .old_layout(old_layout) + .new_layout(new_layout) + .src_queue_family_index(vk::QUEUE_FAMILY_IGNORED) + .dst_queue_family_index(vk::QUEUE_FAMILY_IGNORED) + .image(internal_info.image) + .subresource_range(subresource_range) + .src_access_mask(src_access_mask) + .dst_access_mask(dst_access_mask); + + self.device.raw().cmd_pipeline_barrier( + cmd, + src_stage, + dst_stage, + vk::DependencyFlags::empty(), + &[], + &[], + &[barrier], + ); + Ok(()) + })?; + } + + internal_info.layout = new_layout; + debug!( + "Image {:?} layout transitioned to {:?} 🏳️‍⚧️", + image_handle, new_layout + ); + Ok(()) + } + + fn get_barrier_params( + old_layout: vk::ImageLayout, + new_layout: vk::ImageLayout, + ) -> ( + vk::AccessFlags, + vk::AccessFlags, + vk::PipelineStageFlags, + vk::PipelineStageFlags, + ) { + let src_access_mask; + let dst_access_mask; + let src_stage; + let dst_stage; + + match (old_layout, new_layout) { + (vk::ImageLayout::UNDEFINED, vk::ImageLayout::TRANSFER_DST_OPTIMAL) => { + src_access_mask = vk::AccessFlags::empty(); + dst_access_mask = vk::AccessFlags::TRANSFER_WRITE; + src_stage = vk::PipelineStageFlags::TOP_OF_PIPE; + dst_stage = vk::PipelineStageFlags::TRANSFER; + } + (vk::ImageLayout::TRANSFER_DST_OPTIMAL, vk::ImageLayout::SHADER_READ_ONLY_OPTIMAL) => { + src_access_mask = vk::AccessFlags::TRANSFER_WRITE; + dst_access_mask = vk::AccessFlags::SHADER_READ; + src_stage = vk::PipelineStageFlags::TRANSFER; + dst_stage = vk::PipelineStageFlags::FRAGMENT_SHADER; + } + (vk::ImageLayout::UNDEFINED, vk::ImageLayout::SHADER_READ_ONLY_OPTIMAL) => { + src_access_mask = vk::AccessFlags::empty(); + dst_access_mask = vk::AccessFlags::SHADER_READ; + src_stage = vk::PipelineStageFlags::TOP_OF_PIPE; + dst_stage = vk::PipelineStageFlags::FRAGMENT_SHADER; + } + _ => { + warn!( + "Unsupported layout trasnition: {:?} -> {:?}", + old_layout, new_layout + ); + src_access_mask = vk::AccessFlags::MEMORY_WRITE; + dst_access_mask = vk::AccessFlags::MEMORY_READ; + src_stage = vk::PipelineStageFlags::ALL_COMMANDS; + dst_stage = vk::PipelineStageFlags::ALL_COMMANDS; + } + } + + (src_access_mask, dst_access_mask, src_stage, dst_stage) + } /// Destroys a buffer and frees its memory. pub fn destroy_buffer(&self, handle: BufferHandle) -> Result<()> { @@ -513,23 +1229,47 @@ impl ResourceManager { .ok_or(ResourceManagerError::HandleNotFound(handle.0)) } - /// Gets non-owning information about an image. pub fn get_image_info(&self, handle: ImageHandle) -> Result { - let images_map = self.images.lock()?; + let images_map = self + .images + .lock() + .map_err(|_| ResourceManagerError::Other("Image map mutex poisoned".to_string()))?; images_map .get(&handle.0) - .map(|internal| ImageInfo { - handle: internal.handle, - image: internal.image, - view: internal.view, - format: internal.format, - extent: internal.extent, - usage: internal.usage, - layout: internal.layout, // Note: Layout tracking is basic here + .map(|internal| { + let mapped_ptr = internal + .allocation + .as_ref() + .and_then(|a| a.mapped_ptr().map(|p| p.as_ptr() as *mut u8)); + + ImageInfo { + handle: internal.handle, + image: internal.image, + default_view_handle: internal.default_view_handle, // Return handle + format: internal.format, + extent: internal.extent, + usage: internal.usage, + layout: internal.layout, + mapped_ptr, + } }) .ok_or(ResourceManagerError::HandleNotFound(handle.0)) } + /// Gets non-owning information about a specific image view. + pub fn get_image_view_info(&self, handle: ImageViewHandle) -> Result { + let views_map = self.image_views.lock().map_err(|_| { + ResourceManagerError::Other("Image view map mutex poisoned".to_string()) + })?; + views_map + .get(&handle.0) + .map(|internal| ImageViewInfo { + handle: internal.handle, + view: internal.view, + }) + .ok_or(ResourceManagerError::HandleNotFound(handle.0)) // Use handle.0 + } + /// Explicitly waits for the device to be idle. Useful before shutdown. pub fn wait_device_idle(&self) -> Result<(), ResourceManagerError> { self.device @@ -541,41 +1281,66 @@ impl ResourceManager { impl Drop for ResourceManager { fn drop(&mut self) { debug!("Destroying ResourceManager..."); - // Ensure all GPU operations are finished before freeing memory/destroying resources - if let Err(e) = self.device.wait_idle() { + // Wait for idle BEFORE locking/clearing maps + if let Err(e) = unsafe { self.device.raw().device_wait_idle() } { error!( "Failed to wait for device idle during ResourceManager drop: {}", e ); - // Proceeding with cleanup, but resources might still be in use! + // Proceeding, but cleanup might be unsafe } - // Clear resource maps. This triggers the Drop impl for each Internal*Info, - // which frees allocations and destroys Vulkan objects. - let mut buffers_map = self.buffers.lock().expect("mutex to not be poisoned"); - debug!("Clearing {} buffer entries...", buffers_map.len()); - buffers_map.clear(); - let mut images_map = self.images.lock().expect("mutex to not be poisoned"); - debug!("Clearing {} image entries...", images_map.len()); - images_map.clear(); - - let setup = self - .transfer_setup - .lock() - .expect("mutex to not be poisoned"); - - debug!("Destroying TransferSetup resources..."); - unsafe { - self.device - .raw() - .destroy_command_pool(setup.command_pool, None); + // Clear resource maps. This triggers the Drop impl for each Internal*Info. + if let Ok(mut buffers_map) = self.buffers.lock() { + debug!("Clearing {} buffer entries...", buffers_map.len()); + buffers_map.clear(); + } else { + error!("Buffer map mutex poisoned during drop."); } - 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. - // gpu-allocator's Allocator Drop implementation should be empty, as memory - // is freed via allocator.free(). + if let Ok(mut images_map) = self.images.lock() { + debug!("Clearing {} image entries...", images_map.len()); + images_map.clear(); + } else { + error!("Image map mutex poisoned during drop."); + } + + if let Ok(mut samplers_map) = self.samplers.lock() { + debug!("Clearing {} sampler entries...", samplers_map.len()); + samplers_map.clear(); + } else { + error!("Sampler map mutex poisoned during drop."); + } + + // Clear caches (Arc drops will happen naturally) + if let Ok(mut sampler_cache) = self.sampler_cache_by_desc.lock() { + sampler_cache.clear(); + } else { + error!("Sampler cache mutex poisoned during drop."); + } + if let Ok(mut texture_cache) = self.texture_cache_uri.lock() { + texture_cache.clear(); + } else { + error!("Texture URI cache mutex poisoned during drop."); + } + // Clear other caches... + + // Destroy transfer setup resources + if let Ok(setup) = self.transfer_setup.lock() { + debug!("Destroying TransferSetup resources..."); + unsafe { + self.device + .raw() + .destroy_command_pool(setup.command_pool, None); + } + debug!("TransferSetup resources destroyed."); + } else { + error!("TransferSetup mutex poisoned during drop."); + } + + // Allocator Drop: gpu-allocator's Allocator doesn't do anything in drop. + // Memory is freed via allocator.free() called by Internal*Info drops. + // The Arc> will be dropped when the last reference goes away. debug!("ResourceManager destroyed."); } diff --git a/crates/resource_manager/src/texture.rs b/crates/resource_manager/src/texture.rs new file mode 100644 index 0000000..857e0f5 --- /dev/null +++ b/crates/resource_manager/src/texture.rs @@ -0,0 +1,59 @@ +use std::sync::Arc; + +use ash::vk; + +use crate::{ImageHandle, SamplerHandle}; + +#[derive(Debug, Clone)] +pub struct Texture { + pub handle: ImageHandle, + + pub format: vk::Format, + pub extent: vk::Extent3D, +} + +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct SamplerDesc { + pub mag_filter: vk::Filter, + pub min_filter: vk::Filter, + pub mipmap_mode: vk::SamplerMipmapMode, + pub address_mode_u: vk::SamplerAddressMode, + pub address_mode_v: vk::SamplerAddressMode, + pub address_mode_w: vk::SamplerAddressMode, +} + +impl Default for SamplerDesc { + fn default() -> Self { + Self { + mag_filter: vk::Filter::LINEAR, + min_filter: vk::Filter::LINEAR, + mipmap_mode: vk::SamplerMipmapMode::LINEAR, + address_mode_u: vk::SamplerAddressMode::REPEAT, + address_mode_v: vk::SamplerAddressMode::REPEAT, + address_mode_w: vk::SamplerAddressMode::REPEAT, + } + } +} + +#[derive(Debug, Clone)] +pub struct Material { + pub name: String, + pub base_color_texture: Option>, + pub base_color_sampler: Option, + pub base_color_factor: [f32; 4], + pub metallic_factor: f32, + pub roughness_factor: f32, + // TODO: Add other PBR properties: + // pub metallic_roughness_texture: Option>, + // pub metallic_roughness_sampler: Option, + // pub normal_texture: Option>, + // pub normal_sampler: Option, + // pub occlusion_texture: Option>, + // pub occlusion_sampler: Option, + // pub emissive_texture: Option>, + // pub emissive_sampler: Option, + // pub emissive_factor: [f32; 3], + // pub alpha_mode: gltf::material::AlphaMode, + // pub alpha_cutoff: f32, + // pub double_sided: bool, +} diff --git a/crates/scene/Cargo.toml b/crates/scene/Cargo.toml new file mode 100644 index 0000000..6a67b50 --- /dev/null +++ b/crates/scene/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "scene" +version = "0.1.0" +edition = "2021" + +[dependencies] +ash.workspace = true +thiserror.workspace = true +tracing.workspace = true +glam.workspace = true +gltf.workspace = true + +shared = { path = "../shared" } +resource_manager = { path = "../resource_manager" } diff --git a/crates/scene/src/error.rs b/crates/scene/src/error.rs new file mode 100644 index 0000000..b052c9c --- /dev/null +++ b/crates/scene/src/error.rs @@ -0,0 +1,16 @@ +use thiserror::Error; + +/// Any errors that can be returned from this crate. +#[derive(Error, Debug)] +pub enum SceneError { + #[error("Error from ResourceManager: {0}")] + ResourceManagerError(#[from] resource_manager::ResourceManagerError), + + #[error("Error from GLTF: {0}")] + GltfError(#[from] gltf::Error), + + #[error("InconsistentData: {0}")] + InconsistentData(String), +} + +pub type Result = std::result::Result; diff --git a/crates/scene/src/lib.rs b/crates/scene/src/lib.rs new file mode 100644 index 0000000..fd672ac --- /dev/null +++ b/crates/scene/src/lib.rs @@ -0,0 +1,411 @@ +mod error; + +use ash::vk; +pub use error::{Result, SceneError}; +use glam::Mat4; +use shared::Vertex; + +use std::{collections::HashMap, path::Path, sync::Arc}; + +use resource_manager::{Geometry, Material, ResourceManager, SamplerDesc, SamplerHandle, Texture}; + +/// Represents a drawable entity in the scene, storing geometry with its transform. +#[derive(Clone)] +pub struct Mesh { + pub name: String, + pub material: Arc, + pub geometry: Arc, + pub transform: Mat4, +} + +/// Stores all objects to be rendered by the renderer. +pub struct Scene { + pub name: String, + pub meshes: Vec, +} + +fn sampler_desc_from_gltf(g_sampler: &gltf::texture::Sampler) -> SamplerDesc { + let wrap_s = g_sampler.wrap_s(); + let wrap_t = g_sampler.wrap_t(); + + SamplerDesc { + mag_filter: g_sampler + .mag_filter() + .map_or(vk::Filter::LINEAR, |mf| match mf { + gltf::texture::MagFilter::Nearest => vk::Filter::NEAREST, + gltf::texture::MagFilter::Linear => vk::Filter::LINEAR, + }), + min_filter: g_sampler + .min_filter() + .map_or(vk::Filter::LINEAR, |mf| match mf { + gltf::texture::MinFilter::Nearest + | gltf::texture::MinFilter::NearestMipmapNearest + | gltf::texture::MinFilter::NearestMipmapLinear => vk::Filter::NEAREST, + gltf::texture::MinFilter::Linear + | gltf::texture::MinFilter::LinearMipmapNearest + | gltf::texture::MinFilter::LinearMipmapLinear => vk::Filter::LINEAR, + }), + mipmap_mode: g_sampler + .min_filter() + .map_or(vk::SamplerMipmapMode::LINEAR, |mf| match mf { + gltf::texture::MinFilter::NearestMipmapNearest + | gltf::texture::MinFilter::LinearMipmapNearest => vk::SamplerMipmapMode::NEAREST, + gltf::texture::MinFilter::NearestMipmapLinear + | gltf::texture::MinFilter::LinearMipmapLinear => vk::SamplerMipmapMode::LINEAR, + _ => vk::SamplerMipmapMode::LINEAR, // Default if no mipmapping + }), + address_mode_u: vk_address_mode(wrap_s), + address_mode_v: vk_address_mode(wrap_t), + address_mode_w: vk::SamplerAddressMode::REPEAT, // glTF doesn't define wrapR + } +} + +fn vk_address_mode(g_mode: gltf::texture::WrappingMode) -> vk::SamplerAddressMode { + match g_mode { + gltf::texture::WrappingMode::ClampToEdge => vk::SamplerAddressMode::CLAMP_TO_EDGE, + gltf::texture::WrappingMode::MirroredRepeat => vk::SamplerAddressMode::MIRRORED_REPEAT, + gltf::texture::WrappingMode::Repeat => vk::SamplerAddressMode::REPEAT, + } +} + +impl Scene { + /// Takes a glTF file and returns a `Scene`. + pub fn from_gltf(path: T, resource_manager: Arc) -> Result + where + T: AsRef, + { + let path_ref = path.as_ref(); + let base_path = path_ref.parent().unwrap_or_else(|| Path::new("")); + tracing::info!("Loading glTF from: {:?}", path_ref); + tracing::info!("Base path for resources: {:?}", base_path); + + // Import images as well + let (doc, buffers, images) = gltf::import(path_ref)?; + tracing::info!( + "glTF Stats: {} scenes, {} nodes, {} meshes, {} materials, {} textures, {} images", + doc.scenes().len(), + doc.nodes().len(), + doc.meshes().len(), + doc.materials().len(), + doc.textures().len(), + doc.images().len() + ); + + let mut meshes = Vec::new(); + // Cache Geometry: Key = (mesh_index, primitive_index) + let mut geometry_cache: HashMap<(usize, usize), Arc> = HashMap::new(); + // Cache Materials: Key = glTF material index (usize::MAX for default) + let mut material_cache: HashMap> = HashMap::new(); + // Cache default sampler handle to avoid repeated lookups + let default_sampler_handle = + resource_manager.get_or_create_sampler(&SamplerDesc::default())?; + + let scene_to_load = doc + .default_scene() + .unwrap_or_else(|| doc.scenes().next().expect("No scenes found in glTF")); + + let scene_name = scene_to_load + .name() + .unwrap_or("") + .to_string(); + tracing::info!( + "Processing scene '{}' ({})", + scene_name, + scene_to_load.index() + ); + + // Create a context struct to pass around common data + let mut load_ctx = LoadContext { + doc: &doc, + buffers: &buffers, + images: &images, + base_path, + resource_manager, + geometry_cache: &mut geometry_cache, + material_cache: &mut material_cache, + default_sampler_handle, + meshes: &mut meshes, + }; + + for node in scene_to_load.nodes() { + Self::process_node(&node, &Mat4::IDENTITY, &mut load_ctx)?; + } + + tracing::info!("Successfully loaded {} render meshes.", meshes.len()); + + Ok(Self { + name: scene_name, + meshes, + }) + } + + /// Recursively processes a glTF node. + fn process_node( + node: &gltf::Node, + parent_transform: &Mat4, + ctx: &mut LoadContext, // Pass context mutably for caches + ) -> Result<()> { + let local_transform = Mat4::from_cols_array_2d(&node.transform().matrix()); + let world_transform = *parent_transform * local_transform; + let node_name = node.name().unwrap_or(""); + + if let Some(mesh) = node.mesh() { + let mesh_index = mesh.index(); + let mesh_name = mesh.name().unwrap_or(""); + tracing::debug!( + "Node '{}' ({}) has Mesh '{}' ({})", + node_name, + node.index(), + mesh_name, + mesh_index + ); + + // Process mesh primitives + for (primitive_index, primitive) in mesh.primitives().enumerate() { + // Generate a name for the Mesh object + let primitive_name = format!("{}_prim{}", mesh_name, primitive_index); + + Self::process_primitive( + &primitive, + mesh_index, + primitive_index, + &primitive_name, // Pass name + world_transform, + ctx, // Pass context + )?; + } + } else { + tracing::trace!("Node '{}' ({}) has no mesh.", node_name, node.index()); + } + + // Recursively process child nodes + for child_node in node.children() { + Self::process_node( + &child_node, + &world_transform, // Pass current world transform + ctx, // Pass context + )?; + } + + Ok(()) + } + + /// Processes a single glTF primitive, creating Geometry, Material, and Mesh. + fn process_primitive( + primitive: &gltf::Primitive, + mesh_index: usize, + primitive_index: usize, + mesh_name: &str, // Name for the final Mesh object + world_transform: Mat4, + ctx: &mut LoadContext, // Use context + ) -> Result<()> { + let geometry_cache_key = (mesh_index, primitive_index); + + // --- Get or Create Geometry --- + let geometry = if let Some(cached_geo) = ctx.geometry_cache.get(&geometry_cache_key) { + tracing::trace!("Using cached Geometry for key {:?}", geometry_cache_key); + cached_geo.clone() + } else { + tracing::trace!("Creating new Geometry for key {:?}", geometry_cache_key); + let reader = primitive.reader(|buffer| Some(&ctx.buffers[buffer.index()])); + + let Some(pos_iter) = reader.read_positions() else { + tracing::warn!( + "Primitive {:?} missing positions. Skipping.", + geometry_cache_key + ); + return Ok(()); // Skip this primitive + }; + let positions: Vec<[f32; 3]> = pos_iter.collect(); + let vertex_count = positions.len(); + + if vertex_count == 0 { + tracing::warn!( + "Primitive {:?} has no vertices. Skipping.", + geometry_cache_key + ); + return Ok(()); + } + + let normals: Vec<[f32; 3]> = reader + .read_normals() + .map(|iter| iter.collect()) + .unwrap_or_else(|| { + tracing::debug!( + "Primitive {:?} missing normals, using default.", + geometry_cache_key + ); + vec![[0.0, 1.0, 0.0]; vertex_count] + }); + + // Read Texture Coordinates (Set 0) - needed for vertex struct regardless of material + let tex_coords: Vec<[f32; 2]> = reader + .read_tex_coords(0) // Read UV set 0 + .map(|iter| iter.into_f32().collect()) + .unwrap_or_else(|| { + tracing::trace!( + "Primitive {:?} missing tex_coords (set 0), using default.", + geometry_cache_key + ); + vec![[0.0, 0.0]; vertex_count] + }); + + if normals.len() != vertex_count || tex_coords.len() != vertex_count { + return Err(SceneError::InconsistentData(format!( + "Attribute count mismatch for Primitive {:?} (Pos: {}, Norm: {}, TexCoord0: {}).", + geometry_cache_key, vertex_count, normals.len(), tex_coords.len() + ))); + } + + let vertices: Vec = positions + .into_iter() + .zip(normals) + .zip(tex_coords) + .map(|((pos, normal), tex_coord)| Vertex { + pos, + normal, + tex_coord, + }) + .collect(); + + let indices: Vec = reader + .read_indices() + .map(|read_indices| read_indices.into_u32().collect()) + .unwrap_or_else(|| (0..vertex_count as u32).collect()); + + if indices.is_empty() && vertex_count > 0 { + tracing::warn!( + "Primitive {:?} has vertices but no indices. Skipping.", + geometry_cache_key + ); + return Ok(()); + } + + let new_geo = Arc::new(Geometry::new( + ctx.resource_manager.clone(), + &vertices, + &indices, + )?); + ctx.geometry_cache + .insert(geometry_cache_key, new_geo.clone()); + new_geo + }; + + // --- Get or Create Material --- + let g_material = primitive.material(); + // Use index usize::MAX as key for default material if index() is None + let material_cache_key = g_material.index().unwrap_or(usize::MAX); + let material_name = g_material.name().unwrap_or(""); + + let material = if let Some(cached_mat) = ctx.material_cache.get(&material_cache_key) { + tracing::trace!( + "Using cached Material index {} ('{}')", + material_cache_key, + material_name + ); + cached_mat.clone() + } else { + tracing::trace!( + "Creating new Material for index {} ('{}')", + material_cache_key, + material_name + ); + let pbr = g_material.pbr_metallic_roughness(); + + let base_color_factor = pbr.base_color_factor(); + let metallic_factor = pbr.metallic_factor(); + let roughness_factor = pbr.roughness_factor(); + + let mut loaded_base_color_texture: Option> = None; + let mut loaded_base_color_sampler: Option = None; + + // --- Load Base Color Texture (if it exists) --- + if let Some(color_info) = pbr.base_color_texture() { + let tex_coord_set = color_info.tex_coord(); + if tex_coord_set != 0 { + tracing::warn!( + "Material '{}' requests tex_coord set {}, but only set 0 is currently loaded. Texture ignored.", + material_name, tex_coord_set + ); + // Fall through, texture won't be loaded + } else { + let g_texture = color_info.texture(); + let g_sampler = g_texture.sampler(); + let g_image = g_texture.source(); // This is the gltf::Image + + tracing::debug!( + "Material '{}' uses Texture index {}, Sampler index {:?}, Image index {}", + material_name, + g_texture.index(), + g_sampler.index(), + g_image.index() + ); + + // Get or create sampler + let sampler_desc = sampler_desc_from_gltf(&g_sampler); + let sampler_handle = + ctx.resource_manager.get_or_create_sampler(&sampler_desc)?; + loaded_base_color_sampler = Some(sampler_handle); + + // Load texture image data via ResourceManager + // Pass the correct gltf::Image using its index + let texture = ctx.resource_manager.load_texture( + &ctx.doc + .images() + .nth(g_image.index()) + .expect("Image index out of bounds"), // Get gltf::Image + &g_image.source(), // Get gltf::image::Source + ctx.base_path, + ctx.buffers, + vk::ImageUsageFlags::SAMPLED, // Standard usage for textures + )?; + loaded_base_color_texture = Some(texture); // Store Arc + } + } + + // Assign default sampler if none was loaded via texture info + if loaded_base_color_sampler.is_none() { + loaded_base_color_sampler = Some(ctx.default_sampler_handle); + tracing::trace!("Material '{}' using default sampler.", material_name); + } + + // Create the application Material struct + let new_mat = Arc::new(Material { + name: material_name.to_string(), + base_color_texture: loaded_base_color_texture, + base_color_sampler: loaded_base_color_sampler, + base_color_factor, + metallic_factor, + roughness_factor, + // Initialize other material properties here... + }); + + ctx.material_cache + .insert(material_cache_key, new_mat.clone()); + new_mat + }; + + // Create the final Mesh object + ctx.meshes.push(Mesh { + name: mesh_name.to_string(), + geometry, + material, // Assign the Arc + transform: world_transform, + }); + + Ok(()) + } +} + +// Context struct to avoid passing too many arguments +struct LoadContext<'a> { + doc: &'a gltf::Document, + buffers: &'a [gltf::buffer::Data], + images: &'a [gltf::image::Data], // Keep image data accessible if needed by RM + base_path: &'a Path, + resource_manager: Arc, + geometry_cache: &'a mut HashMap<(usize, usize), Arc>, + material_cache: &'a mut HashMap>, + default_sampler_handle: SamplerHandle, // Store the default sampler + meshes: &'a mut Vec, // Store results directly +} diff --git a/crates/shared/src/lib.rs b/crates/shared/src/lib.rs index b83634a..120f579 100644 --- a/crates/shared/src/lib.rs +++ b/crates/shared/src/lib.rs @@ -4,11 +4,14 @@ use glam::{Mat4, Vec3}; use core::f32; use std::mem::size_of; +mod material; + #[repr(C)] #[derive(Clone, Debug, Copy)] pub struct Vertex { pub pos: [f32; 3], - pub color: [f32; 3], + pub normal: [f32; 3], + pub tex_coord: [f32; 2], } impl Vertex { @@ -19,7 +22,7 @@ impl Vertex { .input_rate(vk::VertexInputRate::VERTEX) } - pub fn get_attribute_descriptions() -> [vk::VertexInputAttributeDescription; 2] { + pub fn get_attribute_descriptions() -> [vk::VertexInputAttributeDescription; 3] { [ vk::VertexInputAttributeDescription::default() .location(0) @@ -30,7 +33,12 @@ impl Vertex { .location(1) .binding(0) .format(vk::Format::R32G32B32_SFLOAT) - .offset(memoffset::offset_of!(Vertex, color) as u32), + .offset(memoffset::offset_of!(Vertex, normal) as u32), + vk::VertexInputAttributeDescription::default() + .location(2) + .binding(0) + .format(vk::Format::R32G32_SFLOAT) + .offset(memoffset::offset_of!(Vertex, tex_coord) as u32), ] } } @@ -38,7 +46,6 @@ impl Vertex { #[repr(C)] #[derive(Clone, Debug, Copy, PartialEq)] pub struct UniformBufferObject { - pub model: Mat4, pub view: Mat4, pub proj: Mat4, } @@ -47,6 +54,7 @@ pub struct UniformBufferObject { pub struct CameraInfo { pub camera_pos: Vec3, pub camera_target: Vec3, + pub camera_up: Vec3, pub camera_fov: f32, } @@ -55,6 +63,7 @@ impl Default for CameraInfo { Self { camera_pos: Vec3::new(10.0, 10.0, 10.0), camera_target: Vec3::new(0.0, 0.0, 0.0), + camera_up: Vec3::Y, camera_fov: 45.0, } } diff --git a/crates/shared/src/material.rs b/crates/shared/src/material.rs new file mode 100644 index 0000000..e69de29 diff --git a/flake.lock b/flake.lock index a74e166..f777db0 100644 --- a/flake.lock +++ b/flake.lock @@ -20,11 +20,11 @@ "nixpkgs-lib": "nixpkgs-lib" }, "locked": { - "lastModified": 1733312601, - "narHash": "sha256-4pDvzqnegAfRkPwO3wmwBhVi/Sye1mzps0zHWYnP88c=", + "lastModified": 1743550720, + "narHash": "sha256-hIshGgKZCgWh6AYJpJmRgFdR3WUbkY04o82X05xqQiY=", "owner": "hercules-ci", "repo": "flake-parts", - "rev": "205b12d8b7cd4802fbcb8e8ef6a0f1408781a4f9", + "rev": "c621e8422220273271f52058f618c94e405bb0f5", "type": "github" }, "original": { @@ -51,23 +51,26 @@ }, "nixpkgs-lib": { "locked": { - "lastModified": 1733096140, - "narHash": "sha256-1qRH7uAUsyQI7R1Uwl4T+XvdNv778H0Nb5njNrqvylY=", - "type": "tarball", - "url": "https://github.com/NixOS/nixpkgs/archive/5487e69da40cbd611ab2cadee0b4637225f7cfae.tar.gz" + "lastModified": 1743296961, + "narHash": "sha256-b1EdN3cULCqtorQ4QeWgLMrd5ZGOjLSLemfa00heasc=", + "owner": "nix-community", + "repo": "nixpkgs.lib", + "rev": "e4822aea2a6d1cdd36653c134cacfd64c97ff4fa", + "type": "github" }, "original": { - "type": "tarball", - "url": "https://github.com/NixOS/nixpkgs/archive/5487e69da40cbd611ab2cadee0b4637225f7cfae.tar.gz" + "owner": "nix-community", + "repo": "nixpkgs.lib", + "type": "github" } }, "nixpkgs_2": { "locked": { - "lastModified": 1728538411, - "narHash": "sha256-f0SBJz1eZ2yOuKUr5CA9BHULGXVSn6miBuUWdTyhUhU=", + "lastModified": 1736320768, + "narHash": "sha256-nIYdTAiKIGnFNugbomgBJR+Xv5F1ZQU+HfaBqJKroC0=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "b69de56fac8c2b6f8fd27f2eca01dcda8e0a4221", + "rev": "4bc9c909d9ac828a039f288cf872d16d38185db8", "type": "github" }, "original": { @@ -79,11 +82,11 @@ }, "nixpkgs_3": { "locked": { - "lastModified": 1733097829, - "narHash": "sha256-9hbb1rqGelllb4kVUCZ307G2k3/UhmA8PPGBoyuWaSw=", + "lastModified": 1735554305, + "narHash": "sha256-zExSA1i/b+1NMRhGGLtNfFGXgLtgo+dcuzHzaWA6w3Q=", "owner": "nixos", "repo": "nixpkgs", - "rev": "2c15aa59df0017ca140d9ba302412298ab4bf22a", + "rev": "0e82ab234249d8eee3e8c91437802b32c74bb3fd", "type": "github" }, "original": { @@ -108,11 +111,11 @@ "nixpkgs": "nixpkgs_2" }, "locked": { - "lastModified": 1735266518, - "narHash": "sha256-2XkWYGgT+911gOLjgBj+8W8ZJk6P0qHJNz8RfKgT/5o=", + "lastModified": 1743820323, + "narHash": "sha256-UXxJogXhPhBFaX4uxmMudcD/x3sEGFtoSc4busTcftY=", "owner": "oxalica", "repo": "rust-overlay", - "rev": "e0b3654b716098b47f3643c65fbb75ef49c033e1", + "rev": "b4734ce867252f92cdc7d25f8cc3b7cef153e703", "type": "github" }, "original": { @@ -141,11 +144,11 @@ "nixpkgs": "nixpkgs_3" }, "locked": { - "lastModified": 1735135567, - "narHash": "sha256-8T3K5amndEavxnludPyfj3Z1IkcFdRpR23q+T0BVeZE=", + "lastModified": 1743748085, + "narHash": "sha256-uhjnlaVTWo5iD3LXics1rp9gaKgDRQj6660+gbUU3cE=", "owner": "numtide", "repo": "treefmt-nix", - "rev": "9e09d30a644c57257715902efbb3adc56c79cf28", + "rev": "815e4121d6a5d504c0f96e5be2dd7f871e4fd99d", "type": "github" }, "original": { diff --git a/flake.nix b/flake.nix index 71450ae..3991877 100644 --- a/flake.nix +++ b/flake.nix @@ -35,11 +35,12 @@ commonArgs, ... }: { - _module.args = { + _module.args = rec { pkgs = import nixpkgs { inherit system; overlays = [inputs.rust-overlay.overlays.default]; }; + craneLib = (inputs.crane.mkLib pkgs).overrideToolchain ( pkgs: pkgs.rust-bin.fromRustupToolchainFile ./rust-toolchain.toml ); diff --git a/rust-toolchain.toml b/rust-toolchain.toml index 551cee7..30f36be 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -1,6 +1,11 @@ [toolchain] channel = "nightly-2024-11-22" -components = ["rust-src", "rustc-dev", "llvm-tools"] +components = [ + "rust-src", + "rustc-dev", + "llvm-tools", + "rustc-codegen-cranelift-preview", +] # commit_hash = b19329a37cedf2027517ae22c87cf201f93d776e # Whenever changing the nightly channel, update the commit hash above, and make diff --git a/shaders/frag.glsl.frag b/shaders/frag.glsl.frag index 5943263..3e46886 100644 --- a/shaders/frag.glsl.frag +++ b/shaders/frag.glsl.frag @@ -1,16 +1,34 @@ + #version 450 // Input from vertex shader -layout(location = 0) in vec3 fragColor; -// layout(location = 1) in vec2 fragTexCoord; // If using textures +layout(location = 0) in vec3 fragNormal; // Receive normal +layout(location = 1) in vec2 fragTexCoord; // Receive texture coordinates // Output color layout(location = 0) out vec4 outColor; -// layout(binding = 1) uniform sampler2D texSampler; // If using textures +// Descriptor set for material properties (Set 1) +layout(set = 1, binding = 0) uniform sampler2D baseColorSampler; + +// Optional: Pass material factors via another UBO or Push Constants if needed +// layout(set = 1, binding = 1) uniform MaterialFactors { +// vec4 baseColorFactor; +// } materialFactors; void main() { - // Use interpolated color - outColor = vec4(fragColor, 1.0); - // outColor = texture(texSampler, fragTexCoord); // If using textures + // Sample the texture + vec4 texColor = texture(baseColorSampler, fragTexCoord); + + // Use the texture color + // You might multiply by baseColorFactor here if you pass it + // outColor = texColor * materialFactors.baseColorFactor; + outColor = texColor; + + // Basic fallback if texture alpha is zero (or use baseColorFactor) + if (outColor.a == 0.0) { + outColor = vec4(0.8, 0.8, 0.8, 1.0); // Default grey + } + + // You could add basic lighting using fragNormal here later } diff --git a/shaders/vert.glsl.vert b/shaders/vert.glsl.vert index 73bc36c..a2053d6 100644 --- a/shaders/vert.glsl.vert +++ b/shaders/vert.glsl.vert @@ -1,26 +1,38 @@ #version 450 -// Matches Vertex struct attribute descriptions +// INPUTS from Vertex Buffer (matching Vertex struct) layout(location = 0) in vec3 inPosition; -layout(location = 1) in vec3 inColor; -// layout(location = 2) in vec2 inTexCoord; // If you add texture coords +layout(location = 1) in vec3 inNormal; +layout(location = 2) in vec2 inTexCoord; // <<< MUST be vec2 -// Matches UniformBufferObject struct and descriptor set layout binding -layout(binding = 0) uniform UniformBufferObject { - mat4 model; +// UNIFORMS (Set 0) +layout(set = 0, binding = 0) uniform UniformBufferObject { 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 +// PUSH CONSTANTS +layout(push_constant) uniform PushConstants { + mat4 model; +} pushConstants; + +// OUTPUTS to Fragment Shader +layout(location = 0) out vec3 fragNormal; // Location 0 for Normal +layout(location = 1) out vec2 fragTexCoord; // Location 1 for TexCoord void main() { - // Transform position: Model -> World -> View -> Clip space - gl_Position = ubo.proj * ubo.view * ubo.model * vec4(inPosition, 1.0); + vec4 worldPos = pushConstants.model * vec4(inPosition, 1.0); - // Pass color (and other attributes) through - fragColor = inColor; - // fragTexCoord = inTexCoord; + // Calculate final position + gl_Position = ubo.proj * ubo.view * worldPos; + + // --- Pass attributes to Fragment Shader --- + + // Pass world-space normal (adjust calculation if needed) + // Ensure fragNormal is assigned a vec3 + fragNormal = normalize(mat3(transpose(inverse(pushConstants.model))) * inNormal); + + // Pass texture coordinates (ensure inTexCoord is vec2) + // Ensure fragTexCoord is assigned a vec2 + fragTexCoord = inTexCoord; }