Compare commits
No commits in common. "74a1be796f039feb8acbd28464e27e222c822b83" and "234e2ab78deec70a9b888e2e36ab5c3c4e3f6ab0" have entirely different histories.
74a1be796f
...
234e2ab78d
29 changed files with 464 additions and 4464 deletions
1643
Cargo.lock
generated
1643
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
10
Cargo.toml
10
Cargo.toml
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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"
|
|
||||||
|
|
|
||||||
|
|
@ -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()
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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).
|
||||||
|
|
|
||||||
|
|
@ -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())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -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::*;
|
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
|
|
@ -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"
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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()
|
||||||
|
|
|
||||||
|
|
@ -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"] }
|
|
||||||
|
|
|
||||||
|
|
@ -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),
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
|
|
@ -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,
|
|
||||||
}
|
|
||||||
|
|
@ -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" }
|
|
||||||
|
|
@ -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>;
|
|
||||||
|
|
@ -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
|
|
||||||
}
|
|
||||||
|
|
@ -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"
|
|
||||||
|
|
@ -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
45
flake.lock
generated
|
|
@ -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": {
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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;
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue