feat: render and manage textures :3
This commit is contained in:
parent
2501390225
commit
74a1be796f
21 changed files with 2908 additions and 320 deletions
|
|
@ -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 {
|
||||
ty: vk::DescriptorType::UNIFORM_BUFFER,
|
||||
descriptor_count: MAX_FRAMES_IN_FLIGHT as u32,
|
||||
};
|
||||
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
Loading…
Add table
Add a link
Reference in a new issue