diff options
| author | s-ol <s+removethis@s-ol.nu> | 2026-05-04 21:45:39 +0000 |
|---|---|---|
| committer | s-ol <s+removethis@s-ol.nu> | 2026-05-14 15:00:13 +0000 |
| commit | 0bdd9ae4e181283ac1d21b52cce7b28c565d936f (patch) | |
| tree | 141632d39cda63d9a605b182041006ada7031ec2 /src | |
| parent | switch to WESL shaders (diff) | |
| download | wgsl-view-0bdd9ae4e181283ac1d21b52cce7b28c565d936f.tar.gz wgsl-view-0bdd9ae4e181283ac1d21b52cce7b28c565d936f.zip | |
tsv-video-*: share code
Diffstat (limited to 'src')
| -rw-r--r-- | src/bin/tsv_video_buffer.rs | 150 | ||||
| -rw-r--r-- | src/bin/tsv_video_stream.rs | 89 | ||||
| -rw-r--r-- | src/ffmpeg.rs | 150 | ||||
| -rw-r--r-- | src/gpu.rs | 24 | ||||
| -rw-r--r-- | src/lib.rs | 1 | ||||
| -rw-r--r-- | src/uniform.rs | 2 | ||||
| -rw-r--r-- | src/wesl.rs | 6 |
7 files changed, 227 insertions, 195 deletions
diff --git a/src/bin/tsv_video_buffer.rs b/src/bin/tsv_video_buffer.rs index 9c8ee99..3a244a8 100644 --- a/src/bin/tsv_video_buffer.rs +++ b/src/bin/tsv_video_buffer.rs @@ -2,77 +2,7 @@ use std::io::Read; use std::process::{Command, Stdio}; use ash::vk; -use wgsl_view::{gpu, hap}; - -/// Options that ffmpeg supports but ffprobe does not. -const FFMPEG_ONLY_OPTIONS: &[&str] = &["-stream_loop", "-t"]; - -fn filter_probe_args(ff_args: &[String]) -> Vec<String> { - let mut out = Vec::new(); - let mut skip_next = false; - for arg in ff_args { - if skip_next { - skip_next = false; - continue; - } - if FFMPEG_ONLY_OPTIONS.contains(&arg.as_str()) { - skip_next = true; - continue; - } - out.push(arg.clone()); - } - out -} - -struct VideoInfo { - width: u32, - height: u32, - num_frames: u32, - codec_tag: String, -} - -fn probe_video(ff_args: &[String], max_frames: Option<u32>) -> VideoInfo { - let probe_args = filter_probe_args(ff_args); - let output = Command::new("ffprobe") - .args(["-v", "error", "-select_streams", "v:0"]) - .args([ - "-show_entries", - "stream=width,height,nb_frames,codec_tag_string", - ]) - .args(["-of", "csv=p=0"]) - .args(&probe_args) - .output() - .expect("failed to run ffprobe"); - - if !output.status.success() { - let stderr = String::from_utf8_lossy(&output.stderr); - panic!("ffprobe failed: {stderr}"); - } - - let stdout = String::from_utf8_lossy(&output.stdout); - let line = stdout.trim(); - let parts: Vec<&str> = line.split(',').collect(); - if parts.len() < 4 { - panic!("unexpected ffprobe output: {line}"); - } - - let num_frames = parts[3].parse().unwrap_or_else(|_| { - max_frames.unwrap_or_else(|| { - panic!( - "could not determine frame count (nb_frames={}).\n\ - Use --frames N or use tsv-video-stream instead.", - parts[3] - ) - }) - }); - - VideoInfo { - codec_tag: parts[0].to_string(), - width: parts[1].parse().expect("parse width"), - height: parts[2].parse().expect("parse height"), - num_frames, - } -} +use wgsl_view::{ffmpeg, gpu, hap}; fn is_hap_codec(codec_tag: &str) -> Option<hap::HapFormat> { match codec_tag { @@ -119,28 +49,29 @@ fn main() { } } - let mut info = probe_video(ff_args, max_frames); - if let Some(max) = max_frames { - info.num_frames = info.num_frames.min(max); - } + let mut info = ffmpeg::probe_video(ff_args, max_frames); + let num_frames = match (info.num_frames, max_frames) { + (Some(a), Some(b)) => u32::min(a, b), + (Some(a), None) => a, + (None, Some(b)) => b, + _ => panic!("failed to detect duration, please specify --frames"), + }; + info.num_frames = Some(num_frames); let hap_format = is_hap_codec(&info.codec_tag); - let (texture_format, tsv_img_format) = match hap_format { - Some(hap::HapFormat::Bc1) => (wgpu::TextureFormat::Bc1RgbaUnorm, gpu::ImgFormat::BC1_RGBA), - Some(hap::HapFormat::Bc3) => (wgpu::TextureFormat::Bc3RgbaUnorm, gpu::ImgFormat::BC3_RGBA), - None => ( - wgpu::TextureFormat::Rgba8UnormSrgb, - gpu::ImgFormat::R8G8B8A8, - ), + let format = match hap_format { + Some(hap::HapFormat::Bc1) => gpu::ImgFormat::BC1_RGBA, + Some(hap::HapFormat::Bc3) => gpu::ImgFormat::BC3_RGBA, + None => gpu::ImgFormat::R8G8B8A8, }; log::info!( - "{}x{}, {} frames, codec_tag={}, format={:?}, tsv image: {}", + "{}x{}, {} frames, codec_tag={}, format={:?}, tsv image: {:?}", info.width, info.height, - info.num_frames, + num_frames, info.codec_tag, - texture_format, + format, name ); @@ -156,10 +87,10 @@ fn main() { wgpu::Features::empty() }; - let (tsv_img_type, wgpu_dimension) = if use_array { - (gpu::ImgType::D2, wgpu::TextureDimension::D2) + let img_type = if use_array { + gpu::ImgType::D2 } else { - (gpu::ImgType::D3, wgpu::TextureDimension::D3) + gpu::ImgType::D3 }; let instance = gpu::create_instance(); @@ -172,9 +103,9 @@ fn main() { &name, info.width, info.height, - info.num_frames, - tsv_img_format, - tsv_img_type, + num_frames, + format, + img_type, true, ) .expect("init tsv image"); @@ -186,12 +117,12 @@ fn main() { size: wgpu::Extent3d { width: info.width, height: info.height, - depth_or_array_layers: info.num_frames, + depth_or_array_layers: num_frames, }, mip_level_count: 1, sample_count: 1, - dimension: wgpu_dimension, - format: texture_format, + dimension: gpu::img_type_to_wgpu(img_type, num_frames), + format: gpu::img_format_to_wgpu(format), usage: wgpu::TextureUsages::COPY_DST | wgpu::TextureUsages::COPY_SRC, view_formats: &[], }); @@ -219,10 +150,7 @@ fn main() { std::process::exit(1); } - log::info!( - "all {} frames uploaded and shared, waiting...", - info.num_frames - ); + log::info!("all {} frames uploaded and shared, waiting...", num_frames); // Keep the process alive so the shared image remains available loop { @@ -235,7 +163,7 @@ fn upload_hap_frames( queue: &wgpu::Queue, texture: &wgpu::Texture, ff_args: &[String], - info: &VideoInfo, + info: &ffmpeg::VideoInfo, hap_fmt: hap::HapFormat, ) { // For HAP, we need raw packet data (not decoded pixels). @@ -254,6 +182,7 @@ fn upload_hap_frames( let compressed_frame_size = hap_fmt.compressed_size(info.width, info.height); let mut decode_buf = vec![0u8; compressed_frame_size + 1024 * 1024]; // extra margin let mut frame_index: u32 = 0; + let num_frames = info.num_frames.unwrap(); let block_bytes = hap_fmt.block_bytes(); let blocks_x = info.width.div_ceil(4); @@ -293,18 +222,18 @@ fn upload_hap_frames( ); frame_index += 1; - if frame_index.is_multiple_of(10) || frame_index == info.num_frames { - log::info!("uploaded frame {}/{}", frame_index, info.num_frames); + if frame_index.is_multiple_of(10) || frame_index == num_frames { + log::info!("uploaded frame {}/{}", frame_index, num_frames); } - if frame_index >= info.num_frames { + if frame_index >= num_frames { break; } } - if frame_index < info.num_frames { + if frame_index < num_frames { log::warn!( "expected {} frames but only decoded {frame_index}", - info.num_frames + num_frames ); } } @@ -314,9 +243,10 @@ fn upload_raw_frames( queue: &wgpu::Queue, texture: &wgpu::Texture, ff_args: &[String], - info: &VideoInfo, + info: &ffmpeg::VideoInfo, ) { let frame_size = (info.width * info.height * 4) as usize; + let num_frames = info.num_frames.unwrap(); let mut ffmpeg = Command::new("ffmpeg") .args(["-v", "quiet"]) @@ -358,10 +288,10 @@ fn upload_raw_frames( ); frame_index += 1; - if frame_index.is_multiple_of(10) || frame_index == info.num_frames { - log::info!("uploaded frame {}/{}", frame_index, info.num_frames); + if frame_index.is_multiple_of(10) || frame_index == num_frames { + log::info!("uploaded frame {}/{}", frame_index, num_frames); } - if frame_index >= info.num_frames { + if frame_index >= num_frames { break; } } @@ -371,10 +301,10 @@ fn upload_raw_frames( drop(reader); ffmpeg.wait().ok(); - if frame_index < info.num_frames { + if frame_index < num_frames { log::warn!( "expected {} frames but only decoded {frame_index}", - info.num_frames + num_frames ); } } diff --git a/src/bin/tsv_video_stream.rs b/src/bin/tsv_video_stream.rs index 3e12f6d..c2db35c 100644 --- a/src/bin/tsv_video_stream.rs +++ b/src/bin/tsv_video_stream.rs @@ -4,63 +4,7 @@ use std::thread; use std::time::{Duration, Instant}; use ash::vk; -use wgsl_view::gpu; - -/// Options that ffmpeg supports but ffprobe does not. -const FFMPEG_ONLY_OPTIONS: &[&str] = &["-stream_loop"]; - -fn filter_probe_args(ff_args: &[String]) -> Vec<String> { - let mut out = Vec::new(); - let mut skip_next = false; - for arg in ff_args { - if skip_next { - skip_next = false; - continue; - } - if FFMPEG_ONLY_OPTIONS.contains(&arg.as_str()) { - skip_next = true; - continue; - } - out.push(arg.clone()); - } - out -} - -fn probe_video(ff_args: &[String]) -> (u32, u32, f64) { - let probe_args = filter_probe_args(ff_args); - let output = Command::new("ffprobe") - .args(["-v", "error", "-select_streams", "v:0"]) - .args(["-show_entries", "stream=width,height,r_frame_rate"]) - .args(["-of", "csv=p=0"]) - .args(&probe_args) - .output() - .expect("failed to run ffprobe"); - - if !output.status.success() { - let stderr = String::from_utf8_lossy(&output.stderr); - panic!("ffprobe failed: {stderr}"); - } - - let stdout = String::from_utf8_lossy(&output.stdout); - let line = stdout.trim(); - let parts: Vec<&str> = line.split(',').collect(); - if parts.len() < 3 { - panic!("unexpected ffprobe output: {line}"); - } - - let width: u32 = parts[0].parse().expect("parse width"); - let height: u32 = parts[1].parse().expect("parse height"); - - let fps: f64 = if let Some((num, den)) = parts[2].split_once('/') { - let n: f64 = num.parse().expect("parse fps numerator"); - let d: f64 = den.parse().expect("parse fps denominator"); - n / d - } else { - parts[2].parse().expect("parse fps") - }; - - (width, height, fps) -} +use wgsl_view::{ffmpeg, gpu}; fn main() { env_logger::init(); @@ -89,8 +33,13 @@ fn main() { } } - let (width, height, fps) = probe_video(ff_args); - log::info!("{width}x{height} @ {fps:.2}fps, tsv image: {name}"); + let info = ffmpeg::probe_video(ff_args, None); + log::info!( + "{}x{} @ {:.2}fps, tsv image: {name}", + info.width, + info.height, + info.fps + ); let instance = gpu::create_instance(); let adapter = gpu::create_adapter(&instance, None); @@ -100,10 +49,10 @@ fn main() { client .init_image( &name, - width, - height, + info.width, + info.height, 1, - gpu::TSV_FORMAT, + gpu::ImgFormat::R8G8B8A8, gpu::ImgType::D2, true, ) @@ -114,20 +63,20 @@ fn main() { let texture = device.create_texture(&wgpu::TextureDescriptor { label: Some("video_frame"), size: wgpu::Extent3d { - width, - height, + width: info.width, + height: info.height, depth_or_array_layers: 1, }, mip_level_count: 1, sample_count: 1, dimension: wgpu::TextureDimension::D2, - format: wgpu::TextureFormat::Rgba8UnormSrgb, + format: wgpu::TextureFormat::Rgba8Unorm, usage: wgpu::TextureUsages::COPY_DST | wgpu::TextureUsages::COPY_SRC, view_formats: &[], }); - let frame_size = (width * height * 4) as usize; - let frame_time = Duration::from_secs_f64(1.0 / fps); + let frame_size = (info.width * info.height * 4) as usize; + let frame_time = Duration::from_secs_f64(1.0 / info.fps); let mut ffmpeg = Command::new("ffmpeg") .args(["-v", "quiet"]) @@ -159,12 +108,12 @@ fn main() { &frame_buf, wgpu::TexelCopyBufferLayout { offset: 0, - bytes_per_row: Some(width * 4), + bytes_per_row: Some(info.width * 4), rows_per_image: None, }, wgpu::Extent3d { - width, - height, + width: info.width, + height: info.height, depth_or_array_layers: 1, }, ); diff --git a/src/ffmpeg.rs b/src/ffmpeg.rs new file mode 100644 index 0000000..fbfdc94 --- /dev/null +++ b/src/ffmpeg.rs @@ -0,0 +1,150 @@ +use std::process::Command; + +/// Options that ffmpeg supports but ffprobe does not. +const FFMPEG_ONLY_OPTIONS: &[&str] = &["-stream_loop", "-t"]; + +const ALPHA_FMTS: &[&str] = &[ + "pal8", + "argb", + "rgba", + "abgr", + "bgra", + "yuva420p", + "ya8", + "yuva422p", + "yuva444p", + "yuva420p9be", + "yuva420p9le", + "yuva422p9be", + "yuva422p9le", + "yuva444p9be", + "yuva444p9le", + "yuva420p10be", + "yuva420p10le", + "yuva422p10be", + "yuva422p10le", + "yuva444p10be", + "yuva444p10le", + "yuva420p16be", + "yuva420p16le", + "yuva422p16be", + "yuva422p16le", + "yuva444p16be", + "yuva444p16le", + "rgba64be", + "rgba64le", + "bgra64be", + "bgra64le", + "ya16be", + "ya16le", + "gbrap", + "gbrap16be", + "gbrap16le", + "ayuv64le", + "ayuv64be", + "gbrap12be", + "gbrap12le", + "gbrap10be", + "gbrap10le", + "gbrapf32be", + "gbrapf32le", + "yuva422p12be", + "yuva422p12le", + "yuva444p12be", + "yuva444p12le", + "vuya", + "rgbaf16be", + "rgbaf16le", + "rgbaf32be", + "rgbaf32le", + "gbrap14be", + "gbrap14le", + "ayuv", + "uyva", + "rgba128be", + "rgba128le", + "gbrapf16be", + "gbrapf16le", + "yaf32be", + "yaf32le", + "yaf16be", + "yaf16le", + "gbrap32be", + "gbrap32le", +]; + +fn filter_probe_args(ff_args: &[String]) -> Vec<String> { + let mut out = Vec::new(); + let mut skip_next = false; + for arg in ff_args { + if skip_next { + skip_next = false; + continue; + } + if FFMPEG_ONLY_OPTIONS.contains(&arg.as_str()) { + skip_next = true; + continue; + } + out.push(arg.clone()); + } + out +} + +pub struct VideoInfo { + pub width: u32, + pub height: u32, + pub num_frames: Option<u32>, + pub fps: f64, + pub alpha: bool, + pub codec_tag: String, +} + +pub fn probe_video(ff_args: &[String], max_frames: Option<u32>) -> VideoInfo { + let probe_args = filter_probe_args(ff_args); + let output = Command::new("ffprobe") + .args(["-v", "error"]) + .args(["-select_streams", "v:0"]) + .args([ + "-show_entries", + "stream=codec_tag_string,width,height,pix_fmt,r_frame_rate,nb_frames", + ]) + .args(["-of", "csv=p=0"]) + .args(&probe_args) + .output() + .expect("failed to run ffprobe"); + + if !output.status.success() { + let stderr = String::from_utf8_lossy(&output.stderr); + panic!("ffprobe failed: {stderr}"); + } + + let stdout = String::from_utf8_lossy(&output.stdout); + let line = stdout.trim(); + let parts: Vec<&str> = line.split(',').collect(); + if parts.len() != 6 { + panic!("unexpected ffprobe output: {line}"); + } + + let codec_tag = parts[0].to_string(); + let width: u32 = parts[1].parse().expect("parse width"); + let height: u32 = parts[2].parse().expect("parse height"); + let alpha = ALPHA_FMTS.contains(&parts[3]); + + let fps: f64 = if let Some((num, den)) = parts[4].split_once('/') { + let n: f64 = num.parse().expect("parse fps numerator"); + let d: f64 = den.parse().expect("parse fps denominator"); + n / d + } else { + parts[4].parse().expect("parse fps") + }; + let num_frames = parts[5].parse().ok().or(max_frames); + + VideoInfo { + width, + height, + num_frames, + fps, + alpha, + codec_tag, + } +} @@ -1,10 +1,9 @@ use std::time::Duration; use ash::vk; -use texture_share_vk_base::vk_device::VkDevice; -use texture_share_vk_base::vk_entry::VkEntry; -use texture_share_vk_base::vk_instance::VkInstance; -use texture_share_vk_base::vk_setup::VkSetup; +use texture_share_vk_client::base::{ + vk_device::VkDevice, vk_entry::VkEntry, vk_instance::VkInstance, vk_setup::VkSetup, +}; use texture_share_vk_client::VkClient; const TSV_SERVER_EXECUTABLE: &str = "/usr/bin/texture-share-vk-server"; @@ -121,12 +120,11 @@ pub fn create_tsv_client(device: &wgpu::Device) -> VkClient { .expect("connect to texture-share-vk server") } -/// Image format for texture-share-vk, matching RGBA8. -pub const TSV_FORMAT: texture_share_vk_base::ipc::platform::img_data::ImgFormat = - texture_share_vk_base::ipc::platform::img_data::ImgFormat::R8G8B8A8; - /// Re-export ImgFormat and ImgType for use in binaries. -pub use texture_share_vk_base::ipc::platform::img_data::{ImgFormat, ImgType}; +pub use texture_share_vk_client::base::ipc::platform::img_data::{ImgFormat, ImgType}; + +/// Image format for texture-share-vk, matching RGBA8. +pub const TSV_FORMAT: ImgFormat = ImgFormat::R8G8B8A8; /// Convert a TSV ImgFormat to a wgpu TextureFormat. pub fn img_format_to_wgpu(fmt: ImgFormat) -> wgpu::TextureFormat { @@ -261,6 +259,14 @@ pub fn refresh_textures( needs_rebind } +pub fn img_type_to_wgpu(image_type: ImgType, depth_or_array_layers: u32) -> wgpu::TextureDimension { + match image_type { + ImgType::D3 => wgpu::TextureDimension::D3, + ImgType::D2 if depth_or_array_layers > 1 => wgpu::TextureDimension::D3, + ImgType::D2 => wgpu::TextureDimension::D2, + } +} + fn img_type_to_view_dimension( image_type: ImgType, depth_or_array_layers: u32, @@ -1,3 +1,4 @@ +pub mod ffmpeg; pub mod gpu; pub mod hap; pub mod osc; diff --git a/src/uniform.rs b/src/uniform.rs index db01e0d..a428102 100644 --- a/src/uniform.rs +++ b/src/uniform.rs @@ -919,6 +919,7 @@ impl UniformCache { .ok_or(BindError::NotFound(slot_name.to_string()))?; slot.resource_id = Some(resource_id.to_string()); self.build_bind_group(device); + log::info!("bound texture {resource_id} to slot {slot_name}"); Ok(()) } @@ -938,6 +939,7 @@ impl UniformCache { .ok_or(BindError::NotFound(slot_name.to_string()))?; slot.resource_id = Some(resource_id.to_string()); self.build_bind_group(device); + log::info!("bound sampler {resource_id} to slot {slot_name}"); Ok(()) } diff --git a/src/wesl.rs b/src/wesl.rs index 446c915..aaaf97d 100644 --- a/src/wesl.rs +++ b/src/wesl.rs @@ -34,12 +34,6 @@ impl ShaderSources { } pub fn compile(&mut self, root_module: &ModulePath) -> Result<String, wesl::Error> { - log::info!("--- all modules ---"); - for (path, text) in self.virtual_resolver.modules() { - log::info!("==== {path}:\n{text}"); - } - log::info!("--- end modules ---"); - Wesl::new_barebones() .set_custom_resolver(&*self) .set_custom_mangler(self.mangler.clone()) |
