Compare commits
	
		
			2 commits
		
	
	
		
			be8ac84e36
			...
			440fc05e55
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 440fc05e55 | |||
| 8cc1f7bf3e | 
					 25 changed files with 4054 additions and 344 deletions
				
			
		
							
								
								
									
										1374
									
								
								Cargo.lock
									
										
									
										generated
									
									
									
								
							
							
						
						
									
										1374
									
								
								Cargo.lock
									
										
									
										generated
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load diff
											
										
									
								
							|  | @ -7,7 +7,6 @@ edition = "2021" | |||
| 
 | ||||
| [dependencies] | ||||
| anyhow = "1.0.80" | ||||
| shrev = "1.1.3" | ||||
| winit = { version = "0.29.15",features = ["rwh_05"] } | ||||
| vulkano = { git = "https://github.com/vulkano-rs/vulkano.git", branch = "master" } | ||||
| vulkano-shaders = { git = "https://github.com/vulkano-rs/vulkano.git", branch = "master" } | ||||
|  | @ -21,3 +20,9 @@ serde-lexpr = "0.1.3" | |||
| tokio = { version = "1.36.0", features = ["full"] } | ||||
| notify = "6.1.1" | ||||
| notify-debouncer-mini = "0.4.1" | ||||
| steel-core = { git="https://github.com/mattwparas/steel.git", branch = "master" } | ||||
| steel-derive = { git="https://github.com/mattwparas/steel.git", branch = "master" } | ||||
| egui = "0.27.1" | ||||
| image = "0.25.0" | ||||
| ahash = "0.8.11" | ||||
| egui-winit = "0.27.1" | ||||
|  |  | |||
							
								
								
									
										185
									
								
								src/app.rs
									
										
									
									
									
								
							
							
						
						
									
										185
									
								
								src/app.rs
									
										
									
									
									
								
							|  | @ -1,22 +1,37 @@ | |||
| #![warn(dead_code)] | ||||
| 
 | ||||
| use flax::{Schedule, World}; | ||||
| use anyhow::Result; | ||||
| use std::collections::HashMap; | ||||
| 
 | ||||
| use crate::{ | ||||
|     core::events::Events, | ||||
|     module::{Module, ModulesStack}, | ||||
|     core::{ | ||||
|         events::Events, | ||||
|         module::{Module, ModulesStack, RenderModule as ThreadLocalModule, RenderModulesStack}, | ||||
|     }, | ||||
|     modules::graphics::egui::{Gui, GuiConfig}, | ||||
| }; | ||||
| use anyhow::Result; | ||||
| use flax::{Schedule, World}; | ||||
| use vulkano::device::DeviceFeatures; | ||||
| use vulkano_util::{ | ||||
|     context::{VulkanoConfig, VulkanoContext}, | ||||
|     window::VulkanoWindows, | ||||
| }; | ||||
| use winit::{event::{Event, WindowEvent}, window::WindowId}; | ||||
| 
 | ||||
| #[allow(dead_code)] | ||||
| pub struct App { | ||||
|     name: String, | ||||
|     modules: ModulesStack, | ||||
|     thread_local_modules: RenderModulesStack, | ||||
|     world: World, | ||||
|     schedule: Schedule, | ||||
|     events: Events, | ||||
|     rx: flume::Receiver<AppEvent>, | ||||
|     running: bool, | ||||
|     event_cleanup_time: std::time::Duration, | ||||
|     vk_context: VulkanoContext, | ||||
|     vk_windows: VulkanoWindows, | ||||
|     guis: HashMap<WindowId, Gui>, | ||||
| } | ||||
| 
 | ||||
