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] | [dependencies] | ||||||
| anyhow = "1.0.80" | anyhow = "1.0.80" | ||||||
| shrev = "1.1.3" |  | ||||||
| winit = { version = "0.29.15",features = ["rwh_05"] } | winit = { version = "0.29.15",features = ["rwh_05"] } | ||||||
| vulkano = { git = "https://github.com/vulkano-rs/vulkano.git", branch = "master" } | vulkano = { git = "https://github.com/vulkano-rs/vulkano.git", branch = "master" } | ||||||
| vulkano-shaders = { 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"] } | tokio = { version = "1.36.0", features = ["full"] } | ||||||
| notify = "6.1.1" | notify = "6.1.1" | ||||||
| notify-debouncer-mini = "0.4.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)] | #![warn(dead_code)] | ||||||
| 
 | 
 | ||||||
| use flax::{Schedule, World}; | use std::collections::HashMap; | ||||||
| use anyhow::Result; | 
 | ||||||
| use crate::{ | use crate::{ | ||||||
|     core::events::Events, |     core::{ | ||||||
|     module::{Module, ModulesStack}, |         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)] | #[allow(dead_code)] | ||||||
| pub struct App { | pub struct App { | ||||||
|     name: String, |     name: String, | ||||||
|     modules: ModulesStack, |     modules: ModulesStack, | ||||||
|  |     thread_local_modules: RenderModulesStack, | ||||||
|     world: World, |     world: World, | ||||||
|     schedule: Schedule, |     schedule: Schedule, | ||||||
|     events: Events, |     events: Events, | ||||||
|     rx: flume::Receiver<AppEvent>, |     rx: flume::Receiver<AppEvent>, | ||||||
|     running: bool, |     running: bool, | ||||||
|     event_cleanup_time: std::time::Duration, |     event_cleanup_time: std::time::Duration, | ||||||
|  |     vk_context: VulkanoContext, | ||||||
|  |     vk_windows: VulkanoWindows, | ||||||
|  |     guis: HashMap<WindowId, Gui>, | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| impl App { | impl App { | ||||||
|  | @ -26,36 +41,158 @@ impl App { | ||||||
|         let (tx, rx) = flume::unbounded(); |         let (tx, rx) = flume::unbounded(); | ||||||
|         events.subscribe_custom(tx); |         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 { |         Self { | ||||||
|             name: "ZTest".into(), |             name: "Khors".into(), | ||||||
|             modules: ModulesStack::new(), |             modules: ModulesStack::new(), | ||||||
|  |             thread_local_modules: RenderModulesStack::new(), | ||||||
|             world: World::new(), |             world: World::new(), | ||||||
|             schedule: Schedule::default(), |             schedule, | ||||||
|             events, |             events, | ||||||
|             rx, |             rx, | ||||||
|             running: false, |             running: false, | ||||||
|             event_cleanup_time: std::time::Duration::from_secs(60), |             event_cleanup_time: std::time::Duration::from_secs(60), | ||||||
|  |             vk_context, | ||||||
|  |             vk_windows, | ||||||
|  |             guis: HashMap::new(), | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     pub fn run(&mut self) -> Result<()> { |     pub fn run(&mut self) -> Result<()> { | ||||||
|         self.running = true; |         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 world = &mut self.world; | ||||||
|         let events = &mut self.events; |         let events = &mut self.events; | ||||||
|         let frame_time = std::time::Duration::from_millis(16); |         let frame_time = std::time::Duration::from_millis(16); | ||||||
|  |         let guis = &mut self.guis; | ||||||
| 
 | 
 | ||||||
|         for module in self.modules.iter_mut() { |         for module in self.modules.iter_mut() { | ||||||
|             module.on_update(world, events, frame_time)?; |             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(); |         self.handle_events(); | ||||||
| 
 | 
 | ||||||
|         Ok(()) |         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) { |     pub fn handle_events(&mut self) { | ||||||
|         for event in self.rx.try_iter() { |         for event in self.rx.try_iter() { | ||||||
|             match event { |             match event { | ||||||
|  | @ -64,14 +201,17 @@ impl App { | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     #[allow(dead_code)] | ||||||
|     pub fn set_schedule(&mut self, schedule: Schedule) { |     pub fn set_schedule(&mut self, schedule: Schedule) { | ||||||
|         self.schedule = schedule; |         self.schedule = schedule; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     #[allow(dead_code)] | ||||||
|     pub fn world(&self) -> &World { |     pub fn world(&self) -> &World { | ||||||
|         &self.world |         &self.world | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     #[allow(dead_code)] | ||||||
|     pub fn world_mut(&mut self) -> &mut World { |     pub fn world_mut(&mut self) -> &mut World { | ||||||
|         &mut self.world |         &mut self.world | ||||||
|     } |     } | ||||||
|  | @ -80,24 +220,49 @@ impl App { | ||||||
|         &self.events |         &self.events | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     #[allow(dead_code)] | ||||||
|     pub fn events_mut(&mut self) -> &mut Events { |     pub fn events_mut(&mut self) -> &mut Events { | ||||||
|         &mut self.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
 |     /// 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.
 |     /// closure to construct the layer takes in the world and events.
 | ||||||
|     pub fn push_module<F, T>(&mut self, func: F) |     pub fn push_module<F, T>(&mut self, func: F) | ||||||
|     where |     where | ||||||
|         F: FnOnce(&mut World, &mut Events) -> T, |         F: FnOnce(&mut Schedule, &mut World, &mut Events) -> T, | ||||||
|         T: 'static + Module, |         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); |         self.modules.push(module); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /// Pushes a module from the provided init closure to to the top of the module stack. The provided
 |     /// 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
 |     /// closure to construct the module takes in the world and events, and may return an error which
 | ||||||
|     /// is propagated to the callee.
 |     /// is propagated to the callee.
 | ||||||
|  |     #[allow(dead_code)] | ||||||
|     pub fn try_push_module<F, T, E>(&mut self, func: F) -> Result<(), E> |     pub fn try_push_module<F, T, E>(&mut self, func: F) -> Result<(), E> | ||||||
|     where |     where | ||||||
|         F: FnOnce(&mut World, &mut Events) -> Result<T, E>, |         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
 |     /// 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.
 |     /// 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) |     pub fn insert_module<F, T>(&mut self, index: usize, func: F) | ||||||
|     where |     where | ||||||
|         F: FnOnce(&mut World, &mut Events) -> T, |         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
 |     /// 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
 |     /// closure to construct the module takes in the world and events, and may return an error which
 | ||||||
|     /// is propagated to the callee.
 |     /// is propagated to the callee.
 | ||||||
|  |     #[allow(dead_code)] | ||||||
|     pub fn try_insert_module<F, T, E>(&mut self, index: usize, func: F) -> Result<(), E> |     pub fn try_insert_module<F, T, E>(&mut self, index: usize, func: F) -> Result<(), E> | ||||||
|     where |     where | ||||||
|         F: FnOnce(&mut World, &mut Events) -> Result<T, E>, |         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 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 @@ | ||||||
| 
 |  | ||||||
							
								
								
									
										107
									
								
								src/main.rs
									
										
									
									
									
								
							
							
						
						
									
										107
									
								
								src/main.rs
									
										
									
									
									
								
							|  | @ -1,112 +1,51 @@ | ||||||
|  | use anyhow::Result; | ||||||
| use app::App; | use app::App; | ||||||
| use config::ConfigModule; | use modules::{config::ConfigModule, graphics::RenderModule, window::WindowModule}; | ||||||
| use tokio::runtime::Builder; | use tokio::runtime::Builder; | ||||||
| use vulkano_util::{ | use winit::event_loop::{ControlFlow, EventLoopBuilder}; | ||||||
|     context::{VulkanoConfig, VulkanoContext}, |  | ||||||
|     renderer::VulkanoWindowRenderer, |  | ||||||
|     window::{VulkanoWindows, WindowDescriptor}, |  | ||||||
| }; |  | ||||||
| use winit::{ |  | ||||||
|     event::{Event, WindowEvent}, |  | ||||||
|     event_loop::{ControlFlow, EventLoopBuilder}, |  | ||||||
| }; |  | ||||||
| 
 | 
 | ||||||
| mod app; | mod app; | ||||||
| mod config; |  | ||||||
| mod core; | mod core; | ||||||
| mod module; | mod modules; | ||||||
| 
 | 
 | ||||||
| fn main() { | fn main() -> Result<()> { | ||||||
|     let event_loop = EventLoopBuilder::new().build().unwrap(); |     let event_loop = EventLoopBuilder::new().build()?; | ||||||
|     let context = VulkanoContext::new(VulkanoConfig::default()); |  | ||||||
|     let mut windows = VulkanoWindows::default(); |  | ||||||
| 
 | 
 | ||||||
|     let runtime = Builder::new_multi_thread().enable_all().build().unwrap(); |     let runtime = Builder::new_multi_thread().enable_all().build()?; | ||||||
|     let (event_tx, event_rx) = flume::unbounded(); |     // let (event_tx, event_rx) = flume::unbounded();
 | ||||||
| 
 | 
 | ||||||
|     runtime.block_on(async { |     runtime.block_on(async { | ||||||
|         runtime.spawn(async move { |         runtime.spawn(async move { | ||||||
|             loop { |             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!(
 |                 // println!(
 | ||||||
|                 //     "Tokio got event: {:?} on thread: {:?}",
 |                 //     "Tokio got event: {:?} on thread: {:?}",
 | ||||||
|                 //     event,
 |                 //     event,
 | ||||||
|                 //     std::thread::current().id()
 |                 //     std::thread::current().id()
 | ||||||
|                 // );
 |                 // );
 | ||||||
|                 std::thread::sleep(std::time::Duration::from_secs(1)); |  | ||||||
|             } |             } | ||||||
|         }); |         }); | ||||||
|     }); |     }); | ||||||
| 
 | 
 | ||||||
|     let _id = windows.create_window( |     let mut app = App::new(); // TODO: Move renderer into App
 | ||||||
|         &event_loop, |     app.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(); |  | ||||||
|     app.push_module(ConfigModule::new); |     app.push_module(ConfigModule::new); | ||||||
|  |     app.push_module(WindowModule::new); | ||||||
|  |     app.push_render_module(RenderModule::new); | ||||||
| 
 | 
 | ||||||
|     event_loop |     event_loop.run(move |event, elwt| { | ||||||
|         .run(move |event, elwt| { |  | ||||||
|         elwt.set_control_flow(ControlFlow::Poll); |         elwt.set_control_flow(ControlFlow::Poll); | ||||||
|             if process_event(primary_window_renderer, &event, &mut app) { | 
 | ||||||
|  |         if app | ||||||
|  |             .process_event_loop(event, elwt) | ||||||
|  |             .expect("Execution failed") | ||||||
|  |         { | ||||||
|             elwt.exit(); |             elwt.exit(); | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|             event_tx.send(event.clone()).unwrap(); |         // event_tx.send(event.clone()).unwrap();
 | ||||||
|         }) |     })?; | ||||||
|         .unwrap(); |  | ||||||
| } |  | ||||||
| 
 | 
 | ||||||
| pub fn process_event( |     Ok(()) | ||||||
|     renderer: &mut VulkanoWindowRenderer, |  | ||||||
|     event: &Event<()>, |  | ||||||
|     app: &mut App, |  | ||||||
| ) -> bool { |  | ||||||
|     match &event { |  | ||||||
|         Event::WindowEvent { |  | ||||||
|             event: WindowEvent::CloseRequested, |  | ||||||
|             .. |  | ||||||
|         } => { |  | ||||||
|             return true; |  | ||||||
|         } |  | ||||||
|         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
 |  | ||||||
| 
 |  | ||||||
|             // 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 |  | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -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}; | use super::{components::{config, notify_file_event, resources}, Config}; | ||||||
| 
 | 
 | ||||||
|  | #[allow(dead_code)] | ||||||
| pub fn read_config_system() -> BoxedSystem { | pub fn read_config_system() -> BoxedSystem { | ||||||
|     let query = Query::new(notify_file_event()).entity(resources()); |     let query = Query::new(notify_file_event()).entity(resources()); | ||||||
|     System::builder() |     System::builder() | ||||||
|         .with_name("read_config") |         .with_name("read_config") | ||||||
|         .with_cmd_mut() |         .with_cmd_mut() | ||||||
|         .with_query(query) |         .with_query(query) | ||||||
|         .build(|cmd: &mut CommandBuffer, mut q: EntityBorrow<_>| { |         .build(|cmd: &mut CommandBuffer, mut _q: EntityBorrow<_>| { | ||||||
|             if let Ok(n_event) = q.get() { |             // if let Ok(n_event) = q.get() {
 | ||||||
|                 if (n_event as ¬ify::Event).kind.is_modify() { |             //     println!("here");
 | ||||||
|                     println!("file modified: {:?}", (n_event as ¬ify::Event).paths); |             //     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()); |             cmd.set(resources(), config(), read_engine_config()); | ||||||
|                 } |                 // }
 | ||||||
|             } |             // }
 | ||||||
|         }) |         }) | ||||||
|         .boxed() |         .boxed() | ||||||
| } | } | ||||||
|  | @ -31,7 +33,7 @@ fn read_engine_config() -> 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()); |     let query = Query::new(config().as_mut()).entity(resources()); | ||||||
|     System::builder() |     System::builder() | ||||||
|         .with_name("first_read_config") |         .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