Add better 3d camera, fix memory 'leak' caused by not clearing the RenderJob queue

This commit is contained in:
SeanOMik 2023-05-18 01:11:04 -04:00
parent 3fc8cefa0e
commit 645dd93f21
Signed by: SeanOMik
GPG Key ID: 568F326C7EB33ACB
8 changed files with 211 additions and 73 deletions

View File

@ -7,7 +7,6 @@ edition = "2021"
[dependencies] [dependencies]
winit = "0.28.1" winit = "0.28.1"
#winit-modular = "0.1.1"
tracing = "0.1.37" tracing = "0.1.37"
tracing-subscriber = { version = "0.3.16", features = [ "tracing-log" ] } tracing-subscriber = { version = "0.3.16", features = [ "tracing-log" ] }
tracing-log = "0.1.3" tracing-log = "0.1.3"
@ -17,7 +16,6 @@ cfg-if = "1"
bytemuck = { version = "1.12", features = [ "derive" ] } bytemuck = { version = "1.12", features = [ "derive" ] }
image = { version = "0.24", default-features = false, features = ["png", "jpeg"] } image = { version = "0.24", default-features = false, features = ["png", "jpeg"] }
anyhow = "1.0" anyhow = "1.0"
#cgmath = "0.18"
tobj = { version = "3.2.1", features = [ tobj = { version = "3.2.1", features = [
"async", "async",
]} ]}
@ -25,4 +23,4 @@ instant = "0.1"
async-trait = "0.1.65" async-trait = "0.1.65"
specs = { version = "0.18.0", features = [ "derive" ] } specs = { version = "0.18.0", features = [ "derive" ] }
hecs = "0.10.3" hecs = "0.10.3"
glam = { version = "0.24.0", features = ["bytemuck"] } glam = { version = "0.24.0", features = ["bytemuck"] }

View File

