From 645dd93f21d40cdf04dbe9767a5b6288d272b537 Mon Sep 17 00:00:00 2001 From: SeanOMik Date: Thu, 18 May 2023 01:11:04 -0400 Subject: [PATCH] Add better 3d camera, fix memory 'leak' caused by not clearing the RenderJob queue --- Cargo.toml | 4 +- shell.nix | 2 + src/ecs/components/camera.rs | 45 +++--------------- src/game.rs | 70 +++++++++++++++++++++++---- src/main.rs | 7 ++- src/math/transform.rs | 30 +++++++++++- src/render/camera.rs | 92 ++++++++++++++++++++++++++++++++---- src/render/renderer.rs | 34 ++++++++----- 8 files changed, 211 insertions(+), 73 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index d27ee74..77dd9df 100755 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,7 +7,6 @@ edition = "2021" [dependencies] winit = "0.28.1" -#winit-modular = "0.1.1" tracing = "0.1.37" tracing-subscriber = { version = "0.3.16", features = [ "tracing-log" ] } tracing-log = "0.1.3" @@ -17,7 +16,6 @@ cfg-if = "1" bytemuck = { version = "1.12", features = [ "derive" ] } image = { version = "0.24", default-features = false, features = ["png", "jpeg"] } anyhow = "1.0" -#cgmath = "0.18" tobj = { version = "3.2.1", features = [ "async", ]} @@ -25,4 +23,4 @@ instant = "0.1" async-trait = "0.1.65" specs = { version = "0.18.0", features = [ "derive" ] } hecs = "0.10.3" -glam = { version = "0.24.0", features = ["bytemuck"] } +glam = { version = "0.24.0", features = ["bytemuck"] } \ No newline at end of file diff --git a/shell.nix b/shell.nix index 3d057cd..0044c65 100755 --- a/shell.nix +++ b/shell.nix @@ -8,6 +8,8 @@ mkShell rec { openssl wasm-pack trunk + valgrind + heaptrack ]; buildInputs = [ udev alsa-lib vulkan-loader diff --git a/src/ecs/components/camera.rs b/src/ecs/components/camera.rs index 646e30b..f677882 100755 --- a/src/ecs/components/camera.rs +++ b/src/ecs/components/camera.rs @@ -1,31 +1,19 @@ use winit::dpi::PhysicalSize; -use crate::math::{Angle, OPENGL_TO_WGPU_MATRIX}; +use crate::{math::{Angle, OPENGL_TO_WGPU_MATRIX, Transform}, render::camera::CameraProjectionMode}; pub struct CameraComponent { - eye: glam::Vec3, - target: glam::Vec3, - up: glam::Vec3, - aspect_ratio: Option, - fov_y: Angle, - znear: f32, - zfar: f32, + pub transform: Transform, + pub fov: Angle, + pub mode: CameraProjectionMode, } impl Default for CameraComponent { fn default() -> Self { Self { - // position the camera one unit up and 2 units back - // +z is out of the screen - eye: (0.0, 1.0, 2.0).into(), - // have it look at the origin - target: (0.0, 0.0, 0.0).into(), - // which way is "up" - up: (0.0, 1.0, 0.0).into(), - aspect_ratio: None,//config.width as f32 / config.height as f32, - fov_y: Angle::Degrees(45.0), - znear: 0.1, - zfar: 100.0, + transform: Transform::new(), + fov: Angle::Degrees(45.0), + mode: CameraProjectionMode::Perspective, } } } @@ -34,23 +22,4 @@ impl CameraComponent { pub fn new() -> Self { Self::default() } - - /// Set the camera aspect ratio. This should only be used internally. - pub(crate) fn set_aspect_ratio(&mut self, size: PhysicalSize) { - self.aspect_ratio = Some(size.width as f32 / size.height as f32); - } - - /// Check if the camera is ready to be used in rendering. - pub fn is_ready(&self) -> bool { - self.aspect_ratio.is_some() - } - - pub fn get_view_projection_matrix(&self) -> glam::Mat4 { - let aspect_ratio = self.aspect_ratio.expect("ERROR: Camera aspect ratio was not set!"); - - let view = glam::Mat4::look_at_rh(self.eye, self.target, self.up); - let projection = glam::Mat4::perspective_rh_gl(self.fov_y.to_radians(), aspect_ratio, self.znear, self.zfar); - - OPENGL_TO_WGPU_MATRIX * projection * view - } } \ No newline at end of file diff --git a/src/game.rs b/src/game.rs index 429a343..66291bd 100755 --- a/src/game.rs +++ b/src/game.rs @@ -3,6 +3,7 @@ use std::{sync::Arc, cell::RefCell}; use async_std::{task::block_on, sync::Mutex}; use hecs::World; +use instant::Instant; use tracing::{metadata::LevelFilter, info, debug, warn}; use tracing_subscriber::{ layer::{Layer, SubscriberExt}, @@ -14,11 +15,67 @@ use winit::{window::{WindowBuilder, Window}, event::{Event, WindowEvent, Keyboar use crate::{render::{renderer::{Renderer, BasicRenderer}, render_job::RenderJob}, input_event::InputEvent, ecs::components::{mesh::MeshComponent, transform::TransformComponent}}; +struct TickCounter { + counter: u32, + last_second: Instant, + changed: bool, + tps: f32, + /// the time (in seconds) that passes between each tick + tick_time: f32 +} + +impl TickCounter { + fn new() -> Self { + Self { + counter: 0, + last_second: Instant::now(), + tps: 0.0, + changed: false, + tick_time: 0.0, + } + } + + /// Returns true if the tps changed + fn tick(&mut self) -> bool { + self.counter += 1; + + if self.last_second.elapsed().as_secs() > 0 { + self.tick_time = 1000.0 / self.counter as f32; + self.tps = self.counter as f32; + + self.changed = true; + self.counter = 0; + self.last_second = Instant::now(); + } + + self.changed + } + + /// Gets the change in ticks per second + fn get_change(&mut self) -> Option { + match self.changed { + true => { + self.changed = false; + + Some(self.tps) + }, + false => None, + } + } + + /// Get the time (in seconds) between ticks + fn get_tick_time(&self) -> f32 { + self.tick_time + } +} + + struct GameLoop { window: Arc, renderer: Box, world: Arc>, + fps_counter: TickCounter, } impl GameLoop { @@ -28,6 +85,7 @@ impl GameLoop { renderer: Box::new(BasicRenderer::create_with_window(window).await), world, + fps_counter: TickCounter::new(), } } @@ -120,16 +178,12 @@ impl GameLoop { // Update the world self.update().await; - let mut world = self.world.lock().await; - - /* for (ent, (transform,)) in world.query_mut::<(&mut TransformComponent,)>() { - transform.transform.translate_vec3(glam::Vec3::new(0.0, 0.001, 0.0)); - } - - for (ent, (transform,)) in world.query::<(&TransformComponent,)>().iter() { - println!("transform pos: {:?}", transform.transform); + /* self.fps_counter.tick(); + if let Some(fps) = self.fps_counter.get_change() { + debug!("FPS: {}fps, {:.2}ms/frame", fps, self.fps_counter.get_tick_time()); } */ + let mut world = self.world.lock().await; self.renderer.as_mut().prepare(&mut world).await; drop(world); diff --git a/src/main.rs b/src/main.rs index 3776dbb..70d3469 100755 --- a/src/main.rs +++ b/src/main.rs @@ -14,6 +14,7 @@ use hecs::World; use crate::render::material::Material; use crate::render::texture::Texture; use crate::ecs::components::camera::CameraComponent; +use crate::math::Angle; //use specs::*; #[derive(Debug, Default)] @@ -79,7 +80,11 @@ async fn main() { TransformComponent::new(), )); - world.spawn((CameraComponent::new(),)); + let mut camera = CameraComponent::new(); + camera.transform.translate_vec3(glam::Vec3::new(0.0, 0.0, 2.0)); + //camera.transform.rotate_y(Angle::Degrees(-25.0)); + camera.transform.rotate_z(Angle::Degrees(-90.0)); + world.spawn((camera,)); Game::initialize().await .with_world(world) diff --git a/src/math/transform.rs b/src/math/transform.rs index e91cc92..27e052c 100755 --- a/src/math/transform.rs +++ b/src/math/transform.rs @@ -118,9 +118,37 @@ impl Transform { /// Rotate around the z axis. pub fn rotate_z(&mut self, angle: Angle) -> &mut Self { - self.rotate_axis(Vec3::new(0.0, 0.0, 0.0), angle) + self.rotate_axis(Vec3::new(0.0, 0.0, 1.0), angle) } + /// Get translation + pub fn get_translation(&self) -> glam::Vec3 { + let (_, _, t) = self.matrix.to_scale_rotation_translation(); + + t + } + + /// Get rotation in radians + pub fn get_rotation_rad(&self) -> glam::Vec3 { + let (_, quat, _) = self.matrix.to_scale_rotation_translation(); + let rot = quat.to_euler(glam::EulerRot::XYZ); + + rot.into() + } + + /// Get rotation in degrees + pub fn get_rotation_deg(&self) -> glam::Vec3 { + let mut rot = self.get_rotation_rad(); + + rot.x = angle::radians_to_degrees(rot.x); + rot.y = angle::radians_to_degrees(rot.y); + rot.z = angle::radians_to_degrees(rot.z); + + rot + } + + + pub fn scale(&mut self, scale: Vec3) -> &mut Self { let scale = Mat4::from_scale(scale); self.matrix *= scale; diff --git a/src/render/camera.rs b/src/render/camera.rs index a34e7f1..50fc38e 100755 --- a/src/render/camera.rs +++ b/src/render/camera.rs @@ -1,21 +1,95 @@ +use tracing::debug; use winit::dpi::PhysicalSize; use crate::{math::{Angle, OPENGL_TO_WGPU_MATRIX}, ecs::components::camera::CameraComponent}; -#[repr(C)] -#[derive(Debug, Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)] -pub struct Camera { - projection: glam::Mat4 +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum CameraProjectionMode { + /// 3d camera projection + Perspective, + /// 2d camera projection + Orthographic, } -impl Camera { - pub fn new() -> Self { +#[derive(Debug, Clone)] +pub struct Projection { + aspect: f32, + znear: f32, + zfar: f32, +} + +impl Projection { + pub fn new(width: u32, height: u32, znear: f32, zfar: f32) -> Self { Self { - projection: glam::Mat4::IDENTITY, + aspect: width as f32 / height as f32, + znear, + zfar, } } - pub fn update_view_projection(&mut self, camera: &CameraComponent) { - self.projection = camera.get_view_projection_matrix().into(); + pub fn resize(&mut self, width: u32, height: u32) { + self.aspect = width as f32 / height as f32; + } + + pub fn calc_matrix(&self, fov: Angle, mode: CameraProjectionMode) -> glam::Mat4 { + OPENGL_TO_WGPU_MATRIX * glam::Mat4::perspective_rh_gl(fov.to_radians(), self.aspect, self.znear, self.zfar) + } +} + +#[derive(Debug, Clone)] +pub struct RenderCamera { + view_proj: glam::Mat4, + + aspect: f32, + znear: f32, + zfar: f32, +} + +impl RenderCamera { + pub fn new(size: PhysicalSize) -> Self { + Self { + view_proj: glam::Mat4::IDENTITY, + + aspect: size.width as f32 / size.height as f32, + znear: 0.1, + zfar: 100.0, + } + } + + pub fn update_aspect_ratio(&mut self, size: PhysicalSize) { + self.aspect = size.width as f32 / size.height as f32; + } + + pub fn update_view_projection(&mut self, camera: &CameraComponent) -> &glam::Mat4 { + let rotation = camera.transform.get_rotation_rad(); + let position = camera.transform.get_translation(); + + let (sin_y, cos_y) = rotation.y.sin_cos(); + let (sin_z, cos_z) = rotation.z.sin_cos(); + + let target = glam::Vec3::new( + cos_y * cos_z, + sin_y, + cos_y * sin_z + ).normalize(); + + /* debug!("Camera rotation: {:?}", rotation); + debug!("Camera position: {:?}", position); + debug!("Camera target: {:?}", target); */ + + let view = glam::Mat4::look_to_rh( + position, + target, + glam::Vec3::new(0.0, 1.0, 0.0) + ); + + let proj = glam::Mat4::perspective_rh_gl(camera.fov.to_radians(), self.aspect, self.znear, self.zfar); + + self.view_proj = OPENGL_TO_WGPU_MATRIX * proj * view; + &self.view_proj + } + + pub fn view_proj(&self) -> &glam::Mat4 { + &self.view_proj } } \ No newline at end of file diff --git a/src/render/renderer.rs b/src/render/renderer.rs index b91f9b6..12d690f 100755 --- a/src/render/renderer.rs +++ b/src/render/renderer.rs @@ -16,10 +16,10 @@ use hecs::{World, Entity}; use crate::ecs::components::camera::CameraComponent; use crate::ecs::components::mesh::MeshComponent; use crate::ecs::components::transform::TransformComponent; -use crate::math::Transform; +use crate::math::{Transform, Angle}; use crate::resources; -use super::camera::Camera; +use super::camera::RenderCamera; use super::desc_buf_lay::DescVertexBufferLayout; use super::texture::RenderTexture; use super::{render_pipeline::FullRenderPipeline, vertex::{VERTICES}, render_buffer::BufferStorage, render_job::RenderJob, mesh::Mesh}; @@ -61,7 +61,7 @@ pub struct BasicRenderer { transform_buffer: wgpu::Buffer, transform_bind_group: wgpu::BindGroup, - inuse_camera: Camera, + inuse_camera: RenderCamera, camera_buffer: wgpu::Buffer, camera_bind_group: wgpu::BindGroup, } @@ -105,6 +105,13 @@ impl BasicRenderer { let surface_caps = surface.get_capabilities(&adapter); + let present_mode = surface_caps.present_modes[0]; /* match surface_caps.present_modes.contains(&wgpu::PresentMode::Immediate) { + true => wgpu::PresentMode::Immediate, + false => surface_caps.present_modes[0] + }; */ + + println!("present mode: {:?}", present_mode); + let surface_format = surface_caps.formats.iter() .copied() .filter(|f| f.describe().srgb) @@ -115,7 +122,7 @@ impl BasicRenderer { format: surface_format, width: size.width, height: size.height, - present_mode: surface_caps.present_modes[0], + present_mode, alpha_mode: surface_caps.alpha_modes[0], view_formats: vec![], }; @@ -190,7 +197,7 @@ impl BasicRenderer { let camera_buffer = device.create_buffer_init( &wgpu::util::BufferInitDescriptor { label: Some("Camera Buffer"), - contents: bytemuck::cast_slice(&[Camera::new()]), + contents: bytemuck::cast_slice(&[glam::Mat4::IDENTITY]), usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST, } ); @@ -247,7 +254,7 @@ impl BasicRenderer { transform_buffer, transform_bind_group, - inuse_camera: Camera::new(), + inuse_camera: RenderCamera::new(size), camera_buffer, camera_bind_group, } @@ -365,12 +372,8 @@ impl Renderer for BasicRenderer { } if let Some((_e, (camera,))) = main_world.query_mut::<(&mut CameraComponent,)>().into_iter().next() { - //if !camera.is_ready() { - camera.set_aspect_ratio(self.size); - //} - - self.inuse_camera.update_view_projection(camera); - self.queue.write_buffer(&self.camera_buffer, 0, bytemuck::cast_slice(&[self.inuse_camera])); + let view_proj = self.inuse_camera.update_view_projection(camera); + self.queue.write_buffer(&self.camera_buffer, 0, bytemuck::cast_slice(&[view_proj.clone()])); } else { warn!("Missing camera!"); } @@ -419,7 +422,7 @@ impl Renderer for BasicRenderer { // If the job has a transform, set the uniform to it if let Some(transform) = job.transform { - self.queue.write_buffer(&self.transform_buffer, 0, bytemuck::cast_slice(&[transform])); + //self.queue.write_buffer(&self.transform_buffer, 0, bytemuck::cast_slice(&[transform])); render_pass.set_bind_group(1, &self.transform_bind_group, &[]); } else { debug!("//TODO: clear transform uniform if the RenderJob doesn't have a transform."); @@ -447,6 +450,9 @@ impl Renderer for BasicRenderer { self.queue.submit(std::iter::once(encoder.finish())); output.present(); + // TODO: Use an actual queue instead of a hashmap + self.render_jobs = HashMap::new(); + Ok(()) } @@ -456,6 +462,8 @@ impl Renderer for BasicRenderer { self.config.width = new_size.width; self.config.height = new_size.height; self.surface.configure(&self.device, &self.config); + + self.inuse_camera.update_aspect_ratio(self.size); } }