From 8743ee6ffd81aa631bd137b6bd109a883e8100f5 Mon Sep 17 00:00:00 2001 From: Okko Hakola Date: Mon, 13 Jan 2025 10:46:22 +0200 Subject: [PATCH] Update watched shader module --- examples/shader_with_includes/main.rs | 81 +++++++++++-------- .../triangle_with_include.wgsl | 3 +- src/utils.rs | 71 +++++++++++++--- 3 files changed, 109 insertions(+), 46 deletions(-) diff --git a/examples/shader_with_includes/main.rs b/examples/shader_with_includes/main.rs index e9d3c86..b066a06 100644 --- a/examples/shader_with_includes/main.rs +++ b/examples/shader_with_includes/main.rs @@ -1,7 +1,8 @@ -use std::{borrow::Cow, collections::HashMap, path::PathBuf}; +use std::{borrow::Cow, path::PathBuf}; use glass::{ - utils::ShaderModule, Glass, GlassApp, GlassConfig, GlassContext, GlassError, RenderData, + utils::{ShaderModule, WatchedShaderModule}, + Glass, GlassApp, GlassConfig, GlassContext, GlassError, RenderData, }; use wgpu::{ CommandBuffer, MultisampleState, PipelineLayoutDescriptor, PrimitiveState, RenderPipeline, @@ -21,18 +22,61 @@ fn main() -> Result<(), GlassError> { #[derive(Default)] struct TriangleApp { triangle_pipeline: Option, + shader_module: Option, } impl GlassApp for TriangleApp { fn start(&mut self, _event_loop: &ActiveEventLoop, context: &mut GlassContext) { - self.triangle_pipeline = Some(create_triangle_pipeline(context)); + // Dynamic includes + let shader_module = WatchedShaderModule::new(&PathBuf::from( + "examples/shader_with_includes/triangle_with_include.wgsl", + )) + .unwrap(); + + // // Static includes + // let mut static_includes = HashMap::default(); + // // Include all files that you wish to refer to in your root shader. Tedious, but this ensures + // // You can keep using includes while containing static shaders. + // static_includes.insert( + // "examples/shader_with_includes/triangle_with_include.wgsl", + // include_str!("triangle_with_include.wgsl"), + // ); + // static_includes.insert( + // "examples/shader_with_includes/consts.wgsl", + // include_str!("consts.wgsl"), + // ); + // static_includes.insert( + // "examples/triangle/triangle.wgsl", + // include_str!("../triangle/triangle.wgsl"), + // ); + // let shader_module = WatchedShaderModule::new_with_static_sources( + // "examples/shader_with_includes/triangle_with_include.wgsl", + // &static_includes, + // ) + // .unwrap(); + + self.triangle_pipeline = Some(create_triangle_pipeline( + context, + shader_module.module().unwrap(), + )); + self.shader_module = Some(shader_module); } fn render( &mut self, - _context: &GlassContext, + context: &GlassContext, render_data: RenderData, ) -> Option> { + let shader_module = self.shader_module.as_mut().unwrap(); + if shader_module.should_reload() { + shader_module.reload().unwrap(); + self.triangle_pipeline = Some(create_triangle_pipeline( + context, + shader_module.module().unwrap(), + )); + println!("Reloaded pipeline {:#?}", shader_module.paths()); + } + let RenderData { encoder, frame, @@ -62,34 +106,7 @@ impl GlassApp for TriangleApp { } } -fn create_triangle_pipeline(context: &GlassContext) -> RenderPipeline { - // Dynamic includes - let _shader_module = ShaderModule::new(&PathBuf::from( - "examples/shader_with_includes/triangle_with_include.wgsl", - )) - .unwrap(); - - // Static includes - let mut static_includes = HashMap::default(); - // Include all files that you wish to refer to in your root shader. Tedious, but this ensures - // You can keep using includes while containing static shaders. - static_includes.insert( - "examples/shader_with_includes/triangle_with_include.wgsl", - include_str!("triangle_with_include.wgsl"), - ); - static_includes.insert( - "examples/shader_with_includes/consts.wgsl", - include_str!("consts.wgsl"), - ); - static_includes.insert( - "examples/triangle/triangle.wgsl", - include_str!("../triangle/triangle.wgsl"), - ); - let shader_module = ShaderModule::new_with_static_sources( - "examples/shader_with_includes/triangle_with_include.wgsl", - &static_includes, - ) - .unwrap(); +fn create_triangle_pipeline(context: &GlassContext, shader_module: ShaderModule) -> RenderPipeline { let shader = context .device() .create_shader_module(ShaderModuleDescriptor { diff --git a/examples/shader_with_includes/triangle_with_include.wgsl b/examples/shader_with_includes/triangle_with_include.wgsl index 8fb5ed0..52ce2bf 100644 --- a/examples/shader_with_includes/triangle_with_include.wgsl +++ b/examples/shader_with_includes/triangle_with_include.wgsl @@ -1,2 +1,3 @@ #include examples/shader_with_includes/consts.wgsl -#include examples/triangle/triangle.wgsl \ No newline at end of file +#include examples/triangle/triangle.wgsl +// Changes here should trigger reload \ No newline at end of file diff --git a/src/utils.rs b/src/utils.rs index d6ac10f..1e8deca 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -4,6 +4,7 @@ use std::{ fmt::Formatter, future::Future, path::{Path, PathBuf}, + time::{Duration, Instant}, }; use flume::{unbounded, Receiver, Sender}; @@ -48,6 +49,16 @@ pub struct WatchedShaderModule { source: ShaderSource, _watchers: HashMap>, _receivers: HashMap>>, + first_event_time: Option, + has_pending_changes: bool, +} + +impl std::fmt::Debug for WatchedShaderModule { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + f.debug_struct("WatchedShaderModule") + .field("source", &self.source) + .finish() + } } impl WatchedShaderModule { @@ -82,6 +93,8 @@ impl WatchedShaderModule { source, _watchers: watchers, _receivers: receivers, + first_event_time: None, + has_pending_changes: false, }) } @@ -115,7 +128,6 @@ impl WatchedShaderModule { for remove in removes { self._receivers.remove(&remove); self._watchers.remove(&remove); - info!("Reload removed: {}", remove); } // Insert new watchers for path in new_watches { @@ -139,26 +151,51 @@ impl WatchedShaderModule { modify_fn(&mut self.source) } - pub fn changed_paths(&self) -> HashSet { - let mut paths: HashSet = HashSet::default(); + pub fn should_reload(&mut self) -> bool { for (_path, receiver) in self._receivers.iter() { + // Process any new events for event in receiver.try_iter().flatten() { - for path in event.paths.iter() { - if let Some(p) = path.to_str() { - if !p.ends_with('~') { - paths.insert(p.to_string()); - } + if event + .paths + .iter() + .filter_map(|p| p.to_str()) + .any(|p| !p.ends_with('~')) + { + self.has_pending_changes = true; + if self.first_event_time.is_none() { + self.first_event_time = Some(Instant::now()); } } } } - paths + if self.has_pending_changes + && self + .first_event_time + .is_some_and(|t| t.elapsed() >= Duration::from_millis(300)) + { + self.first_event_time = None; + self.has_pending_changes = false; + return true; + } + + false } pub fn module(&self) -> Result { ShaderModule::new_from_source(self.source.clone()) } + + pub fn paths(&self) -> Vec<&str> { + let paths = vec![self.source.path.as_str()]; + let other_paths = self + .source + .parts + .iter() + .map(|p| p.file_path.as_str()) + .collect::>(); + [paths, other_paths].concat() + } } fn file_watcher( @@ -289,7 +326,12 @@ impl ShaderSource { 0, )?; Ok(ShaderSource { - path: path.clean().display().to_string(), + path: path + .clean() + .to_str() + .unwrap() + .replace('\\', "/") + .to_string(), source, parts: included_parts, is_static: false, @@ -316,7 +358,7 @@ impl ShaderSource { 0, )?; Ok(ShaderSource { - path: root_source_path.to_string(), + path: root_source_path.replace('\\', "/").to_string(), source, parts: included_parts, is_static: true, @@ -373,10 +415,13 @@ fn wgsl_source_with_includes( for line in source.lines() { if line.starts_with("#include") { - let included_file_name = line.trim_start_matches("#include ").trim(); + let included_file_name = line + .trim_start_matches("#include ") + .trim() + .replace('\\', "/"); let included_file_path = std::env::current_dir() .unwrap() - .join(included_file_name) + .join(&included_file_name) .clean(); let included_file_path_str = included_file_path.to_string_lossy().into_owned();