@ -8,6 +8,8 @@ mkShell rec {
openssl openssl
wasm-pack wasm-pack
trunk trunk
valgrind
heaptrack
]; ];
buildInputs = [ buildInputs = [
udev alsa-lib vulkan-loader udev alsa-lib vulkan-loader

View File

@ -1,31 +1,19 @@
use winit::dpi::PhysicalSize; 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 { pub struct CameraComponent {
eye: glam::Vec3, pub transform: Transform,
target: glam::Vec3, pub fov: Angle,
up: glam::Vec3, pub mode: CameraProjectionMode,
aspect_ratio: Option<f32>,
fov_y: Angle,
znear: f32,
zfar: f32,
} }
impl Default for CameraComponent { impl Default for CameraComponent {
fn default() -> Self { fn default() -> Self {
Self { Self {
// position the camera one unit up and 2 units back transform: Transform::new(),
// +z is out of the screen fov: Angle::Degrees(45.0),
eye: (0.0, 1.0, 2.0).into(), mode: CameraProjectionMode::Perspective,
// 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,
} }
} }
} }
@ -34,23 +22,4 @@ impl CameraComponent {
pub fn new() -> Self { pub fn new() -> Self {
Self::default() Self::default()
} }
/// Set the camera aspect ratio. This should only be used internally.
pub(crate) fn set_aspect_ratio(&mut self, size: PhysicalSize<u32>) {
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
}
} }

View File

@ -3,6 +3,7 @@ use std::{sync::Arc, cell::RefCell};
use async_std::{task::block_on, sync::Mutex}; use async_std::{task::block_on, sync::Mutex};
use hecs::World; use hecs::World;
use instant::Instant;
use tracing::{metadata::LevelFilter, info, debug, warn}; use tracing::{metadata::LevelFilter, info, debug, warn};
use tracing_subscriber::{ use tracing_subscriber::{
layer::{Layer, SubscriberExt}, 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}}; 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<f32> {
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 { struct GameLoop {
window: Arc<Window>, window: Arc<Window>,
renderer: Box<dyn Renderer>, renderer: Box<dyn Renderer>,
world: Arc<Mutex<World>>, world: Arc<Mutex<World>>,
fps_counter: TickCounter,
} }
impl GameLoop { impl GameLoop {
@ -28,6 +85,7 @@ impl GameLoop {
renderer: Box::new(BasicRenderer::create_with_window(window).await), renderer: Box::new(BasicRenderer::create_with_window(window).await),
world, world,
fps_counter: TickCounter::new(),
} }
} }
@ -120,16 +178,12 @@ impl GameLoop {
// Update the world // Update the world
self.update().await; self.update().await;
let mut world = self.world.lock().await; /* self.fps_counter.tick();
if let Some(fps) = self.fps_counter.get_change() {
/* for (ent, (transform,)) in world.query_mut::<(&mut TransformComponent,)>() { debug!("FPS: {}fps, {:.2}ms/frame", fps, self.fps_counter.get_tick_time());
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);
} */ } */
let mut world = self.world.lock().await;
self.renderer.as_mut().prepare(&mut world).await; self.renderer.as_mut().prepare(&mut world).await;
drop(world); drop(world);

View File

@ -14,6 +14,7 @@ use hecs::World;
use crate::render::material::Material; use crate::render::material::Material;
use crate::render::texture::Texture; use crate::render::texture::Texture;
use crate::ecs::components::camera::CameraComponent; use crate::ecs::components::camera::CameraComponent;
use crate::math::Angle;
//use specs::*; //use specs::*;
#[derive(Debug, Default)] #[derive(Debug, Default)]
@ -79,7 +80,11 @@ async fn main() {
TransformComponent::new(), 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 Game::initialize().await
.with_world(world) .with_world(world)

View File

@ -118,9 +118,37 @@ impl Transform {
/// Rotate around the z axis. /// Rotate around the z axis.
pub fn rotate_z(&mut self, angle: Angle) -> &mut Self { 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 { pub fn scale(&mut self, scale: Vec3) -> &mut Self {
let scale = Mat4::from_scale(scale); let scale = Mat4::from_scale(scale);
self.matrix *= scale; self.matrix *= scale;

View File

@ -1,21 +1,95 @@
use tracing::debug;
use winit::dpi::PhysicalSize; use winit::dpi::PhysicalSize;
use crate::{math::{Angle, OPENGL_TO_WGPU_MATRIX}, ecs::components::camera::CameraComponent}; use crate::{math::{Angle, OPENGL_TO_WGPU_MATRIX}, ecs::components::camera::CameraComponent};
#[repr(C)] #[derive(Debug, Clone, PartialEq, Eq)]
#[derive(Debug, Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)] pub enum CameraProjectionMode {
pub struct Camera { /// 3d camera projection
projection: glam::Mat4 Perspective,
/// 2d camera projection
Orthographic,
} }
impl Camera { #[derive(Debug, Clone)]
pub fn new() -> Self { pub struct Projection {
aspect: f32,
znear: f32,
zfar: f32,
}
impl Projection {
pub fn new(width: u32, height: u32, znear: f32, zfar: f32) -> Self {
Self { Self {
projection: glam::Mat4::IDENTITY, aspect: width as f32 / height as f32,
znear,
zfar,
} }
} }
pub fn update_view_projection(&mut self, camera: &CameraComponent) { pub fn resize(&mut self, width: u32, height: u32) {
self.projection = camera.get_view_projection_matrix().into(); 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<u32>) -> 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<u32>) {
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
} }
} }

View File

@ -16,10 +16,10 @@ use hecs::{World, Entity};
use crate::ecs::components::camera::CameraComponent; use crate::ecs::components::camera::CameraComponent;
use crate::ecs::components::mesh::MeshComponent; use crate::ecs::components::mesh::MeshComponent;
use crate::ecs::components::transform::TransformComponent; use crate::ecs::components::transform::TransformComponent;
use crate::math::Transform; use crate::math::{Transform, Angle};
use crate::resources; use crate::resources;
use super::camera::Camera; use super::camera::RenderCamera;
use super::desc_buf_lay::DescVertexBufferLayout; use super::desc_buf_lay::DescVertexBufferLayout;
use super::texture::RenderTexture; use super::texture::RenderTexture;
use super::{render_pipeline::FullRenderPipeline, vertex::{VERTICES}, render_buffer::BufferStorage, render_job::RenderJob, mesh::Mesh}; 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_buffer: wgpu::Buffer,
transform_bind_group: wgpu::BindGroup, transform_bind_group: wgpu::BindGroup,
inuse_camera: Camera, inuse_camera: RenderCamera,
camera_buffer: wgpu::Buffer, camera_buffer: wgpu::Buffer,
camera_bind_group: wgpu::BindGroup, camera_bind_group: wgpu::BindGroup,
} }
@ -105,6 +105,13 @@ impl BasicRenderer {
let surface_caps = surface.get_capabilities(&adapter); 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() let surface_format = surface_caps.formats.iter()
.copied() .copied()
.filter(|f| f.describe().srgb) .filter(|f| f.describe().srgb)
@ -115,7 +122,7 @@ impl BasicRenderer {
format: surface_format, format: surface_format,
width: size.width, width: size.width,
height: size.height, height: size.height,
present_mode: surface_caps.present_modes[0], present_mode,
alpha_mode: surface_caps.alpha_modes[0], alpha_mode: surface_caps.alpha_modes[0],
view_formats: vec![], view_formats: vec![],
}; };
@ -190,7 +197,7 @@ impl BasicRenderer {
let camera_buffer = device.create_buffer_init( let camera_buffer = device.create_buffer_init(
&wgpu::util::BufferInitDescriptor { &wgpu::util::BufferInitDescriptor {
label: Some("Camera Buffer"), 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, usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
} }
); );
@ -247,7 +254,7 @@ impl BasicRenderer {
transform_buffer, transform_buffer,
transform_bind_group, transform_bind_group,
inuse_camera: Camera::new(), inuse_camera: RenderCamera::new(size),
camera_buffer, camera_buffer,
camera_bind_group, 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 let Some((_e, (camera,))) = main_world.query_mut::<(&mut CameraComponent,)>().into_iter().next() {
//if !camera.is_ready() { let view_proj = self.inuse_camera.update_view_projection(camera);
camera.set_aspect_ratio(self.size); self.queue.write_buffer(&self.camera_buffer, 0, bytemuck::cast_slice(&[view_proj.clone()]));
//}
self.inuse_camera.update_view_projection(camera);
self.queue.write_buffer(&self.camera_buffer, 0, bytemuck::cast_slice(&[self.inuse_camera]));
} else { } else {
warn!("Missing camera!"); warn!("Missing camera!");
} }
@ -419,7 +422,7 @@ impl Renderer for BasicRenderer {
// If the job has a transform, set the uniform to it // If the job has a transform, set the uniform to it
if let Some(transform) = job.transform { 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, &[]); render_pass.set_bind_group(1, &self.transform_bind_group, &[]);
} else { } else {
debug!("//TODO: clear transform uniform if the RenderJob doesn't have a transform."); 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())); self.queue.submit(std::iter::once(encoder.finish()));
output.present(); output.present();
// TODO: Use an actual queue instead of a hashmap
self.render_jobs = HashMap::new();
Ok(()) Ok(())
} }
@ -456,6 +462,8 @@ impl Renderer for BasicRenderer {
self.config.width = new_size.width; self.config.width = new_size.width;
self.config.height = new_size.height; self.config.height = new_size.height;
self.surface.configure(&self.device, &self.config); self.surface.configure(&self.device, &self.config);
self.inuse_camera.update_aspect_ratio(self.size);
} }
} }