Skip to content

Commit

Permalink
Update watched shader module
Browse files Browse the repository at this point in the history
  • Loading branch information
hakolao committed Jan 13, 2025
1 parent 76d3ad7 commit 8743ee6
Show file tree
Hide file tree
Showing 3 changed files with 109 additions and 46 deletions.
81 changes: 49 additions & 32 deletions examples/shader_with_includes/main.rs
Original file line number Diff line number Diff line change
@@ -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,
Expand All @@ -21,18 +22,61 @@ fn main() -> Result<(), GlassError> {
#[derive(Default)]
struct TriangleApp {
triangle_pipeline: Option<RenderPipeline>,
shader_module: Option<WatchedShaderModule>,
}

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<Vec<CommandBuffer>> {
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,
Expand Down Expand Up @@ -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 {
Expand Down
3 changes: 2 additions & 1 deletion examples/shader_with_includes/triangle_with_include.wgsl
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
#include examples/shader_with_includes/consts.wgsl
#include examples/triangle/triangle.wgsl
#include examples/triangle/triangle.wgsl
// Changes here should trigger reload
71 changes: 58 additions & 13 deletions src/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ use std::{
fmt::Formatter,
future::Future,
path::{Path, PathBuf},
time::{Duration, Instant},
};

use flume::{unbounded, Receiver, Sender};
Expand Down Expand Up @@ -48,6 +49,16 @@ pub struct WatchedShaderModule {
source: ShaderSource,
_watchers: HashMap<String, Option<RecommendedWatcher>>,
_receivers: HashMap<String, Receiver<notify::Result<Event>>>,
first_event_time: Option<Instant>,
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 {
Expand Down Expand Up @@ -82,6 +93,8 @@ impl WatchedShaderModule {
source,
_watchers: watchers,
_receivers: receivers,
first_event_time: None,
has_pending_changes: false,
})
}

Expand Down Expand Up @@ -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 {
Expand All @@ -139,26 +151,51 @@ impl WatchedShaderModule {
modify_fn(&mut self.source)
}

pub fn changed_paths(&self) -> HashSet<String> {
let mut paths: HashSet<String> = 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, ShaderError> {
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::<Vec<&str>>();
[paths, other_paths].concat()
}
}

fn file_watcher(
Expand Down Expand Up @@ -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,
Expand All @@ -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,
Expand Down Expand Up @@ -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();
Expand Down

0 comments on commit 8743ee6

Please sign in to comment.