| impl App { | ||||
|  | @ -26,36 +41,158 @@ impl App { | |||
|         let (tx, rx) = flume::unbounded(); | ||||
|         events.subscribe_custom(tx); | ||||
| 
 | ||||
|         let schedule = Schedule::builder().build(); | ||||
| 
 | ||||
|         let vk_config = VulkanoConfig { | ||||
|             device_features: DeviceFeatures { | ||||
|                 dynamic_rendering: true, | ||||
|                 ..Default::default() | ||||
|             }, | ||||
|             ..Default::default() | ||||
|         }; | ||||
|         let vk_context = VulkanoContext::new(vk_config); | ||||
|         let vk_windows = VulkanoWindows::default(); | ||||
| 
 | ||||
|         Self { | ||||
|             name: "ZTest".into(), | ||||
|             name: "Khors".into(), | ||||
|             modules: ModulesStack::new(), | ||||
|             thread_local_modules: RenderModulesStack::new(), | ||||
|             world: World::new(), | ||||
|             schedule: Schedule::default(), | ||||
|             schedule, | ||||
|             events, | ||||
|             rx, | ||||
|             running: false, | ||||
|             event_cleanup_time: std::time::Duration::from_secs(60), | ||||
|             vk_context, | ||||
|             vk_windows, | ||||
|             guis: HashMap::new(), | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     pub fn run(&mut self) -> Result<()> { | ||||
|         self.running = true; | ||||
| 
 | ||||
|         // self.schedule.execute_par(&mut self.world).unwrap();
 | ||||
|         self.schedule.execute_par(&mut self.world).unwrap(); | ||||
| 
 | ||||
|         let vk_context = &mut self.vk_context; | ||||
|         let vk_windows = &mut self.vk_windows; | ||||
|         let world = &mut self.world; | ||||
|         let events = &mut self.events; | ||||
|         let frame_time = std::time::Duration::from_millis(16); | ||||
|         let guis = &mut self.guis; | ||||
| 
 | ||||
|         for module in self.modules.iter_mut() { | ||||
|             module.on_update(world, events, frame_time)?; | ||||
|         } | ||||
| 
 | ||||
|         for module in self.thread_local_modules.iter_mut() { | ||||
|             module.on_update(guis, vk_context, vk_windows, world, events, frame_time)?; | ||||
|         } | ||||
| 
 | ||||
|         self.handle_events(); | ||||
| 
 | ||||
|         Ok(()) | ||||
|     } | ||||
| 
 | ||||
|     pub fn create_window<T>(&mut self, event_loop: &winit::event_loop::EventLoopWindowTarget<T>) | ||||
|     where | ||||
|         T: Clone + Send + Sync, | ||||
|     { | ||||
|         let window = self.vk_windows.create_window( | ||||
|             event_loop, | ||||
|             &self.vk_context, | ||||
|             &vulkano_util::window::WindowDescriptor { | ||||
|                 title: self.name.clone(), | ||||
|                 present_mode: vulkano::swapchain::PresentMode::Fifo, | ||||
|                 ..Default::default() | ||||
|             }, | ||||
|             |_| {}, | ||||
|         ); | ||||
| 
 | ||||
|         let renderer = self.vk_windows.get_renderer(window).unwrap(); | ||||
|         let gui = Gui::new( | ||||
|             event_loop, | ||||
|             renderer.surface().clone(), | ||||
|             renderer.graphics_queue().clone(), | ||||
|             renderer.swapchain_format(), | ||||
|             GuiConfig { is_overlay: true, allow_srgb_render_target: false, ..Default::default() }, | ||||
|         ); | ||||
| 
 | ||||
|         self.guis.insert(window, gui); | ||||
|     } | ||||
| 
 | ||||
|     pub fn process_event_loop<T>( | ||||
|         &mut self, | ||||
|         event: winit::event::Event<T>, | ||||
|         _elwt: &winit::event_loop::EventLoopWindowTarget<T>, | ||||
|     ) -> Result<bool> | ||||
|     where | ||||
|         T: Clone + Send + Sync, | ||||
|     { | ||||
|         match &event { | ||||
|             Event::WindowEvent { | ||||
|                 event: WindowEvent::CloseRequested, | ||||
|                 .. | ||||
|             } => { | ||||
|                 return Ok(true); | ||||
|             } | ||||
|             Event::WindowEvent { | ||||
|                 event: WindowEvent::Focused(_), | ||||
|                 .. | ||||
|             } => self.events().send(event.clone()), | ||||
|             Event::WindowEvent { | ||||
|                 event: WindowEvent::Resized(..) | WindowEvent::ScaleFactorChanged { .. }, | ||||
|                 window_id, | ||||
|             } => self | ||||
|                 .vk_windows | ||||
|                 .get_renderer_mut(*window_id) | ||||
|                 .unwrap() | ||||
|                 .resize(), | ||||
|             Event::WindowEvent { | ||||
|                 event: WindowEvent::RedrawRequested, | ||||
|                 window_id, | ||||
|             } => 'redraw: { | ||||
|                 // Tasks for redrawing:
 | ||||
|                 // 1. Update state based on events
 | ||||
|                 // 2. Compute & Render
 | ||||
|                 // 3. Reset input state
 | ||||
|                 // 4. Update time & title
 | ||||
| 
 | ||||
|                 // The rendering part goes here:
 | ||||
|                 match self | ||||
|                     .vk_windows | ||||
|                     .get_renderer(*window_id) | ||||
|                     .unwrap() | ||||
|                     .window_size() | ||||
|                 { | ||||
|                     [w, h] => { | ||||
|                         // Skip this frame when minimized.
 | ||||
|                         if w == 0.0 || h == 0.0 { | ||||
|                             break 'redraw; | ||||
|                         } | ||||
|                     } | ||||
|                 } | ||||
| 
 | ||||
|                 self.run()?; | ||||
|             } | ||||
|             Event::WindowEvent { window_id, event } => { | ||||
|                 let window = self.vk_windows.get_window(*window_id).unwrap(); | ||||
|                 let gui = self.guis.get_mut(window_id).unwrap(); | ||||
|                 gui.update(window, event); | ||||
|             } | ||||
|             Event::AboutToWait => { | ||||
|                 for (window_id, _) in self.vk_windows.iter() { | ||||
|                     self.vk_windows | ||||
|                         .get_window(*window_id) | ||||
|                         .unwrap() | ||||
|                         .request_redraw() | ||||
|                 } | ||||
|             } | ||||
|             _ => (), | ||||
|         } | ||||
|         Ok(false) | ||||
|     } | ||||
| 
 | ||||
|     pub fn handle_events(&mut self) { | ||||
|         for event in self.rx.try_iter() { | ||||
|             match event { | ||||
|  | @ -64,14 +201,17 @@ impl App { | |||
|         } | ||||
|     } | ||||
| 
 | ||||
|     #[allow(dead_code)] | ||||
|     pub fn set_schedule(&mut self, schedule: Schedule) { | ||||
|         self.schedule = schedule; | ||||
|     } | ||||
| 
 | ||||
|     #[allow(dead_code)] | ||||
|     pub fn world(&self) -> &World { | ||||
|         &self.world | ||||
|     } | ||||
| 
 | ||||
|     #[allow(dead_code)] | ||||
|     pub fn world_mut(&mut self) -> &mut World { | ||||
|         &mut self.world | ||||
|     } | ||||
|  | @ -80,24 +220,49 @@ impl App { | |||
|         &self.events | ||||
|     } | ||||
| 
 | ||||
|     #[allow(dead_code)] | ||||
|     pub fn events_mut(&mut self) -> &mut Events { | ||||
|         &mut self.events | ||||
|     } | ||||
| 
 | ||||
|     /// Pushes a module from the provided init closure to to the top of the layer stack. The provided
 | ||||
|     /// closure to construct the layer takes in the world and events.
 | ||||
|     pub fn push_render_module<F, T>(&mut self, func: F) | ||||
|     where | ||||
|         F: FnOnce( | ||||
|             &mut VulkanoContext, | ||||
|             &mut VulkanoWindows, | ||||
|             &mut Schedule, | ||||
|             &mut World, | ||||
|             &mut Events, | ||||
|         ) -> T, | ||||
|         T: 'static + ThreadLocalModule, | ||||
|     { | ||||
|         let module = func( | ||||
|             &mut self.vk_context, | ||||
|             &mut self.vk_windows, | ||||
|             &mut self.schedule, | ||||
|             &mut self.world, | ||||
|             &mut self.events, | ||||
|         ); | ||||
|         self.thread_local_modules.push(module); | ||||
|     } | ||||
| 
 | ||||
|     /// Pushes a layer from the provided init closure to to the top of the layer stack. The provided
 | ||||
|     /// closure to construct the layer takes in the world and events.
 | ||||
|     pub fn push_module<F, T>(&mut self, func: F) | ||||
|     where | ||||
|         F: FnOnce(&mut World, &mut Events) -> T, | ||||
|         F: FnOnce(&mut Schedule, &mut World, &mut Events) -> T, | ||||
|         T: 'static + Module, | ||||
|     { | ||||
|         let module = func(&mut self.world, &mut self.events); | ||||
|         let module = func(&mut self.schedule, &mut self.world, &mut self.events); | ||||
|         self.modules.push(module); | ||||
|     } | ||||
| 
 | ||||
|     /// Pushes a module from the provided init closure to to the top of the module stack. The provided
 | ||||
|     /// closure to construct the module takes in the world and events, and may return an error which
 | ||||
|     /// is propagated to the callee.
 | ||||
|     #[allow(dead_code)] | ||||
|     pub fn try_push_module<F, T, E>(&mut self, func: F) -> Result<(), E> | ||||
|     where | ||||
|         F: FnOnce(&mut World, &mut Events) -> Result<T, E>, | ||||
|  | @ -110,6 +275,7 @@ impl App { | |||
| 
 | ||||
|     /// Inserts a module from the provided init closure to to the top of the module stack. The provided
 | ||||
|     /// closure to construct the module takes in the world and events.
 | ||||
|     #[allow(dead_code)] | ||||
|     pub fn insert_module<F, T>(&mut self, index: usize, func: F) | ||||
|     where | ||||
|         F: FnOnce(&mut World, &mut Events) -> T, | ||||
|  | @ -122,6 +288,7 @@ impl App { | |||
|     /// Pushes a module from the provided init closure to to the top of the module stack. The provided
 | ||||
|     /// closure to construct the module takes in the world and events, and may return an error which
 | ||||
|     /// is propagated to the callee.
 | ||||
|     #[allow(dead_code)] | ||||
|     pub fn try_insert_module<F, T, E>(&mut self, index: usize, func: F) -> Result<(), E> | ||||
|     where | ||||
|         F: FnOnce(&mut World, &mut Events) -> Result<T, E>, | ||||
|  |  | |||
							
								
								
									
										42
									
								
								src/comp.rs
									
										
									
									
									
								
							
							
						
						
									
										42
									
								
								src/comp.rs
									
										
									
									
									
								
							|  | @ -1,42 +0,0 @@ | |||
| use flax::{component, BoxedSystem, EntityBorrow, Query, System}; | ||||
| use winit::window::Window; | ||||
| 
 | ||||
| component! { | ||||
|     pub window_width: f32, | ||||
|     pub window: Window, | ||||
|     pub counter: i32, | ||||
| 
 | ||||
|     pub resources, | ||||
| } | ||||
| 
 | ||||
| pub fn update_distance_system() -> BoxedSystem { | ||||
|     System::builder() | ||||
|         .with_name("update_distance") | ||||
|         .with_query( | ||||
|             Query::new((window_width().as_mut(), window(), counter().as_mut())).entity(resources()), | ||||
|         ) | ||||
|         .build(|mut query: EntityBorrow<_>| { | ||||
|             if let Ok((window_width, _window, counter)) = query.get() { | ||||
|                 // println!("Win width: {window_width}");
 | ||||
|                 *(window_width as &mut f32) = *(counter as &mut i32) as f32; | ||||
|                 *(counter as &mut i32) += 1; | ||||
|             } | ||||
|         }) | ||||
|         .boxed() | ||||
| } | ||||
| 
 | ||||
| pub fn log_window_system() -> BoxedSystem { | ||||
|     let query = Query::new((window_width(), window())).entity(resources()); | ||||
| 
 | ||||
|     System::builder() | ||||
|         .with_query(query) | ||||
|         .build(|mut q: EntityBorrow<_>| { | ||||
|             if let Ok((width, wind)) = q.get() { | ||||
|                 println!("window id: {:?}", (wind as &Window).id()); | ||||
|                 println!("Config changed width: {width}"); | ||||
|             } else { | ||||
|                 println!("No config change"); | ||||
|             } | ||||
|         }) | ||||
|         .boxed() | ||||
| } | ||||
|  | @ -1,10 +0,0 @@ | |||
| use std::sync::Arc; | ||||
| 
 | ||||
| use specs::{Component, VecStorage}; | ||||
| use winit::window::Window; | ||||
| 
 | ||||
| #[derive(Component, Debug)] | ||||
| #[storage(VecStorage)] | ||||
| pub struct EntityWindow { | ||||
|     pub window: Arc<Window>, | ||||
| } | ||||
|  | @ -1,68 +0,0 @@ | |||
| use flax::{Schedule, World}; | ||||
| use notify::{Config as NotifyConfig, INotifyWatcher, RecommendedWatcher, RecursiveMode, Watcher}; | ||||
| use serde::{Deserialize, Serialize}; | ||||
| use std::env::current_dir; | ||||
| 
 | ||||
| use crate::module::Module; | ||||
| 
 | ||||
| use self::{components::{notify_file_event, resources}, systems::{read_config_system, read_notify_events_system}}; | ||||
| 
 | ||||
| pub mod components; | ||||
| pub mod systems; | ||||
| 
 | ||||
| #[derive(Serialize, Deserialize, Debug, PartialEq, Eq)] | ||||
| pub struct Config { | ||||
|     pub asset_path: String, | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| #[allow(dead_code)] | ||||
| pub struct ConfigModule { | ||||
|     schedule: Schedule, | ||||
|     watcher: INotifyWatcher, | ||||
|     watcher_rx: std::sync::mpsc::Receiver<Result<notify::Event, notify::Error>>, | ||||
| } | ||||
| 
 | ||||
| impl ConfigModule { | ||||
|     pub fn new(_world: &mut World, _events: &mut crate::core::events::Events) -> Self { | ||||
|         let (tx, rx) = std::sync::mpsc::channel(); | ||||
|         let mut watcher = RecommendedWatcher::new(tx, NotifyConfig::default().with_poll_interval(std::time::Duration::from_secs(2))).unwrap(); | ||||
| 
 | ||||
|         watcher | ||||
|             .watch(¤t_dir().unwrap(), RecursiveMode::NonRecursive) | ||||
|             .unwrap(); | ||||
| 
 | ||||
|         let schedule = Schedule::builder() | ||||
|             .with_system(read_config_system()) | ||||
|             .with_system(read_notify_events_system()) | ||||
|             .build(); | ||||
| 
 | ||||
|         Self { | ||||
|             schedule, | ||||
|             watcher, | ||||
|             watcher_rx: rx, | ||||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| impl Module for ConfigModule { | ||||
|     fn on_update( | ||||
|         &mut self, | ||||
|         world: &mut World, | ||||
|         _events: &mut crate::core::events::Events, | ||||
|         _frame_time: std::time::Duration, | ||||
|     ) -> anyhow::Result<()> { | ||||
|         self.schedule.execute_par(world).unwrap(); | ||||
| 
 | ||||
|         if let Ok(event) = self.watcher_rx.recv() { | ||||
|             match event { | ||||
|                 Ok(e) => { | ||||
|                     world.set(resources(), notify_file_event(), e.clone()).unwrap(); | ||||
|                 } | ||||
|                 Err(e) => println!("Watcher error. {}", e), | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         Ok(()) | ||||
|     } | ||||
| } | ||||
|  | @ -1,2 +1,2 @@ | |||
| pub mod events; | ||||
| // pub mod render;
 | ||||
| pub mod module; | ||||
|  |  | |||
							
								
								
									
										125
									
								
								src/core/module.rs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										125
									
								
								src/core/module.rs
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,125 @@ | |||
| use std::{collections::HashMap, time::Duration}; | ||||
| 
 | ||||
| use anyhow::Result; | ||||
| use flax::World; | ||||
| use winit::window::WindowId; | ||||
| 
 | ||||
| use crate::{core::events::Events, modules::graphics::egui::Gui}; | ||||
| 
 | ||||
| pub trait Module { | ||||
|     fn on_update(&mut self, world: &mut World, events: &mut Events, frame_time: Duration) -> Result<()>; | ||||
| } | ||||
| 
 | ||||
| pub struct ModulesStack { | ||||
|     modules: Vec<Box<dyn Module>>, | ||||
| } | ||||
| 
 | ||||
| impl ModulesStack { | ||||
|     pub fn new() -> Self { | ||||
|         Self { modules: Vec::new() } | ||||
|     } | ||||
| 
 | ||||
|     pub fn iter(&self) -> std::slice::Iter<Box<dyn Module>> { | ||||
|         self.modules.iter() | ||||
|     } | ||||
| 
 | ||||
|     pub fn iter_mut(&mut self) -> std::slice::IterMut<Box<dyn Module>> { | ||||
|         self.modules.iter_mut() | ||||
|     } | ||||
| 
 | ||||
|     pub fn push<T: 'static + Module>(&mut self, layer: T) { | ||||
|         let layer = Box::new(layer); | ||||
|         self.modules.push(layer); | ||||
|     } | ||||
| 
 | ||||
|     pub fn insert<T: 'static + Module>(&mut self, index: usize, layer: T) { | ||||
|         let layer = Box::new(layer); | ||||
|         self.modules.insert(index, layer); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| impl Default for ModulesStack { | ||||
|     fn default() -> Self { | ||||
|         Self::new() | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| impl<'a> IntoIterator for &'a ModulesStack { | ||||
|     type Item = &'a Box<dyn Module>; | ||||
| 
 | ||||
|     type IntoIter = std::slice::Iter<'a, Box<dyn Module>>; | ||||
| 
 | ||||
|     fn into_iter(self) -> Self::IntoIter { | ||||
|         self.iter() | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| impl<'a> IntoIterator for &'a mut ModulesStack { | ||||
|     type Item = &'a mut Box<dyn Module>; | ||||
| 
 | ||||
|     type IntoIter = std::slice::IterMut<'a, Box<dyn Module>>; | ||||
| 
 | ||||
|     fn into_iter(self) -> Self::IntoIter { | ||||
|         self.iter_mut() | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| // THREAD LOCAL STUFF
 | ||||
| 
 | ||||
| pub trait RenderModule { | ||||
|     fn on_update(&mut self, gui: &mut HashMap<WindowId, Gui>, vk_context: &mut vulkano_util::context::VulkanoContext, vk_windows: &mut vulkano_util::window::VulkanoWindows, world: &mut World, events: &mut Events, frame_time: Duration) -> Result<()>; | ||||
| } | ||||
| 
 | ||||
| pub struct RenderModulesStack { | ||||
|     modules: Vec<Box<dyn RenderModule>>, | ||||
| } | ||||
| 
 | ||||
| impl RenderModulesStack { | ||||
|     pub fn new() -> Self { | ||||
|         Self { modules: Vec::new() } | ||||
|     } | ||||
| 
 | ||||
|     pub fn iter(&self) -> std::slice::Iter<Box<dyn RenderModule>> { | ||||
|         self.modules.iter() | ||||
|     } | ||||
| 
 | ||||
|     pub fn iter_mut(&mut self) -> std::slice::IterMut<Box<dyn RenderModule>> { | ||||
|         self.modules.iter_mut() | ||||
|     } | ||||
| 
 | ||||
|     pub fn push<T: 'static + RenderModule>(&mut self, layer: T) { | ||||
|         let layer = Box::new(layer); | ||||
|         self.modules.push(layer); | ||||
|     } | ||||
| 
 | ||||
|     pub fn insert<T: 'static + RenderModule>(&mut self, index: usize, layer: T) { | ||||
|         let layer = Box::new(layer); | ||||
|         self.modules.insert(index, layer); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| impl Default for RenderModulesStack { | ||||
|     fn default() -> Self { | ||||
|         Self::new() | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| impl<'a> IntoIterator for &'a RenderModulesStack { | ||||
|     type Item = &'a Box<dyn RenderModule>; | ||||
| 
 | ||||
|     type IntoIter = std::slice::Iter<'a, Box<dyn RenderModule>>; | ||||
| 
 | ||||
|     fn into_iter(self) -> Self::IntoIter { | ||||
|         self.iter() | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| impl<'a> IntoIterator for &'a mut RenderModulesStack { | ||||
|     type Item = &'a mut Box<dyn RenderModule>; | ||||
| 
 | ||||
|     type IntoIter = std::slice::IterMut<'a, Box<dyn RenderModule>>; | ||||
| 
 | ||||
|     fn into_iter(self) -> Self::IntoIter { | ||||
|         self.iter_mut() | ||||
|     } | ||||
| } | ||||
|  | @ -1 +0,0 @@ | |||
| 
 | ||||
							
								
								
									
										109
									
								
								src/main.rs
									
										
									
									
									
								
							
							
						
						
									
										109
									
								
								src/main.rs
									
										
									
									
									
								
							|  | @ -1,112 +1,51 @@ | |||
| use anyhow::Result; | ||||
| use app::App; | ||||
| use config::ConfigModule; | ||||
| use modules::{config::ConfigModule, graphics::RenderModule, window::WindowModule}; | ||||
| use tokio::runtime::Builder; | ||||
| use vulkano_util::{ | ||||
|     context::{VulkanoConfig, VulkanoContext}, | ||||
|     renderer::VulkanoWindowRenderer, | ||||
|     window::{VulkanoWindows, WindowDescriptor}, | ||||
| }; | ||||
| use winit::{ | ||||
|     event::{Event, WindowEvent}, | ||||
|     event_loop::{ControlFlow, EventLoopBuilder}, | ||||
| }; | ||||
| use winit::event_loop::{ControlFlow, EventLoopBuilder}; | ||||
| 
 | ||||
| mod app; | ||||
| mod config; | ||||
| mod core; | ||||
| mod module; | ||||
| mod modules; | ||||
| 
 | ||||
| fn main() { | ||||
|     let event_loop = EventLoopBuilder::new().build().unwrap(); | ||||
|     let context = VulkanoContext::new(VulkanoConfig::default()); | ||||
|     let mut windows = VulkanoWindows::default(); | ||||
| fn main() -> Result<()> { | ||||
|     let event_loop = EventLoopBuilder::new().build()?; | ||||
| 
 | ||||
|     let runtime = Builder::new_multi_thread().enable_all().build().unwrap(); | ||||
|     let (event_tx, event_rx) = flume::unbounded(); | ||||
|     let runtime = Builder::new_multi_thread().enable_all().build()?; | ||||
|     // let (event_tx, event_rx) = flume::unbounded();
 | ||||
| 
 | ||||
|     runtime.block_on(async { | ||||
|         runtime.spawn(async move { | ||||
|             loop { | ||||
|                 let _event = event_rx.recv_async().await.unwrap(); | ||||
|                 std::thread::sleep(std::time::Duration::from_secs(1)); | ||||
|                 // let _event = event_rx.recv_async().await.unwrap();
 | ||||
|                 // println!(
 | ||||
|                 //     "Tokio got event: {:?} on thread: {:?}",
 | ||||
|                 //     event,
 | ||||
|                 //     std::thread::current().id()
 | ||||
|                 // );
 | ||||
|                 std::thread::sleep(std::time::Duration::from_secs(1)); | ||||
|             } | ||||
|         }); | ||||
|     }); | ||||
| 
 | ||||
|     let _id = windows.create_window( | ||||
|         &event_loop, | ||||
|         &context, | ||||
|         &WindowDescriptor { | ||||
|             title: "ztest".into(), | ||||
|             present_mode: vulkano::swapchain::PresentMode::Fifo, | ||||
|             ..Default::default() | ||||
|         }, | ||||
|         |_| {}, | ||||
|     ); | ||||
| 
 | ||||
|     let primary_window_renderer = windows.get_primary_renderer_mut().unwrap(); | ||||
|     let _gfx_queue = context.graphics_queue(); | ||||
| 
 | ||||
|     let mut app = App::new(); | ||||
|     let mut app = App::new(); // TODO: Move renderer into App
 | ||||
|     app.create_window(&event_loop); | ||||
|     app.push_module(ConfigModule::new); | ||||
|     app.push_module(WindowModule::new); | ||||
|     app.push_render_module(RenderModule::new); | ||||
| 
 | ||||
|     event_loop | ||||
|         .run(move |event, elwt| { | ||||
|             elwt.set_control_flow(ControlFlow::Poll); | ||||
|             if process_event(primary_window_renderer, &event, &mut app) { | ||||
|                 elwt.exit(); | ||||
|             } | ||||
|     event_loop.run(move |event, elwt| { | ||||
|         elwt.set_control_flow(ControlFlow::Poll); | ||||
| 
 | ||||
|             event_tx.send(event.clone()).unwrap(); | ||||
|         }) | ||||
|         .unwrap(); | ||||
| } | ||||
| 
 | ||||
| pub fn process_event( | ||||
|     renderer: &mut VulkanoWindowRenderer, | ||||
|     event: &Event<()>, | ||||
|     app: &mut App, | ||||
| ) -> bool { | ||||
|     match &event { | ||||
|         Event::WindowEvent { | ||||
|             event: WindowEvent::CloseRequested, | ||||
|             .. | ||||
|         } => { | ||||
|             return true; | ||||
|         if app | ||||
|             .process_event_loop(event, elwt) | ||||
|             .expect("Execution failed") | ||||
|         { | ||||
|             elwt.exit(); | ||||
|         } | ||||
|         Event::WindowEvent { | ||||
|             event: WindowEvent::Resized(..) | WindowEvent::ScaleFactorChanged { .. }, | ||||
|             .. | ||||
|         } => renderer.resize(), | ||||
|         Event::WindowEvent { | ||||
|             event: WindowEvent::RedrawRequested, | ||||
|             .. | ||||
|         } => 'redraw: { | ||||
|             app.run().unwrap(); | ||||
| 
 | ||||
|             // Tasks for redrawing:
 | ||||
|             // 1. Update state based on events
 | ||||
|             // 2. Compute & Render
 | ||||
|             // 3. Reset input state
 | ||||
|             // 4. Update time & title
 | ||||
|         // event_tx.send(event.clone()).unwrap();
 | ||||
|     })?; | ||||
| 
 | ||||
|             // The rendering part goes here:
 | ||||
|             match renderer.window_size() { | ||||
|                 [w, h] => { | ||||
|                     // Skip this frame when minimized.
 | ||||
|                     if w == 0.0 || h == 0.0 { | ||||
|                         break 'redraw; | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|         Event::AboutToWait => renderer.window().request_redraw(), | ||||
|         _ => (), | ||||
|     } | ||||
|     false | ||||
|     Ok(()) | ||||
| } | ||||
|  |  | |||
|  | @ -1,64 +0,0 @@ | |||
| use std::time::Duration; | ||||
| 
 | ||||
| use anyhow::Result; | ||||
| use flax::World; | ||||
| 
 | ||||
| use crate::core::events::Events; | ||||
| 
 | ||||
| pub trait Module { | ||||
|     fn on_update(&mut self, world: &mut World, events: &mut Events, frame_time: Duration) -> Result<()>; | ||||
| } | ||||
| 
 | ||||
| pub struct ModulesStack { | ||||
|     modules: Vec<Box<dyn Module>>, | ||||
| } | ||||
| 
 | ||||
| impl ModulesStack { | ||||
|     pub fn new() -> Self { | ||||
|         Self { modules: Vec::new() } | ||||
|     } | ||||
| 
 | ||||
|     pub fn iter(&self) -> std::slice::Iter<Box<dyn Module>> { | ||||
|         self.modules.iter() | ||||
|     } | ||||
| 
 | ||||
|     pub fn iter_mut(&mut self) -> std::slice::IterMut<Box<dyn Module>> { | ||||
|         self.modules.iter_mut() | ||||
|     } | ||||
| 
 | ||||
|     pub fn push<T: 'static + Module>(&mut self, layer: T) { | ||||
|         let layer = Box::new(layer); | ||||
|         self.modules.push(layer); | ||||
|     } | ||||
| 
 | ||||
|     pub fn insert<T: 'static + Module>(&mut self, index: usize, layer: T) { | ||||
|         let layer = Box::new(layer); | ||||
|         self.modules.insert(index, layer); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| impl Default for ModulesStack { | ||||
|     fn default() -> Self { | ||||
|         Self::new() | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| impl<'a> IntoIterator for &'a ModulesStack { | ||||
|     type Item = &'a Box<dyn Module>; | ||||
| 
 | ||||
|     type IntoIter = std::slice::Iter<'a, Box<dyn Module>>; | ||||
| 
 | ||||
|     fn into_iter(self) -> Self::IntoIter { | ||||
|         self.iter() | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| impl<'a> IntoIterator for &'a mut ModulesStack { | ||||
|     type Item = &'a mut Box<dyn Module>; | ||||
| 
 | ||||
|     type IntoIter = std::slice::IterMut<'a, Box<dyn Module>>; | ||||
| 
 | ||||
|     fn into_iter(self) -> Self::IntoIter { | ||||
|         self.iter_mut() | ||||
|     } | ||||
| } | ||||
							
								
								
									
										54
									
								
								src/modules/config/mod.rs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										54
									
								
								src/modules/config/mod.rs
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,54 @@ | |||
| use flax::{Schedule, World}; | ||||
| use serde::{Deserialize, Serialize}; | ||||
| 
 | ||||
| use crate::core::module::Module; | ||||
| 
 | ||||
| use self::systems::first_read_config_system; | ||||
| 
 | ||||
| pub mod components; | ||||
| pub mod systems; | ||||
| 
 | ||||
| #[derive(Serialize, Deserialize, Debug, PartialEq, Eq)] | ||||
| pub struct Config { | ||||
|     pub asset_path: String, | ||||
| } | ||||
| 
 | ||||
| #[allow(dead_code)] | ||||
| pub struct ConfigModule { | ||||
|     // watcher: INotifyWatcher,
 | ||||
|     // watcher_rx: std::sync::mpsc::Receiver<Result<notify::Event, notify::Error>>,
 | ||||
| } | ||||
| 
 | ||||
| impl ConfigModule { | ||||
|     pub fn new( | ||||
|         schedule: &mut Schedule, | ||||
|         _world: &mut World, | ||||
|         _events: &mut crate::core::events::Events, | ||||
|     ) -> Self { | ||||
|         let schedule_r = Schedule::builder() | ||||
|             // .with_system(read_config_system())
 | ||||
|             .with_system(first_read_config_system()) | ||||
|             .build(); | ||||
| 
 | ||||
|         schedule.append(schedule_r); | ||||
| 
 | ||||
|         Self { | ||||
|             // schedule,
 | ||||
|             // watcher,
 | ||||
|             // watcher_rx: rx,
 | ||||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| impl Module for ConfigModule { | ||||
|     fn on_update( | ||||
|         &mut self, | ||||
|         _world: &mut World, | ||||
|         _events: &mut crate::core::events::Events, | ||||
|         _frame_time: std::time::Duration, | ||||
|     ) -> anyhow::Result<()> { | ||||
|         // println!("ConfigModule on_update");
 | ||||
| 
 | ||||
|         Ok(()) | ||||
|     } | ||||
| } | ||||
|  | @ -5,19 +5,21 @@ use serde_lexpr::from_str; | |||
| 
 | ||||
| use super::{components::{config, notify_file_event, resources}, Config}; | ||||
| 
 | ||||
| #[allow(dead_code)] | ||||
| pub fn read_config_system() -> BoxedSystem { | ||||
|     let query = Query::new(notify_file_event()).entity(resources()); | ||||
|     System::builder() | ||||
|         .with_name("read_config") | ||||
|         .with_cmd_mut() | ||||
|         .with_query(query) | ||||
|         .build(|cmd: &mut CommandBuffer, mut q: EntityBorrow<_>| { | ||||
|             if let Ok(n_event) = q.get() { | ||||
|                 if (n_event as ¬ify::Event).kind.is_modify() { | ||||
|                     println!("file modified: {:?}", (n_event as ¬ify::Event).paths); | ||||
|                     cmd.set(resources(), config(), read_engine_config()); | ||||
|                 } | ||||
|             } | ||||
|         .build(|cmd: &mut CommandBuffer, mut _q: EntityBorrow<_>| { | ||||
|             // if let Ok(n_event) = q.get() {
 | ||||
|             //     println!("here");
 | ||||
|             //     if (n_event as ¬ify::Event).kind.is_modify() {
 | ||||
|                     // println!("file modified: {:?}", (n_event as ¬ify::Event).paths);
 | ||||
|             cmd.set(resources(), config(), read_engine_config()); | ||||
|                 // }
 | ||||
|             // }
 | ||||
|         }) | ||||
|         .boxed() | ||||
| } | ||||
|  | @ -31,7 +33,7 @@ fn read_engine_config() -> Config { | |||
|     config | ||||
| } | ||||
| 
 | ||||
| pub fn read_notify_events_system() -> BoxedSystem { | ||||
| pub fn first_read_config_system() -> BoxedSystem { | ||||
|     let query = Query::new(config().as_mut()).entity(resources()); | ||||
|     System::builder() | ||||
|         .with_name("first_read_config") | ||||
							
								
								
									
										321
									
								
								src/modules/graphics/egui/integration.rs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										321
									
								
								src/modules/graphics/egui/integration.rs
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,321 @@ | |||
| // Copyright (c) 2021 Okko Hakola, 2024 Klink
 | ||||
| // Licensed under the Apache License, Version 2.0
 | ||||
| // <LICENSE-APACHE or
 | ||||
| // https://www.apache.org/licenses/LICENSE-2.0> or the MIT
 | ||||
| // license <LICENSE-MIT or https://opensource.org/licenses/MIT>,
 | ||||
| // at your option. All files in the project carrying such
 | ||||
| // notice may not be copied, modified, or distributed except
 | ||||
| // according to those terms.
 | ||||
| use std::sync::Arc; | ||||
| 
 | ||||
| use egui::{ClippedPrimitive, TexturesDelta}; | ||||
| use egui_winit::winit::event_loop::EventLoopWindowTarget; | ||||
| use vulkano::{ | ||||
|     command_buffer::CommandBuffer, device::Queue, format::{Format, NumericFormat}, image::{sampler::SamplerCreateInfo, view::ImageView, SampleCount}, render_pass::Subpass, swapchain::Surface, sync::GpuFuture | ||||
| }; | ||||
| use winit::window::Window; | ||||
| 
 | ||||
| use super::{ | ||||
|     renderer::{RenderResources, Renderer}, | ||||
|     utils::{immutable_texture_from_bytes, immutable_texture_from_file}, | ||||
| }; | ||||
| 
 | ||||
| pub struct GuiConfig { | ||||
|     /// Allows supplying sRGB ImageViews as render targets instead of just UNORM ImageViews, defaults to false.
 | ||||
|     /// **Using sRGB will cause minor discoloration of UI elements** due to blending in linear color space and not
 | ||||
|     /// sRGB as Egui expects.
 | ||||
|     ///
 | ||||
|     /// If you would like to visually compare between UNORM and sRGB render targets, run the `demo_app` example of
 | ||||
|     /// this crate.
 | ||||
|     pub allow_srgb_render_target: bool, | ||||
|     /// Whether to render gui as overlay. Only relevant in the case of `Gui::new`, not when using
 | ||||
|     /// subpass. Determines whether the pipeline should clear the target image.
 | ||||
|     pub is_overlay: bool, | ||||
|     /// Multisample count. Defaults to 1. If you use more than 1, you'll have to ensure your
 | ||||
|     /// pipeline and target image matches that.
 | ||||
|     pub samples: SampleCount, | ||||
| } | ||||
| 
 | ||||
| impl Default for GuiConfig { | ||||
|     fn default() -> Self { | ||||
|         GuiConfig { | ||||
|             allow_srgb_render_target: false, | ||||
|             is_overlay: false, | ||||
|             samples: SampleCount::Sample1, | ||||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| impl GuiConfig { | ||||
|     pub fn validate(&self, output_format: Format) { | ||||
|         if output_format.numeric_format_color().unwrap() == NumericFormat::SRGB { | ||||
|             assert!( | ||||
|                 self.allow_srgb_render_target, | ||||
|                 "Using an output format with sRGB requires `GuiConfig::allow_srgb_render_target` \ | ||||
|                  to be set! Egui prefers UNORM render targets. Using sRGB will cause minor \ | ||||
|                  discoloration of UI elements due to blending in linear color space and not sRGB \ | ||||
|                  as Egui expects." | ||||
|             ); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| pub struct Gui { | ||||
|     pub egui_ctx: egui::Context, | ||||
|     pub egui_winit: egui_winit::State, | ||||
|     renderer: Renderer, | ||||
|     surface: Arc<Surface>, | ||||
| 
 | ||||
|     shapes: Vec<egui::epaint::ClippedShape>, | ||||
|     textures_delta: egui::TexturesDelta, | ||||
| } | ||||
| 
 | ||||
| impl Gui { | ||||
|     /// Creates new Egui to Vulkano integration by setting the necessary parameters
 | ||||
|     /// This is to be called once we have access to vulkano_win's winit window surface
 | ||||
|     /// and gfx queue. Created with this, the renderer will own a render pass which is useful to e.g. place your render pass' images
 | ||||
|     /// onto egui windows
 | ||||
|     pub fn new<T>( | ||||
|         event_loop: &EventLoopWindowTarget<T>, | ||||
|         surface: Arc<Surface>, | ||||
|         gfx_queue: Arc<Queue>, | ||||
|         output_format: Format, | ||||
|         config: GuiConfig, | ||||
|     ) -> Gui { | ||||
|         config.validate(output_format); | ||||
|         let renderer = Renderer::new_with_render_pass( | ||||
|             gfx_queue, | ||||
|             output_format, | ||||
|             config.is_overlay, | ||||
|             config.samples, | ||||
|         ); | ||||
|         Self::new_internal(event_loop, surface, renderer) | ||||
|     } | ||||
| 
 | ||||
|     /// Same as `new` but instead of integration owning a render pass, egui renders on your subpass
 | ||||
|     pub fn new_with_subpass<T>( | ||||
|         event_loop: &EventLoopWindowTarget<T>, | ||||
|         surface: Arc<Surface>, | ||||
|         gfx_queue: Arc<Queue>, | ||||
|         subpass: Subpass, | ||||
|         output_format: Format, | ||||
|         config: GuiConfig, | ||||
|     ) -> Gui { | ||||
|         config.validate(output_format); | ||||
|         let renderer = Renderer::new_with_subpass(gfx_queue, output_format, subpass); | ||||
|         Self::new_internal(event_loop, surface, renderer) | ||||
|     } | ||||
| 
 | ||||
|     /// Same as `new` but instead of integration owning a render pass, egui renders on your subpass
 | ||||
|     fn new_internal<T>( | ||||
|         event_loop: &EventLoopWindowTarget<T>, | ||||
|         surface: Arc<Surface>, | ||||
|         renderer: Renderer, | ||||
|     ) -> Gui { | ||||
|         let max_texture_side = renderer | ||||
|             .queue() | ||||
|             .device() | ||||
|             .physical_device() | ||||
|             .properties() | ||||
|             .max_image_dimension2_d as usize; | ||||
|         let egui_ctx: egui::Context = Default::default(); | ||||
|         let egui_winit = egui_winit::State::new( | ||||
|             egui_ctx.clone(), | ||||
|             egui_ctx.viewport_id(), | ||||
|             event_loop, | ||||
|             Some(surface_window(&surface).scale_factor() as f32), | ||||
|             Some(max_texture_side), | ||||
|         ); | ||||
|         Gui { | ||||
|             egui_ctx, | ||||
|             egui_winit, | ||||
|             renderer, | ||||
|             surface, | ||||
|             shapes: vec![], | ||||
|             textures_delta: Default::default(), | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     /// Returns the pixels per point of the window of this gui.
 | ||||
|     fn pixels_per_point(&self) -> f32 { | ||||
|         egui_winit::pixels_per_point(&self.egui_ctx, surface_window(&self.surface)) | ||||
|     } | ||||
| 
 | ||||
|     /// Returns a set of resources used to construct the render pipeline. These can be reused
 | ||||
|     /// to create additional pipelines and buffers to be rendered in a `PaintCallback`.
 | ||||
|     pub fn render_resources(&self) -> RenderResources { | ||||
|         self.renderer.render_resources() | ||||
|     } | ||||
| 
 | ||||
|     /// Updates context state by winit window event.
 | ||||
|     /// Returns `true` if egui wants exclusive use of this event
 | ||||
|     /// (e.g. a mouse click on an egui window, or entering text into a text field).
 | ||||
|     /// For instance, if you use egui for a game, you want to first call this
 | ||||
|     /// and only when this returns `false` pass on the events to your game.
 | ||||
|     ///
 | ||||
|     /// Note that egui uses `tab` to move focus between elements, so this will always return `true` for tabs.
 | ||||
|     pub fn update(&mut self, window: &Window, winit_event: &winit::event::WindowEvent) -> bool { | ||||
|         self.egui_winit | ||||
|             .on_window_event(window, winit_event) | ||||
|             .consumed | ||||
|     } | ||||
| 
 | ||||
|     /// Begins Egui frame & determines what will be drawn later. This must be called before draw, and after `update` (winit event).
 | ||||
|     pub fn immediate_ui(&mut self, layout_function: impl FnOnce(&mut Self)) { | ||||
|         let raw_input = self | ||||
|             .egui_winit | ||||
|             .take_egui_input(surface_window(&self.surface)); | ||||
|         self.egui_ctx.begin_frame(raw_input); | ||||
|         // Render Egui
 | ||||
|         layout_function(self); | ||||
|     } | ||||
| 
 | ||||
|     /// If you wish to better control when to begin frame, do so by calling this function
 | ||||
|     /// (Finish by drawing)
 | ||||
|     pub fn begin_frame(&mut self) { | ||||
|         let raw_input = self | ||||
|             .egui_winit | ||||
|             .take_egui_input(surface_window(&self.surface)); | ||||
|         self.egui_ctx.begin_frame(raw_input); | ||||
|     } | ||||
| 
 | ||||
|     /// Renders ui on `final_image` & Updates cursor icon
 | ||||
|     /// Finishes Egui frame
 | ||||
|     /// - `before_future` = Vulkano's GpuFuture
 | ||||
|     /// - `final_image` = Vulkano's image (render target)
 | ||||
|     pub fn draw_on_image<F>( | ||||
|         &mut self, | ||||
|         before_future: F, | ||||
|         final_image: Arc<ImageView>, | ||||
|     ) -> Box<dyn GpuFuture> | ||||
|     where | ||||
|         F: GpuFuture + 'static, | ||||
|     { | ||||
|         if !self.renderer.has_renderpass() { | ||||
|             panic!( | ||||
|                 "Gui integration has been created with subpass, use `draw_on_subpass_image` \ | ||||
|                  instead" | ||||
|             ) | ||||
|         } | ||||
| 
 | ||||
|         let (clipped_meshes, textures_delta) = self.extract_draw_data_at_frame_end(); | ||||
| 
 | ||||
|         self.renderer.draw_on_image( | ||||
|             &clipped_meshes, | ||||
|             &textures_delta, | ||||
|             self.pixels_per_point(), | ||||
|             before_future, | ||||
|             final_image, | ||||
|         ) | ||||
|     } | ||||
| 
 | ||||
|     /// Creates commands for rendering ui on subpass' image and returns the command buffer for execution on your side
 | ||||
|     /// - Finishes Egui frame
 | ||||
|     /// - You must execute the secondary command buffer yourself
 | ||||
|     pub fn draw_on_subpass_image( | ||||
|         &mut self, | ||||
|         image_dimensions: [u32; 2], | ||||
|     ) -> Arc<CommandBuffer> { | ||||
|         if self.renderer.has_renderpass() { | ||||
|             panic!( | ||||
|                 "Gui integration has been created with its own render pass, use `draw_on_image` \ | ||||
|                  instead" | ||||
|             ) | ||||
|         } | ||||
| 
 | ||||
|         let (clipped_meshes, textures_delta) = self.extract_draw_data_at_frame_end(); | ||||
| 
 | ||||
|         self.renderer.draw_on_subpass_image( | ||||
|             &clipped_meshes, | ||||
|             &textures_delta, | ||||
|             self.pixels_per_point(), | ||||
|             image_dimensions, | ||||
|         ) | ||||
|     } | ||||
| 
 | ||||
|     fn extract_draw_data_at_frame_end(&mut self) -> (Vec<ClippedPrimitive>, TexturesDelta) { | ||||
|         self.end_frame(); | ||||
|         let shapes = std::mem::take(&mut self.shapes); | ||||
|         let textures_delta = std::mem::take(&mut self.textures_delta); | ||||
|         let clipped_meshes = self.egui_ctx.tessellate(shapes, self.pixels_per_point()); | ||||
|         (clipped_meshes, textures_delta) | ||||
|     } | ||||
| 
 | ||||
|     fn end_frame(&mut self) { | ||||
|         let egui::FullOutput { | ||||
|             platform_output, | ||||
|             textures_delta, | ||||
|             shapes, | ||||
|             pixels_per_point: _, | ||||
|             viewport_output: _, | ||||
|         } = self.egui_ctx.end_frame(); | ||||
| 
 | ||||
|         self.egui_winit.handle_platform_output( | ||||
|             surface_window(&self.surface), | ||||
|             platform_output, | ||||
|         ); | ||||
|         self.shapes = shapes; | ||||
|         self.textures_delta = textures_delta; | ||||
|     } | ||||
| 
 | ||||
|     /// Registers a user image from Vulkano image view to be used by egui
 | ||||
|     pub fn register_user_image_view( | ||||
|         &mut self, | ||||
|         image: Arc<ImageView>, | ||||
|         sampler_create_info: SamplerCreateInfo, | ||||
|     ) -> egui::TextureId { | ||||
|         self.renderer.register_image(image, sampler_create_info) | ||||
|     } | ||||
| 
 | ||||
|     /// Registers a user image to be used by egui
 | ||||
|     /// - `image_file_bytes`: e.g. include_bytes!("./assets/tree.png")
 | ||||
|     /// - `format`: e.g. vulkano::format::Format::R8G8B8A8Unorm
 | ||||
|     pub fn register_user_image( | ||||
|         &mut self, | ||||
|         image_file_bytes: &[u8], | ||||
|         format: vulkano::format::Format, | ||||
|         sampler_create_info: SamplerCreateInfo, | ||||
|     ) -> egui::TextureId { | ||||
|         let image = immutable_texture_from_file( | ||||
|             self.renderer.allocators(), | ||||
|             self.renderer.queue(), | ||||
|             image_file_bytes, | ||||
|             format, | ||||
|         ) | ||||
|         .expect("Failed to create image"); | ||||
|         self.renderer.register_image(image, sampler_create_info) | ||||
|     } | ||||
| 
 | ||||
|     pub fn register_user_image_from_bytes( | ||||
|         &mut self, | ||||
|         image_byte_data: &[u8], | ||||
|         dimensions: [u32; 2], | ||||
|         format: vulkano::format::Format, | ||||
|         sampler_create_info: SamplerCreateInfo, | ||||
|     ) -> egui::TextureId { | ||||
|         let image = immutable_texture_from_bytes( | ||||
|             self.renderer.allocators(), | ||||
|             self.renderer.queue(), | ||||
|             image_byte_data, | ||||
|             dimensions, | ||||
|             format, | ||||
|         ) | ||||
|         .expect("Failed to create image"); | ||||
|         self.renderer.register_image(image, sampler_create_info) | ||||
|     } | ||||
| 
 | ||||
|     /// Unregisters a user image
 | ||||
|     pub fn unregister_user_image(&mut self, texture_id: egui::TextureId) { | ||||
|         self.renderer.unregister_image(texture_id); | ||||
|     } | ||||
| 
 | ||||
|     /// Access egui's context (which can be used to e.g. set fonts, visuals etc)
 | ||||
|     pub fn context(&self) -> egui::Context { | ||||
|         self.egui_ctx.clone() | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| // Helper to retrieve Window from surface object
 | ||||
| fn surface_window(surface: &Surface) -> &Window { | ||||
|     surface.object().unwrap().downcast_ref::<Window>().unwrap() | ||||
| } | ||||
							
								
								
									
										19
									
								
								src/modules/graphics/egui/mod.rs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								src/modules/graphics/egui/mod.rs
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,19 @@ | |||
| // Copyright (c) 2021 Okko Hakola, 2024 Klink
 | ||||
| // Licensed under the Apache License, Version 2.0
 | ||||
| // <LICENSE-APACHE or
 | ||||
| // https://www.apache.org/licenses/LICENSE-2.0> or the MIT
 | ||||
| // license <LICENSE-MIT or https://opensource.org/licenses/MIT>,
 | ||||
| // at your option. All files in the project carrying such
 | ||||
| // notice may not be copied, modified, or distributed except
 | ||||
| // according to those terms.
 | ||||
| 
 | ||||
| mod integration; | ||||
| mod renderer; | ||||
| mod utils; | ||||
| 
 | ||||
| pub use egui; | ||||
| pub use integration::*; | ||||
| #[allow(unused_imports)] | ||||
| pub use renderer::{CallbackContext, CallbackFn, RenderResources}; | ||||
| #[allow(unused_imports)] | ||||
| pub use utils::{immutable_texture_from_bytes, immutable_texture_from_file}; | ||||
							
								
								
									
										1208
									
								
								src/modules/graphics/egui/renderer.rs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										1208
									
								
								src/modules/graphics/egui/renderer.rs
									
										
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load diff
											
										
									
								
							
							
								
								
									
										144
									
								
								src/modules/graphics/egui/utils.rs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										144
									
								
								src/modules/graphics/egui/utils.rs
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,144 @@ | |||
| // Copyright (c) 2021 Okko Hakola, 2024 Klink
 | ||||
| // Licensed under the Apache License, Version 2.0
 | ||||
| // <LICENSE-APACHE or
 | ||||
| // https://www.apache.org/licenses/LICENSE-2.0> or the MIT
 | ||||
| // license <LICENSE-MIT or https://opensource.org/licenses/MIT>,
 | ||||
| // at your option. All files in the project carrying such
 | ||||
| // notice may not be copied, modified, or distributed except
 | ||||
| // according to those terms.
 | ||||
| 
 | ||||
| use std::sync::Arc; | ||||
| 
 | ||||
| use image::RgbaImage; | ||||
| use vulkano::{ | ||||
|     buffer::{AllocateBufferError, Buffer, BufferCreateInfo, BufferUsage}, | ||||
|     command_buffer::{ | ||||
|         allocator::{StandardCommandBufferAllocator, StandardCommandBufferAllocatorCreateInfo}, CommandBufferBeginInfo, CommandBufferLevel, CommandBufferUsage, CopyBufferToImageInfo, RecordingCommandBuffer | ||||
|     }, | ||||
|     descriptor_set::allocator::StandardDescriptorSetAllocator, | ||||
|     device::{Device, Queue}, | ||||
|     image::{view::ImageView, AllocateImageError, Image, ImageCreateInfo, ImageType, ImageUsage}, | ||||
|     memory::allocator::{AllocationCreateInfo, MemoryTypeFilter, StandardMemoryAllocator}, | ||||
|     Validated, ValidationError, VulkanError, | ||||
| }; | ||||
| 
 | ||||
| #[derive(Debug)] | ||||
| pub enum ImageCreationError { | ||||
|     Vulkan(Validated<VulkanError>), | ||||
|     AllocateImage(Validated<AllocateImageError>), | ||||
|     AllocateBuffer(Validated<AllocateBufferError>), | ||||
|     Validation(Box<ValidationError>), | ||||
| } | ||||
| 
 | ||||
| pub fn immutable_texture_from_bytes( | ||||
|     allocators: &Allocators, | ||||
|     queue: Arc<Queue>, | ||||
|     byte_data: &[u8], | ||||
|     dimensions: [u32; 2], | ||||
|     format: vulkano::format::Format, | ||||
| ) -> Result<Arc<ImageView>, ImageCreationError> { | ||||
|     let mut cbb = RecordingCommandBuffer::new( | ||||
|         allocators.command_buffer.clone(), | ||||
|         queue.queue_family_index(), | ||||
|         CommandBufferLevel::Primary, | ||||
|         CommandBufferBeginInfo { | ||||
|             usage: CommandBufferUsage::OneTimeSubmit, | ||||
|             ..Default::default() | ||||
|         } | ||||
|     ) | ||||
|     .map_err(ImageCreationError::Vulkan)?; | ||||
| 
 | ||||
|     let texture_data_buffer = Buffer::from_iter( | ||||
|         allocators.memory.clone(), | ||||
|         BufferCreateInfo { | ||||
|             usage: BufferUsage::TRANSFER_SRC, | ||||
|             ..Default::default() | ||||
|         }, | ||||
|         AllocationCreateInfo { | ||||
|             memory_type_filter: MemoryTypeFilter::PREFER_HOST | ||||
|                 | MemoryTypeFilter::HOST_SEQUENTIAL_WRITE, | ||||
|             ..Default::default() | ||||
|         }, | ||||
|         byte_data.iter().cloned(), | ||||
|     ) | ||||
|     .map_err(ImageCreationError::AllocateBuffer)?; | ||||
| 
 | ||||
|     let texture = Image::new( | ||||
|         allocators.memory.clone(), | ||||
|         ImageCreateInfo { | ||||
|             image_type: ImageType::Dim2d, | ||||
|             format, | ||||
|             extent: [dimensions[0], dimensions[1], 1], | ||||
|             usage: ImageUsage::TRANSFER_DST | ImageUsage::SAMPLED, | ||||
|             ..Default::default() | ||||
|         }, | ||||
|         AllocationCreateInfo::default(), | ||||
|     ) | ||||
|     .map_err(ImageCreationError::AllocateImage)?; | ||||
| 
 | ||||
|     cbb.copy_buffer_to_image(CopyBufferToImageInfo::buffer_image( | ||||
|         texture_data_buffer, | ||||
|         texture.clone(), | ||||
|     )) | ||||
|     .map_err(ImageCreationError::Validation)?; | ||||
| 
 | ||||
|     let _fut = cbb.end().unwrap().execute(queue).unwrap(); | ||||
| 
 | ||||
|     Ok(ImageView::new_default(texture).unwrap()) | ||||
| } | ||||
| 
 | ||||
| pub fn immutable_texture_from_file( | ||||
|     allocators: &Allocators, | ||||
|     queue: Arc<Queue>, | ||||
|     file_bytes: &[u8], | ||||
|     format: vulkano::format::Format, | ||||
| ) -> Result<Arc<ImageView>, ImageCreationError> { | ||||
|     use image::GenericImageView; | ||||
| 
 | ||||
|     let img = image::load_from_memory(file_bytes).expect("Failed to load image from bytes"); | ||||
|     let rgba = if let Some(rgba) = img.as_rgba8() { | ||||
|         rgba.to_owned().to_vec() | ||||
|     } else { | ||||
|         // Convert rgb to rgba
 | ||||
|         let rgb = img.as_rgb8().unwrap().to_owned(); | ||||
|         let mut raw_data = vec![]; | ||||
|         for val in rgb.chunks(3) { | ||||
|             raw_data.push(val[0]); | ||||
|             raw_data.push(val[1]); | ||||
|             raw_data.push(val[2]); | ||||
|             raw_data.push(255); | ||||
|         } | ||||
|         let new_rgba = RgbaImage::from_raw(rgb.width(), rgb.height(), raw_data).unwrap(); | ||||
|         new_rgba.to_vec() | ||||
|     }; | ||||
|     let dimensions = img.dimensions(); | ||||
|     immutable_texture_from_bytes( | ||||
|         allocators, | ||||
|         queue, | ||||
|         &rgba, | ||||
|         [dimensions.0, dimensions.1], | ||||
|         format, | ||||
|     ) | ||||
| } | ||||
| 
 | ||||
| pub struct Allocators { | ||||
|     pub memory: Arc<StandardMemoryAllocator>, | ||||
|     pub descriptor_set: Arc<StandardDescriptorSetAllocator>, | ||||
|     pub command_buffer: Arc<StandardCommandBufferAllocator>, | ||||
| } | ||||
| 
 | ||||
| impl Allocators { | ||||
|     pub fn new_default(device: &Arc<Device>) -> Self { | ||||
|         Self { | ||||
|             memory: Arc::new(StandardMemoryAllocator::new_default(device.clone())), | ||||
|             descriptor_set: Arc::new(StandardDescriptorSetAllocator::new(device.clone(), Default::default())), | ||||
|             command_buffer: Arc::new(StandardCommandBufferAllocator::new( | ||||
|                 device.clone(), | ||||
|                 StandardCommandBufferAllocatorCreateInfo { | ||||
|                     secondary_buffer_count: 32, | ||||
|                     ..Default::default() | ||||
|                 }, | ||||
|             )), | ||||
|         } | ||||
|     } | ||||
| } | ||||
							
								
								
									
										7
									
								
								src/modules/graphics/events.rs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								src/modules/graphics/events.rs
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,7 @@ | |||
| #[derive(Debug, Clone, Copy, PartialEq)] | ||||
| #[allow(dead_code)] | ||||
| pub enum GraphicsEvent { | ||||
|     /// Signifies that the swapchain was recreated. This requires images that
 | ||||
|     /// reference the old swapchain to be recreated.
 | ||||
|     SwapchainRecreation, | ||||
| } | ||||
							
								
								
									
										287
									
								
								src/modules/graphics/mod.rs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										287
									
								
								src/modules/graphics/mod.rs
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,287 @@ | |||
| use std::{collections::HashMap, sync::Arc}; | ||||
| 
 | ||||
| use flax::{entity_ids, BoxedSystem, Query, QueryBorrow, Schedule, System, World}; | ||||
| use vulkano::{ | ||||
|     command_buffer::{ | ||||
|         allocator::{CommandBufferAllocator, StandardCommandBufferAllocator}, | ||||
|         CommandBufferBeginInfo, CommandBufferLevel, CommandBufferUsage, RecordingCommandBuffer, | ||||
|         RenderingAttachmentInfo, RenderingInfo, | ||||
|     }, | ||||
|     image::view::ImageView, | ||||
|     pipeline::graphics::viewport::Viewport, | ||||
|     render_pass::{AttachmentLoadOp, AttachmentStoreOp}, | ||||
|     sync::GpuFuture, | ||||
| }; | ||||
| use vulkano_util::{ | ||||
|     context::VulkanoContext, renderer::VulkanoWindowRenderer, window::VulkanoWindows, | ||||
| }; | ||||
| use winit::window::WindowId; | ||||
| 
 | ||||
| use crate::core::module::RenderModule as ThreadLocalModule; | ||||
| 
 | ||||
| use self::{egui::Gui, test_pipeline::test_pipeline}; | ||||
| 
 | ||||
| pub mod egui; | ||||
| pub mod events; | ||||
| mod test_pipeline; | ||||
| 
 | ||||
| pub struct RenderModule { | ||||
|     schedule: Schedule, | ||||
|     command_buffer_allocator: Arc<dyn CommandBufferAllocator>, | ||||
|     viewport: Viewport, | ||||
| } | ||||
| 
 | ||||
| impl RenderModule { | ||||
|     pub fn new( | ||||
|         vk_context: &mut VulkanoContext, | ||||
|         _vk_windows: &mut VulkanoWindows, | ||||
|         _schedule: &mut Schedule, | ||||
|         _world: &mut World, | ||||
|         _events: &mut crate::core::events::Events, | ||||
|     ) -> Self { | ||||
|         let schedule = Schedule::builder() | ||||
|             .with_system(add_distance_system()) | ||||
|             .build(); | ||||
| 
 | ||||
|         let command_buffer_allocator = Arc::new(StandardCommandBufferAllocator::new( | ||||
|             vk_context.device().clone(), | ||||
|             Default::default(), | ||||
|         )); | ||||
| 
 | ||||
|         let viewport = Viewport { | ||||
|             offset: [0.0, 0.0], | ||||
|             extent: [0.0, 0.0], | ||||
|             depth_range: 0.0..=1.0, | ||||
|         }; | ||||
| 
 | ||||
|         Self { | ||||
|             schedule, | ||||
|             command_buffer_allocator, | ||||
|             viewport, | ||||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| impl ThreadLocalModule for RenderModule { | ||||
|     fn on_update( | ||||
|         &mut self, | ||||
|         guis: &mut HashMap<WindowId, Gui>, | ||||
|         vk_context: &mut VulkanoContext, | ||||
|         vk_windows: &mut vulkano_util::window::VulkanoWindows, | ||||
|         world: &mut World, | ||||
|         _events: &mut crate::core::events::Events, | ||||
|         _frame_time: std::time::Duration, | ||||
|     ) -> anyhow::Result<()> { | ||||
|         self.schedule.execute_seq(world).unwrap(); | ||||
| 
 | ||||
|         let viewport = &mut self.viewport; | ||||
| 
 | ||||
|         for (window_id, renderer) in vk_windows.iter_mut() { | ||||
|             let gui = guis.get_mut(window_id).unwrap(); | ||||
|             draw( | ||||
|                 self.command_buffer_allocator.clone(), | ||||
|                 viewport, | ||||
|                 vk_context, | ||||
|                 renderer, | ||||
|                 gui, | ||||
|             ); | ||||
|         } | ||||
|         Ok(()) | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| pub fn add_distance_system() -> BoxedSystem { | ||||
|     let query = Query::new(entity_ids()); | ||||
| 
 | ||||
|     System::builder() | ||||
|         .with_query(query) | ||||
|         .build(|mut query: QueryBorrow<'_, flax::EntityIds, _>| { | ||||
|             for _id in &mut query { | ||||
|                 // println!("----------: {}", _id.index());
 | ||||
|             } | ||||
|         }) | ||||
|         .boxed() | ||||
| } | ||||
| 
 | ||||
| fn draw( | ||||
|     command_buffer_allocator: Arc<dyn CommandBufferAllocator>, | ||||
|     viewport: &mut Viewport, | ||||
|     context: &mut VulkanoContext, | ||||
|     renderer: &mut VulkanoWindowRenderer, | ||||
|     gui: &mut Gui, | ||||
| ) { | ||||
|     let (vertex_buffer, pipeline) = test_pipeline( | ||||
|         context.device().clone(), | ||||
|         context.memory_allocator().clone(), | ||||
|         renderer.swapchain_format(), | ||||
|     ); | ||||
| 
 | ||||
|     // Do not draw the frame when the screen size is zero. On Windows, this can
 | ||||
|     // occur when minimizing the application.
 | ||||
|     let image_extent: [u32; 2] = renderer.window().inner_size().into(); | ||||
| 
 | ||||
|     if image_extent.contains(&0) { | ||||
|         return; | ||||
|     } | ||||
| 
 | ||||
|     // Begin rendering by acquiring the gpu future from the window renderer.
 | ||||
|     let previous_frame_end = renderer | ||||
|         .acquire(|swapchain_images| { | ||||
|             // Whenever the window resizes we need to recreate everything dependent
 | ||||
|             // on the window size. In this example that
 | ||||
|             // includes the swapchain, the framebuffers
 | ||||
|             // and the dynamic state viewport.
 | ||||
|             window_size_dependent_setup(swapchain_images, viewport); | ||||
|         }) | ||||
|         .unwrap(); | ||||
| 
 | ||||
|     let mut builder = RecordingCommandBuffer::new( | ||||
|         command_buffer_allocator.clone(), | ||||
|         context.graphics_queue().queue_family_index(), | ||||
|         CommandBufferLevel::Primary, | ||||
|         CommandBufferBeginInfo { | ||||
|             usage: CommandBufferUsage::OneTimeSubmit, | ||||
|             ..Default::default() | ||||
|         }, | ||||
|     ) | ||||
|     .unwrap(); | ||||
| 
 | ||||
|     builder | ||||
|         // Before we can draw, we have to *enter a render pass*. We specify which
 | ||||
|         // attachments we are going to use for rendering here, which needs to match
 | ||||
|         // what was previously specified when creating the pipeline.
 | ||||
|         .begin_rendering(RenderingInfo { | ||||
|             // As before, we specify one color attachment, but now we specify the image
 | ||||
|             // view to use as well as how it should be used.
 | ||||
|             color_attachments: vec![Some(RenderingAttachmentInfo { | ||||
|                 // `Clear` means that we ask the GPU to clear the content of this
 | ||||
|                 // attachment at the start of rendering.
 | ||||
|                 load_op: AttachmentLoadOp::Clear, | ||||
|                 // `Store` means that we ask the GPU to store the rendered output in
 | ||||
|                 // the attachment image. We could also ask it to discard the result.
 | ||||
|                 store_op: AttachmentStoreOp::Store, | ||||
|                 // The value to clear the attachment with. Here we clear it with a blue
 | ||||
|                 // color.
 | ||||
|                 //
 | ||||
|                 // Only attachments that have `AttachmentLoadOp::Clear` are provided
 | ||||
|                 // with clear values, any others should use `None` as the clear value.
 | ||||
|                 clear_value: Some([0.0, 0.0, 1.0, 1.0].into()), | ||||
|                 ..RenderingAttachmentInfo::image_view( | ||||
|                     // We specify image view corresponding to the currently acquired
 | ||||
|                     // swapchain image, to use for this attachment.
 | ||||
|                     // attachment_image_views[image_index as usize].clone(),
 | ||||
|                     renderer.swapchain_image_view().clone(), | ||||
|                 ) | ||||
|             })], | ||||
|             ..Default::default() | ||||
|         }) | ||||
|         .unwrap() | ||||
|         // We are now inside the first subpass of the render pass.
 | ||||
|         //
 | ||||
|         // TODO: Document state setting and how it affects subsequent draw commands.
 | ||||
|         .set_viewport(0, [viewport.clone()].into_iter().collect()) | ||||
|         .unwrap() | ||||
|         .bind_pipeline_graphics(pipeline.clone()) | ||||
|         .unwrap() | ||||
|         .bind_vertex_buffers(0, vertex_buffer.clone()) | ||||
|         .unwrap(); | ||||
| 
 | ||||
|     unsafe { | ||||
|         builder | ||||
|             // We add a draw command.
 | ||||
|             .draw(vertex_buffer.len() as u32, 1, 0, 0) | ||||
|             .unwrap(); | ||||
|     } | ||||
| 
 | ||||
|     builder | ||||
|         // We leave the render pass.
 | ||||
|         .end_rendering() | ||||
|         .unwrap(); | ||||
| 
 | ||||
|     // Finish recording the command buffer by calling `end`.
 | ||||
|     let command_buffer = builder.end().unwrap(); | ||||
| 
 | ||||
|     draw_gui(gui); | ||||
| 
 | ||||
|     let before_future = previous_frame_end | ||||
|         .then_execute(context.graphics_queue().clone(), command_buffer) | ||||
|         .unwrap() | ||||
|         .boxed(); | ||||
| 
 | ||||
|     let after_future = gui | ||||
|         .draw_on_image(before_future, renderer.swapchain_image_view()) | ||||
|         .boxed(); | ||||
| 
 | ||||
|     // The color output is now expected to contain our triangle. But in order to
 | ||||
|     // show it on the screen, we have to *present* the image by calling
 | ||||
|     // `present` on the window renderer.
 | ||||
|     //
 | ||||
|     // This function does not actually present the image immediately. Instead it
 | ||||
|     // submits a present command at the end of the queue. This means that it will
 | ||||
|     // only be presented once the GPU has finished executing the command buffer
 | ||||
|     // that draws the triangle.
 | ||||
|     renderer.present(after_future, true); | ||||
| } | ||||
| 
 | ||||
| fn draw_gui(gui: &mut Gui) { | ||||
|     let mut code = CODE.to_owned(); | ||||
|     gui.immediate_ui(|gui| { | ||||
|         let ctx = gui.context(); | ||||
|         egui::egui::Window::new("Colors") | ||||
|             .vscroll(true) | ||||
|             .show(&ctx, |ui| { | ||||
|                 ui.vertical_centered(|ui| { | ||||
|                     ui.add(egui::egui::widgets::Label::new("Hi there!")); | ||||
|                     sized_text(ui, "Rich Text", 32.0); | ||||
|                 }); | ||||
|                 ui.separator(); | ||||
|                 ui.columns(2, |columns| { | ||||
|                     egui::egui::ScrollArea::vertical().id_source("source").show( | ||||
|                         &mut columns[0], | ||||
|                         |ui| { | ||||
|                             ui.add( | ||||
|                                 egui::egui::TextEdit::multiline(&mut code) | ||||
|                                     .font(egui::egui::TextStyle::Monospace), | ||||
|                             ); | ||||
|                         }, | ||||
|                     ); | ||||
|                     egui::egui::ScrollArea::vertical() | ||||
|                         .id_source("rendered") | ||||
|                         .show(&mut columns[1], |ui| { | ||||
|                             ui.add(egui::egui::widgets::Label::new("Good day!")); | ||||
|                         }); | ||||
|                 }); | ||||
|             }); | ||||
|     }); | ||||
| } | ||||
| 
 | ||||
| fn sized_text(ui: &mut egui::egui::Ui, text: impl Into<String>, size: f32) { | ||||
|     ui.label( | ||||
|         egui::egui::RichText::new(text) | ||||
|             .size(size) | ||||
|             .family(::egui::FontFamily::Monospace), | ||||
|     ); | ||||
| } | ||||
| 
 | ||||
| const CODE: &str = r" | ||||
| # Some markup | ||||
| ``` | ||||
| let mut gui = Gui::new(&event_loop, renderer.surface(), None, renderer.queue(), SampleCount::Sample1); | ||||
| ``` | ||||
| ";
 | ||||
| 
 | ||||
| fn window_size_dependent_setup( | ||||
|     image_views: &[Arc<ImageView>], | ||||
|     viewport: &mut Viewport, | ||||
| ) -> Vec<Arc<ImageView>> { | ||||
|     let extent = image_views[0].image().extent(); | ||||
|     viewport.extent = [extent[0] as f32, extent[1] as f32]; | ||||
| 
 | ||||
|     image_views | ||||
|         .iter() | ||||
|         .map(|image_view| { | ||||
|             let image = image_view.image().clone(); | ||||
|             ImageView::new_default(image).unwrap() | ||||
|         }) | ||||
|         .collect::<Vec<_>>() | ||||
| } | ||||
							
								
								
									
										190
									
								
								src/modules/graphics/test_pipeline.rs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										190
									
								
								src/modules/graphics/test_pipeline.rs
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,190 @@ | |||
| use std::sync::Arc; | ||||
| 
 | ||||
| use vulkano::{ | ||||
|     buffer::{Buffer, BufferContents, BufferCreateInfo, BufferUsage, Subbuffer}, | ||||
|     device::Device, | ||||
|     format::Format, | ||||
|     memory::allocator::{AllocationCreateInfo, MemoryAllocator, MemoryTypeFilter}, | ||||
|     pipeline::{ | ||||
|         graphics::{ | ||||
|             color_blend::{ColorBlendAttachmentState, ColorBlendState}, | ||||
|             input_assembly::InputAssemblyState, | ||||
|             multisample::MultisampleState, | ||||
|             rasterization::RasterizationState, | ||||
|             subpass::PipelineRenderingCreateInfo, | ||||
|             vertex_input::{Vertex, VertexDefinition}, | ||||
|             viewport::ViewportState, | ||||
|             GraphicsPipelineCreateInfo, | ||||
|         }, | ||||
|         layout::PipelineDescriptorSetLayoutCreateInfo, | ||||
|         DynamicState, GraphicsPipeline, PipelineLayout, PipelineShaderStageCreateInfo, | ||||
|     }, | ||||
| }; | ||||
| 
 | ||||
| pub fn test_pipeline( | ||||
|     device: Arc<Device>, | ||||
|     memory_allocator: Arc<dyn MemoryAllocator>, | ||||
|     image_format: Format, | ||||
| ) -> (Subbuffer<[MyVertex]>, Arc<GraphicsPipeline>) { | ||||
|     let vertices = [ | ||||
|         MyVertex { | ||||
|             position: [-0.5, -0.25], | ||||
|         }, | ||||
|         MyVertex { | ||||
|             position: [0.0, 0.5], | ||||
|         }, | ||||
|         MyVertex { | ||||
|             position: [0.25, -0.1], | ||||
|         }, | ||||
|     ]; | ||||
|     let vertex_buffer = Buffer::from_iter( | ||||
|         memory_allocator, | ||||
|         BufferCreateInfo { | ||||
|             usage: BufferUsage::VERTEX_BUFFER, | ||||
|             ..Default::default() | ||||
|         }, | ||||
|         AllocationCreateInfo { | ||||
|             memory_type_filter: MemoryTypeFilter::PREFER_DEVICE | ||||
|                 | MemoryTypeFilter::HOST_SEQUENTIAL_WRITE, | ||||
|             ..Default::default() | ||||
|         }, | ||||
|         vertices, | ||||
|     ) | ||||
|     .unwrap(); | ||||
| 
 | ||||
|     
 | ||||
| 
 | ||||
|     let pipeline = { | ||||
|         // First, we load the shaders that the pipeline will use:
 | ||||
|         // the vertex shader and the fragment shader.
 | ||||
|         //
 | ||||
|         // A Vulkan shader can in theory contain multiple entry points, so we have to specify which
 | ||||
|         // one.
 | ||||
|         let vs = vs::load(device.clone()) | ||||
|             .unwrap() | ||||
|             .entry_point("main") | ||||
|             .unwrap(); | ||||
|         let fs = fs::load(device.clone()) | ||||
|             .unwrap() | ||||
|             .entry_point("main") | ||||
|             .unwrap(); | ||||
| 
 | ||||
|         // Automatically generate a vertex input state from the vertex shader's input interface,
 | ||||
|         // that takes a single vertex buffer containing `Vertex` structs.
 | ||||
|         let vertex_input_state = MyVertex::per_vertex().definition(&vs).unwrap(); | ||||
| 
 | ||||
|         // Make a list of the shader stages that the pipeline will have.
 | ||||
|         let stages = [ | ||||
|             PipelineShaderStageCreateInfo::new(vs), | ||||
|             PipelineShaderStageCreateInfo::new(fs), | ||||
|         ]; | ||||
| 
 | ||||
|         // We must now create a **pipeline layout** object, which describes the locations and types
 | ||||
|         // of descriptor sets and push constants used by the shaders in the pipeline.
 | ||||
|         //
 | ||||
|         // Multiple pipelines can share a common layout object, which is more efficient.
 | ||||
|         // The shaders in a pipeline must use a subset of the resources described in its pipeline
 | ||||
|         // layout, but the pipeline layout is allowed to contain resources that are not present in
 | ||||
|         // the shaders; they can be used by shaders in other pipelines that share the same
 | ||||
|         // layout. Thus, it is a good idea to design shaders so that many pipelines have
 | ||||
|         // common resource locations, which allows them to share pipeline layouts.
 | ||||
|         let layout = PipelineLayout::new( | ||||
|             device.clone(), | ||||
|             // Since we only have one pipeline in this example, and thus one pipeline layout,
 | ||||
|             // we automatically generate the creation info for it from the resources used in the
 | ||||
|             // shaders. In a real application, you would specify this information manually so that
 | ||||
|             // you can re-use one layout in multiple pipelines.
 | ||||
|             PipelineDescriptorSetLayoutCreateInfo::from_stages(&stages) | ||||
|                 .into_pipeline_layout_create_info(device.clone()) | ||||
|                 .unwrap(), | ||||
|         ) | ||||
|             .unwrap(); | ||||
| 
 | ||||
|         // We describe the formats of attachment images where the colors, depth and/or stencil
 | ||||
|         // information will be written. The pipeline will only be usable with this particular
 | ||||
|         // configuration of the attachment images.
 | ||||
|         let subpass = PipelineRenderingCreateInfo { | ||||
|             // We specify a single color attachment that will be rendered to. When we begin
 | ||||
|             // rendering, we will specify a swapchain image to be used as this attachment, so here
 | ||||
|             // we set its format to be the same format as the swapchain.
 | ||||
|             color_attachment_formats: vec![Some(image_format)], | ||||
|             ..Default::default() | ||||
|         }; | ||||
| 
 | ||||
|         // Finally, create the pipeline.
 | ||||
|         GraphicsPipeline::new( | ||||
|             device.clone(), | ||||
|             None, | ||||
|             GraphicsPipelineCreateInfo { | ||||
|                 stages: stages.into_iter().collect(), | ||||
|                 // How vertex data is read from the vertex buffers into the vertex shader.
 | ||||
|                 vertex_input_state: Some(vertex_input_state), | ||||
|                 // How vertices are arranged into primitive shapes.
 | ||||
|                 // The default primitive shape is a triangle.
 | ||||
|                 input_assembly_state: Some(InputAssemblyState::default()), | ||||
|                 // How primitives are transformed and clipped to fit the framebuffer.
 | ||||
|                 // We use a resizable viewport, set to draw over the entire window.
 | ||||
|                 viewport_state: Some(ViewportState::default()), | ||||
|                 // How polygons are culled and converted into a raster of pixels.
 | ||||
|                 // The default value does not perform any culling.
 | ||||
|                 rasterization_state: Some(RasterizationState::default()), | ||||
|                 // How multiple fragment shader samples are converted to a single pixel value.
 | ||||
|                 // The default value does not perform any multisampling.
 | ||||
|                 multisample_state: Some(MultisampleState::default()), | ||||
|                 // How pixel values are combined with the values already present in the framebuffer.
 | ||||
|                 // The default value overwrites the old value with the new one, without any
 | ||||
|                 // blending.
 | ||||
|                 color_blend_state: Some(ColorBlendState::with_attachment_states( | ||||
|                     subpass.color_attachment_formats.len() as u32, | ||||
|                     ColorBlendAttachmentState::default(), | ||||
|                 )), | ||||
|                 // Dynamic states allows us to specify parts of the pipeline settings when
 | ||||
|                 // recording the command buffer, before we perform drawing.
 | ||||
|                 // Here, we specify that the viewport should be dynamic.
 | ||||
|                 dynamic_state: [DynamicState::Viewport].into_iter().collect(), | ||||
|                 subpass: Some(subpass.into()), | ||||
|                 ..GraphicsPipelineCreateInfo::layout(layout) | ||||
|             }, | ||||
|         ) | ||||
|         .unwrap() | ||||
|     }; | ||||
| 
 | ||||
|     (vertex_buffer, pipeline) | ||||
| } | ||||
| 
 | ||||
| #[derive(BufferContents, Vertex)] | ||||
| #[repr(C)] | ||||
| pub struct MyVertex { | ||||
|     #[format(R32G32_SFLOAT)] | ||||
|     position: [f32; 2], | ||||
| } | ||||
| 
 | ||||
| mod vs { | ||||
|     vulkano_shaders::shader! { | ||||
|         ty: "vertex", | ||||
|         src: r" | ||||
|                 #version 450 | ||||
| 
 | ||||
|                 layout(location = 0) in vec2 position; | ||||
| 
 | ||||
|                 void main() { | ||||
|                     gl_Position = vec4(position, 0.0, 1.0); | ||||
|                 } | ||||
|             ",
 | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| mod fs { | ||||
|     vulkano_shaders::shader! { | ||||
|         ty: "fragment", | ||||
|         src: r" | ||||
|                 #version 450 | ||||
| 
 | ||||
|                 layout(location = 0) out vec4 f_color; | ||||
| 
 | ||||
|                 void main() { | ||||
|                     f_color = vec4(1.0, 0.0, 0.0, 1.0); | ||||
|                 } | ||||
|             ",
 | ||||
|     } | ||||
| } | ||||
							
								
								
									
										4
									
								
								src/modules/mod.rs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								src/modules/mod.rs
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,4 @@ | |||
| pub mod config; | ||||
| pub mod graphics; | ||||
| pub mod window; | ||||
| // pub mod steel;
 | ||||
							
								
								
									
										104
									
								
								src/modules/steel/mod.rs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										104
									
								
								src/modules/steel/mod.rs
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,104 @@ | |||
| use std::sync::Arc; | ||||
| 
 | ||||
| use flax::{component, BoxedSystem, EntityBorrow, Query, QueryBorrow, Schedule, System, World}; | ||||
| use steel::steel_vm::engine::Engine; | ||||
| use steel::steel_vm::register_fn::RegisterFn; | ||||
| use steel_derive::Steel; | ||||
| 
 | ||||
| use crate::core::module::Module; | ||||
| 
 | ||||
| component! { | ||||
|     steel_script: String, | ||||
|     steel_event_tx: flume::Sender<SteelEvent>, | ||||
| 
 | ||||
|     resources, | ||||
| } | ||||
| 
 | ||||
| pub fn execute_script_system() -> BoxedSystem { | ||||
|     let tx_query = Query::new(steel_event_tx()).entity(resources()); | ||||
|     let script_query = Query::new(steel_script()); | ||||
| 
 | ||||
|     System::builder() | ||||
|         .with_query(tx_query) | ||||
|         .with_query(script_query) | ||||
|         .build(|mut tx_query: EntityBorrow<'_, flax::Component<flume::Sender<SteelEvent>>>, mut script_query: QueryBorrow<flax::Component<String>>| { | ||||
|             if let Ok(tx) = tx_query.get() { | ||||
|                 for script in &mut script_query { | ||||
|                     println!("Got script and tx"); | ||||
|                     tx.send(SteelEvent::Execute(script.into())).unwrap(); | ||||
|                 } | ||||
|             } | ||||
|         }) | ||||
|         .boxed() | ||||
| } | ||||
| 
 | ||||
| #[derive(Debug, Steel)] | ||||
| enum SteelEvent { | ||||
|     Execute(String), | ||||
| } | ||||
| 
 | ||||
| #[allow(dead_code)] | ||||
| #[derive(Steel, Clone)] | ||||
| pub struct SteelModule { | ||||
|     engine: Engine, | ||||
|     // schedule: Schedule,
 | ||||
|     rx: flume::Receiver<SteelEvent>, | ||||
| } | ||||
| 
 | ||||
| impl SteelModule { | ||||
|     pub fn new( | ||||
|         schedule: &mut Schedule, | ||||
|         world: &mut World, | ||||
|         _events: &mut crate::core::events::Events, | ||||
|     ) -> Self { | ||||
|         let mut engine = Engine::new(); | ||||
| 
 | ||||
|         let (tx, rx) = flume::unbounded::<SteelEvent>(); | ||||
| 
 | ||||
|         let schedule_r = Schedule::builder() | ||||
|             .with_system(execute_script_system()) | ||||
|             .build(); | ||||
|         schedule.append(schedule_r); | ||||
| 
 | ||||
|         world.set(resources(), steel_event_tx(), tx).unwrap(); | ||||
| 
 | ||||
|         // Some testing
 | ||||
|         let entity = world.spawn(); | ||||
|         world.set(entity, steel_script(), r#" | ||||
| (require-builtin steel/time) | ||||
| (display "Hello ") | ||||
| (time/sleep-ms 5000) | ||||
| (display "World!")"#.into()).unwrap();
 | ||||
| 
 | ||||
|         Self { | ||||
|             engine, | ||||
|             // schedule,
 | ||||
|             rx, | ||||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| impl Module for SteelModule { | ||||
|     fn on_update( | ||||
|         &mut self, | ||||
|         world: &mut World, | ||||
|         _events: &mut crate::core::events::Events, | ||||
|         _frame_time: std::time::Duration, | ||||
|     ) -> anyhow::Result<()> { | ||||
|         // self.schedule.execute_par(world).unwrap();
 | ||||
| 
 | ||||
|         if let Ok(event) = self.rx.recv() { | ||||
|             match event { | ||||
|                 SteelEvent::Execute(script) => { | ||||
|                     let handle = std::thread::spawn(|| { | ||||
|                         let mut engine = Engine::new(); | ||||
|                         let val = engine.run(script).unwrap(); | ||||
|                         println!("Steel val: {:?}", val); | ||||
|                     }); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         Ok(()) | ||||
|     } | ||||
| } | ||||
							
								
								
									
										34
									
								
								src/modules/window/mod.rs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										34
									
								
								src/modules/window/mod.rs
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,34 @@ | |||
| use flax::{Schedule, World}; | ||||
| 
 | ||||
| use crate::core::module::Module; | ||||
| 
 | ||||
| pub struct WindowModule { | ||||
| } | ||||
| 
 | ||||
| impl WindowModule { | ||||
|     pub fn new( | ||||
|         schedule: &mut Schedule, | ||||
|         _world: &mut World, | ||||
|         _events: &mut crate::core::events::Events, | ||||
|     ) -> Self { | ||||
|         let schedule_r = Schedule::builder() | ||||
|             .build(); | ||||
|         schedule.append(schedule_r); | ||||
|         Self { | ||||
| 
 | ||||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| impl Module for WindowModule { | ||||
|     fn on_update( | ||||
|         &mut self, | ||||
|         _world: &mut World, | ||||
|         _events: &mut crate::core::events::Events, | ||||
|         _frame_time: std::time::Duration, | ||||
|     ) -> anyhow::Result<()> { | ||||
|         // println!("WindowModule on_update");
 | ||||
|         
 | ||||
|         Ok(()) | ||||
|     } | ||||
| } | ||||
|  | @ -1,19 +0,0 @@ | |||
| use vulkano::device::DeviceFeatures; | ||||
| use vulkano_util::context::{VulkanoConfig, VulkanoContext}; | ||||
| 
 | ||||
| pub fn make_render_config() -> VulkanoConfig { | ||||
|     let device_features: DeviceFeatures = DeviceFeatures { | ||||
|         dynamic_rendering: true, | ||||
|         ..DeviceFeatures::empty() | ||||
|     }; | ||||
|     
 | ||||
|     VulkanoConfig { | ||||
|         device_features, | ||||
|         print_device_name: true, | ||||
|         ..Default::default() | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| pub fn make_render_context() -> VulkanoContext { | ||||
|     VulkanoContext::new(make_render_config()) | ||||
| } | ||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue