Compare commits

..

No commits in common. "74a1be796f039feb8acbd28464e27e222c822b83" and "234e2ab78deec70a9b888e2e36ab5c3c4e3f6ab0" have entirely different histories.

29 changed files with 464 additions and 4464 deletions

1643
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -6,8 +6,6 @@ members = [
"crates/gfx_hal", "crates/gfx_hal",
"crates/renderer", "crates/renderer",
"crates/resource_manager", "crates/resource_manager",
"crates/scene",
"crates/shared",
] ]
[workspace.dependencies] [workspace.dependencies]
@ -26,12 +24,11 @@ egui-ash-renderer = { version = "0.8.0", features = [
"dynamic-rendering", "dynamic-rendering",
] } ] }
egui = "0.31" egui = "0.31"
egui_tiles = "0.12"
bytemuck = { version = "1.21.0", features = ["derive"] } bytemuck = { version = "1.21.0", features = ["derive"] }
tracing = { features = ["release_max_level_warn"], version = "0.1" } tracing = "0.1"
tracing-subscriber = { version = "0.3", features = ["json"] } tracing-subscriber = { version = "0.3", features = ["json"] }
parking_lot = "0.12.3"
thiserror = "2.0.12" thiserror = "2.0.12"
gltf = "1.4.1"
# # Enable incremental by default in release mode. # # Enable incremental by default in release mode.
@ -60,7 +57,4 @@ gltf = "1.4.1"
# #
# rustflags = ["-Zshare-generics=off"] # rustflags = ["-Zshare-generics=off"]
# codegen-units = 1 # codegen-units = 1
opt-level = 1
[profile.dev.package."*"]
opt-level = 3 opt-level = 3

View file

@ -5,22 +5,15 @@ edition = "2021"
[dependencies] [dependencies]
egui.workspace = true egui.workspace = true
egui_tiles.workspace = true
ash.workspace = true ash.workspace = true
ash-window.workspace = true ash-window.workspace = true
color-eyre.workspace = true
tracing.workspace = true tracing.workspace = true
tracing-subscriber.workspace = true tracing-subscriber.workspace = true
winit.workspace = true winit.workspace = true
raw-window-handle.workspace = true raw-window-handle.workspace = true
thiserror.workspace = true thiserror.workspace = true
glam.workspace = true
gfx_hal = { path = "../gfx_hal" } gfx_hal = { path = "../gfx_hal" }
renderer = { path = "../renderer" } renderer = { path = "../renderer" }
resource_manager = { path = "../resource_manager" } resource_manager = { path = "../resource_manager" }
shared = { path = "../shared" }
scene = { path = "../scene" }
clap = { version = "4.5.34", features = ["derive"] } clap = { version = "4.5.34", features = ["derive"] }
egui-winit = "0.31.1"

View file

@ -8,28 +8,21 @@ use std::{
use ash::vk; use ash::vk;
use clap::Parser; use clap::Parser;
use egui::{Context, Slider, ViewportId};
use egui_winit::State;
use gfx_hal::{ use gfx_hal::{
device::Device, error::GfxHalError, instance::Instance, instance::InstanceConfig, device::Device, error::GfxHalError, instance::Instance, instance::InstanceConfig,
physical_device::PhysicalDevice, queue::Queue, surface::Surface, physical_device::PhysicalDevice, queue::Queue, surface::Surface,
}; };
use glam::Vec3;
use raw_window_handle::HasDisplayHandle; use raw_window_handle::HasDisplayHandle;
use renderer::{Renderer, RendererError}; use renderer::{Renderer, RendererError};
use resource_manager::{ResourceManager, ResourceManagerError}; use resource_manager::{ResourceManager, ResourceManagerError};
use scene::Scene;
use shared::CameraInfo;
use tracing::{debug, error, info, warn}; use tracing::{debug, error, info, warn};
use tracing_subscriber::{filter, layer::SubscriberExt, util::SubscriberInitExt, Layer}; use tracing_subscriber::{filter, layer::SubscriberExt, util::SubscriberInitExt, Layer};
use winit::{ use winit::{
application::ApplicationHandler, application::ApplicationHandler,
event::{ElementState, KeyEvent, MouseButton, WindowEvent}, event::WindowEvent,
event_loop::{ActiveEventLoop, EventLoop}, event_loop::{ActiveEventLoop, EventLoop},
keyboard::{KeyCode, PhysicalKey},
window::Window, window::Window,
}; };
// --- Configuration --- // --- Configuration ---
const APP_NAME: &str = "BeginDisregard"; const APP_NAME: &str = "BeginDisregard";
const ENGINE_NAME: &str = "Engine"; const ENGINE_NAME: &str = "Engine";
@ -49,8 +42,6 @@ enum AppError {
NoSuitableDevice, NoSuitableDevice,
#[error("Failed to create CString: {0}")] #[error("Failed to create CString: {0}")]
NulError(#[from] std::ffi::NulError), NulError(#[from] std::ffi::NulError),
#[error("Scene Error: {0}")]
SceneError(#[from] scene::SceneError),
} }
struct Application { struct Application {
@ -66,125 +57,12 @@ struct Application {
// Renderer // Renderer
renderer: Renderer, renderer: Renderer,
egui_ctx: Context,
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 // Windowing
window: Arc<Window>, // Use Arc for potential multi-threading later window: Arc<Window>, // Use Arc for potential multi-threading later
frame_count: u32, frame_count: u32,
last_fps_update_time: Instant, last_fps_update_time: Instant,
last_frame_time: Instant, last_frame_time: Instant,
current_fps: f64,
}
#[derive(Default)]
struct EditorUI {}
impl EditorUI {
fn title() -> String {
"engine".to_string()
}
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();
egui::Grid::new("main_grid")
.spacing([40.0, 4.0])
.striped(true)
.show(ui, |ui| {
ui.label("FOV");
// 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<EditorUI> {
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)] #[derive(Default)]
@ -344,15 +222,30 @@ impl Application {
// Get specific queues (assuming graphics and present are the same for simplicity) // Get specific queues (assuming graphics and present are the same for simplicity)
let graphics_queue = device.get_graphics_queue(); 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 --- // --- 4. Resource Manager ---
let resource_manager = Arc::new(ResourceManager::new(instance.clone(), device.clone())?); let resource_manager = Arc::new(ResourceManager::new(instance.clone(), device.clone())?);
info!("Resource Manager initialized."); info!("Resource Manager initialized.");
let scene = Scene::from_gltf( let renderer_device_handle_to_pass = device.raw().handle();
"./sponza/NewSponza_Main_glTF_003.gltf", let renderer_queue_device_handle_to_pass = graphics_queue.device().raw().handle();
resource_manager.clone(), 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 --- // --- 5. Renderer ---
let initial_size = window.inner_size(); let initial_size = window.inner_size();
@ -362,26 +255,11 @@ impl Application {
graphics_queue.clone(), graphics_queue.clone(),
surface.clone(), surface.clone(),
resource_manager.clone(), resource_manager.clone(),
scene,
initial_size.width, initial_size.width,
initial_size.height, 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."); info!("Renderer initialized.");
let camera_info = CameraInfo::default(); // Get default camera settings
Ok(Self { Ok(Self {
_instance: instance, _instance: instance,
_physical_device: physical_device, _physical_device: physical_device,
@ -391,40 +269,13 @@ impl Application {
_resource_manager: resource_manager, _resource_manager: resource_manager,
renderer, renderer,
window, window,
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, frame_count: 0,
current_fps: 0.,
last_fps_update_time: Instant::now(), last_fps_update_time: Instant::now(),
last_frame_time: Instant::now(), last_frame_time: Instant::now(),
}) })
} }
fn handle_event(&mut self, event: &WindowEvent, active_event_loop: &ActiveEventLoop) { fn handle_event(&mut self, event: &WindowEvent, active_event_loop: &ActiveEventLoop) {
// 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 { match event {
WindowEvent::CloseRequested => { WindowEvent::CloseRequested => {
info!("Close requested. Exiting..."); info!("Close requested. Exiting...");
@ -447,153 +298,30 @@ impl Application {
.resize(new_inner_size.width, new_inner_size.height); .resize(new_inner_size.width, new_inner_size.height);
} }
// Handle other inputs if not consumed by egui // Handle other inputs if not consumed by egui
WindowEvent::MouseInput { state, button, .. } => { WindowEvent::KeyboardInput { .. }
if *button == MouseButton::Right { | WindowEvent::CursorMoved { .. }
let is_pressed = *state == ElementState::Pressed; | WindowEvent::MouseInput { .. } => {}
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 => { WindowEvent::RedrawRequested => {
let now = Instant::now(); let now = Instant::now();
let delta_time = now.duration_since(self.last_frame_time).as_secs_f32(); let _delta_time = now.duration_since(self.last_frame_time);
self.last_frame_time = now; self.last_frame_time = now;
// --- FPS Calculation --- let elapsed_sice_last_update = now.duration_since(self.last_fps_update_time);
let elapsed_since_last_update = now.duration_since(self.last_fps_update_time);
self.frame_count += 1; self.frame_count += 1;
if elapsed_since_last_update >= Duration::from_secs(1) {
self.current_fps = if elapsed_sice_last_update >= Duration::from_secs(1) {
self.frame_count as f64 / elapsed_since_last_update.as_secs_f64(); let fps = self.frame_count as f64 / elapsed_sice_last_update.as_secs_f64();
let new_title = format!(
"{} - {} - {:.0} FPS", let new_title = format!("{} - {} - {:.0} FPS", ENGINE_NAME, APP_NAME, fps);
ENGINE_NAME, APP_NAME, self.current_fps
);
self.window.set_title(&new_title); self.window.set_title(&new_title);
self.frame_count = 0; self.frame_count = 0;
self.last_fps_update_time = now; 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 {
platform_output,
textures_delta,
shapes,
pixels_per_point,
..
} = self.egui_ctx.run(raw_input, |ctx| {
self.egui_app
.build_ui(ctx, self.current_fps, &mut self.camera_info);
});
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 --- // --- Render Frame ---
match self.renderer.render_frame( match self.renderer.render_frame() {
pixels_per_point,
&clipped_primitives,
self.camera_info,
) {
Ok(_) => { Ok(_) => {
self.window.request_redraw(); self.window.request_redraw();
} }
@ -615,106 +343,6 @@ 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 --- // --- Helper Functions ---
@ -822,7 +450,6 @@ struct Args {
// --- Entry Point --- // --- Entry Point ---
fn main() -> Result<(), Box<dyn Error>> { fn main() -> Result<(), Box<dyn Error>> {
color_eyre::install()?;
let args = Args::parse(); let args = Args::parse();
let fmt_layer = tracing_subscriber::fmt::layer() let fmt_layer = tracing_subscriber::fmt::layer()

View file

@ -9,4 +9,4 @@ ash-window.workspace = true
thiserror.workspace = true thiserror.workspace = true
tracing.workspace = true tracing.workspace = true
winit.workspace = true winit.workspace = true
gpu-allocator.workspace = true parking_lot.workspace = true

View file

@ -1,10 +1,9 @@
use ash::vk; use ash::vk;
use parking_lot::Mutex;
use std::collections::HashSet; use std::collections::HashSet;
use std::ffi::CStr; use std::ffi::CStr;
use std::{ use std::sync::Weak;
collections::HashMap, use std::{collections::HashMap, sync::Arc};
sync::{Arc, Mutex},
};
use crate::error::{GfxHalError, Result}; use crate::error::{GfxHalError, Result};
use crate::instance::Instance; use crate::instance::Instance;
@ -15,7 +14,7 @@ use crate::queue::Queue;
/// ///
/// Owns the `ash::Device` and provides access to device functions and queues. /// Owns the `ash::Device` and provides access to device functions and queues.
pub struct Device { pub struct Device {
_instance: Arc<Instance>, instance: Arc<Instance>,
physical_device: vk::PhysicalDevice, physical_device: vk::PhysicalDevice,
device: ash::Device, device: ash::Device,
queues: Mutex<HashMap<(u32, u32), Arc<Queue>>>, queues: Mutex<HashMap<(u32, u32), Arc<Queue>>>,
@ -33,7 +32,6 @@ impl Device {
/// - `queue_family_indicies` must be valid indicies obtained from the `physical_device_handle`. /// - `queue_family_indicies` must be valid indicies obtained from the `physical_device_handle`.
/// - `required_extensions` must be supported by 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`. /// - All feature structs passed must be supported by the `physical_device_handle`.
#[allow(clippy::too_many_arguments)]
pub(crate) unsafe fn new( pub(crate) unsafe fn new(
instance: Arc<Instance>, instance: Arc<Instance>,
physical_device_handle: vk::PhysicalDevice, physical_device_handle: vk::PhysicalDevice,
@ -114,7 +112,7 @@ impl Device {
// --- 4. Create the Device struct in an Arc (Stage 1) --- // --- 4. Create the Device struct in an Arc (Stage 1) ---
// Initialize the queues map as empty for now. // Initialize the queues map as empty for now.
let device_arc = Arc::new(Device { let device_arc = Arc::new(Device {
_instance: instance.clone(), instance: instance.clone(),
physical_device: physical_device_handle, physical_device: physical_device_handle,
device: ash_device, // Move the created ash::Device here device: ash_device, // Move the created ash::Device here
queues: Mutex::new(HashMap::new()), // Start with empty map queues: Mutex::new(HashMap::new()), // Start with empty map
@ -148,7 +146,7 @@ impl Device {
// Lock the mutex and insert the created queues into the map within the Arc<Device> // Lock the mutex and insert the created queues into the map within the Arc<Device>
{ {
// Scope for the mutex guard // 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 *queues_map_guard = queues_to_insert; // Replace the empty map with the populated one
tracing::debug!( tracing::debug!(
"Device Arc populated with {} queues (Stage 2).", "Device Arc populated with {} queues (Stage 2).",
@ -187,21 +185,15 @@ impl Device {
/// Gets a wrapped queue handle. /// Gets a wrapped queue handle.
/// Currently only supports queue index 0 for each family. /// Currently only supports queue index 0 for each family.
pub fn get_queue(&self, family_index: u32, queue_index: u32) -> Result<Arc<Queue>> { pub fn get_queue(&self, family_index: u32, queue_index: u32) -> Option<Arc<Queue>> {
if queue_index != 0 { if queue_index != 0 {
tracing::warn!("get_queue currently only supports queue_index 0"); tracing::warn!("get_queue currently only supports queue_index 0");
return Err(GfxHalError::MissingQueueFamily( return None;
"get_queue only supports queue_index 0".to_string(),
));
} }
self.queues self.queues
.lock()? .lock()
.get(&(family_index, queue_index)) .get(&(family_index, queue_index))
.cloned() .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). /// Gets the primary graphics queue (family index from `graphics_queue_family_index`, queue index 0).

View file

@ -56,23 +56,9 @@ pub enum GfxHalError {
#[error("Error loading the ash entry.")] #[error("Error loading the ash entry.")]
AshEntryError(#[from] ash::LoadingError), AshEntryError(#[from] ash::LoadingError),
/// Poisoned Mutex
#[error("Error from poisoned mutex: {0}")]
MutexPoisoned(String),
/// Placeholder for other specific errors. /// Placeholder for other specific errors.
#[error("An unexpected error occurred: {0}")] #[error("An unexpected error occurred: {0}")]
Other(String), Other(String),
/// Size for Buffer is invalid.
#[error("Buffer size is invalid.")]
BufferSizeInvalid,
} }
pub type Result<T, E = GfxHalError> = std::result::Result<T, E>; pub type Result<T, E = GfxHalError> = std::result::Result<T, E>;
impl<T> From<std::sync::PoisonError<T>> for GfxHalError {
fn from(e: std::sync::PoisonError<T>) -> Self {
Self::MutexPoisoned(e.to_string())
}
}

View file

@ -6,12 +6,3 @@ pub mod queue;
pub mod surface; pub mod surface;
pub mod swapchain; pub mod swapchain;
pub mod sync; 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::*;

View file

@ -1,6 +1,7 @@
use std::sync::{Arc, Mutex}; use std::sync::Arc;
use ash::{vk, Device as AshDevice}; use ash::{vk, Device as AshDevice};
use parking_lot::Mutex;
use crate::device::Device; use crate::device::Device;
use crate::error::Result; use crate::error::Result;
@ -65,6 +66,18 @@ impl Queue {
submits: &[vk::SubmitInfo], submits: &[vk::SubmitInfo],
signal_fence: Option<&Fence>, signal_fence: Option<&Fence>,
) -> Result<()> { ) -> 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()); let fence_handle = signal_fence.map_or(vk::Fence::null(), |f| f.handle());
// Keep the lock for thread-safety on the VkQueue object itself // Keep the lock for thread-safety on the VkQueue object itself

View file

@ -10,7 +10,6 @@ use crate::{
/// Wraps a `vk::Fence`, used for CPU-GPU synchronization. /// Wraps a `vk::Fence`, used for CPU-GPU synchronization.
/// ///
/// Owns the `vk::Fence` handle. /// Owns the `vk::Fence` handle.
#[derive(Clone)]
pub struct Fence { pub struct Fence {
device: Arc<Device>, device: Arc<Device>,
fence: vk::Fence, fence: vk::Fence,

View file

@ -13,11 +13,10 @@ gpu-allocator.workspace = true
egui.workspace = true egui.workspace = true
egui-ash-renderer.workspace = true egui-ash-renderer.workspace = true
winit.workspace = true winit.workspace = true
parking_lot.workspace = true
gfx_hal = { path = "../gfx_hal" } gfx_hal = { path = "../gfx_hal" }
resource_manager = { path = "../resource_manager" } resource_manager = { path = "../resource_manager" }
shared = { path = "../shared" }
scene = { path = "../scene" }
[build-dependencies] [build-dependencies]
shaderc = "0.9.1" shaderc = "0.9.1"

View file

@ -16,8 +16,6 @@ fn main() -> Result<()> {
let out_dir = PathBuf::from(env::var("OUT_DIR")?).join("shaders"); // Put shaders in a subdirectory for clarity 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")?; 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 compiler = Compiler::new().context("Failed to create shader compiler")?;
let mut options = CompileOptions::new().context("Failed to create compile options")?; let mut options = CompileOptions::new().context("Failed to create compile options")?;
@ -51,7 +49,6 @@ fn main() -> Result<()> {
.filter(|e| e.file_type().is_file()) .filter(|e| e.file_type().is_file())
// Only process files // Only process files
{ {
println!("cargo:rerun-if-changed={:?}", entry.path());
let in_path = entry.path(); let in_path = entry.path();
// Determine shader kind from extension // Determine shader kind from extension

View file

@ -1,32 +1,21 @@
use std::{ use std::{ffi::CStr, sync::Arc};
collections::HashMap,
ffi::c_void,
mem,
sync::{Arc, Mutex},
time::Instant,
};
use ash::vk; use ash::vk;
use egui::{ClippedPrimitive, TextureId, TexturesDelta};
use egui_ash_renderer::{DynamicRendering, Options, Renderer as EguiRenderer};
use gfx_hal::{ use gfx_hal::{
device::Device, error::GfxHalError, queue::Queue, surface::Surface, swapchain::Swapchain, device::Device, error::GfxHalError, queue::Queue, surface::Surface, swapchain::Swapchain,
swapchain::SwapchainConfig, sync::Fence, sync::Semaphore, swapchain::SwapchainConfig, sync::Fence, sync::Semaphore,
}; };
use glam::{Mat4, Vec3}; use gpu_allocator::{vulkan::Allocator, MemoryLocation};
use gpu_allocator::{ use parking_lot::Mutex;
vulkan::{Allocation, AllocationCreateDesc, Allocator}, use resource_manager::{ImageHandle, ResourceManager, ResourceManagerError};
MemoryLocation,
};
use resource_manager::{
ImageHandle, Material, ResourceManager, ResourceManagerError, SamplerHandle, Texture,
};
use shared::{CameraInfo, UniformBufferObject};
use thiserror::Error; use thiserror::Error;
use tracing::{debug, error, info, warn}; 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; const MAX_FRAMES_IN_FLIGHT: usize = 2;
const MAX_MATERIALS: usize = 150;
#[derive(Debug, Error)] #[derive(Debug, Error)]
pub enum RendererError { pub enum RendererError {
@ -62,17 +51,6 @@ pub enum RendererError {
ImageInfoUnavailable, ImageInfoUnavailable,
#[error("Failed to get allocator from resource manager")] #[error("Failed to get allocator from resource manager")]
AllocatorUnavailable, // Added based on egui requirement AllocatorUnavailable, // Added based on egui requirement
#[error("Allocator Error: {0}")]
AllocatorError(#[from] gpu_allocator::AllocationError),
#[error("Other Error: {0}")]
Other(String),
}
impl<T> From<std::sync::PoisonError<T>> for RendererError {
fn from(_: std::sync::PoisonError<T>) -> Self {
Self::AllocatorUnavailable
}
} }
struct FrameData { struct FrameData {
@ -80,14 +58,7 @@ struct FrameData {
command_buffer: vk::CommandBuffer, command_buffer: vk::CommandBuffer,
image_available_semaphore: Semaphore, image_available_semaphore: Semaphore,
render_finished_semaphore: Semaphore, render_finished_semaphore: Semaphore,
textures_to_free: Option<Vec<TextureId>>,
in_flight_fence: Fence, 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 { struct SwapchainSupportDetails {
@ -108,26 +79,12 @@ pub struct Renderer {
swapchain_format: vk::SurfaceFormatKHR, swapchain_format: vk::SurfaceFormatKHR,
swapchain_extent: vk::Extent2D, swapchain_extent: vk::Extent2D,
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, depth_image_handle: ImageHandle,
depth_image_view: vk::ImageView, // Store the view directly depth_image_view: vk::ImageView, // Store the view directly
depth_format: vk::Format, depth_format: vk::Format,
model_pipeline_layout: vk::PipelineLayout, triangle_pipeline_layout: vk::PipelineLayout,
model_pipeline: vk::Pipeline, triangle_pipeline: vk::Pipeline,
material_descriptor_sets: HashMap<usize, vk::DescriptorSet>,
default_white_texture: Option<Arc<Texture>>,
default_sampler: SamplerHandle,
frames_data: Vec<FrameData>, frames_data: Vec<FrameData>,
current_frame: usize, current_frame: usize,
@ -136,19 +93,15 @@ pub struct Renderer {
window_resized: bool, window_resized: bool,
current_width: u32, current_width: u32,
current_height: u32, current_height: u32,
start_time: Instant,
} }
impl Renderer { impl Renderer {
#[allow(clippy::too_many_arguments)]
pub fn new( pub fn new(
instance: Arc<gfx_hal::instance::Instance>, // Needed for allocator instance: Arc<gfx_hal::instance::Instance>, // Needed for allocator
device: Arc<Device>, device: Arc<Device>,
graphics_queue: Arc<Queue>, graphics_queue: Arc<Queue>,
surface: Arc<Surface>, surface: Arc<Surface>,
resource_manager: Arc<ResourceManager>, resource_manager: Arc<ResourceManager>,
scene: scene::Scene,
initial_width: u32, initial_width: u32,
initial_height: u32, initial_height: u32,
) -> Result<Self, RendererError> { ) -> Result<Self, RendererError> {
@ -168,215 +121,33 @@ impl Renderer {
let (depth_image_handle, depth_image_view) = let (depth_image_handle, depth_image_view) =
Self::create_depth_resources(&device, &resource_manager, extent, depth_format)?; Self::create_depth_resources(&device, &resource_manager, extent, depth_format)?;
let descriptor_set_layout = Self::create_descriptor_set_layout(&device)?; let (triangle_pipeline_layout, triangle_pipeline) =
let material_descriptor_set_layout = Self::create_material_descriptor_set_layout(&device)?; Self::create_triangle_pipeline(&device, format.format, depth_format)?;
let descriptor_set_layouts = [descriptor_set_layout, material_descriptor_set_layout]; let frames_data = Self::create_frame_data(&device)?;
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_layouts,
)?;
let start_time = Instant::now();
let frames_data = Self::create_frame_data(
&device,
&resource_manager,
descriptor_pool,
&descriptor_set_layouts,
swapchain.extent(),
)?;
info!("Renderer initialized successfully."); 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 {
srgb_framebuffer: true,
in_flight_frames: MAX_FRAMES_IN_FLIGHT,
..Default::default()
},
)?;
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 { Ok(Self {
device, device,
graphics_queue, graphics_queue,
resource_manager, resource_manager,
egui_renderer,
allocator, // Store the allocator Arc allocator, // Store the allocator Arc
surface, surface,
swapchain: Some(swapchain), swapchain: Some(swapchain),
swapchain_image_views: image_views, swapchain_image_views: image_views,
swapchain_format: format, swapchain_format: format,
swapchain_extent: extent, swapchain_extent: extent,
descriptor_set_layout,
descriptor_pool,
material_descriptor_set_layout,
depth_image_handle, depth_image_handle,
depth_image_view, depth_image_view,
depth_format, depth_format,
model_pipeline_layout, triangle_pipeline_layout,
model_pipeline, triangle_pipeline,
material_descriptor_sets: HashMap::new(),
default_white_texture,
default_sampler,
frames_data, frames_data,
scene,
current_frame: 0, current_frame: 0,
window_resized: false, window_resized: false,
current_width: initial_width, current_width: initial_width,
current_height: initial_height, current_height: initial_height,
start_time,
})
}
/// Gets or creates/updates a descriptor set for a given material.
fn get_or_create_material_set(
&mut self,
material: &Arc<Material>, // Use Arc<Material> directly if hashable, or use a unique ID
) -> Result<vk::DescriptorSet, RendererError> {
// Return generic error
// Use a unique identifier for the material instance if Arc<Material> 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<Material> 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<Device>, // Need device Arc for RM
resource_manager: Arc<ResourceManager>,
) -> Arc<Texture> {
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,
},
}) })
} }
@ -391,33 +162,7 @@ impl Renderer {
} }
} }
pub fn update_textures(&mut self, textures_delta: TexturesDelta) -> Result<(), RendererError> { pub fn render_frame(&mut self) -> 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,
clipped_primitives: &[ClippedPrimitive],
camera_info: CameraInfo,
) -> Result<(), RendererError> {
// --- Handle Resize --- // --- Handle Resize ---
if self.window_resized { if self.window_resized {
self.window_resized = false; self.window_resized = false;
@ -429,7 +174,7 @@ impl Renderer {
// --- Wait for Previous Frame --- // --- Wait for Previous Frame ---
let frame_index = self.current_frame; let frame_index = self.current_frame;
let frame_data = &mut self.frames_data[frame_index]; let frame_data = &self.frames_data[frame_index];
frame_data.in_flight_fence.wait(None)?; // Wait indefinitely frame_data.in_flight_fence.wait(None)?; // Wait indefinitely
@ -438,7 +183,6 @@ impl Renderer {
.swapchain .swapchain
.as_ref() .as_ref()
.ok_or(RendererError::SwapchainAcquisitionFailed)?; .ok_or(RendererError::SwapchainAcquisitionFailed)?;
let (image_index, suboptimal) = unsafe { let (image_index, suboptimal) = unsafe {
// Need unsafe block for acquire_next_image // Need unsafe block for acquire_next_image
swapchain_ref.acquire_next_image( swapchain_ref.acquire_next_image(
@ -459,10 +203,6 @@ impl Renderer {
// --- Reset Fence (only after successful acquisition) --- // --- Reset Fence (only after successful acquisition) ---
frame_data.in_flight_fence.reset()?; frame_data.in_flight_fence.reset()?;
if let Some(textures) = frame_data.textures_to_free.take() {
self.egui_renderer.free_textures(&textures)?;
}
// --- Record Command Buffer --- // --- Record Command Buffer ---
unsafe { unsafe {
// Need unsafe for Vulkan commands // Need unsafe for Vulkan commands
@ -475,15 +215,6 @@ impl Renderer {
let cmd_begin_info = vk::CommandBufferBeginInfo::default() let cmd_begin_info = vk::CommandBufferBeginInfo::default()
.flags(vk::CommandBufferUsageFlags::ONE_TIME_SUBMIT); .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 { unsafe {
// Need unsafe for Vulkan commands // Need unsafe for Vulkan commands
self.device self.device
@ -585,74 +316,18 @@ impl Renderer {
.cmd_set_scissor(command_buffer, 0, &[scissor]); .cmd_set_scissor(command_buffer, 0, &[scissor]);
} }
// --- Draw Triangle ---
unsafe { unsafe {
// Need unsafe for Vulkan commands
self.device.raw().cmd_bind_pipeline( self.device.raw().cmd_bind_pipeline(
command_buffer, command_buffer,
vk::PipelineBindPoint::GRAPHICS, vk::PipelineBindPoint::GRAPHICS,
self.model_pipeline, self.triangle_pipeline,
);
self.device.raw().cmd_bind_descriptor_sets(
command_buffer,
vk::PipelineBindPoint::GRAPHICS,
self.model_pipeline_layout,
0,
&[frame_data.descriptor_set],
&[],
); );
// Draw 3 vertices, 1 instance, 0 first vertex, 0 first instance
self.device.raw().cmd_draw(command_buffer, 3, 1, 0, 0);
} }
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::<Mat4>(),
)
};
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,
self.swapchain_extent,
pixels_per_point,
clipped_primitives,
)?;
tracing::trace!("Rendered EGUI");
// --- End Dynamic Rendering --- // --- End Dynamic Rendering ---
unsafe { unsafe {
// Need unsafe for Vulkan commands // Need unsafe for Vulkan commands
@ -707,6 +382,12 @@ impl Renderer {
.command_buffers(&command_buffers) .command_buffers(&command_buffers)
.signal_semaphores(&signal_semaphores); .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 { unsafe {
// Need unsafe for queue submit // Need unsafe for queue submit
self.graphics_queue.submit( self.graphics_queue.submit(
@ -794,7 +475,7 @@ impl Renderer {
// --- Helper: Cleanup Swapchain Dependent Resources --- // --- Helper: Cleanup Swapchain Dependent Resources ---
fn cleanup_swapchain_resources(&mut self) { fn cleanup_swapchain_resources(&mut self) {
debug!("Cleaning up swapchain resources..."); debug!("Cleaning up swapchain resources...");
// Destroy depth buffer view
unsafe { unsafe {
self.device self.device
.raw() .raw()
@ -803,8 +484,9 @@ impl Renderer {
// Destroy depth buffer image via resource manager // Destroy depth buffer image via resource manager
if let Err(e) = self.resource_manager.destroy_image(self.depth_image_handle) { if let Err(e) = self.resource_manager.destroy_image(self.depth_image_handle) {
error!("Failed to destroy depth image: {}", e); 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; self.swapchain = None;
debug!("Swapchain resources cleaned up."); debug!("Swapchain resources cleaned up.");
} }
@ -851,10 +533,38 @@ impl Renderer {
let swapchain = let swapchain =
unsafe { Swapchain::new(device.clone(), surface.clone(), config, old_swapchain)? }; unsafe { Swapchain::new(device.clone(), surface.clone(), config, old_swapchain)? };
// Create Image Views
let image_views = swapchain let image_views = swapchain
.image_views() // Assuming Swapchain::new creates and stores these .image_views() // Assuming Swapchain::new creates and stores these
.to_vec(); // Clone the slice into a Vec .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)) Ok((swapchain, surface_format, extent, image_views))
} }
@ -908,12 +618,33 @@ impl Renderer {
} }
// --- Helper: Create Triangle Pipeline --- // --- Helper: Create Triangle Pipeline ---
fn create_model_pipeline( fn create_triangle_pipeline(
device: &Arc<Device>, device: &Arc<Device>,
color_format: vk::Format, color_format: vk::Format,
depth_format: vk::Format, depth_format: vk::Format,
descriptor_set_layouts: &[vk::DescriptorSetLayout],
) -> Result<(vk::PipelineLayout, vk::Pipeline), RendererError> { ) -> 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) // 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 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 let frag_shader_code = include_bytes!(concat!(env!("OUT_DIR"), "/shaders/frag.glsl.spv")); // Placeholder path
@ -921,7 +652,7 @@ impl Renderer {
let vert_module = Self::create_shader_module(device, vert_shader_code)?; let vert_module = Self::create_shader_module(device, vert_shader_code)?;
let frag_module = Self::create_shader_module(device, frag_shader_code)?; let frag_module = Self::create_shader_module(device, frag_shader_code)?;
let main_function_name = c"main"; let main_function_name = CStr::from_bytes_with_nul(b"main\0").unwrap();
let vert_stage_info = vk::PipelineShaderStageCreateInfo::default() let vert_stage_info = vk::PipelineShaderStageCreateInfo::default()
.stage(vk::ShaderStageFlags::VERTEX) .stage(vk::ShaderStageFlags::VERTEX)
@ -935,13 +666,8 @@ impl Renderer {
let shader_stages = [vert_stage_info, frag_stage_info]; 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 --- // --- Fixed Function State ---
let vertex_input_info = vk::PipelineVertexInputStateCreateInfo::default() let vertex_input_info = vk::PipelineVertexInputStateCreateInfo::default(); // No vertex buffers/attributes
.vertex_binding_descriptions(std::slice::from_ref(&binding_description))
.vertex_attribute_descriptions(&attribute_descriptions);
let input_assembly = vk::PipelineInputAssemblyStateCreateInfo::default() let input_assembly = vk::PipelineInputAssemblyStateCreateInfo::default()
.topology(vk::PrimitiveTopology::TRIANGLE_LIST) .topology(vk::PrimitiveTopology::TRIANGLE_LIST)
@ -983,15 +709,8 @@ impl Renderer {
let dynamic_state = let dynamic_state =
vk::PipelineDynamicStateCreateInfo::default().dynamic_states(&dynamic_states); 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::<Mat4>() as u32);
// --- Pipeline Layout --- // --- Pipeline Layout ---
let layout_info = vk::PipelineLayoutCreateInfo::default() let layout_info = vk::PipelineLayoutCreateInfo::default(); // No descriptors/push constants
.set_layouts(descriptor_set_layouts)
.push_constant_ranges(std::slice::from_ref(&push_constant_range));
let pipeline_layout = unsafe { let pipeline_layout = unsafe {
device device
.raw() .raw()
@ -1075,7 +794,7 @@ impl Renderer {
// -------------------------------------------------------------------- // --------------------------------------------------------------------
// 3. Create the shader module // 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 { unsafe {
device device
@ -1089,13 +808,7 @@ impl Renderer {
} }
// --- Helper: Create Frame Sync Objects & Command Resources --- // --- Helper: Create Frame Sync Objects & Command Resources ---
fn create_frame_data( fn create_frame_data(device: &Arc<Device>) -> Result<Vec<FrameData>, RendererError> {
device: &Arc<Device>,
resource_manager: &Arc<ResourceManager>,
descriptor_pool: vk::DescriptorPool,
descriptor_set_layouts: &[vk::DescriptorSetLayout],
swapchain_extent: vk::Extent2D,
) -> Result<Vec<FrameData>, RendererError> {
let mut frames_data = Vec::with_capacity(MAX_FRAMES_IN_FLIGHT); let mut frames_data = Vec::with_capacity(MAX_FRAMES_IN_FLIGHT);
for _ in 0..MAX_FRAMES_IN_FLIGHT { for _ in 0..MAX_FRAMES_IN_FLIGHT {
let image_available_semaphore = Semaphore::new(device.clone())?; let image_available_semaphore = Semaphore::new(device.clone())?;
@ -1128,30 +841,12 @@ impl Renderer {
.map_err(RendererError::CommandBufferAllocation)?[0] .map_err(RendererError::CommandBufferAllocation)?[0]
}; };
tracing::info!("Allocated frame_data command_buffer: {:?}", command_buffer);
let descriptor_set =
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);
frames_data.push(FrameData { frames_data.push(FrameData {
textures_to_free: None,
command_pool, command_pool,
command_buffer, // Stays allocated, just reset/rerecorded command_buffer, // Stays allocated, just reset/rerecorded
image_available_semaphore, image_available_semaphore,
render_finished_semaphore, render_finished_semaphore,
in_flight_fence, in_flight_fence,
descriptor_set,
uniform_buffer,
uniform_buffer_allocation,
uniform_buffer_mapped_ptr,
uniform_buffer_object,
}); });
} }
Ok(frames_data) Ok(frames_data)
@ -1176,20 +871,22 @@ impl Renderer {
} }
fn choose_swapchain_format(available_formats: &[vk::SurfaceFormatKHR]) -> vk::SurfaceFormatKHR { fn choose_swapchain_format(available_formats: &[vk::SurfaceFormatKHR]) -> vk::SurfaceFormatKHR {
*available_formats available_formats
.iter() .iter()
.find(|format| { .find(|format| {
format.format == vk::Format::B8G8R8A8_SRGB // Prefer SRGB format.format == vk::Format::B8G8R8A8_SRGB // Prefer SRGB
&& format.color_space == vk::ColorSpaceKHR::SRGB_NONLINEAR && format.color_space == vk::ColorSpaceKHR::SRGB_NONLINEAR
}) })
.unwrap_or(&available_formats[0]) .unwrap_or(&available_formats[0]) // Fallback to first available
.clone()
} }
fn choose_swapchain_present_mode(available_modes: &[vk::PresentModeKHR]) -> vk::PresentModeKHR { fn choose_swapchain_present_mode(available_modes: &[vk::PresentModeKHR]) -> vk::PresentModeKHR {
*available_modes available_modes
.iter() .iter()
.find(|&&mode| mode == vk::PresentModeKHR::FIFO) // Prefer Mailbox (low latency) .find(|&&mode| mode == vk::PresentModeKHR::MAILBOX) // Prefer Mailbox (low latency)
.unwrap_or(&vk::PresentModeKHR::FIFO) .unwrap_or(&vk::PresentModeKHR::FIFO) // Guaranteed fallback
.clone()
} }
fn choose_swapchain_extent( fn choose_swapchain_extent(
@ -1240,197 +937,8 @@ impl Renderer {
} }
Err(RendererError::Vulkan( Err(RendererError::Vulkan(
vk::Result::ERROR_FORMAT_NOT_SUPPORTED, vk::Result::ERROR_FORMAT_NOT_SUPPORTED,
)) )) // Or custom error
} }
fn create_material_descriptor_set_layout(
device: &Arc<Device>,
) -> Result<vk::DescriptorSetLayout, RendererError> {
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<Device>,
) -> Result<vk::DescriptorSetLayout, 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)?
};
Ok(descriptor_set_layout)
}
fn create_descriptor_pool(device: &Arc<Device>) -> Result<vk::DescriptorPool, RendererError> {
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(&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_pool)
}
fn create_descriptor_set(
device: &Arc<Device>,
descriptor_set_layouts: &[vk::DescriptorSetLayout],
descriptor_pool: vk::DescriptorPool,
) -> Result<vk::DescriptorSet, RendererError> {
let alloc_info = vk::DescriptorSetAllocateInfo::default()
.descriptor_pool(descriptor_pool)
.set_layouts(descriptor_set_layouts);
let descriptor_set = unsafe { device.raw().allocate_descriptor_sets(&alloc_info)? }[0];
Ok(descriptor_set)
}
fn create_uniform_buffer(
device: &Arc<Device>,
resource_manager: &Arc<ResourceManager>,
) -> Result<(vk::Buffer, Allocation, *mut std::ffi::c_void), RendererError> {
let buffer_size = mem::size_of::<UniformBufferObject>() 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<Device>,
descriptor_set: vk::DescriptorSet,
buffer: vk::Buffer,
) {
let buffer_info = vk::DescriptorBufferInfo::default()
.buffer(buffer)
.offset(0)
.range(mem::size_of::<UniformBufferObject>() 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);
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) -> UniformBufferObject {
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 { view, proj }
} }
// --- Drop Implementation --- // --- Drop Implementation ---
@ -1454,39 +962,16 @@ impl Drop for Renderer {
unsafe { unsafe {
self.device self.device
.raw() .raw()
.destroy_pipeline(self.model_pipeline, None); .destroy_pipeline(self.triangle_pipeline, None);
self.device self.device
.raw() .raw()
.destroy_pipeline_layout(self.model_pipeline_layout, None); .destroy_pipeline_layout(self.triangle_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) // Destroy frame data (fences, semaphores, command pools)
// Fences/Semaphores are handled by gfx_hal::Drop // Fences/Semaphores are handled by gfx_hal::Drop
// Command buffers are freed with the pool // Command buffers are freed with the pool
for frame_data in self.frames_data.drain(..) { 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 { unsafe {
self.device self.device
.raw() .raw()

View file

@ -7,8 +7,7 @@ edition = "2021"
ash.workspace = true ash.workspace = true
gpu-allocator.workspace = true gpu-allocator.workspace = true
thiserror.workspace = true thiserror.workspace = true
parking_lot.workspace = true
tracing.workspace = true tracing.workspace = true
gltf.workspace = true
gfx_hal = { path = "../gfx_hal" } gfx_hal = { path = "../gfx_hal" }
image = { version = "0.25.6", features = ["rayon"] }

View file

@ -34,12 +34,6 @@ pub enum ResourceManagerError {
#[error("Error occurred in GfxHal: {0}")] #[error("Error occurred in GfxHal: {0}")]
GfxHalError(#[from] gfx_hal::error::GfxHalError), 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}")] #[error("An unexpected error occurred: {0}")]
Other(String), Other(String),
} }

View file

@ -1,175 +0,0 @@
use std::sync::Arc;
use ash::vk;
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<T: Sized>(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.
#[derive(Clone)]
pub struct Geometry {
resource_manager: Arc<ResourceManager>,
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<V: Sized + Copy>(
resource_manager: Arc<ResourceManager>,
vertices: &[V],
indicies: &[u32],
) -> Result<Self> {
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<ResourceManager> reference count decreases automatically.
}
}

File diff suppressed because it is too large Load diff

View file

@ -1,59 +0,0 @@
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<Arc<Texture>>,
pub base_color_sampler: Option<SamplerHandle>,
pub base_color_factor: [f32; 4],
pub metallic_factor: f32,
pub roughness_factor: f32,
// TODO: Add other PBR properties:
// pub metallic_roughness_texture: Option<Arc<Texture>>,
// pub metallic_roughness_sampler: Option<SamplerHandle>,
// pub normal_texture: Option<Arc<Texture>>,
// pub normal_sampler: Option<SamplerHandle>,
// pub occlusion_texture: Option<Arc<Texture>>,
// pub occlusion_sampler: Option<SamplerHandle>,
// pub emissive_texture: Option<Arc<Texture>>,
// pub emissive_sampler: Option<SamplerHandle>,
// pub emissive_factor: [f32; 3],
// pub alpha_mode: gltf::material::AlphaMode,
// pub alpha_cutoff: f32,
// pub double_sided: bool,
}

View file

@ -1,14 +0,0 @@
[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" }

View file

@ -1,16 +0,0 @@
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<T> = std::result::Result<T, SceneError>;

View file

@ -1,411 +0,0 @@
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<Material>,
pub geometry: Arc<Geometry>,
pub transform: Mat4,
}
/// Stores all objects to be rendered by the renderer.
pub struct Scene {
pub name: String,
pub meshes: Vec<Mesh>,
}
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<T>(path: T, resource_manager: Arc<ResourceManager>) -> Result<Self>
where
T: AsRef<Path>,
{
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<Geometry>> = HashMap::new();
// Cache Materials: Key = glTF material index (usize::MAX for default)
let mut material_cache: HashMap<usize, Arc<Material>> = 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("<Default Scene>")
.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("<Unnamed Node>");
if let Some(mesh) = node.mesh() {
let mesh_index = mesh.index();
let mesh_name = mesh.name().unwrap_or("<Unnamed Mesh>");
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<Vertex> = positions
.into_iter()
.zip(normals)
.zip(tex_coords)
.map(|((pos, normal), tex_coord)| Vertex {
pos,
normal,
tex_coord,
})
.collect();
let indices: Vec<u32> = 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("<Default Material>");
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<Arc<Texture>> = None;
let mut loaded_base_color_sampler: Option<SamplerHandle> = 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<Texture>
}
}
// 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<Material>
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<ResourceManager>,
geometry_cache: &'a mut HashMap<(usize, usize), Arc<Geometry>>,
material_cache: &'a mut HashMap<usize, Arc<Material>>,
default_sampler_handle: SamplerHandle, // Store the default sampler
meshes: &'a mut Vec<Mesh>, // Store results directly
}

View file

@ -1,11 +0,0 @@
[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"

View file

@ -1,70 +0,0 @@
use ash::vk;
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 normal: [f32; 3],
pub tex_coord: [f32; 2],
}
impl Vertex {
pub fn get_binding_decription() -> vk::VertexInputBindingDescription {
vk::VertexInputBindingDescription::default()
.binding(0)
.stride(size_of::<Self>() as u32)
.input_rate(vk::VertexInputRate::VERTEX)
}
pub fn get_attribute_descriptions() -> [vk::VertexInputAttributeDescription; 3] {
[
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, normal) as u32),
vk::VertexInputAttributeDescription::default()
.location(2)
.binding(0)
.format(vk::Format::R32G32_SFLOAT)
.offset(memoffset::offset_of!(Vertex, tex_coord) as u32),
]
}
}
#[repr(C)]
#[derive(Clone, Debug, Copy, PartialEq)]
pub struct UniformBufferObject {
pub view: Mat4,
pub proj: Mat4,
}
#[derive(Clone, Debug, Copy, PartialEq)]
pub struct CameraInfo {
pub camera_pos: Vec3,
pub camera_target: Vec3,
pub camera_up: 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_up: Vec3::Y,
camera_fov: 45.0,
}
}
}

45
flake.lock generated
View file

@ -20,11 +20,11 @@
"nixpkgs-lib": "nixpkgs-lib" "nixpkgs-lib": "nixpkgs-lib"
}, },
"locked": { "locked": {
"lastModified": 1743550720, "lastModified": 1733312601,
"narHash": "sha256-hIshGgKZCgWh6AYJpJmRgFdR3WUbkY04o82X05xqQiY=", "narHash": "sha256-4pDvzqnegAfRkPwO3wmwBhVi/Sye1mzps0zHWYnP88c=",
"owner": "hercules-ci", "owner": "hercules-ci",
"repo": "flake-parts", "repo": "flake-parts",
"rev": "c621e8422220273271f52058f618c94e405bb0f5", "rev": "205b12d8b7cd4802fbcb8e8ef6a0f1408781a4f9",
"type": "github" "type": "github"
}, },
"original": { "original": {
@ -51,26 +51,23 @@
}, },
"nixpkgs-lib": { "nixpkgs-lib": {
"locked": { "locked": {
"lastModified": 1743296961, "lastModified": 1733096140,
"narHash": "sha256-b1EdN3cULCqtorQ4QeWgLMrd5ZGOjLSLemfa00heasc=", "narHash": "sha256-1qRH7uAUsyQI7R1Uwl4T+XvdNv778H0Nb5njNrqvylY=",
"owner": "nix-community", "type": "tarball",
"repo": "nixpkgs.lib", "url": "https://github.com/NixOS/nixpkgs/archive/5487e69da40cbd611ab2cadee0b4637225f7cfae.tar.gz"
"rev": "e4822aea2a6d1cdd36653c134cacfd64c97ff4fa",
"type": "github"
}, },
"original": { "original": {
"owner": "nix-community", "type": "tarball",
"repo": "nixpkgs.lib", "url": "https://github.com/NixOS/nixpkgs/archive/5487e69da40cbd611ab2cadee0b4637225f7cfae.tar.gz"
"type": "github"
} }
}, },
"nixpkgs_2": { "nixpkgs_2": {
"locked": { "locked": {
"lastModified": 1736320768, "lastModified": 1728538411,
"narHash": "sha256-nIYdTAiKIGnFNugbomgBJR+Xv5F1ZQU+HfaBqJKroC0=", "narHash": "sha256-f0SBJz1eZ2yOuKUr5CA9BHULGXVSn6miBuUWdTyhUhU=",
"owner": "NixOS", "owner": "NixOS",
"repo": "nixpkgs", "repo": "nixpkgs",
"rev": "4bc9c909d9ac828a039f288cf872d16d38185db8", "rev": "b69de56fac8c2b6f8fd27f2eca01dcda8e0a4221",
"type": "github" "type": "github"
}, },
"original": { "original": {
@ -82,11 +79,11 @@
}, },
"nixpkgs_3": { "nixpkgs_3": {
"locked": { "locked": {
"lastModified": 1735554305, "lastModified": 1733097829,
"narHash": "sha256-zExSA1i/b+1NMRhGGLtNfFGXgLtgo+dcuzHzaWA6w3Q=", "narHash": "sha256-9hbb1rqGelllb4kVUCZ307G2k3/UhmA8PPGBoyuWaSw=",
"owner": "nixos", "owner": "nixos",
"repo": "nixpkgs", "repo": "nixpkgs",
"rev": "0e82ab234249d8eee3e8c91437802b32c74bb3fd", "rev": "2c15aa59df0017ca140d9ba302412298ab4bf22a",
"type": "github" "type": "github"
}, },
"original": { "original": {
@ -111,11 +108,11 @@
"nixpkgs": "nixpkgs_2" "nixpkgs": "nixpkgs_2"
}, },
"locked": { "locked": {
"lastModified": 1743820323, "lastModified": 1735266518,
"narHash": "sha256-UXxJogXhPhBFaX4uxmMudcD/x3sEGFtoSc4busTcftY=", "narHash": "sha256-2XkWYGgT+911gOLjgBj+8W8ZJk6P0qHJNz8RfKgT/5o=",
"owner": "oxalica", "owner": "oxalica",
"repo": "rust-overlay", "repo": "rust-overlay",
"rev": "b4734ce867252f92cdc7d25f8cc3b7cef153e703", "rev": "e0b3654b716098b47f3643c65fbb75ef49c033e1",
"type": "github" "type": "github"
}, },
"original": { "original": {
@ -144,11 +141,11 @@
"nixpkgs": "nixpkgs_3" "nixpkgs": "nixpkgs_3"
}, },
"locked": { "locked": {
"lastModified": 1743748085, "lastModified": 1735135567,
"narHash": "sha256-uhjnlaVTWo5iD3LXics1rp9gaKgDRQj6660+gbUU3cE=", "narHash": "sha256-8T3K5amndEavxnludPyfj3Z1IkcFdRpR23q+T0BVeZE=",
"owner": "numtide", "owner": "numtide",
"repo": "treefmt-nix", "repo": "treefmt-nix",
"rev": "815e4121d6a5d504c0f96e5be2dd7f871e4fd99d", "rev": "9e09d30a644c57257715902efbb3adc56c79cf28",
"type": "github" "type": "github"
}, },
"original": { "original": {

View file

@ -35,12 +35,11 @@
commonArgs, commonArgs,
... ...
}: { }: {
_module.args = rec { _module.args = {
pkgs = import nixpkgs { pkgs = import nixpkgs {
inherit system; inherit system;
overlays = [inputs.rust-overlay.overlays.default]; overlays = [inputs.rust-overlay.overlays.default];
}; };
craneLib = (inputs.crane.mkLib pkgs).overrideToolchain ( craneLib = (inputs.crane.mkLib pkgs).overrideToolchain (
pkgs: pkgs.rust-bin.fromRustupToolchainFile ./rust-toolchain.toml pkgs: pkgs.rust-bin.fromRustupToolchainFile ./rust-toolchain.toml
); );

View file

@ -1,11 +1,6 @@
[toolchain] [toolchain]
channel = "nightly-2024-11-22" channel = "nightly-2024-11-22"
components = [ components = ["rust-src", "rustc-dev", "llvm-tools"]
"rust-src",
"rustc-dev",
"llvm-tools",
"rustc-codegen-cranelift-preview",
]
# commit_hash = b19329a37cedf2027517ae22c87cf201f93d776e # commit_hash = b19329a37cedf2027517ae22c87cf201f93d776e
# Whenever changing the nightly channel, update the commit hash above, and make # Whenever changing the nightly channel, update the commit hash above, and make

View file

@ -1,34 +1,7 @@
#version 450 #version 450
// Input from vertex shader
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(location = 0) out vec4 outColor;
// 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() { void main() {
// Sample the texture outColor = vec4(1.0, 0.5, 0.0, 1.0); // Orange
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
} }

View file

@ -1,38 +1,10 @@
#version 450 #version 450
vec2 positions[3] = vec2[](
// INPUTS from Vertex Buffer (matching Vertex struct) vec2(0.0, -0.5),
layout(location = 0) in vec3 inPosition; vec2(0.5, 0.5),
layout(location = 1) in vec3 inNormal; vec2(-0.5, 0.5)
layout(location = 2) in vec2 inTexCoord; // <<< MUST be vec2 );
// UNIFORMS (Set 0)
layout(set = 0, binding = 0) uniform UniformBufferObject {
mat4 view;
mat4 proj;
} ubo;
// 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() { void main() {
vec4 worldPos = pushConstants.model * vec4(inPosition, 1.0); gl_Position = vec4(positions[gl_VertexIndex], 0.0, 1.0);
// 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;
} }