diff options
| author | s-ol <s+removethis@s-ol.nu> | 2026-04-16 15:29:50 +0000 |
|---|---|---|
| committer | s-ol <s+removethis@s-ol.nu> | 2026-05-14 14:46:13 +0000 |
| commit | 5925137f3b41cb3a87f098604ef482bced91341e (patch) | |
| tree | 613ee305358e319bab5d88a85cc923b4870d73f1 /src/bin/wgsl_render.rs | |
| parent | refactor (diff) | |
| download | wgsl-view-5925137f3b41cb3a87f098604ef482bced91341e.tar.gz wgsl-view-5925137f3b41cb3a87f098604ef482bced91341e.zip | |
tsv integration, split binaries
Diffstat (limited to 'src/bin/wgsl_render.rs')
| -rw-r--r-- | src/bin/wgsl_render.rs | 281 |
1 files changed, 281 insertions, 0 deletions
diff --git a/src/bin/wgsl_render.rs b/src/bin/wgsl_render.rs new file mode 100644 index 0000000..1860b7c --- /dev/null +++ b/src/bin/wgsl_render.rs @@ -0,0 +1,281 @@ +use std::thread; +use std::time::{Duration, Instant}; + +use ash::vk; +use rosc::OscType; +use wgsl_view::gpu; +use wgsl_view::osc::OscServer; +use wgsl_view::renderer::Renderer; +use wgsl_view::uniform::UniformError; + +fn main() { + env_logger::init(); + + let mut args = std::env::args().skip(1); + let mut width = 1920u32; + let mut height = 1080u32; + let mut port = 9000u16; + let mut name = "wgsl-view".to_string(); + let mut fps = 60u32; + let mut continuous = false; + + while let Some(arg) = args.next() { + match arg.as_str() { + "--width" => width = args.next().expect("--width VALUE").parse().expect("u32"), + "--height" => height = args.next().expect("--height VALUE").parse().expect("u32"), + "--port" => port = args.next().expect("--port VALUE").parse().expect("u16"), + "--name" => name = args.next().expect("--name VALUE"), + "--fps" => fps = args.next().expect("--fps VALUE").parse().expect("u32"), + "--continuous" => continuous = true, + other => panic!("unknown argument: {other}"), + } + } + + let instance = gpu::create_instance(); + let adapter = gpu::create_adapter(&instance, None); + let (device, queue) = gpu::create_device(&adapter); + + let mut client = gpu::create_tsv_client(&device); + client + .init_image(&name, width, height, gpu::TSV_FORMAT, true) + .expect("init tsv image"); + + let fence = gpu::create_fence(&client); + let mut renderer = Renderer::new(&device, &queue, width, height); + let mut osc = OscServer::new(&format!("0.0.0.0:{port}")).expect("bind OSC socket"); + + let frame_time = Duration::from_secs_f64(1.0 / fps as f64); + log::info!( + "rendering {width}x{height}{}, tsv image: {name}", + if continuous { + format!(" at {fps}fps") + } else { + " on OSC input".to_string() + } + ); + + loop { + let frame_start = Instant::now(); + + let handle_msg = |msg: rosc::OscMessage| { + let path = &msg.addr; + + if path == "/shader" { + match &msg.args[..] { + [OscType::String(code)] => { + if let Err(e) = renderer.load_shader(&device, code) { + log::error!("shader error: {e}"); + } + } + _ => log::warn!("/shader: expected single string argument"), + } + } else if let Some(rest) = path.strip_prefix("/uniform/") { + if let Err(e) = set_uniform(renderer.uniforms(), rest, &msg.args) { + log::warn!("uniform error: {e}"); + } + } else if let Some(tex_name) = path.strip_prefix("/texture/") { + match &msg.args[..] { + [OscType::String(tsv_name)] => { + match renderer.uniforms().texture_slot_mut(tex_name) { + Some(slot) => { + slot.set_tsv_name(tsv_name.clone()); + log::info!("texture '{tex_name}' → tsv '{tsv_name}'"); + } + None => log::warn!("texture '{tex_name}' not found"), + } + } + _ => log::warn!("/texture/{tex_name}: expected string (TSV image name)"), + } + } else if let Some(smp_name) = path.strip_prefix("/sampler/") { + match &msg.args[..] { + [OscType::String(filter), OscType::String(clamp)] => { + if let Err(e) = configure_sampler( + renderer.uniforms(), + &device, + smp_name, + filter, + clamp, + ) { + log::warn!("sampler error: {e}"); + } + } + _ => log::warn!("/sampler/{smp_name}: expected two strings (filter, clamp)"), + } + } else { + log::debug!("unhandled OSC: {path}"); + } + }; + + let dirty = if continuous { + osc.poll(handle_msg); + true + } else { + osc.recv(handle_msg) + }; + + if dirty { + refresh_textures(renderer.uniforms(), &device, &mut client, fence); + renderer.render(&device, &queue); + device.poll(wgpu::PollType::wait_indefinitely()).unwrap(); + + let canvas_image = unsafe { gpu::raw_image(renderer.canvas_texture()) }; + if let Err(e) = client.send_image( + &name, + canvas_image, + vk::ImageLayout::COLOR_ATTACHMENT_OPTIMAL, + vk::ImageLayout::COLOR_ATTACHMENT_OPTIMAL, + fence, + ) { + log::warn!("send_image error: {e}"); + } + } + + if continuous { + let elapsed = frame_start.elapsed(); + if elapsed < frame_time { + thread::sleep(frame_time - elapsed); + } + } + } +} + +/// Set a uniform value from OSC args, navigating the path. +fn set_uniform( + cache: &mut wgsl_view::uniform::UniformCache, + path: &str, + args: &[OscType], +) -> Result<(), Box<dyn std::error::Error>> { + let mut parts = path.split('/'); + let name = parts.next().ok_or("missing uniform name")?; + + let mut uref = cache.get(name).ok_or("uniform not found")?; + for component in parts { + uref = uref.field(component)?; + } + + let scalar = uref.leaf_scalar()?; + match scalar.kind { + naga::ScalarKind::Float => { + let values: Vec<f32> = args + .iter() + .map(|a| match a { + OscType::Float(f) => Ok(*f), + OscType::Double(d) => Ok(*d as f32), + OscType::Int(i) => Ok(*i as f32), + OscType::Bool(b) => Ok(if *b { 1.0 } else { 0.0 }), + _ => Err(UniformError::TypeMismatch), + }) + .collect::<Result<_, _>>()?; + uref.set_f32(&values)?; + } + naga::ScalarKind::Sint => { + let values: Vec<i32> = args + .iter() + .map(|a| match a { + OscType::Int(i) => Ok(*i), + OscType::Float(f) => Ok(*f as i32), + OscType::Double(d) => Ok(*d as i32), + OscType::Bool(b) => Ok(if *b { 1 } else { 0 }), + _ => Err(UniformError::TypeMismatch), + }) + .collect::<Result<_, _>>()?; + uref.set_i32(&values)?; + } + naga::ScalarKind::Uint => { + let values: Vec<u32> = args + .iter() + .map(|a| match a { + OscType::Int(i) => Ok(*i as u32), + OscType::Float(f) => Ok(*f as u32), + OscType::Double(d) => Ok(*d as u32), + OscType::Bool(b) => Ok(if *b { 1 } else { 0 }), + _ => Err(UniformError::TypeMismatch), + }) + .collect::<Result<_, _>>()?; + uref.set_u32(&values)?; + } + _ => return Err(Box::new(UniformError::TypeMismatch)), + } + + Ok(()) +} + +/// Configure a named sampler from string filter/clamp mode names. +fn configure_sampler( + cache: &mut wgsl_view::uniform::UniformCache, + device: &wgpu::Device, + name: &str, + filter: &str, + clamp: &str, +) -> Result<(), String> { + let filter_mode = match filter { + "linear" => wgpu::FilterMode::Linear, + "nearest" => wgpu::FilterMode::Nearest, + _ => return Err(format!("unknown filter mode '{filter}' (linear|nearest)")), + }; + let address_mode = match clamp { + "clamp" => wgpu::AddressMode::ClampToEdge, + "repeat" => wgpu::AddressMode::Repeat, + "mirror" => wgpu::AddressMode::MirrorRepeat, + _ => return Err(format!("unknown clamp mode '{clamp}' (clamp|repeat|mirror)")), + }; + let slot = cache + .sampler_slot_mut(name) + .ok_or_else(|| format!("sampler '{name}' not found"))?; + slot.configure(device, filter_mode, address_mode); + cache.rebuild_bind_group(device); + log::info!("sampler '{name}' → {filter}, {clamp}"); + Ok(()) +} + +/// Refresh all texture inputs from TSV shared images. +fn refresh_textures( + cache: &mut wgsl_view::uniform::UniformCache, + device: &wgpu::Device, + client: &mut texture_share_vk_client::VkClient, + fence: vk::Fence, +) { + let mut needs_rebind = false; + + for slot in cache.texture_slots_mut() { + let tsv_name = match slot.tsv_name() { + Some(n) => n.to_string(), + None => continue, + }; + + if !slot.tsv_registered() { + if let Err(e) = client.find_image(&tsv_name, true) { + log::debug!("tsv find '{tsv_name}': {e}"); + continue; + } + + match client.find_image_data(&tsv_name, true) { + Ok(Some((_lock, data))) => { + if slot.resize(device, data.width, data.height) { + needs_rebind = true; + } + } + _ => continue, + } + + slot.set_tsv_registered(); + } + + let raw = unsafe { gpu::raw_image(slot.texture()) }; + match client.recv_image( + &tsv_name, + raw, + vk::ImageLayout::UNDEFINED, + vk::ImageLayout::SHADER_READ_ONLY_OPTIMAL, + fence, + ) { + Ok(Some(())) => log::trace!("texture '{}' updated from '{tsv_name}'", slot.name()), + Ok(None) => {} + Err(e) => log::warn!("recv_image '{tsv_name}': {e}"), + } + } + + if needs_rebind { + cache.rebuild_bind_group(device); + } +} |
