From 8b1077cab7b13ccc3816a042f06611bf33697bf5 Mon Sep 17 00:00:00 2001 From: SeanOMik Date: Wed, 18 Sep 2024 21:45:15 -0400 Subject: [PATCH] engine: get a window showing and things rendered --- examples/simple_scene/src/main.rs | 20 +- lyra-game/src/game.rs | 104 ++++++++- lyra-game/src/lib.rs | 1 + lyra-game/src/plugin.rs | 11 +- .../src/render/shaders/light_cull.comp.wgsl | 10 +- lyra-game/src/render/window.rs | 203 ++++++++++++------ lyra-game/src/winit/mod.rs | 88 ++++++++ 7 files changed, 346 insertions(+), 91 deletions(-) create mode 100644 lyra-game/src/winit/mod.rs diff --git a/examples/simple_scene/src/main.rs b/examples/simple_scene/src/main.rs index a44dd9d..aef2890 100644 --- a/examples/simple_scene/src/main.rs +++ b/examples/simple_scene/src/main.rs @@ -80,26 +80,24 @@ async fn main() { ) .finish(); - let world = app.world_mut(); - world.add_resource(action_handler); + //let world = app.world; + app.add_resource(action_handler); app.with_plugin(InputActionPlugin); }; - App::initialize() - .await - .with_plugin(lyra_engine::DefaultPlugins) + let mut a = App::new(); + a.with_plugin(lyra_engine::DefaultPlugins) .with_plugin(setup_scene_plugin) .with_plugin(action_handler_plugin) //.with_plugin(camera_debug_plugin) - .with_plugin(FreeFlyCameraPlugin) - .run() - .await; + .with_plugin(FreeFlyCameraPlugin); + a.run(); } fn setup_scene_plugin(app: &mut App) { - let world = game.world_mut(); + let world = &mut app.world; let resman = world.get_resource_mut::(); - + /* let camera_gltf = resman .request::("../assets/AntiqueCamera.glb") .unwrap(); @@ -146,4 +144,4 @@ fn setup_scene_plugin(app: &mut App) { let mut camera = CameraComponent::new_3d(); camera.transform.translation += math::Vec3::new(0.0, 0.0, 5.5); world.spawn((camera, FreeFlyCamera::default())); -} \ No newline at end of file +} diff --git a/lyra-game/src/game.rs b/lyra-game/src/game.rs index 54c7d58..4a349d4 100755 --- a/lyra-game/src/game.rs +++ b/lyra-game/src/game.rs @@ -1,9 +1,10 @@ use std::{cell::OnceCell, collections::VecDeque, ptr::NonNull, sync::Arc}; +use async_std::task::block_on; use lyra_ecs::{system::{IntoSystem, System}, ResourceObject, World}; use lyra_math::{IVec2, Vec2}; use rustc_hash::FxHashMap; -use tracing::{debug, debug_span, info, Level}; +use tracing::{debug, debug_span, info, warn, error, Level}; use tracing_appender::non_blocking; use tracing_subscriber::{ layer::SubscriberExt, @@ -13,7 +14,7 @@ use tracing_subscriber::{ use winit::{application::ApplicationHandler, event::{DeviceEvent, ElementState, Event, KeyEvent, WindowEvent}, event_loop::{ControlFlow, EventLoop}, keyboard::{Key, NamedKey}, window::{Window, WindowId}}; -use crate::{render::{renderer::{Renderer, BasicRenderer}, window::WindowOptions}, input::InputEvent, plugin::Plugin, change_tracker::Ct, EventQueue, StagedExecutor, Stage}; +use crate::{change_tracker::Ct, input::InputEvent, plugin::Plugin, render::{renderer::{BasicRenderer, Renderer}, window::{PrimaryWindow, WindowOptions}}, winit::WinitWindows, EventQueue, Stage, StagedExecutor}; #[derive(Clone, Copy, Hash, Debug)] pub enum GameStages { @@ -64,17 +65,56 @@ pub struct App { impl App { pub fn new() -> Self { + // init logging + let (stdout_layer, stdout_nb) = non_blocking(std::io::stdout()); + { + let t = tracing_subscriber::registry() + .with(fmt::layer().with_writer(stdout_layer)); + + #[cfg(feature = "tracy")] + let t = t.with(tracing_tracy::TracyLayer::default()); + + t.with(filter::Targets::new() + // done by prefix, so it includes all lyra subpackages + .with_target("lyra", Level::DEBUG) + .with_target("wgsl_preprocessor", Level::DEBUG) + .with_target("wgpu", Level::WARN) + .with_target("winit", Level::DEBUG) + .with_default(Level::INFO)) + .init(); + } + + // store the logger worker guard to ensure logging still happens + let mut world = World::new(); + world.add_resource(stdout_nb); + + // initialize ecs system stages + let mut staged = StagedExecutor::new(); + staged.add_stage(GameStages::First); + staged.add_stage_after(GameStages::First, GameStages::PreUpdate); + staged.add_stage_after(GameStages::PreUpdate, GameStages::Update); + staged.add_stage_after(GameStages::Update, GameStages::PostUpdate); + staged.add_stage_after(GameStages::PostUpdate, GameStages::Last); + Self { windows: FxHashMap::default(), renderer: OnceCell::new(), - world: World::new(), + world, plugins: Default::default(), startup_systems: Default::default(), - staged_exec: StagedExecutor::new(), + staged_exec: staged, run_fn: OnceCell::new(), } } + pub fn update(&mut self) { + let wptr = NonNull::from(&self.world); + + if let Err(e) = self.staged_exec.execute(wptr, true) { + error!("Error when executing staged systems: '{}'", e); + } + } + fn on_resize(&mut self, new_size: winit::dpi::PhysicalSize) { self.renderer.get_mut() .expect("renderer was not initialized") @@ -167,7 +207,7 @@ impl App { self } - fn set_run_fn(&self, f: F) + pub fn set_run_fn(&self, f: F) where F: FnOnce(App) + 'static { @@ -175,7 +215,7 @@ impl App { let _ = self.run_fn.set(Box::new(f)); } - fn run(mut self) { + pub fn run(mut self) { let f = self.run_fn.take() .expect("No run function set"); f(self); @@ -183,8 +223,46 @@ impl App { } impl ApplicationHandler for App { + fn about_to_wait(&mut self, event_loop: &winit::event_loop::ActiveEventLoop) { + debug!("update now"); + + self.update(); + + let renderer = self.renderer.get_mut().expect("renderer was not initialized"); + renderer.prepare(&mut self.world); + + if let Some(mut event_queue) = self.world.try_get_resource_mut::() { + event_queue.update_events(); + } + + match renderer.render() { + Ok(_) => {} + // Reconfigure the surface if lost + //Err(wgpu::SurfaceError::Lost) => self.on_resize(.surface_size()), + // The system is out of memory, we should probably quit + Err(wgpu::SurfaceError::OutOfMemory) => { + error!("OOM"); + event_loop.exit(); + } + // All other errors (Outdated, Timeout) should be resolved by the next frame + Err(e) => eprintln!("{:?}", e), + } + } + fn resumed(&mut self, event_loop: &winit::event_loop::ActiveEventLoop) { - debug!("Resumed"); + let world = &mut self.world; + let en = world.spawn((WindowOptions::default(), PrimaryWindow)); + + let attr = Window::default_attributes(); + let mut windows = world.get_resource_mut::(); + let wid = windows.create_window(event_loop, en, attr).unwrap(); + let window = windows.windows.get(&wid).unwrap().clone(); + drop(windows); + + let renderer = block_on(BasicRenderer::create_with_window(world, window)); + if self.renderer.set(Box::new(renderer)).is_err() { + warn!("renderer was re-initialized"); + } } fn window_event( @@ -194,10 +272,12 @@ impl ApplicationHandler for App { event: WindowEvent, ) { //let _e = debug_span!("window_event", window=window_id).entered(); - let window = match self.windows.get_mut(&window_id) { - Some(w) => w, + let windows = self.world.get_resource::(); + let window = match windows.windows.get(&window_id) { + Some(w) => w.clone(), None => return, }; + drop(windows); // If try_from failed, that means that the WindowEvent is not an // input related event. @@ -230,7 +310,7 @@ impl ApplicationHandler for App { let mut state = self.world.get_resource_or_else(WindowState::new); state.focused = focused; }, - WindowEvent::ModifiersChanged(modifiers) => todo!(), + WindowEvent::ModifiersChanged(modifiers) => debug!("modifiers changed: {:?}", modifiers), WindowEvent::ScaleFactorChanged { scale_factor, inner_size_writer } => { info!("changed scale to {scale_factor}"); }, @@ -239,7 +319,9 @@ impl ApplicationHandler for App { let mut state = self.world.get_resource_or_else(WindowState::new); state.occluded = occ; }, - WindowEvent::RedrawRequested => todo!(), + WindowEvent::RedrawRequested => { + debug!("should redraw"); + }, _ => {} } } diff --git a/lyra-game/src/lib.rs b/lyra-game/src/lib.rs index 83d7615..46c08a4 100644 --- a/lyra-game/src/lib.rs +++ b/lyra-game/src/lib.rs @@ -9,6 +9,7 @@ pub mod game; pub mod render; pub mod resources; pub mod input; +pub mod winit; pub mod as_any; pub mod plugin; pub mod change_tracker; diff --git a/lyra-game/src/plugin.rs b/lyra-game/src/plugin.rs index 6a049bb..af4253d 100644 --- a/lyra-game/src/plugin.rs +++ b/lyra-game/src/plugin.rs @@ -8,6 +8,7 @@ use lyra_ecs::CommandQueue; use lyra_resource::ResourceManager; use crate::game::App; +use crate::winit::WinitPlugin; use crate::EventsPlugin; use crate::DeltaTimePlugin; use crate::input::InputPlugin; @@ -19,15 +20,16 @@ pub trait Plugin { fn setup(&self, app: &mut App); fn is_ready(&self, app: &mut App) -> bool { + let _ = app; true } - fn complete(&self, _app: &mut App) { - + fn complete(&self, app: &mut App) { + let _ = app; } - fn cleanup(&self, _app: &mut App) { - + fn cleanup(&self, app: &mut App) { + let _ = app; } } @@ -127,6 +129,7 @@ pub struct DefaultPlugins; impl Plugin for DefaultPlugins { fn setup(&self, app: &mut App) { + WinitPlugin.setup(app); CommandQueuePlugin.setup(app); EventsPlugin.setup(app); InputPlugin.setup(app); diff --git a/lyra-game/src/render/shaders/light_cull.comp.wgsl b/lyra-game/src/render/shaders/light_cull.comp.wgsl index 7897095..b77aa71 100644 --- a/lyra-game/src/render/shaders/light_cull.comp.wgsl +++ b/lyra-game/src/render/shaders/light_cull.comp.wgsl @@ -5,9 +5,9 @@ const LIGHT_TY_DIRECTIONAL = 0u; const LIGHT_TY_POINT = 1u; const LIGHT_TY_SPOT = 2u; -type vec2f = vec2; -type vec3f = vec3; -type vec4f = vec4; +alias vec2f = vec2; +alias vec3f = vec3; +alias vec4f = vec4; struct CameraUniform { view: mat4x4, @@ -317,8 +317,8 @@ fn cone_inside_plane(cone: Cone, plane: Plane) -> bool { return point_inside_plane(cone.tip, plane) && point_inside_plane(furthest, plane); } -fn cone_inside_frustum(cone: Cone, frustum: array) -> bool { - var frustum = frustum; +fn cone_inside_frustum(cone: Cone, frustum_in: array) -> bool { + var frustum = frustum_in; for (var i = 0u; i < 4u; i++) { // TODO: better cone checking if (sphere_inside_plane(cone.tip, cone.radius, frustum[i])) { diff --git a/lyra-game/src/render/window.rs b/lyra-game/src/render/window.rs index 29ae65b..3299c0f 100644 --- a/lyra-game/src/render/window.rs +++ b/lyra-game/src/render/window.rs @@ -1,28 +1,15 @@ use std::sync::Arc; use glam::{IVec2, Vec2}; -use lyra_ecs::World; +use lyra_ecs::{query::{Entities, ResMut, TickOf}, Component, World}; use tracing::{error, warn}; use winit::{ - dpi::{LogicalPosition, LogicalSize, PhysicalPosition}, - error::ExternalError, - window::{Fullscreen, Window}, + dpi::{LogicalPosition, LogicalSize, PhysicalPosition}, error::ExternalError, monitor::VideoModeHandle, window::{Fullscreen, Window} }; pub use winit::window::{CursorGrabMode, CursorIcon, Icon, Theme, WindowButtons, WindowLevel}; -use crate::{change_tracker::Ct, input::InputEvent, plugin::Plugin, EventQueue}; - -#[derive(Default, Clone)] -pub enum WindowMode { - /// The window will use the full size of the screen. - Fullscreen, - /// The window will be fullscreen with the full size of the screen without a border. - Borderless, - /// The window will not be fullscreen and will use the windows resolution size. - #[default] - Windowed, -} +use crate::{change_tracker::Ct, input::InputEvent, plugin::Plugin, winit::WinitWindows, EventQueue}; #[derive(Default, Clone, Copy)] pub struct Area { @@ -93,8 +80,12 @@ impl Position { } } +/// Flag component that +#[derive(Clone, Component)] +pub struct PrimaryWindow; + /// Options that the window will be created with. -#[derive(Clone)] +#[derive(Clone, Component)] pub struct WindowOptions { /// Prevents the window contents from being captured by other apps. /// @@ -136,6 +127,37 @@ pub struct WindowOptions { /// * iOS / Android / Orbital: Unsupported. pub cursor_visible: bool, + /// The window’s current visibility state. + /// + /// Platform-specific + /// * **X11:** Not implemented. + /// * **Wayland / iOS / Android / Web:** Unsupported. + pub visible: bool, + + /// The window's transparency state. + /// + /// This is just a hint that may not change anything about the window transparency, however + /// doing a mismatch between the content of your window and this hint may result in visual + /// artifacts. + /// + /// The default value follows the [`winit::window::WindowAttributes::with_transparent`]. + /// + /// Platform-specific + /// * **macOS:** This will reset the window’s background color. + /// * **Web / iOS / Android:** Unsupported. + /// * **X11:** Can only be set while building the window, with + /// [`winit::window::WindowAttributes::with_transparent`]. + pub transparent: bool, + + /// The current blur state of the window + /// + /// If `true`, this will make the transparent window background blurry. + /// + /// Platform-specific + /// * **Android / iOS / X11 / Web / Windows:** Unsupported. + /// * **Wayland:** Only works with org_kde_kwin_blur_manager protocol. + pub blur: bool, + /// Turn window decorations on or off. /// Enable/disable window decorations provided by the server or Winit. By default this is enabled. /// @@ -150,8 +172,10 @@ pub struct WindowOptions { /// * Web / iOS / Android: Unsupported. pub enabled_buttons: WindowButtons, - /// The window mode. Can be used to set fullscreen and borderless. - pub mode: WindowMode, + /// The fullscreen settings for the monitor. + /// + /// Set to `None` for windowed. + pub fullscreen: Option, /// Sets whether the window should get IME events. /// @@ -198,6 +222,31 @@ pub struct WindowOptions { /// Platform-specific: /// * iOS / Android / Web / Orbital: Unsupported. pub min_inner_size: Option, + + /// The top-left hand corner of the window relative to the top-left hand corner of the desktop. + /// + /// Note that the top-left hand corner of the desktop is not necessarily the same as the + /// screen. If the user uses a desktop with multiple monitors, the top-left hand corner of + /// the desktop is the top-left hand corner of the monitor at the top-left of the desktop. + /// + /// Platform-specific + /// * **iOS:** Can only be called on the main thread. Returns the top left coordinates of + /// the window in the screen space coordinate system. + /// * **Web:** Returns the top-left coordinates relative to the viewport. + /// * **Android / Wayland:** Always returns NotSupportedError. + pub outer_position: Option, + + /// Returns the position of the top-left hand corner of the window’s client area relative to + /// the top-left hand corner of the desktop. + /// + /// The same conditions that apply to `WindowOptions::outer_position` apply to this. + /// + /// Platform-specific + /// * **iOS:** Can only be called on the main thread. Returns the top left coordinates of the + /// window’s safe area in the screen space coordinate system. + /// * **Web:** Returns the top-left coordinates relative to the viewport. + /// * **Android / Wayland:** Always returns NotSupportedError. + pub inner_position: Option, /// Sets the window to maximized or back. /// @@ -231,7 +280,7 @@ pub struct WindowOptions { /// Platform-specific: /// * Wayland / Windows: Not implemented. /// * iOS / Android / Web / Orbital: Unsupported. - pub resize_increments: Option, + pub resize_increments: Option, /// Sets the current window theme. Use None to fallback to system default. /// @@ -277,7 +326,7 @@ impl Default for WindowOptions { cursor_visible: true, decorations: true, enabled_buttons: WindowButtons::all(), - mode: WindowMode::Windowed, + fullscreen: None, ime_allowed: false, ime_cursor_area: Area::default(), inner_size: Size::new_physical(800, 600), @@ -294,10 +343,74 @@ impl Default for WindowOptions { level: WindowLevel::Normal, focused: false, cursor_inside_window: false, + blur: false, + inner_position: None, + outer_position: None, + transparent: false, + visible: true, } } } +impl WindowOptions { + pub(crate) fn as_winit_attributes(&self) -> winit::window::WindowAttributes { + let mut att = Window::default_attributes(); + + att.inner_size = Some(self.inner_size.into()); + + if let Some(min_inner_size) = self.min_inner_size { + att.min_inner_size = Some(min_inner_size.into()); + } + + if let Some(max_inner_size) = self.max_inner_size { + att.max_inner_size = Some(max_inner_size.into()); + } + + if let Some(position) = self.outer_position.clone() + .or(self.inner_position.clone()) + { + att.position = Some(position.into()); + } + + att.resizable = self.resizeable; + att.enabled_buttons = self.enabled_buttons.clone(); + att.title = self.title.clone(); + att.maximized = self.maximized; + att.visible = self.visible; + att.transparent = self.transparent; + att.blur = self.blur; + att.decorations = self.decorations; + //att.window_icon = self.icon.clone + + todo!() + + /* winit::window::WindowAttributes { + inner_size: Some(self.inner_size.into()), + min_inner_size: self.min_inner_size.map(|v| v.into()), + max_inner_size: self.max_inner_size.map(|v| v.into()), + position: self.outer_position.clone().or(self.inner_position.clone()).map(|v| v.into()), // TODO: sync in system + resizable: self.resizeable, + enabled_buttons: self.enabled_buttons.clone(), + title: self.title.clone(), + maximized: self.maximized, + visible: self.visible, // TODO: sync in system + transparent: self.transparent, // TODO: sync in system + blur: self.blur, // TODO: sync in system + decorations: self.decorations, + window_icon: self.icon.clone(), + preferred_theme: self.theme, + resize_increments: self.resize_increments.map(|v| v.into()), + content_protected: self.content_protected, + window_level: self.level, + active: false, // TODO + cursor: self.cursor.clone(), + fullscreen: self.fullscreen.clone(), + parent_window: todo!(), + platform_specific: todo!(), + } */ + } +} + #[derive(Default)] pub struct WindowPlugin { #[allow(dead_code)] @@ -373,17 +486,16 @@ fn center_mouse(window: &Window, options: &WindowOptions) { } fn window_updater_system(world: &mut World) -> anyhow::Result<()> { - if let (Some(window), Some(opts)) = ( + /* if let (Some(window), Some(opts)) = ( world.try_get_resource::>(), world.try_get_resource::>(), - ) { - // if the options changed, update the window - if opts.peek_changed() { - drop(opts); // drop the Ref, we're about to get a RefMut - - // now we can get it mutable, this will trigger the ChangeTracker, so it will be reset at the end of this scope. - let mut opts = world.get_resource_mut::>(); + ) { */ + let tick = world.tick(); + for (entity, mut opts, window_tick, windows) in world.view_iter::<(Entities, &mut WindowOptions, TickOf, ResMut)>() { + let window = windows.get_entity_window(entity) + .expect("entity's window is missing"); + if window_tick == tick { if opts.focused { window.focus_window(); } @@ -400,26 +512,7 @@ fn window_updater_system(world: &mut World) -> anyhow::Result<()> { window.set_cursor_visible(opts.cursor_visible); // TODO: Handle unsupported platforms window.set_decorations(opts.decorations); // TODO: Handle unsupported platforms window.set_enabled_buttons(opts.enabled_buttons); // TODO: Handle unsupported platforms - - // Update the window mode. can only be done if the monitor is found - if let Some(monitor) = window - .current_monitor() - .or_else(|| window.primary_monitor()) - .or_else(|| window.available_monitors().next()) - { - match opts.mode { - WindowMode::Borderless => { - window.set_fullscreen(Some(Fullscreen::Borderless(Some(monitor)))) - } - WindowMode::Fullscreen => window.set_fullscreen(Some(Fullscreen::Exclusive( - monitor.video_modes().next().unwrap(), - ))), - WindowMode::Windowed => window.set_fullscreen(None), - } - } else { - warn!("Failure to get monitor handle, could not update WindowMode"); - } - + window.set_fullscreen(opts.fullscreen.clone()); window.set_ime_allowed(opts.ime_allowed); window.set_ime_cursor_area(opts.ime_cursor_area.position, opts.ime_cursor_area.size); window.request_inner_size(opts.inner_size); @@ -432,20 +525,14 @@ fn window_updater_system(world: &mut World) -> anyhow::Result<()> { window.set_maximized(opts.maximized); window.set_minimized(opts.minimized); window.set_resizable(opts.resizeable); - window.set_resize_increments(vec2_to_logical_size_op(opts.resize_increments)); + window.set_resize_increments(opts.resize_increments); window.set_theme(opts.theme); window.set_title(&opts.title); window.set_window_icon(opts.icon.clone()); window.set_window_level(opts.level); - // reset the tracker after we mutably used it - opts.reset(); - center_mouse(&window, &opts); } else { - drop(opts); // drop the Ref, we're about to get a RefMut - let mut opts = world.get_resource_mut::>(); - if let Some(event_queue) = world.try_get_resource_mut::() { if let Some(events) = event_queue.read_events::() { for ev in events { @@ -463,11 +550,7 @@ fn window_updater_system(world: &mut World) -> anyhow::Result<()> { } // update the stored state of the window to match the actual window - opts.focused = window.has_focus(); - - opts.reset(); - center_mouse(&window, &opts); } } diff --git a/lyra-game/src/winit/mod.rs b/lyra-game/src/winit/mod.rs new file mode 100644 index 0000000..727a96a --- /dev/null +++ b/lyra-game/src/winit/mod.rs @@ -0,0 +1,88 @@ +use std::sync::Arc; + +use lyra_ecs::Entity; +use rustc_hash::FxHashMap; +use tracing::debug; +use winit::{application::ApplicationHandler, event_loop::{ActiveEventLoop, EventLoop}, window::{Window, WindowAttributes, WindowId}}; + +use crate::{game::App, plugin::Plugin, render::window::WindowOptions}; + +#[derive(Default)] +pub struct WinitPlugin; + +impl Plugin for WinitPlugin { + fn setup(&self, app: &mut crate::game::App) { + app.set_run_fn(winit_app_runner); + app.add_resource(WinitWindows::default()); + } + + fn is_ready(&self, app: &mut crate::game::App) -> bool { + true + } + + fn complete(&self, app: &mut crate::game::App) { + + } + + fn cleanup(&self, app: &mut crate::game::App) { + + } +} + +#[derive(Default)] +pub struct WinitWindows { + pub windows: FxHashMap>, + pub entity_to_window: FxHashMap, +} + +impl WinitWindows { + pub fn create_window(&mut self, event_loop: &ActiveEventLoop, entity: Entity, attr: WindowAttributes) -> Result { + let win = event_loop.create_window(attr)?; + let id = win.id(); + + self.windows.insert(id, Arc::new(win)); + self.entity_to_window.insert(entity, id); + + Ok(id) + } + + pub fn get_entity_window(&self, entity: Entity) -> Option<&Arc> { + self.entity_to_window.get(&entity) + .and_then(|id| self.windows.get(id)) + } +} + +pub fn winit_app_runner(mut app: App) { + let evloop = EventLoop::new() + .expect("failed to create winit EventLoop"); + + evloop.run_app(&mut app) + .expect("loop error"); +} + +/* struct WinitRunner { + app: App +} + +impl ApplicationHandler for WinitRunner { + fn resumed(&mut self, event_loop: &ActiveEventLoop) { + debug!("resumed") + } + + fn window_event( + &mut self, + event_loop: &ActiveEventLoop, + window_id: WindowId, + event: winit::event::WindowEvent, + ) { + let world = &mut self.app.world; + let mut windows = world.get_resource_mut::(); + + let window = match windows.windows.get_mut(&window_id) { + Some(w) => w, + None => return, + }; + + + } +} */ \ No newline at end of file