diff --git a/crates/comet_app/src/app.rs b/crates/comet_app/src/app.rs index 5d20566..59aa978 100644 --- a/crates/comet_app/src/app.rs +++ b/crates/comet_app/src/app.rs @@ -1,8 +1,8 @@ -use std::sync::Arc; +use std::sync::{Arc, Mutex}; use std::time::{Duration, Instant}; -use comet_ecs::{Component, ComponentSet, Render, Renderer2D, Transform2D, World}; +use comet_ecs::{Component, ComponentSet, Render, Transform2D, World}; use comet_resources::{ResourceManager, Vertex}; -use comet_renderer::{Renderer}; +use comet_renderer::{Renderer2D}; use winit::{ event::{self, *}, @@ -19,6 +19,7 @@ use winit::platform::windows::WindowBuilderExtWindows; use winit_input_helper::WinitInputHelper; use comet_input::input_handler::InputHandler; use comet_input::keyboard::Key; +use comet_renderer::renderer::Renderer; pub enum ApplicationType { App2D, @@ -30,7 +31,6 @@ pub struct App<'a> { icon: Option, size: Option>, clear_color: Option, - setup: Option, input_manager: WinitInputHelper, delta_time: f32, update_timer: f32, @@ -51,7 +51,6 @@ impl<'a> App<'a> { icon: None, size: None, clear_color: None, - setup: None, input_manager: WinitInputHelper::new(), delta_time: 0.0, update_timer: 0.0166667, @@ -81,11 +80,6 @@ impl<'a> App<'a> { self } - pub fn with_setup(mut self, setup: fn(&mut World)) -> Self { - self.setup = Some(setup); - self - } - fn load_icon(path: &std::path::Path) -> Option { let image = image::open(path).expect("Failed to open icon image"); let rgba_image = image.to_rgba8(); @@ -117,114 +111,97 @@ impl<'a> App<'a> { self.input_manager.key_released(key) } - pub fn dt(&self) -> f32 { - self.delta_time - } - pub fn quit(&mut self) { self.should_quit = true; } - pub fn get_time_step(&self) -> f32 { + pub fn dt(&self) -> f32 { self.update_timer } - pub fn set_time_step(&mut self, time_step: f32) { - self.update_timer = time_step; + pub fn set_update_rate(&mut self, update_rate: u32) { + if update_rate == 0 { + self.update_timer = f32::INFINITY; + return; + } + self.update_timer = 1.0/update_rate as f32; } - fn create_window(app_title: &str, app_icon: &Option, window_size: &Option>, event_loop: &EventLoop<()>) -> winit::window::Window { + fn create_window(app_title: &str, app_icon: &Option, window_size: &Option>, event_loop: &EventLoop<()>) -> Window { let winit_window = winit::window::WindowBuilder::new() .with_title(app_title); let winit_window = if let Some(icon) = app_icon.clone() { winit_window.with_window_icon(Some(icon)) - } - else { - winit_window - }; - let winit_window = if let Some(icon) = app_icon.clone() { - winit_window.with_taskbar_icon(Some(icon)) - } - else { + } else { winit_window }; let winit_window = if let Some(size) = window_size.clone() { winit_window.with_inner_size(size) - } - else { + } else { winit_window }; winit_window.build(event_loop).unwrap() } - async fn run_app(mut self, update: U) { - let event_loop = EventLoop::new().unwrap(); - let window = Self::create_window(self.title, &self.icon, &self.size ,&event_loop); - let mut renderer = Renderer::new(&window, self.clear_color.clone()).await.unwrap(); - window.set_maximized(true); + pub fn run(mut self, setup: fn(&mut App, &mut R), update: fn(&mut App, &mut R, f32)) { + pollster::block_on(async { + let event_loop = EventLoop::new().unwrap(); + let window = Arc::new(Self::create_window(self.title, &self.icon, &self.size ,&event_loop)); + let mut renderer = R::new(window.clone(), self.clear_color.clone()).await; // Pass Arc> to renderer + window.set_maximized(true); // Lock window to set maximized - if let Some(setup) = self.setup { - setup(&mut self.world); - } + setup(&mut self, &mut renderer); - renderer.initialize_atlas(); + let mut time_stack = 0.0; - let mut time_stack = 0.0; + event_loop.run(|event, elwt| { + self.delta_time = renderer.update(); - event_loop.run(|event, elwt| { - self.delta_time = renderer.update(); + if self.should_quit { + elwt.exit() + } - if self.should_quit { - elwt.exit() - } + self.input_manager.update(&event); - self.input_manager.update(&event); - - time_stack += self.delta_time; - while time_stack > self.update_timer { - let time = self.get_time_step(); - update(&mut self, &mut renderer, time); - time_stack -= self.update_timer; - } - - renderer.render_scene_2d(self.world()); - - match event { - Event::WindowEvent { ref event, window_id, } - if window_id == renderer.window().id() => { - match event { - WindowEvent::CloseRequested {} => elwt.exit(), - WindowEvent::Resized(physical_size) => { - renderer.resize(*physical_size); - } - WindowEvent::RedrawRequested => { - renderer.window().request_redraw(); - - match renderer.render() { - Ok(_) => {} - Err( - wgpu::SurfaceError::Lost | wgpu::SurfaceError::Outdated, - ) => renderer.resize(renderer.size()), - Err(wgpu::SurfaceError::OutOfMemory) => { - error!("OutOfMemory"); - elwt.exit(); - } - Err(wgpu::SurfaceError::Timeout) => { - warn!("Surface timeout") - } - } - } - _ => {} + if self.dt() != f32::INFINITY { + time_stack += self.delta_time; + while time_stack > self.update_timer { + let time = self.dt(); + update(&mut self, &mut renderer, time); + time_stack -= self.update_timer; } } - _ => {} - } - }).unwrap(); - } - pub fn run(mut self, update: U) { - pollster::block_on(self.run_app(update)); + match event { + Event::WindowEvent { ref event, window_id, } => + match event { + WindowEvent::CloseRequested {} => elwt.exit(), + WindowEvent::Resized(physical_size) => { + renderer.resize(*physical_size); + } + WindowEvent::RedrawRequested => { + window.request_redraw(); + + match renderer.render() { + Ok(_) => {} + Err(wgpu::SurfaceError::Lost | wgpu::SurfaceError::Outdated) => renderer.resize(renderer.size()), + Err(wgpu::SurfaceError::OutOfMemory) => { + error!("OutOfMemory"); + elwt.exit(); + } + Err(wgpu::SurfaceError::Timeout) => { + warn!("Surface timeout") + } + } + } + _ => {} + } + _ => {} + } + }).unwrap() + } + ); } } \ No newline at end of file diff --git a/crates/comet_ecs/src/component.rs b/crates/comet_ecs/src/component.rs index 94399c2..5a0462f 100644 --- a/crates/comet_ecs/src/component.rs +++ b/crates/comet_ecs/src/component.rs @@ -39,7 +39,7 @@ pub struct Rectangle2D{ } #[derive(Component)] -pub struct Renderer2D { +pub struct Render2D { is_visible: bool, texture: &'static str, scale: Vec2 @@ -196,7 +196,7 @@ impl Collider for Rectangle2D { } } -impl Render for Renderer2D { +impl Render for Render2D { fn is_visible(&self) -> bool { self.is_visible } diff --git a/crates/comet_renderer/src/lib.rs b/crates/comet_renderer/src/lib.rs index ffba0bc..53f6028 100644 --- a/crates/comet_renderer/src/lib.rs +++ b/crates/comet_renderer/src/lib.rs @@ -1,9 +1,10 @@ mod camera; +pub mod renderer; use core::default::Default; use std::iter; use std::path::PathBuf; -use std::sync::Arc; +use std::sync::{Arc, Mutex}; use std::time::Instant; use cgmath::num_traits::FloatConst; use image::GenericImageView; @@ -15,13 +16,14 @@ use winit::{ }; use winit::dpi::Position; use comet_colors::LinearRgba; -use comet_ecs::{Component, ComponentSet, Render, Renderer2D, Transform2D, World}; +use comet_ecs::{Component, ComponentSet, Render, Renderer2D as R2D, Transform2D, World}; use comet_log::*; use comet_math; use comet_math::{Mat4, Point3, Vec2, Vec3}; use comet_resources::{ResourceManager, texture, Vertex, Texture}; use comet_resources::texture_atlas::TextureRegion; use crate::camera::{Camera, CameraUniform}; +use crate::renderer::Renderer; pub struct Projection { aspect: f32, @@ -47,8 +49,7 @@ impl Projection { } } -pub struct Renderer<'a> { - window: &'a Window, +pub struct Renderer2D<'a> { surface: wgpu::Surface<'a>, device: wgpu::Device, queue: wgpu::Queue, @@ -73,15 +74,13 @@ pub struct Renderer<'a> { camera_bind_group: wgpu::BindGroup, } -impl<'a> Renderer<'a> { - pub async fn new(window: &'a Window, clear_color: Option) -> anyhow::Result> { +impl<'a> Renderer2D<'a> { + pub async fn new(window: Arc, clear_color: Option) -> Renderer2D<'a> { let vertex_data: Vec = vec![]; let index_data: Vec = vec![]; - let size = PhysicalSize::::new(1920, 1080); //window.inner_size(); + let size = PhysicalSize::::new(1920, 1080); - // The instance is a handle to our GPU - // BackendBit::PRIMARY => Vulkan + Metal + DX12 + Browser WebGPU let instance = wgpu::Instance::new(wgpu::InstanceDescriptor { backends: wgpu::Backends::PRIMARY, ..Default::default() @@ -103,8 +102,6 @@ impl<'a> Renderer<'a> { &wgpu::DeviceDescriptor { label: None, required_features: wgpu::Features::empty(), - // WebGL doesn't support all of wgpu's features, so if - // we're building for the web we'll have to disable some. required_limits: wgpu::Limits::default(), memory_hints: Default::default(), }, @@ -114,9 +111,6 @@ impl<'a> Renderer<'a> { .unwrap(); let surface_caps = surface.get_capabilities(&adapter); - // Shader code in this tutorial assumes an Srgb surface texture. Using a different - // one will result all the colors comming out darker. If you want to support non - // Srgb surfaces, you'll need to account for that when drawing to the frame. let surface_format = surface_caps .formats .iter() @@ -279,12 +273,8 @@ impl<'a> Renderer<'a> { strip_index_format: None, front_face: wgpu::FrontFace::Ccw, cull_mode: Some(wgpu::Face::Back), - // Setting this to anything other than Fill requires Features::POLYGON_MODE_LINE - // or Features::POLYGON_MODE_POINT polygon_mode: wgpu::PolygonMode::Fill, - // Requires Features::DEPTH_CLIP_CONTROL unclipped_depth: false, - // Requires Features::CONSERVATIVE_RASTERIZATION conservative: false, }, depth_stencil: None, @@ -293,10 +283,7 @@ impl<'a> Renderer<'a> { mask: !0, alpha_to_coverage_enabled: false, }, - // If the pipeline will be used with a multiview render pass, this - // indicates how many array layers the attachments will have. multiview: None, - // Useful for optimizing shader compilation on Android cache: None, }); @@ -310,8 +297,7 @@ impl<'a> Renderer<'a> { } }; - Ok(Self { - window, + Self { surface, device, queue, @@ -334,7 +320,7 @@ impl<'a> Renderer<'a> { camera_uniform, camera_buffer, camera_bind_group, - }) + } } pub fn dt(&self) -> f32 { @@ -570,12 +556,12 @@ impl<'a> Renderer<'a> { } pub fn render_scene_2d(&mut self, world: &World) { - let entities = world.get_entities_with(ComponentSet::from_ids(vec![Renderer2D::type_id()])); + let entities = world.get_entities_with(ComponentSet::from_ids(vec![R2D::type_id()])); let mut vertex_buffer: Vec = Vec::new(); let mut index_buffer: Vec = Vec::new(); for entity in entities { - let renderer_component = world.get_component::(entity as usize); + let renderer_component = world.get_component::(entity as usize); let transform_component = world.get_component::(entity as usize); if renderer_component.is_visible() { @@ -608,10 +594,6 @@ impl<'a> Renderer<'a> { self.set_buffers(vertex_buffer, index_buffer); } - pub fn window(&self) -> &Window { - &self.window - } - pub fn size(&self) -> PhysicalSize { self.size } @@ -674,4 +656,26 @@ impl<'a> Renderer<'a> { Ok(()) } +} + +impl<'a> Renderer for Renderer2D<'a> { + async fn new(window: Arc, clear_color: Option) -> Renderer2D<'a> { + Self::new(window.clone(), clear_color).await + } + + fn size(&self) -> PhysicalSize { + self.size() + } + + fn resize(&mut self, new_size: winit::dpi::PhysicalSize) { + self.resize(new_size) + } + + fn update(&mut self) -> f32 { + self.update() + } + + fn render(&mut self) -> Result<(), wgpu::SurfaceError> { + self.render() + } } \ No newline at end of file diff --git a/crates/comet_renderer/src/renderer.rs b/crates/comet_renderer/src/renderer.rs new file mode 100644 index 0000000..7bb52f4 --- /dev/null +++ b/crates/comet_renderer/src/renderer.rs @@ -0,0 +1,12 @@ +use std::sync::Arc; +use winit::dpi::PhysicalSize; +use winit::window::Window; +use comet_colors::LinearRgba; + +pub trait Renderer: Sized { + async fn new(window: Arc, clear_color: Option) -> Self; + fn size(&self) -> PhysicalSize; + fn resize(&mut self, new_size: winit::dpi::PhysicalSize); + fn update(&mut self) -> f32; + fn render(&mut self) -> Result<(), wgpu::SurfaceError>; +} \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index dd52d95..d997755 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3,8 +3,13 @@ use comet::{ App, ApplicationType::* }, - renderer::Renderer, - ecs::*, + renderer::Renderer2D, + ecs::{ + Render2D, + Transform2D, + Component, + Render + }, math::*, input::keyboard::*, log::* @@ -44,11 +49,14 @@ fn update_position(input: WinitInputHelper, transform: &mut Transform2D, dt: f32 } } -fn setup(world: &mut World) { - world.register_component::(); +fn setup(app: &mut App, renderer: &mut Renderer2D) { + renderer.initialize_atlas(); + + let world = app.world_mut(); + world.register_component::(); //world.register_component::(); - let mut renderer2d = Renderer2D::new(); + let mut renderer2d = Render2D::new(); renderer2d.set_texture(r"resources/textures/comet_icon.png"); renderer2d.set_visibility(true); @@ -71,10 +79,14 @@ fn setup(world: &mut World) { } -fn update(app: &mut App, renderer: &mut Renderer, dt: f32) { +fn update(app: &mut App, renderer: &mut Renderer2D, dt: f32) { if app.key_pressed(Key::Escape) { app.quit() } - if app.key_pressed(Key::KeyC) { app.set_time_step(0.0016667) } - if app.key_pressed(Key::KeyV) { app.set_time_step(0.0166667) } + if app.key_pressed(Key::KeyP) { + if app.dt() == f32::INFINITY { app.set_update_rate(60) } + else { + app.set_update_rate(0); + } + } if app.key_pressed(Key::KeyE) { app.world_mut().get_component_mut::(0).translate([0f32,0f32].into()) } if app.key_held(Key::KeyW) || app.key_held(Key::KeyA) @@ -86,7 +98,7 @@ fn update(app: &mut App, renderer: &mut Renderer, dt: f32) { let mut transform = app.world_mut().get_component_mut::(0); - + renderer.render_scene_2d(app.world()); } fn main() { @@ -94,7 +106,6 @@ fn main() { .with_title("Comet App") .with_icon(r"resources/textures/comet_icon.png") .with_size(1920, 1080) - .with_setup(setup) - .run(update) + .run::(setup, update) ; } \ No newline at end of file