summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authors-ol <s+removethis@s-ol.nu>2026-05-04 21:45:39 +0000
committers-ol <s+removethis@s-ol.nu>2026-05-14 15:00:13 +0000
commit0bdd9ae4e181283ac1d21b52cce7b28c565d936f (patch)
tree141632d39cda63d9a605b182041006ada7031ec2 /src
parentswitch to WESL shaders (diff)
downloadwgsl-view-0bdd9ae4e181283ac1d21b52cce7b28c565d936f.tar.gz
wgsl-view-0bdd9ae4e181283ac1d21b52cce7b28c565d936f.zip
tsv-video-*: share code
Diffstat (limited to 'src')
-rw-r--r--src/bin/tsv_video_buffer.rs150
-rw-r--r--src/bin/tsv_video_stream.rs89
-rw-r--r--src/ffmpeg.rs150
-rw-r--r--src/gpu.rs24
-rw-r--r--src/lib.rs1
-rw-r--r--src/uniform.rs2
-rw-r--r--src/wesl.rs6
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,
+ }
+}
diff --git a/src/gpu.rs b/src/gpu.rs
index 88c37da..38c39f3 100644
--- a/src/gpu.rs
+++ b/src/gpu.rs
@@ -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,
diff --git a/src/lib.rs b/src/lib.rs
index 157a9b2..b711924 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -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())