feat: render and manage textures :3
This commit is contained in:
parent
2501390225
commit
74a1be796f
21 changed files with 2908 additions and 320 deletions
862
Cargo.lock
generated
862
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
11
Cargo.toml
11
Cargo.toml
|
|
@ -5,7 +5,9 @@ members = [
|
|||
"crates/engine",
|
||||
"crates/gfx_hal",
|
||||
"crates/renderer",
|
||||
"crates/resource_manager", "crates/shared",
|
||||
"crates/resource_manager",
|
||||
"crates/scene",
|
||||
"crates/shared",
|
||||
]
|
||||
|
||||
[workspace.dependencies]
|
||||
|
|
@ -24,10 +26,12 @@ egui-ash-renderer = { version = "0.8.0", features = [
|
|||
"dynamic-rendering",
|
||||
] }
|
||||
egui = "0.31"
|
||||
egui_tiles = "0.12"
|
||||
bytemuck = { version = "1.21.0", features = ["derive"] }
|
||||
tracing = "0.1"
|
||||
tracing = { features = ["release_max_level_warn"], version = "0.1" }
|
||||
tracing-subscriber = { version = "0.3", features = ["json"] }
|
||||
thiserror = "2.0.12"
|
||||
gltf = "1.4.1"
|
||||
|
||||
|
||||
# # Enable incremental by default in release mode.
|
||||
|
|
@ -56,4 +60,7 @@ thiserror = "2.0.12"
|
|||
#
|
||||
# rustflags = ["-Zshare-generics=off"]
|
||||
# codegen-units = 1
|
||||
opt-level = 1
|
||||
|
||||
[profile.dev.package."*"]
|
||||
opt-level = 3
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ edition = "2021"
|
|||
|
||||
[dependencies]
|
||||
egui.workspace = true
|
||||
egui_tiles.workspace = true
|
||||
ash.workspace = true
|
||||
ash-window.workspace = true
|
||||
color-eyre.workspace = true
|
||||
|
|
@ -19,6 +20,7 @@ gfx_hal = { path = "../gfx_hal" }
|
|||
renderer = { path = "../renderer" }
|
||||
resource_manager = { path = "../resource_manager" }
|
||||
shared = { path = "../shared" }
|
||||
scene = { path = "../scene" }
|
||||
|
||||
clap = { version = "4.5.34", features = ["derive"] }
|
||||
egui-winit = "0.31.1"
|
||||
|
|
|
|||
|
|
@ -17,14 +17,16 @@ use gfx_hal::{
|
|||
use glam::Vec3;
|
||||
use raw_window_handle::HasDisplayHandle;
|
||||
use renderer::{Renderer, RendererError};
|
||||
use resource_manager::{Geometry, ResourceManager, ResourceManagerError};
|
||||
use shared::{CameraInfo, Vertex};
|
||||
use resource_manager::{ResourceManager, ResourceManagerError};
|
||||
use scene::Scene;
|
||||
use shared::CameraInfo;
|
||||
use tracing::{debug, error, info, warn};
|
||||
use tracing_subscriber::{filter, layer::SubscriberExt, util::SubscriberInitExt, Layer};
|
||||
use winit::{
|
||||
application::ApplicationHandler,
|
||||
event::WindowEvent,
|
||||
event::{ElementState, KeyEvent, MouseButton, WindowEvent},
|
||||
event_loop::{ActiveEventLoop, EventLoop},
|
||||
keyboard::{KeyCode, PhysicalKey},
|
||||
window::Window,
|
||||
};
|
||||
|
||||
|
|
@ -47,6 +49,8 @@ enum AppError {
|
|||
NoSuitableDevice,
|
||||
#[error("Failed to create CString: {0}")]
|
||||
NulError(#[from] std::ffi::NulError),
|
||||
#[error("Scene Error: {0}")]
|
||||
SceneError(#[from] scene::SceneError),
|
||||
}
|
||||
|
||||
struct Application {
|
||||
|
|
@ -66,6 +70,23 @@ struct Application {
|
|||
egui_winit: State,
|
||||
egui_app: EditorUI,
|
||||
|
||||
// --- Camera State ---
|
||||
camera_info: CameraInfo,
|
||||
camera_speed: f32,
|
||||
camera_sensitivity: f32,
|
||||
|
||||
// --- Input State ---
|
||||
is_forward_pressed: bool,
|
||||
is_backward_pressed: bool,
|
||||
is_left_pressed: bool,
|
||||
is_right_pressed: bool,
|
||||
is_up_pressed: bool, // Optional: For flying up
|
||||
is_down_pressed: bool, // Optional: For flying down
|
||||
is_rmb_pressed: bool, // Right mouse button
|
||||
last_mouse_pos: Option<(f64, f64)>,
|
||||
mouse_delta: (f64, f64),
|
||||
capture_mouse: bool, // Flag to indicate if mouse should control camera
|
||||
|
||||
// Windowing
|
||||
window: Arc<Window>, // Use Arc for potential multi-threading later
|
||||
|
||||
|
|
@ -76,17 +97,15 @@ struct Application {
|
|||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct EditorUI {
|
||||
camera_info: CameraInfo,
|
||||
}
|
||||
struct EditorUI {}
|
||||
|
||||
impl EditorUI {
|
||||
fn title() -> String {
|
||||
"engine".to_string()
|
||||
}
|
||||
|
||||
fn build_ui(&mut self, ctx: &egui::Context, current_fps: f64) {
|
||||
egui::Window::new(Self::title()).show(ctx, |ui| {
|
||||
fn build_ui(&mut self, ctx: &egui::Context, current_fps: f64, camera_info: &mut CameraInfo) {
|
||||
egui::SidePanel::new(egui::panel::Side::Left, Self::title()).show(ctx, |ui| {
|
||||
ui.label(format!("FPS - {:.2}", current_fps));
|
||||
|
||||
ui.separator();
|
||||
|
|
@ -96,12 +115,78 @@ impl EditorUI {
|
|||
.striped(true)
|
||||
.show(ui, |ui| {
|
||||
ui.label("FOV");
|
||||
ui.add(Slider::new(&mut self.camera_info.camera_fov, 10.0..=120.0));
|
||||
// Modify the passed-in camera_info
|
||||
ui.add(Slider::new(&mut camera_info.camera_fov, 10.0..=120.0));
|
||||
ui.end_row(); // Good practice in grids
|
||||
|
||||
// You could add more camera controls here if needed
|
||||
// e.g., sliders for position, target (though direct manipulation is better)
|
||||
ui.label("Camera Pos");
|
||||
ui.label(format!(
|
||||
"({:.1}, {:.1}, {:.1})",
|
||||
camera_info.camera_pos.x,
|
||||
camera_info.camera_pos.y,
|
||||
camera_info.camera_pos.z
|
||||
));
|
||||
ui.end_row();
|
||||
|
||||
ui.label("Camera Target");
|
||||
ui.label(format!(
|
||||
"({:.1}, {:.1}, {:.1})",
|
||||
camera_info.camera_target.x,
|
||||
camera_info.camera_target.y,
|
||||
camera_info.camera_target.z
|
||||
));
|
||||
ui.end_row();
|
||||
});
|
||||
|
||||
ui.separator();
|
||||
ui.label("Controls:");
|
||||
ui.label("RMB + Drag: Look");
|
||||
ui.label("WASD: Move");
|
||||
ui.label("Space: Up");
|
||||
ui.label("Shift: Down");
|
||||
ui.label("Hold RMB to activate controls.");
|
||||
});
|
||||
|
||||
// let mut tree = create_tree();
|
||||
//
|
||||
// egui::panel::SidePanel::new(egui::panel::Side::Left, Id::new("main_panel")).show(
|
||||
// ctx,
|
||||
// |ui| {
|
||||
// let mut behavior = TreeBehavior {};
|
||||
// tree.ui(&mut behavior, ui);
|
||||
// },
|
||||
// );
|
||||
}
|
||||
}
|
||||
|
||||
fn create_tree() -> egui_tiles::Tree<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)]
|
||||
struct ApplicationWrapper {
|
||||
app: Option<Application>,
|
||||
|
|
@ -264,56 +349,10 @@ impl Application {
|
|||
let resource_manager = Arc::new(ResourceManager::new(instance.clone(), device.clone())?);
|
||||
info!("Resource Manager initialized.");
|
||||
|
||||
let vertices = vec![
|
||||
// Define 8 vertices for a cube with positions and colors
|
||||
// Make sure winding order is correct (e.g., counter-clockwise for front faces)
|
||||
Vertex {
|
||||
pos: Vec3::new(-0.5, -0.5, 0.5).into(),
|
||||
color: Vec3::new(1.0, 0.0, 0.0).into(),
|
||||
}, // Front bottom left 0
|
||||
Vertex {
|
||||
pos: Vec3::new(0.5, -0.5, 0.5).into(),
|
||||
color: Vec3::new(0.0, 1.0, 0.0).into(),
|
||||
}, // Front bottom right 1
|
||||
Vertex {
|
||||
pos: Vec3::new(0.5, 0.5, 0.5).into(),
|
||||
color: Vec3::new(0.0, 0.0, 1.0).into(),
|
||||
}, // Front top right 2
|
||||
Vertex {
|
||||
pos: Vec3::new(-0.5, 0.5, 0.5).into(),
|
||||
color: Vec3::new(1.0, 1.0, 0.0).into(),
|
||||
}, // Front top left 3
|
||||
// ... add back face vertices (4-7) ...
|
||||
Vertex {
|
||||
pos: Vec3::new(-0.5, -0.5, -0.5).into(),
|
||||
color: Vec3::new(1.0, 0.0, 1.0).into(),
|
||||
}, // Back bottom left 4
|
||||
Vertex {
|
||||
pos: Vec3::new(0.5, -0.5, -0.5).into(),
|
||||
color: Vec3::new(0.0, 1.0, 1.0).into(),
|
||||
}, // Back bottom right 5
|
||||
Vertex {
|
||||
pos: Vec3::new(0.5, 0.5, -0.5).into(),
|
||||
color: Vec3::new(0.5, 0.5, 0.5).into(),
|
||||
}, // Back top right 6
|
||||
Vertex {
|
||||
pos: Vec3::new(-0.5, 0.5, -0.5).into(),
|
||||
color: Vec3::new(1.0, 1.0, 1.0).into(),
|
||||
}, // Back top left 7
|
||||
];
|
||||
|
||||
let indices = vec![
|
||||
// Define 12 triangles (36 indices) for the cube faces
|
||||
// Front face
|
||||
0, 1, 2, 2, 3, 0, // Right face
|
||||
1, 5, 6, 6, 2, 1, // Back face
|
||||
5, 4, 7, 7, 6, 5, // Left face
|
||||
4, 0, 3, 3, 7, 4, // Top face
|
||||
3, 2, 6, 6, 7, 3, // Bottom face
|
||||
4, 5, 1, 1, 0, 4,
|
||||
];
|
||||
|
||||
let cube_geometry = Geometry::new(resource_manager.clone(), &vertices, &indices)?;
|
||||
let scene = Scene::from_gltf(
|
||||
"./sponza/NewSponza_Main_glTF_003.gltf",
|
||||
resource_manager.clone(),
|
||||
)?;
|
||||
|
||||
// --- 5. Renderer ---
|
||||
let initial_size = window.inner_size();
|
||||
|
|
@ -323,7 +362,7 @@ impl Application {
|
|||
graphics_queue.clone(),
|
||||
surface.clone(),
|
||||
resource_manager.clone(),
|
||||
vec![cube_geometry],
|
||||
scene,
|
||||
initial_size.width,
|
||||
initial_size.height,
|
||||
)?;
|
||||
|
|
@ -341,6 +380,8 @@ impl Application {
|
|||
|
||||
info!("Renderer initialized.");
|
||||
|
||||
let camera_info = CameraInfo::default(); // Get default camera settings
|
||||
|
||||
Ok(Self {
|
||||
_instance: instance,
|
||||
_physical_device: physical_device,
|
||||
|
|
@ -353,6 +394,23 @@ impl Application {
|
|||
egui_winit,
|
||||
egui_ctx,
|
||||
egui_app,
|
||||
|
||||
// --- Camera ---
|
||||
camera_info, // Store the camera state here
|
||||
camera_speed: 5.0, // Adjust as needed
|
||||
camera_sensitivity: 0.002, // Adjust as needed
|
||||
|
||||
// --- Input ---
|
||||
is_forward_pressed: false,
|
||||
is_backward_pressed: false,
|
||||
is_left_pressed: false,
|
||||
is_right_pressed: false,
|
||||
is_up_pressed: false,
|
||||
is_down_pressed: false,
|
||||
is_rmb_pressed: false,
|
||||
last_mouse_pos: None,
|
||||
mouse_delta: (0.0, 0.0),
|
||||
capture_mouse: false, // Start with mouse free
|
||||
frame_count: 0,
|
||||
current_fps: 0.,
|
||||
last_fps_update_time: Instant::now(),
|
||||
|
|
@ -361,7 +419,11 @@ impl Application {
|
|||
}
|
||||
|
||||
fn handle_event(&mut self, event: &WindowEvent, active_event_loop: &ActiveEventLoop) {
|
||||
let _ = self.egui_winit.on_window_event(&self.window, event);
|
||||
// Let egui process the event first
|
||||
let egui_consumed_event = self.egui_winit.on_window_event(&self.window, event);
|
||||
|
||||
// Only process input for camera if egui didn't consume it AND we are capturing
|
||||
let process_camera_input = !egui_consumed_event.consumed && self.capture_mouse;
|
||||
|
||||
match event {
|
||||
WindowEvent::CloseRequested => {
|
||||
|
|
@ -385,29 +447,127 @@ impl Application {
|
|||
.resize(new_inner_size.width, new_inner_size.height);
|
||||
}
|
||||
// Handle other inputs if not consumed by egui
|
||||
WindowEvent::KeyboardInput { .. }
|
||||
| WindowEvent::CursorMoved { .. }
|
||||
| WindowEvent::MouseInput { .. } => {}
|
||||
WindowEvent::MouseInput { state, button, .. } => {
|
||||
if *button == MouseButton::Right {
|
||||
let is_pressed = *state == ElementState::Pressed;
|
||||
self.is_rmb_pressed = is_pressed;
|
||||
|
||||
// Decide whether to capture/release mouse based on RMB
|
||||
// Only capture if pressed *outside* an egui interactive area
|
||||
if is_pressed && !self.egui_ctx.is_pointer_over_area() {
|
||||
self.capture_mouse = true;
|
||||
self.window
|
||||
.set_cursor_grab(winit::window::CursorGrabMode::Confined)
|
||||
.or_else(|_| {
|
||||
self.window
|
||||
.set_cursor_grab(winit::window::CursorGrabMode::Locked)
|
||||
})
|
||||
.unwrap_or(());
|
||||
self.window.set_cursor_visible(false);
|
||||
self.last_mouse_pos = None; // Reset last pos on capture start
|
||||
} else if !is_pressed {
|
||||
self.capture_mouse = false;
|
||||
self.window
|
||||
.set_cursor_grab(winit::window::CursorGrabMode::None)
|
||||
.unwrap_or(());
|
||||
self.window.set_cursor_visible(true);
|
||||
self.mouse_delta = (0.0, 0.0); // Stop camera movement
|
||||
}
|
||||
}
|
||||
// Let egui handle its mouse clicks regardless of capture state
|
||||
// (handled by on_window_event)
|
||||
}
|
||||
|
||||
WindowEvent::CursorMoved { position, .. } => {
|
||||
let current_pos = (position.x, position.y);
|
||||
if self.capture_mouse {
|
||||
// Only calculate delta if capturing
|
||||
if let Some(last_pos) = self.last_mouse_pos {
|
||||
self.mouse_delta.0 += current_pos.0 - last_pos.0;
|
||||
self.mouse_delta.1 += current_pos.1 - last_pos.1;
|
||||
}
|
||||
// Store position relative to window center might be more robust
|
||||
// with set_cursor_position, but this works with grab/confine too.
|
||||
self.last_mouse_pos = Some(current_pos);
|
||||
} else {
|
||||
// Still update egui's pointer position even if not capturing
|
||||
// (handled by on_window_event)
|
||||
self.last_mouse_pos = None; // Reset if not capturing
|
||||
}
|
||||
}
|
||||
|
||||
// Use PhysicalKey for layout-independent keys
|
||||
WindowEvent::KeyboardInput {
|
||||
event:
|
||||
KeyEvent {
|
||||
physical_key,
|
||||
state,
|
||||
..
|
||||
},
|
||||
..
|
||||
} => {
|
||||
// Let egui handle keyboard input first if it wants it
|
||||
if egui_consumed_event.consumed {
|
||||
return;
|
||||
}
|
||||
|
||||
let is_pressed = *state == ElementState::Pressed;
|
||||
match physical_key {
|
||||
PhysicalKey::Code(KeyCode::KeyW) | PhysicalKey::Code(KeyCode::ArrowUp) => {
|
||||
self.is_forward_pressed = is_pressed;
|
||||
}
|
||||
PhysicalKey::Code(KeyCode::KeyS) | PhysicalKey::Code(KeyCode::ArrowDown) => {
|
||||
self.is_backward_pressed = is_pressed;
|
||||
}
|
||||
PhysicalKey::Code(KeyCode::KeyA) | PhysicalKey::Code(KeyCode::ArrowLeft) => {
|
||||
self.is_left_pressed = is_pressed;
|
||||
}
|
||||
PhysicalKey::Code(KeyCode::KeyD) | PhysicalKey::Code(KeyCode::ArrowRight) => {
|
||||
self.is_right_pressed = is_pressed;
|
||||
}
|
||||
PhysicalKey::Code(KeyCode::Space) => {
|
||||
self.is_up_pressed = is_pressed;
|
||||
}
|
||||
PhysicalKey::Code(KeyCode::ShiftLeft)
|
||||
| PhysicalKey::Code(KeyCode::ShiftRight) => {
|
||||
self.is_down_pressed = is_pressed;
|
||||
}
|
||||
// Optional: Escape to release mouse capture
|
||||
PhysicalKey::Code(KeyCode::Escape) if is_pressed && self.capture_mouse => {
|
||||
self.capture_mouse = false;
|
||||
self.is_rmb_pressed = false; // Ensure RMB state is also reset
|
||||
self.window
|
||||
.set_cursor_grab(winit::window::CursorGrabMode::None)
|
||||
.unwrap_or(());
|
||||
self.window.set_cursor_visible(true);
|
||||
self.mouse_delta = (0.0, 0.0);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
WindowEvent::RedrawRequested => {
|
||||
let now = Instant::now();
|
||||
let _delta_time = now.duration_since(self.last_frame_time);
|
||||
let delta_time = now.duration_since(self.last_frame_time).as_secs_f32();
|
||||
self.last_frame_time = now;
|
||||
|
||||
let elapsed_sice_last_update = now.duration_since(self.last_fps_update_time);
|
||||
// --- FPS Calculation ---
|
||||
let elapsed_since_last_update = now.duration_since(self.last_fps_update_time);
|
||||
self.frame_count += 1;
|
||||
|
||||
if elapsed_sice_last_update >= Duration::from_secs(1) {
|
||||
let fps = self.frame_count as f64 / elapsed_sice_last_update.as_secs_f64();
|
||||
self.current_fps = fps;
|
||||
|
||||
let new_title = format!("{} - {} - {:.0} FPS", ENGINE_NAME, APP_NAME, fps);
|
||||
if elapsed_since_last_update >= Duration::from_secs(1) {
|
||||
self.current_fps =
|
||||
self.frame_count as f64 / elapsed_since_last_update.as_secs_f64();
|
||||
let new_title = format!(
|
||||
"{} - {} - {:.0} FPS",
|
||||
ENGINE_NAME, APP_NAME, self.current_fps
|
||||
);
|
||||
self.window.set_title(&new_title);
|
||||
|
||||
self.frame_count = 0;
|
||||
self.last_fps_update_time = now;
|
||||
}
|
||||
|
||||
self.update_camera(delta_time); // Call the new update function
|
||||
|
||||
let raw_input = self.egui_winit.take_egui_input(&self.window);
|
||||
|
||||
let egui::FullOutput {
|
||||
|
|
@ -417,7 +577,8 @@ impl Application {
|
|||
pixels_per_point,
|
||||
..
|
||||
} = self.egui_ctx.run(raw_input, |ctx| {
|
||||
self.egui_app.build_ui(ctx, self.current_fps);
|
||||
self.egui_app
|
||||
.build_ui(ctx, self.current_fps, &mut self.camera_info);
|
||||
});
|
||||
|
||||
self.renderer.update_textures(textures_delta).unwrap();
|
||||
|
|
@ -431,7 +592,7 @@ impl Application {
|
|||
match self.renderer.render_frame(
|
||||
pixels_per_point,
|
||||
&clipped_primitives,
|
||||
self.egui_app.camera_info,
|
||||
self.camera_info,
|
||||
) {
|
||||
Ok(_) => {
|
||||
self.window.request_redraw();
|
||||
|
|
@ -454,6 +615,106 @@ impl Application {
|
|||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
// --- New Camera Update Function ---
|
||||
fn update_camera(&mut self, dt: f32) {
|
||||
if !self.capture_mouse
|
||||
&& self.mouse_delta == (0.0, 0.0)
|
||||
&& !self.is_forward_pressed
|
||||
&& !self.is_backward_pressed
|
||||
&& !self.is_left_pressed
|
||||
&& !self.is_right_pressed
|
||||
&& !self.is_up_pressed
|
||||
&& !self.is_down_pressed
|
||||
{
|
||||
return; // No input, no update needed
|
||||
}
|
||||
|
||||
let mut cam_pos = self.camera_info.camera_pos;
|
||||
let mut cam_target = self.camera_info.camera_target;
|
||||
let cam_up = self.camera_info.camera_up; // Usually Vec3::Y
|
||||
|
||||
// --- Mouse Look (Rotation) ---
|
||||
if self.capture_mouse && self.mouse_delta != (0.0, 0.0) {
|
||||
let (delta_x, delta_y) = self.mouse_delta;
|
||||
self.mouse_delta = (0.0, 0.0); // Consume the delta
|
||||
|
||||
let sensitivity = self.camera_sensitivity;
|
||||
let yaw_delta = delta_x as f32 * sensitivity;
|
||||
let pitch_delta = delta_y as f32 * sensitivity;
|
||||
|
||||
let forward_dir = (cam_target - cam_pos).normalize();
|
||||
let right_dir = forward_dir.cross(cam_up).normalize();
|
||||
// Recalculate up to prevent roll if needed, though cross product handles it here
|
||||
let current_up = right_dir.cross(forward_dir).normalize();
|
||||
|
||||
// --- Pitch (Up/Down) ---
|
||||
// Calculate new forward direction based on pitch rotation around right axis
|
||||
let pitch_quat = glam::Quat::from_axis_angle(right_dir, -pitch_delta); // Negative for standard mouse look
|
||||
let mut new_forward = pitch_quat * forward_dir;
|
||||
|
||||
// Clamp pitch to avoid flipping over (e.g., +/- 89 degrees)
|
||||
let max_pitch_angle = 89.0f32.to_radians();
|
||||
let current_pitch = new_forward.angle_between(cam_up) - 90.0f32.to_radians();
|
||||
if current_pitch.abs() > max_pitch_angle {
|
||||
// Revert pitch if it exceeds limits
|
||||
new_forward = forward_dir; // Keep previous forward if clamp needed
|
||||
}
|
||||
|
||||
// --- Yaw (Left/Right) ---
|
||||
// Rotate the (potentially pitch-adjusted) forward direction and right vector around the global up axis (Y)
|
||||
let yaw_quat = glam::Quat::from_axis_angle(Vec3::Y, -yaw_delta); // Negative for standard mouse look
|
||||
new_forward = yaw_quat * new_forward;
|
||||
|
||||
// Update target based on the new forward direction
|
||||
cam_target = cam_pos + new_forward;
|
||||
|
||||
// Update the camera's internal up vector based on yaw rotation as well
|
||||
// This prevents weird tilting when looking straight up/down if up wasn't Vec3::Y
|
||||
// self.camera_info.camera_up = yaw_quat * current_up; // Optional: only if up can change
|
||||
}
|
||||
|
||||
// --- Keyboard Movement ---
|
||||
let forward_dir = (cam_target - cam_pos).normalize();
|
||||
// Use Vec3::Y for world-relative right/up movement, or calculate from forward/up
|
||||
let right_dir = forward_dir.cross(Vec3::Y).normalize();
|
||||
// let up_dir = right_dir.cross(forward_dir).normalize(); // Camera's local up
|
||||
let world_up_dir = Vec3::Y; // Use world up for space/shift
|
||||
|
||||
let effective_speed = self.camera_speed * dt;
|
||||
let mut move_delta = Vec3::ZERO;
|
||||
|
||||
if self.is_forward_pressed {
|
||||
move_delta += forward_dir;
|
||||
}
|
||||
if self.is_backward_pressed {
|
||||
move_delta -= forward_dir;
|
||||
}
|
||||
if self.is_left_pressed {
|
||||
move_delta -= right_dir;
|
||||
}
|
||||
if self.is_right_pressed {
|
||||
move_delta += right_dir;
|
||||
}
|
||||
if self.is_up_pressed {
|
||||
move_delta += world_up_dir; // Move along world Y
|
||||
}
|
||||
if self.is_down_pressed {
|
||||
move_delta -= world_up_dir; // Move along world Y
|
||||
}
|
||||
|
||||
// Normalize move_delta if non-zero to ensure consistent speed diagonally
|
||||
if move_delta != Vec3::ZERO {
|
||||
let move_vec = move_delta.normalize() * effective_speed;
|
||||
cam_pos += move_vec;
|
||||
cam_target += move_vec; // Move target along with position
|
||||
}
|
||||
|
||||
// --- Apply Changes ---
|
||||
self.camera_info.camera_pos = cam_pos;
|
||||
self.camera_info.camera_target = cam_target;
|
||||
// self.camera_info.camera_up remains Vec3::Y usually
|
||||
}
|
||||
}
|
||||
|
||||
// --- Helper Functions ---
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ winit.workspace = true
|
|||
gfx_hal = { path = "../gfx_hal" }
|
||||
resource_manager = { path = "../resource_manager" }
|
||||
shared = { path = "../shared" }
|
||||
scene = { path = "../scene" }
|
||||
|
||||
[build-dependencies]
|
||||
shaderc = "0.9.1"
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
use std::{
|
||||
collections::HashMap,
|
||||
ffi::c_void,
|
||||
mem,
|
||||
sync::{Arc, Mutex},
|
||||
|
|
@ -17,12 +18,15 @@ use gpu_allocator::{
|
|||
vulkan::{Allocation, AllocationCreateDesc, Allocator},
|
||||
MemoryLocation,
|
||||
};
|
||||
use resource_manager::{Geometry, ImageHandle, ResourceManager, ResourceManagerError};
|
||||
use resource_manager::{
|
||||
ImageHandle, Material, ResourceManager, ResourceManagerError, SamplerHandle, Texture,
|
||||
};
|
||||
use shared::{CameraInfo, UniformBufferObject};
|
||||
use thiserror::Error;
|
||||
use tracing::{debug, error, info, warn};
|
||||
|
||||
const MAX_FRAMES_IN_FLIGHT: usize = 2;
|
||||
const MAX_MATERIALS: usize = 150;
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum RendererError {
|
||||
|
|
@ -60,6 +64,9 @@ pub enum RendererError {
|
|||
AllocatorUnavailable, // Added based on egui requirement
|
||||
#[error("Allocator Error: {0}")]
|
||||
AllocatorError(#[from] gpu_allocator::AllocationError),
|
||||
|
||||
#[error("Other Error: {0}")]
|
||||
Other(String),
|
||||
}
|
||||
|
||||
impl<T> From<std::sync::PoisonError<T>> for RendererError {
|
||||
|
|
@ -101,11 +108,13 @@ pub struct Renderer {
|
|||
swapchain_format: vk::SurfaceFormatKHR,
|
||||
swapchain_extent: vk::Extent2D,
|
||||
|
||||
scene: Vec<Geometry>,
|
||||
scene: scene::Scene,
|
||||
|
||||
descriptor_set_layout: vk::DescriptorSetLayout,
|
||||
descriptor_pool: vk::DescriptorPool,
|
||||
|
||||
material_descriptor_set_layout: vk::DescriptorSetLayout,
|
||||
|
||||
egui_renderer: EguiRenderer,
|
||||
|
||||
depth_image_handle: ImageHandle,
|
||||
|
|
@ -115,6 +124,11 @@ pub struct Renderer {
|
|||
model_pipeline_layout: vk::PipelineLayout,
|
||||
model_pipeline: vk::Pipeline,
|
||||
|
||||
material_descriptor_sets: HashMap<usize, vk::DescriptorSet>,
|
||||
|
||||
default_white_texture: Option<Arc<Texture>>,
|
||||
default_sampler: SamplerHandle,
|
||||
|
||||
frames_data: Vec<FrameData>,
|
||||
current_frame: usize,
|
||||
|
||||
|
|
@ -134,7 +148,7 @@ impl Renderer {
|
|||
graphics_queue: Arc<Queue>,
|
||||
surface: Arc<Surface>,
|
||||
resource_manager: Arc<ResourceManager>,
|
||||
scene: Vec<Geometry>,
|
||||
scene: scene::Scene,
|
||||
initial_width: u32,
|
||||
initial_height: u32,
|
||||
) -> Result<Self, RendererError> {
|
||||
|
|
@ -154,14 +168,18 @@ impl Renderer {
|
|||
let (depth_image_handle, depth_image_view) =
|
||||
Self::create_depth_resources(&device, &resource_manager, extent, depth_format)?;
|
||||
|
||||
let (descriptor_set_layout, descriptor_pool) =
|
||||
Self::create_descriptor_sets_resources(&device)?;
|
||||
let descriptor_set_layout = Self::create_descriptor_set_layout(&device)?;
|
||||
let material_descriptor_set_layout = Self::create_material_descriptor_set_layout(&device)?;
|
||||
|
||||
let descriptor_set_layouts = [descriptor_set_layout, material_descriptor_set_layout];
|
||||
|
||||
let descriptor_pool = Self::create_descriptor_pool(&device)?;
|
||||
|
||||
let (model_pipeline_layout, model_pipeline) = Self::create_model_pipeline(
|
||||
&device,
|
||||
format.format,
|
||||
depth_format,
|
||||
descriptor_set_layout,
|
||||
&descriptor_set_layouts,
|
||||
)?;
|
||||
|
||||
let start_time = Instant::now();
|
||||
|
|
@ -170,9 +188,8 @@ impl Renderer {
|
|||
&device,
|
||||
&resource_manager,
|
||||
descriptor_pool,
|
||||
descriptor_set_layout,
|
||||
&descriptor_set_layouts,
|
||||
swapchain.extent(),
|
||||
start_time,
|
||||
)?;
|
||||
|
||||
info!("Renderer initialized successfully.");
|
||||
|
|
@ -191,6 +208,13 @@ impl Renderer {
|
|||
},
|
||||
)?;
|
||||
|
||||
let default_sampler = resource_manager.get_or_create_sampler(&Default::default())?;
|
||||
|
||||
let default_white_texture = Some(Self::create_default_texture(
|
||||
device.clone(),
|
||||
resource_manager.clone(),
|
||||
));
|
||||
|
||||
Ok(Self {
|
||||
device,
|
||||
graphics_queue,
|
||||
|
|
@ -204,11 +228,19 @@ impl Renderer {
|
|||
swapchain_extent: extent,
|
||||
descriptor_set_layout,
|
||||
descriptor_pool,
|
||||
|
||||
material_descriptor_set_layout,
|
||||
depth_image_handle,
|
||||
depth_image_view,
|
||||
depth_format,
|
||||
model_pipeline_layout,
|
||||
model_pipeline,
|
||||
|
||||
material_descriptor_sets: HashMap::new(),
|
||||
|
||||
default_white_texture,
|
||||
default_sampler,
|
||||
|
||||
frames_data,
|
||||
scene,
|
||||
current_frame: 0,
|
||||
|
|
@ -220,6 +252,134 @@ impl Renderer {
|
|||
})
|
||||
}
|
||||
|
||||
/// Gets or creates/updates a descriptor set for a given material.
|
||||
fn get_or_create_material_set(
|
||||
&mut self,
|
||||
material: &Arc<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,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
pub fn resize(&mut self, width: u32, height: u32) {
|
||||
if width > 0 && height > 0 {
|
||||
self.window_resized = true;
|
||||
|
|
@ -425,9 +585,7 @@ impl Renderer {
|
|||
.cmd_set_scissor(command_buffer, 0, &[scissor]);
|
||||
}
|
||||
|
||||
// --- Draw Triangle ---
|
||||
unsafe {
|
||||
// Need unsafe for Vulkan commands
|
||||
self.device.raw().cmd_bind_pipeline(
|
||||
command_buffer,
|
||||
vk::PipelineBindPoint::GRAPHICS,
|
||||
|
|
@ -444,10 +602,48 @@ impl Renderer {
|
|||
);
|
||||
}
|
||||
|
||||
for g in &self.scene {
|
||||
g.draw(self.device.raw(), command_buffer)?;
|
||||
let meshes = self.scene.meshes.clone();
|
||||
|
||||
for mesh in meshes {
|
||||
let material_set = self.get_or_create_material_set(&mesh.material)?;
|
||||
|
||||
unsafe {
|
||||
self.device.raw().cmd_bind_descriptor_sets(
|
||||
command_buffer,
|
||||
vk::PipelineBindPoint::GRAPHICS,
|
||||
self.model_pipeline_layout,
|
||||
1,
|
||||
&[material_set],
|
||||
&[],
|
||||
);
|
||||
}
|
||||
|
||||
let model_matrix_bytes = unsafe {
|
||||
std::slice::from_raw_parts(
|
||||
mesh.transform.as_ref().as_ptr() as *const u8,
|
||||
std::mem::size_of::<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,
|
||||
|
|
@ -716,7 +912,7 @@ impl Renderer {
|
|||
device: &Arc<Device>,
|
||||
color_format: vk::Format,
|
||||
depth_format: vk::Format,
|
||||
descriptor_set_layout: vk::DescriptorSetLayout,
|
||||
descriptor_set_layouts: &[vk::DescriptorSetLayout],
|
||||
) -> Result<(vk::PipelineLayout, vk::Pipeline), RendererError> {
|
||||
// Load compiled SPIR-V (replace with actual loading)
|
||||
let vert_shader_code = include_bytes!(concat!(env!("OUT_DIR"), "/shaders/vert.glsl.spv")); // Placeholder path
|
||||
|
|
@ -787,9 +983,15 @@ impl Renderer {
|
|||
let dynamic_state =
|
||||
vk::PipelineDynamicStateCreateInfo::default().dynamic_states(&dynamic_states);
|
||||
|
||||
let push_constant_range = vk::PushConstantRange::default()
|
||||
.stage_flags(vk::ShaderStageFlags::VERTEX)
|
||||
.offset(0)
|
||||
.size(mem::size_of::<Mat4>() as u32);
|
||||
|
||||
// --- Pipeline Layout ---
|
||||
let layout_info = vk::PipelineLayoutCreateInfo::default()
|
||||
.set_layouts(std::slice::from_ref(&descriptor_set_layout)); // No descriptors/push constants
|
||||
.set_layouts(descriptor_set_layouts)
|
||||
.push_constant_ranges(std::slice::from_ref(&push_constant_range));
|
||||
let pipeline_layout = unsafe {
|
||||
device
|
||||
.raw()
|
||||
|
|
@ -891,9 +1093,8 @@ impl Renderer {
|
|||
device: &Arc<Device>,
|
||||
resource_manager: &Arc<ResourceManager>,
|
||||
descriptor_pool: vk::DescriptorPool,
|
||||
descriptor_set_layout: vk::DescriptorSetLayout,
|
||||
descriptor_set_layouts: &[vk::DescriptorSetLayout],
|
||||
swapchain_extent: vk::Extent2D,
|
||||
instant: Instant,
|
||||
) -> Result<Vec<FrameData>, RendererError> {
|
||||
let mut frames_data = Vec::with_capacity(MAX_FRAMES_IN_FLIGHT);
|
||||
for _ in 0..MAX_FRAMES_IN_FLIGHT {
|
||||
|
|
@ -930,15 +1131,14 @@ impl Renderer {
|
|||
tracing::info!("Allocated frame_data command_buffer: {:?}", command_buffer);
|
||||
|
||||
let descriptor_set =
|
||||
Self::create_descriptor_set(device, descriptor_set_layout, descriptor_pool)?;
|
||||
Self::create_descriptor_set(device, descriptor_set_layouts, descriptor_pool)?;
|
||||
|
||||
let (uniform_buffer, uniform_buffer_allocation, uniform_buffer_mapped_ptr) =
|
||||
Self::create_uniform_buffer(device, resource_manager)?;
|
||||
|
||||
Self::update_descriptor_set(device.clone(), descriptor_set, uniform_buffer);
|
||||
|
||||
let uniform_buffer_object =
|
||||
calculate_ubo(CameraInfo::default(), swapchain_extent, instant);
|
||||
let uniform_buffer_object = calculate_ubo(CameraInfo::default(), swapchain_extent);
|
||||
|
||||
frames_data.push(FrameData {
|
||||
textures_to_free: None,
|
||||
|
|
@ -1043,9 +1243,37 @@ impl Renderer {
|
|||
))
|
||||
}
|
||||
|
||||
fn create_descriptor_sets_resources(
|
||||
fn create_material_descriptor_set_layout(
|
||||
device: &Arc<Device>,
|
||||
) -> Result<(vk::DescriptorSetLayout, vk::DescriptorPool), RendererError> {
|
||||
) -> 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)
|
||||
|
|
@ -1061,29 +1289,38 @@ impl Renderer {
|
|||
.create_descriptor_set_layout(&layout_info, None)?
|
||||
};
|
||||
|
||||
let pool_size = vk::DescriptorPoolSize {
|
||||
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(std::slice::from_ref(&pool_size))
|
||||
.max_sets(MAX_FRAMES_IN_FLIGHT as u32);
|
||||
.pool_sizes(&pool_sizes)
|
||||
.max_sets(MAX_FRAMES_IN_FLIGHT as u32 + MAX_MATERIALS as u32);
|
||||
|
||||
let descriptor_pool = unsafe { device.raw().create_descriptor_pool(&pool_info, None)? };
|
||||
|
||||
Ok((descriptor_set_layout, descriptor_pool))
|
||||
Ok(descriptor_pool)
|
||||
}
|
||||
|
||||
fn create_descriptor_set(
|
||||
device: &Arc<Device>,
|
||||
descriptor_set_layout: vk::DescriptorSetLayout,
|
||||
descriptor_set_layouts: &[vk::DescriptorSetLayout],
|
||||
descriptor_pool: vk::DescriptorPool,
|
||||
) -> Result<vk::DescriptorSet, RendererError> {
|
||||
let layouts = vec![descriptor_set_layout; 1];
|
||||
let alloc_info = vk::DescriptorSetAllocateInfo::default()
|
||||
.descriptor_pool(descriptor_pool)
|
||||
.set_layouts(&layouts);
|
||||
.set_layouts(descriptor_set_layouts);
|
||||
|
||||
let descriptor_set = unsafe { device.raw().allocate_descriptor_sets(&alloc_info)? }[0];
|
||||
|
||||
|
|
@ -1167,7 +1404,7 @@ impl Renderer {
|
|||
fn update_uniform_buffer(&mut self, camera_info: CameraInfo) -> Result<(), RendererError> {
|
||||
let frame_data = &mut self.frames_data[self.current_frame];
|
||||
|
||||
let ubo = calculate_ubo(camera_info, self.swapchain_extent, self.start_time);
|
||||
let ubo = calculate_ubo(camera_info, self.swapchain_extent);
|
||||
|
||||
if frame_data.uniform_buffer_object != ubo {
|
||||
let ptr = frame_data.uniform_buffer_mapped_ptr;
|
||||
|
|
@ -1181,15 +1418,7 @@ impl Renderer {
|
|||
}
|
||||
}
|
||||
|
||||
fn calculate_ubo(
|
||||
camera_info: CameraInfo,
|
||||
swapchain_extent: vk::Extent2D,
|
||||
start: Instant,
|
||||
) -> UniformBufferObject {
|
||||
let time = start.elapsed();
|
||||
|
||||
let model = Mat4::from_rotation_y(time.as_secs_f32());
|
||||
|
||||
fn calculate_ubo(camera_info: CameraInfo, swapchain_extent: vk::Extent2D) -> UniformBufferObject {
|
||||
let view = Mat4::look_at_rh(camera_info.camera_pos, camera_info.camera_target, Vec3::Y);
|
||||
|
||||
let mut proj = Mat4::perspective_rh(
|
||||
|
|
@ -1201,7 +1430,7 @@ fn calculate_ubo(
|
|||
|
||||
proj.y_axis.y *= -1.0;
|
||||
|
||||
UniformBufferObject { model, view, proj }
|
||||
UniformBufferObject { view, proj }
|
||||
}
|
||||
|
||||
// --- Drop Implementation ---
|
||||
|
|
|
|||
|
|
@ -8,5 +8,7 @@ ash.workspace = true
|
|||
gpu-allocator.workspace = true
|
||||
thiserror.workspace = true
|
||||
tracing.workspace = true
|
||||
gltf.workspace = true
|
||||
|
||||
gfx_hal = { path = "../gfx_hal" }
|
||||
image = { version = "0.25.6", features = ["rayon"] }
|
||||
|
|
|
|||
|
|
@ -34,6 +34,12 @@ pub enum ResourceManagerError {
|
|||
#[error("Error occurred in GfxHal: {0}")]
|
||||
GfxHalError(#[from] gfx_hal::error::GfxHalError),
|
||||
|
||||
#[error("I/O Error occurred: {0}")]
|
||||
Io(#[from] std::io::Error),
|
||||
|
||||
#[error("Image Error occurred: {0}")]
|
||||
ImageError(#[from] image::ImageError),
|
||||
|
||||
#[error("An unexpected error occurred: {0}")]
|
||||
Other(String),
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ unsafe fn as_byte_slice<T: Sized>(data: &[T]) -> &[u8] {
|
|||
|
||||
/// Represents geometry data (verticies and indicies) stored in GPU buffers managed by
|
||||
/// ResourceManager. Handles automatic cleanup via a `Drop` implementation.
|
||||
#[derive(Clone)]
|
||||
pub struct Geometry {
|
||||
resource_manager: Arc<ResourceManager>,
|
||||
pub vertex_buffer: BufferHandle,
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
59
crates/resource_manager/src/texture.rs
Normal file
59
crates/resource_manager/src/texture.rs
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
use std::sync::Arc;
|
||||
|
||||
use ash::vk;
|
||||
|
||||
use crate::{ImageHandle, SamplerHandle};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Texture {
|
||||
pub handle: ImageHandle,
|
||||
|
||||
pub format: vk::Format,
|
||||
pub extent: vk::Extent3D,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
pub struct SamplerDesc {
|
||||
pub mag_filter: vk::Filter,
|
||||
pub min_filter: vk::Filter,
|
||||
pub mipmap_mode: vk::SamplerMipmapMode,
|
||||
pub address_mode_u: vk::SamplerAddressMode,
|
||||
pub address_mode_v: vk::SamplerAddressMode,
|
||||
pub address_mode_w: vk::SamplerAddressMode,
|
||||
}
|
||||
|
||||
impl Default for SamplerDesc {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
mag_filter: vk::Filter::LINEAR,
|
||||
min_filter: vk::Filter::LINEAR,
|
||||
mipmap_mode: vk::SamplerMipmapMode::LINEAR,
|
||||
address_mode_u: vk::SamplerAddressMode::REPEAT,
|
||||
address_mode_v: vk::SamplerAddressMode::REPEAT,
|
||||
address_mode_w: vk::SamplerAddressMode::REPEAT,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Material {
|
||||
pub name: String,
|
||||
pub base_color_texture: Option<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,
|
||||
}
|
||||
14
crates/scene/Cargo.toml
Normal file
14
crates/scene/Cargo.toml
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
[package]
|
||||
name = "scene"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
ash.workspace = true
|
||||
thiserror.workspace = true
|
||||
tracing.workspace = true
|
||||
glam.workspace = true
|
||||
gltf.workspace = true
|
||||
|
||||
shared = { path = "../shared" }
|
||||
resource_manager = { path = "../resource_manager" }
|
||||
16
crates/scene/src/error.rs
Normal file
16
crates/scene/src/error.rs
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
use thiserror::Error;
|
||||
|
||||
/// Any errors that can be returned from this crate.
|
||||
#[derive(Error, Debug)]
|
||||
pub enum SceneError {
|
||||
#[error("Error from ResourceManager: {0}")]
|
||||
ResourceManagerError(#[from] resource_manager::ResourceManagerError),
|
||||
|
||||
#[error("Error from GLTF: {0}")]
|
||||
GltfError(#[from] gltf::Error),
|
||||
|
||||
#[error("InconsistentData: {0}")]
|
||||
InconsistentData(String),
|
||||
}
|
||||
|
||||
pub type Result<T> = std::result::Result<T, SceneError>;
|
||||
411
crates/scene/src/lib.rs
Normal file
411
crates/scene/src/lib.rs
Normal file
|
|
@ -0,0 +1,411 @@
|
|||
mod error;
|
||||
|
||||
use ash::vk;
|
||||
pub use error::{Result, SceneError};
|
||||
use glam::Mat4;
|
||||
use shared::Vertex;
|
||||
|
||||
use std::{collections::HashMap, path::Path, sync::Arc};
|
||||
|
||||
use resource_manager::{Geometry, Material, ResourceManager, SamplerDesc, SamplerHandle, Texture};
|
||||
|
||||
/// Represents a drawable entity in the scene, storing geometry with its transform.
|
||||
#[derive(Clone)]
|
||||
pub struct Mesh {
|
||||
pub name: String,
|
||||
pub material: Arc<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
|
||||
}
|
||||
|
|
@ -4,11 +4,14 @@ use glam::{Mat4, Vec3};
|
|||
use core::f32;
|
||||
use std::mem::size_of;
|
||||
|
||||
mod material;
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Clone, Debug, Copy)]
|
||||
pub struct Vertex {
|
||||
pub pos: [f32; 3],
|
||||
pub color: [f32; 3],
|
||||
pub normal: [f32; 3],
|
||||
pub tex_coord: [f32; 2],
|
||||
}
|
||||
|
||||
impl Vertex {
|
||||
|
|
@ -19,7 +22,7 @@ impl Vertex {
|
|||
.input_rate(vk::VertexInputRate::VERTEX)
|
||||
}
|
||||
|
||||
pub fn get_attribute_descriptions() -> [vk::VertexInputAttributeDescription; 2] {
|
||||
pub fn get_attribute_descriptions() -> [vk::VertexInputAttributeDescription; 3] {
|
||||
[
|
||||
vk::VertexInputAttributeDescription::default()
|
||||
.location(0)
|
||||
|
|
@ -30,7 +33,12 @@ impl Vertex {
|
|||
.location(1)
|
||||
.binding(0)
|
||||
.format(vk::Format::R32G32B32_SFLOAT)
|
||||
.offset(memoffset::offset_of!(Vertex, color) as u32),
|
||||
.offset(memoffset::offset_of!(Vertex, normal) as u32),
|
||||
vk::VertexInputAttributeDescription::default()
|
||||
.location(2)
|
||||
.binding(0)
|
||||
.format(vk::Format::R32G32_SFLOAT)
|
||||
.offset(memoffset::offset_of!(Vertex, tex_coord) as u32),
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
@ -38,7 +46,6 @@ impl Vertex {
|
|||
#[repr(C)]
|
||||
#[derive(Clone, Debug, Copy, PartialEq)]
|
||||
pub struct UniformBufferObject {
|
||||
pub model: Mat4,
|
||||
pub view: Mat4,
|
||||
pub proj: Mat4,
|
||||
}
|
||||
|
|
@ -47,6 +54,7 @@ pub struct UniformBufferObject {
|
|||
pub struct CameraInfo {
|
||||
pub camera_pos: Vec3,
|
||||
pub camera_target: Vec3,
|
||||
pub camera_up: Vec3,
|
||||
pub camera_fov: f32,
|
||||
}
|
||||
|
||||
|
|
@ -55,6 +63,7 @@ impl Default for CameraInfo {
|
|||
Self {
|
||||
camera_pos: Vec3::new(10.0, 10.0, 10.0),
|
||||
camera_target: Vec3::new(0.0, 0.0, 0.0),
|
||||
camera_up: Vec3::Y,
|
||||
camera_fov: 45.0,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
0
crates/shared/src/material.rs
Normal file
0
crates/shared/src/material.rs
Normal file
45
flake.lock
generated
45
flake.lock
generated
|
|
@ -20,11 +20,11 @@
|
|||
"nixpkgs-lib": "nixpkgs-lib"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1733312601,
|
||||
"narHash": "sha256-4pDvzqnegAfRkPwO3wmwBhVi/Sye1mzps0zHWYnP88c=",
|
||||
"lastModified": 1743550720,
|
||||
"narHash": "sha256-hIshGgKZCgWh6AYJpJmRgFdR3WUbkY04o82X05xqQiY=",
|
||||
"owner": "hercules-ci",
|
||||
"repo": "flake-parts",
|
||||
"rev": "205b12d8b7cd4802fbcb8e8ef6a0f1408781a4f9",
|
||||
"rev": "c621e8422220273271f52058f618c94e405bb0f5",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
|
@ -51,23 +51,26 @@
|
|||
},
|
||||
"nixpkgs-lib": {
|
||||
"locked": {
|
||||
"lastModified": 1733096140,
|
||||
"narHash": "sha256-1qRH7uAUsyQI7R1Uwl4T+XvdNv778H0Nb5njNrqvylY=",
|
||||
"type": "tarball",
|
||||
"url": "https://github.com/NixOS/nixpkgs/archive/5487e69da40cbd611ab2cadee0b4637225f7cfae.tar.gz"
|
||||
"lastModified": 1743296961,
|
||||
"narHash": "sha256-b1EdN3cULCqtorQ4QeWgLMrd5ZGOjLSLemfa00heasc=",
|
||||
"owner": "nix-community",
|
||||
"repo": "nixpkgs.lib",
|
||||
"rev": "e4822aea2a6d1cdd36653c134cacfd64c97ff4fa",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"type": "tarball",
|
||||
"url": "https://github.com/NixOS/nixpkgs/archive/5487e69da40cbd611ab2cadee0b4637225f7cfae.tar.gz"
|
||||
"owner": "nix-community",
|
||||
"repo": "nixpkgs.lib",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs_2": {
|
||||
"locked": {
|
||||
"lastModified": 1728538411,
|
||||
"narHash": "sha256-f0SBJz1eZ2yOuKUr5CA9BHULGXVSn6miBuUWdTyhUhU=",
|
||||
"lastModified": 1736320768,
|
||||
"narHash": "sha256-nIYdTAiKIGnFNugbomgBJR+Xv5F1ZQU+HfaBqJKroC0=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "b69de56fac8c2b6f8fd27f2eca01dcda8e0a4221",
|
||||
"rev": "4bc9c909d9ac828a039f288cf872d16d38185db8",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
|
@ -79,11 +82,11 @@
|
|||
},
|
||||
"nixpkgs_3": {
|
||||
"locked": {
|
||||
"lastModified": 1733097829,
|
||||
"narHash": "sha256-9hbb1rqGelllb4kVUCZ307G2k3/UhmA8PPGBoyuWaSw=",
|
||||
"lastModified": 1735554305,
|
||||
"narHash": "sha256-zExSA1i/b+1NMRhGGLtNfFGXgLtgo+dcuzHzaWA6w3Q=",
|
||||
"owner": "nixos",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "2c15aa59df0017ca140d9ba302412298ab4bf22a",
|
||||
"rev": "0e82ab234249d8eee3e8c91437802b32c74bb3fd",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
|
@ -108,11 +111,11 @@
|
|||
"nixpkgs": "nixpkgs_2"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1735266518,
|
||||
"narHash": "sha256-2XkWYGgT+911gOLjgBj+8W8ZJk6P0qHJNz8RfKgT/5o=",
|
||||
"lastModified": 1743820323,
|
||||
"narHash": "sha256-UXxJogXhPhBFaX4uxmMudcD/x3sEGFtoSc4busTcftY=",
|
||||
"owner": "oxalica",
|
||||
"repo": "rust-overlay",
|
||||
"rev": "e0b3654b716098b47f3643c65fbb75ef49c033e1",
|
||||
"rev": "b4734ce867252f92cdc7d25f8cc3b7cef153e703",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
|
@ -141,11 +144,11 @@
|
|||
"nixpkgs": "nixpkgs_3"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1735135567,
|
||||
"narHash": "sha256-8T3K5amndEavxnludPyfj3Z1IkcFdRpR23q+T0BVeZE=",
|
||||
"lastModified": 1743748085,
|
||||
"narHash": "sha256-uhjnlaVTWo5iD3LXics1rp9gaKgDRQj6660+gbUU3cE=",
|
||||
"owner": "numtide",
|
||||
"repo": "treefmt-nix",
|
||||
"rev": "9e09d30a644c57257715902efbb3adc56c79cf28",
|
||||
"rev": "815e4121d6a5d504c0f96e5be2dd7f871e4fd99d",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
|
|
|||
|
|
@ -35,11 +35,12 @@
|
|||
commonArgs,
|
||||
...
|
||||
}: {
|
||||
_module.args = {
|
||||
_module.args = rec {
|
||||
pkgs = import nixpkgs {
|
||||
inherit system;
|
||||
overlays = [inputs.rust-overlay.overlays.default];
|
||||
};
|
||||
|
||||
craneLib = (inputs.crane.mkLib pkgs).overrideToolchain (
|
||||
pkgs: pkgs.rust-bin.fromRustupToolchainFile ./rust-toolchain.toml
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,6 +1,11 @@
|
|||
[toolchain]
|
||||
channel = "nightly-2024-11-22"
|
||||
components = ["rust-src", "rustc-dev", "llvm-tools"]
|
||||
components = [
|
||||
"rust-src",
|
||||
"rustc-dev",
|
||||
"llvm-tools",
|
||||
"rustc-codegen-cranelift-preview",
|
||||
]
|
||||
# commit_hash = b19329a37cedf2027517ae22c87cf201f93d776e
|
||||
|
||||
# Whenever changing the nightly channel, update the commit hash above, and make
|
||||
|
|
|
|||
|
|
@ -1,16 +1,34 @@
|
|||
|
||||
#version 450
|
||||
|
||||
// Input from vertex shader
|
||||
layout(location = 0) in vec3 fragColor;
|
||||
// layout(location = 1) in vec2 fragTexCoord; // If using textures
|
||||
layout(location = 0) in vec3 fragNormal; // Receive normal
|
||||
layout(location = 1) in vec2 fragTexCoord; // Receive texture coordinates
|
||||
|
||||
// Output color
|
||||
layout(location = 0) out vec4 outColor;
|
||||
|
||||
// layout(binding = 1) uniform sampler2D texSampler; // If using textures
|
||||
// Descriptor set for material properties (Set 1)
|
||||
layout(set = 1, binding = 0) uniform sampler2D baseColorSampler;
|
||||
|
||||
// Optional: Pass material factors via another UBO or Push Constants if needed
|
||||
// layout(set = 1, binding = 1) uniform MaterialFactors {
|
||||
// vec4 baseColorFactor;
|
||||
// } materialFactors;
|
||||
|
||||
void main() {
|
||||
// Use interpolated color
|
||||
outColor = vec4(fragColor, 1.0);
|
||||
// outColor = texture(texSampler, fragTexCoord); // If using textures
|
||||
// Sample the texture
|
||||
vec4 texColor = texture(baseColorSampler, fragTexCoord);
|
||||
|
||||
// Use the texture color
|
||||
// You might multiply by baseColorFactor here if you pass it
|
||||
// outColor = texColor * materialFactors.baseColorFactor;
|
||||
outColor = texColor;
|
||||
|
||||
// Basic fallback if texture alpha is zero (or use baseColorFactor)
|
||||
if (outColor.a == 0.0) {
|
||||
outColor = vec4(0.8, 0.8, 0.8, 1.0); // Default grey
|
||||
}
|
||||
|
||||
// You could add basic lighting using fragNormal here later
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,26 +1,38 @@
|
|||
#version 450
|
||||
|
||||
// Matches Vertex struct attribute descriptions
|
||||
// INPUTS from Vertex Buffer (matching Vertex struct)
|
||||
layout(location = 0) in vec3 inPosition;
|
||||
layout(location = 1) in vec3 inColor;
|
||||
// layout(location = 2) in vec2 inTexCoord; // If you add texture coords
|
||||
layout(location = 1) in vec3 inNormal;
|
||||
layout(location = 2) in vec2 inTexCoord; // <<< MUST be vec2
|
||||
|
||||
// Matches UniformBufferObject struct and descriptor set layout binding
|
||||
layout(binding = 0) uniform UniformBufferObject {
|
||||
mat4 model;
|
||||
// UNIFORMS (Set 0)
|
||||
layout(set = 0, binding = 0) uniform UniformBufferObject {
|
||||
mat4 view;
|
||||
mat4 proj;
|
||||
} ubo;
|
||||
|
||||
// Output to fragment shader
|
||||
layout(location = 0) out vec3 fragColor;
|
||||
// layout(location = 1) out vec2 fragTexCoord; // If you add texture coords
|
||||
// PUSH CONSTANTS
|
||||
layout(push_constant) uniform PushConstants {
|
||||
mat4 model;
|
||||
} pushConstants;
|
||||
|
||||
// OUTPUTS to Fragment Shader
|
||||
layout(location = 0) out vec3 fragNormal; // Location 0 for Normal
|
||||
layout(location = 1) out vec2 fragTexCoord; // Location 1 for TexCoord
|
||||
|
||||
void main() {
|
||||
// Transform position: Model -> World -> View -> Clip space
|
||||
gl_Position = ubo.proj * ubo.view * ubo.model * vec4(inPosition, 1.0);
|
||||
vec4 worldPos = pushConstants.model * vec4(inPosition, 1.0);
|
||||
|
||||
// Pass color (and other attributes) through
|
||||
fragColor = inColor;
|
||||
// fragTexCoord = inTexCoord;
|
||||
// Calculate final position
|
||||
gl_Position = ubo.proj * ubo.view * worldPos;
|
||||
|
||||
// --- Pass attributes to Fragment Shader ---
|
||||
|
||||
// Pass world-space normal (adjust calculation if needed)
|
||||
// Ensure fragNormal is assigned a vec3
|
||||
fragNormal = normalize(mat3(transpose(inverse(pushConstants.model))) * inNormal);
|
||||
|
||||
// Pass texture coordinates (ensure inTexCoord is vec2)
|
||||
// Ensure fragTexCoord is assigned a vec2
|
||||
fragTexCoord = inTexCoord;
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue