Add hot reloading of shaders to the winit example (#252)

This commit is contained in:
Daniel McNab 2023-01-16 17:24:48 +00:00 committed by GitHub
parent db4fc4e449
commit ed60031185
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 117 additions and 8 deletions

11
Cargo.lock generated
View file

@ -2452,6 +2452,16 @@ dependencies = [
"winapi",
]
[[package]]
name = "notify-debouncer-mini"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e23e9fa24f094b143c1eb61f90ac6457de87be6987bc70746e0179f7dbc9007b"
dependencies = [
"crossbeam-channel",
"notify",
]
[[package]]
name = "nu-ansi-term"
version = "0.46.0"
@ -4186,6 +4196,7 @@ dependencies = [
"clap",
"console_error_panic_hook",
"console_log",
"notify-debouncer-mini",
"pollster",
"roxmltree",
"vello",

View file

@ -29,4 +29,5 @@ moscato = { git = "https://github.com/dfrg/pinot" }
peniko = { git = "https://github.com/linebender/peniko" }
[features]
hot_reload = []
buffer_labels = []

View file

@ -16,6 +16,10 @@ pollster = "0.2.5"
roxmltree = "0.13"
clap = { version = "4.1.0", features = ["derive"] }
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
vello = { path = "../../", features = ["hot_reload"] }
notify-debouncer-mini = "0.2.1"
[target.'cfg(target_arch = "wasm32")'.dependencies]
console_error_panic_hook = "0.1.7"
console_log = "0.2"

View file

@ -0,0 +1,28 @@
use std::{path::Path, time::Duration};
use notify_debouncer_mini::{new_debouncer, notify::*, DebounceEventResult};
pub(crate) fn hot_reload(mut f: impl FnMut() -> Option<()> + Send + 'static) -> impl Sized {
let mut debouncer = new_debouncer(
Duration::from_millis(500),
None,
move |res: DebounceEventResult| match res {
Ok(_) => f().unwrap(),
Err(errors) => errors.iter().for_each(|e| println!("Error {:?}", e)),
},
)
.unwrap();
debouncer
.watcher()
.watch(
dbg!(&Path::new(env!("CARGO_MANIFEST_DIR"))
.join("../../shader")
.canonicalize()
.unwrap()),
// We currently don't support hot reloading the imports, so don't recurse into there
RecursiveMode::NonRecursive,
)
.expect("Could watch shaders directory");
debouncer
}

View file

@ -18,7 +18,7 @@ mod pico_svg;
mod simple_text;
mod test_scene;
use std::{borrow::Cow, path::PathBuf};
use std::{borrow::Cow, path::PathBuf, time::Instant};
use clap::Parser;
use vello::{
@ -26,7 +26,13 @@ use vello::{
util::RenderContext,
Renderer, Scene, SceneBuilder,
};
use winit::{event_loop::EventLoop, window::Window};
use winit::{
event_loop::{EventLoop, EventLoopBuilder},
window::Window,
};
#[cfg(not(target_arch = "wasm32"))]
mod hot_reload;
#[derive(Parser, Debug)]
#[command(about, long_about = None)]
@ -46,7 +52,7 @@ struct Args {
const TIGER: &'static str = include_str!("../../assets/Ghostscript_Tiger.svg");
async fn run(event_loop: EventLoop<()>, window: Window, args: Args) {
async fn run(event_loop: EventLoop<UserEvent>, window: Window, args: Args) {
use winit::{event::*, event_loop::ControlFlow};
let mut render_cx = RenderContext::new().unwrap();
let size = window.inner_size();
@ -194,16 +200,40 @@ async fn run(event_loop: EventLoop<()>, window: Window, args: Args) {
surface_texture.present();
device_handle.device.poll(wgpu::Maintain::Wait);
}
Event::UserEvent(event) => match event {
#[cfg(not(target_arch = "wasm32"))]
UserEvent::HotReload => {
let device_handle = &render_cx.devices[surface.dev_id];
eprintln!("==============\nReloading shaders");
let start = Instant::now();
let result = renderer.reload_shaders(&device_handle.device);
// We know that the only async here is actually sync, so we just block
match pollster::block_on(result) {
Ok(_) => eprintln!("Reloading took {:?}", start.elapsed()),
Err(e) => eprintln!("Failed to reload shaders because of {e}"),
}
}
},
_ => {}
});
}
enum UserEvent {
#[cfg(not(target_arch = "wasm32"))]
HotReload,
}
fn main() {
let args = Args::parse();
#[cfg(not(target_arch = "wasm32"))]
{
use winit::{dpi::LogicalSize, window::WindowBuilder};
let event_loop = EventLoop::new();
let event_loop = EventLoopBuilder::<UserEvent>::with_user_event().build();
let proxy = event_loop.create_proxy();
let _keep =
hot_reload::hot_reload(move || proxy.send_event(UserEvent::HotReload).ok().map(drop));
let window = WindowBuilder::new()
.with_inner_size(LogicalSize::new(1044, 800))
.with_resizable(true)
@ -214,7 +244,7 @@ fn main() {
}
#[cfg(target_arch = "wasm32")]
{
let event_loop = EventLoop::new();
let event_loop = EventLoopBuilder::<UserEvent>::with_user_event().build();
let window = winit::window::Window::new(&event_loop).unwrap();
std::panic::set_hook(Box::new(console_error_panic_hook::hook));

View file

@ -149,6 +149,21 @@ impl Renderer {
self.target = Some(target);
Ok(())
}
/// Reload the shaders. This should only be used during `vello` development
#[cfg(feature = "hot_reload")]
pub async fn reload_shaders(&mut self, device: &Device) -> Result<()> {
device.push_error_scope(wgpu::ErrorFilter::Validation);
let mut engine = Engine::new();
let shaders = shaders::full_shaders(device, &mut engine)?;
let error = device.pop_error_scope().await;
if let Some(error) = error {
return Err(error.into());
}
self.engine = engine;
self.shaders = shaders;
Ok(())
}
}
struct TargetTexture {

View file

@ -31,9 +31,29 @@ pub const PATH_DRAWOBJ_WG: u32 = 256;
pub const CLIP_REDUCE_WG: u32 = 256;
macro_rules! shader {
($name:expr) => {
include_str!(concat!("../shader/", $name, ".wgsl"))
};
($name:expr) => {&{
let shader = include_str!(concat!(
env!("CARGO_MANIFEST_DIR"),
"/shader/",
$name,
".wgsl"
));
#[cfg(feature = "hot_reload")]
let shader = std::fs::read_to_string(concat!(
env!("CARGO_MANIFEST_DIR"),
"/shader/",
$name,
".wgsl"
))
.unwrap_or_else(|e| {
eprintln!(
"Failed to read shader {name}, error falling back to version at compilation time. Error: {e:?}",
name = $name
);
shader.to_string()
});
shader
}};
}
pub struct Shaders {