use std::error::Error; 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; 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_with_features( &adapter, wgpu::Features::TEXTURE_COMPRESSION_BC | wgpu::Features::TEXTURE_COMPRESSION_BC_SLICED_3D, ); let mut client = gpu::create_tsv_client(&device); client .init_image( &name, width, height, 1, gpu::TSV_FORMAT, gpu::ImgType::D2, 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| -> Result<(), Box> { let path: Vec<_> = msg.addr.strip_prefix("/").unwrap().split('/').collect(); match (&path[..], &msg.args[..]) { // create shaders (["module", module_name], [OscType::String(code)]) => { renderer.load_module(module_name, code)? } (["entrypoint"], [OscType::String(module_name)]) => { renderer.compile(&device, module_name)? } // bind values (["uniform", module, item, rest @ ..], args) => { renderer.set_uniform(module, item, rest, args)? } (["binding", module, item], [OscType::String(target)]) => { renderer.set_binding(&device, module, item, target)? } // create/destroy texture resources (["texture", id], [OscType::String(tsv_name)]) => { renderer.uniforms().create_texture(&device, id, tsv_name); } (["texture", id, "destroy"], []) => { renderer.uniforms().destroy_texture(&device, id); } // create/destroy sampler resources (["sampler", id], [OscType::String(filter), OscType::String(clamp)]) => { let (filter_mode, address_mode) = parse_sampler_modes(filter, clamp)?; renderer .uniforms() .create_sampler(&device, id, filter_mode, address_mode); } (["sampler", id, "destroy"], []) => { renderer.uniforms().destroy_sampler(&device, id); } _ => Err(format!("unhandled OSC message {} {:?}", msg.addr, msg.args))?, } Ok(()) }; let dirty = if continuous { osc.poll(handle_msg); true } else { osc.recv(handle_msg) }; if dirty { gpu::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 parse_sampler_modes( filter: &str, address: &str, ) -> Result<(wgpu::FilterMode, wgpu::AddressMode), String> { let filter_mode = match filter { "linear" => wgpu::FilterMode::Linear, "nearest" => wgpu::FilterMode::Nearest, _ => return Err(format!("unknown filter mode '{filter}'")), }; let address_mode = match address { "clamp" => wgpu::AddressMode::ClampToEdge, "repeat" => wgpu::AddressMode::Repeat, "mirror" => wgpu::AddressMode::MirrorRepeat, _ => return Err(format!("unknown address mode mode '{address}'")), }; Ok((filter_mode, address_mode)) }