diff --git a/Cargo.lock b/Cargo.lock index c7bc2cb3..76fff5dc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -43,7 +43,7 @@ dependencies = [ "getrandom 0.3.4", "once_cell", "version_check", - "zerocopy", + "zerocopy 0.8.50", ] [[package]] @@ -312,6 +312,12 @@ dependencies = [ "zbus", ] +[[package]] +name = "assert_matches" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b34d609dfbaf33d6889b2b7106d3ca345eacad44200913df5ba02bfd31d2ba9" + [[package]] name = "async-broadcast" version = "0.7.2" @@ -489,6 +495,26 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "628d228f918ac3b82fe590352cc719d30664a0c13ca3a60266fe02c7132d480a" +[[package]] +name = "atomig" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd0f41f4bb89f5c6450325e283fb78c4a3d042181b54f3855ee2f872919f9863" +dependencies = [ + "atomig-macro", +] + +[[package]] +name = "atomig-macro" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49c98dba06b920588de7d63f6acc23f1e6a9fade5fd6198e564506334fb5a4f5" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + [[package]] name = "atty" version = "0.2.14" @@ -609,6 +635,19 @@ dependencies = [ "thiserror 1.0.69", ] +[[package]] +name = "av-data" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fca67ba5d317924c02180c576157afd54babe48a76ebc66ce6d34bb8ba08308e" +dependencies = [ + "byte-slice-cast", + "bytes", + "num-derive", + "num-rational", + "num-traits", +] + [[package]] name = "av-scenechange" version = "0.14.1" @@ -771,6 +810,12 @@ version = "3.20.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649" +[[package]] +name = "byte-slice-cast" +version = "1.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7575182f7272186991736b70173b0ea045398f984bf5ebbb3804736ce1330c9d" + [[package]] name = "bytemuck" version = "1.25.0" @@ -2222,7 +2267,7 @@ dependencies = [ "bytemuck", "cfg-if", "crunchy", - "zerocopy", + "zerocopy 0.8.50", ] [[package]] @@ -3237,6 +3282,16 @@ version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1d87ecb2933e8aeadb3e3a02b828fed80a7528047e68b4f424523a0981a3a084" +[[package]] +name = "nasm-rs" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "706bf8a5e8c8ddb99128c3291d31bd21f4bcde17f0f4c20ec678d85c74faa149" +dependencies = [ + "jobserver", + "log", +] + [[package]] name = "native-tls" version = "0.2.18" @@ -4201,7 +4256,7 @@ version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" dependencies = [ - "zerocopy", + "zerocopy 0.8.50", ] [[package]] @@ -4545,6 +4600,28 @@ version = "1.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "973443cf09a9c8656b574a866ab68dfa19f0867d0340648c7d2f6a71b8a8ea68" +[[package]] +name = "rav1d" +version = "1.1.0" +source = "git+https://github.com/memorysafety/rav1d.git?rev=2a734dc9cd52190146796fb24b29a8c7e3d2af1e#2a734dc9cd52190146796fb24b29a8c7e3d2af1e" +dependencies = [ + "assert_matches", + "atomig", + "av-data", + "bitflags 2.12.1", + "cc", + "cfg-if", + "libc", + "nasm-rs", + "parking_lot", + "paste", + "raw-cpuid", + "static_assertions", + "strum", + "to_method", + "zerocopy 0.7.35", +] + [[package]] name = "rav1e" version = "0.8.1" @@ -4595,6 +4672,15 @@ dependencies = [ "rgb", ] +[[package]] +name = "raw-cpuid" +version = "11.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "498cd0dc59d73224351ee52a95fee0f1a617a2eae0e7d9d720cc622c73a54186" +dependencies = [ + "bitflags 2.12.1", +] + [[package]] name = "raw-window-handle" version = "0.6.2" @@ -5802,6 +5888,12 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" +[[package]] +name = "to_method" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7c4ceeeca15c8384bbc3e011dbd8fccb7f068a440b752b7d9b32ceb0ca0e2e8" + [[package]] name = "tokio" version = "1.52.3" @@ -6640,6 +6732,7 @@ name = "wgui" version = "0.1.0" dependencies = [ "anyhow", + "bytes", "cosmic-text", "etagere", "flate2", @@ -6649,6 +6742,7 @@ dependencies = [ "lru", "ouroboros", "parking_lot", + "rav1d", "regex", "resvg", "roxmltree 0.21.1", @@ -7467,13 +7561,34 @@ version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6df3dc4292935e51816d896edcd52aa30bc297907c26167fec31e2b0c6a32524" +[[package]] +name = "zerocopy" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" +dependencies = [ + "byteorder", + "zerocopy-derive 0.7.35", +] + [[package]] name = "zerocopy" version = "0.8.50" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3b065d4f0e55f82fae73202e189638116a87c55ab6b8e6c2721e13dd9d854ad1" dependencies = [ - "zerocopy-derive", + "zerocopy-derive 0.8.50", +] + +[[package]] +name = "zerocopy-derive" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 1550b9d5..b24d9681 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,6 +14,7 @@ members = [ [workspace.dependencies] anyhow = "1.0.100" +bytes = { version = "1.11.1" } clap = { version = "4.5.53", features = ["derive"] } glam = { version = "0.30.9", features = ["mint", "serde"] } idmap = "0.2.2" @@ -26,16 +27,23 @@ serde_json = "1.0.145" slotmap = "1.1.1" smol = "2.0.2" strum = { version = "0.27.2", features = ["derive"] } -uuid = { version = "1.19.0", features = ["fast-rng", "v4", "serde"] } -vulkano = { version = "0.35.2", default-features = false, features = [ - "macros", -] } +uuid = { version = "1.19.0", features = ["fast-rng", "serde", "v4"] } +vulkano = { + version = "0.35.2", + default-features = false, + features = [ + "macros", + ] +} vulkano-shaders = "0.35.0" wayland-client = { version = "0.31.11" } xdg = "3.0.0" [patch.crates-io] -vulkano = { git = "https://github.com/galister/vulkano.git", rev = "cf7f92867928a56ce16b376037c1120f2b167678" } +vulkano = { + git = "https://github.com/galister/vulkano.git", + rev = "cf7f92867928a56ce16b376037c1120f2b167678" +} [profile.dev] opt-level = 1 diff --git a/dash-frontend/src/tab/settings/tab_features.rs b/dash-frontend/src/tab/settings/tab_features.rs index a69113ba..96d40fc3 100644 --- a/dash-frontend/src/tab/settings/tab_features.rs +++ b/dash-frontend/src/tab/settings/tab_features.rs @@ -1,6 +1,6 @@ use crate::tab::settings::{ SettingType, SettingsMountParams, SettingsTab, - macros::{options_category, options_checkbox, options_range_f32, options_slider_f32}, + macros::{options_category, options_checkbox, options_range_f32}, }; pub struct State {} diff --git a/uidev/assets/gui/video.xml b/uidev/assets/gui/video.xml new file mode 100644 index 00000000..622a8b54 --- /dev/null +++ b/uidev/assets/gui/video.xml @@ -0,0 +1,9 @@ + + + +
+
+
+
diff --git a/wayvr-ipc/Cargo.toml b/wayvr-ipc/Cargo.toml index bcdead92..bc3fee03 100644 --- a/wayvr-ipc/Cargo.toml +++ b/wayvr-ipc/Cargo.toml @@ -8,18 +8,18 @@ authors = ["galister", "oo8dev"] repository = "https://github.com/wlx-team/wayvr" [dependencies] -bytes = "1.11.1" -smallvec = "1.13.2" -serde.workspace = true anyhow = "1.0.93" +bytes.workspace = true log = "0.4.22" +serde.workspace = true +smallvec = "1.13.2" # client-only deps interprocess = { version = "2.2.2", features = ["tokio"], optional = true } +serde_json.workspace = true tokio = { version = "1.43.1", features = ["macros"], optional = true } tokio-util = { version = "0.7.13", optional = true } -serde_json.workspace = true [features] default = ["client"] -client = ["dep:tokio", "dep:tokio-util", "dep:interprocess"] +client = ["dep:interprocess", "dep:tokio", "dep:tokio-util"] diff --git a/wayvr/Cargo.toml b/wayvr/Cargo.toml index aedac498..d1ac12ba 100644 --- a/wayvr/Cargo.toml +++ b/wayvr/Cargo.toml @@ -43,28 +43,41 @@ vulkano-shaders.workspace = true xdg.workspace = true ash = "^0.38.0" # must match vulkano -bytes = { version = "1.11.1" } +bytes = { workspace = true } chrono = { version = "0.4.42", features = ["unstable-locales"] } chrono-tz = "0.10.4" config = "0.15.19" dbus = { version = "0.9.9" } futures = "0.3.31" -image_dds = { version = "0.7.2", default-features = false, features = [ - "ddsfile", -] } +image_dds = { + version = "0.7.2", + default-features = false, + features = [ + "ddsfile", + ] +} input-linux = "0.7.1" interprocess = { version = "2.2.3" } json = { version = "0.12.4", optional = true } json5 = "1.3.0" libc = "0.2.178" -libmonado = { git = "https://github.com/wayvr-org/libmonado-rs.git", rev = "6f66b26930c24a8a2fc57ddcd85704784894c750", optional = true } +libmonado = { + git = "https://github.com/wayvr-org/libmonado-rs.git", + rev = "6f66b26930c24a8a2fc57ddcd85704784894c750", + optional = true +} log-panics = { version = "2.1.0", features = ["with-backtrace"] } mint = "0.5.9" openxr = { version = "0.21.0", features = ["linked", "mint"], optional = true } -ovr_overlay = { git = "https://github.com/galister/ovr_overlay_oyasumi", rev = "e477bd2a9e04293ea68c1e7529ef2cb131f32acc", features = [ - "ovr_input", - "ovr_system", -], optional = true } +ovr_overlay = { + git = "https://github.com/galister/ovr_overlay_oyasumi", + rev = "e477bd2a9e04293ea68c1e7529ef2cb131f32acc", + features = [ + "ovr_input", + "ovr_system", + ], + optional = true +} prost = { version = "0.14.3", optional = true } pure-rust-locales = "0.8.2" rosc = { version = "0.11.4", optional = true } @@ -72,12 +85,16 @@ serde_json5 = "0.2.1" serde_yaml = "0.9.34" signal-hook = "0.3.18" smallvec = "1.15.1" -smithay = { version = "0.7.0", default-features = false, features = [ - "backend_vulkan", - "desktop", - "wayland_frontend", - "xwayland", -] } +smithay = { + version = "0.7.0", + default-features = false, + features = [ + "backend_vulkan", + "desktop", + "wayland_frontend", + "xwayland", + ] +} smol = { workspace = true } sysinfo = { version = "0.37" } thiserror = "2.0" @@ -86,10 +103,16 @@ tracing-subscriber = { version = "0.3.22", features = ["env-filter"] } uuid = { workspace = true } wayland-client = { workspace = true } winit = { version = "0.30.12", optional = true } -xcb = { version = "1.6.0", features = [ - "as-raw-xcb-connection", -], optional = true } -xkbcommon = { version = "0.8.0" } # 0.9.0 breaks keymap import on some distros +xcb = { + version = "1.6.0", + features = [ + "as-raw-xcb-connection", + ], + optional = true +} +xkbcommon = { + version = "0.8.0" +} # 0.9.0 breaks keymap import on some distros [build-dependencies] regex.workspace = true diff --git a/wgui/Cargo.toml b/wgui/Cargo.toml index 3b6ab1f8..b67fab4f 100644 --- a/wgui/Cargo.toml +++ b/wgui/Cargo.toml @@ -11,14 +11,19 @@ repository = "https://github.com/wlx-team/wayvr" anyhow.workspace = true cosmic-text = "0.15.0" etagere = "0.2.15" +flate2 = "1.1.5" glam.workspace = true -image = { version = "0.25.9", default-features = false, features = [ - "gif", - "jpeg", - "png", - "rayon", - "webp", -] } +image = { + version = "0.25.9", + default-features = false, + features = [ + "gif", + "jpeg", + "png", + "rayon", + "webp", + ] +} log.workspace = true lru = "0.16.2" ouroboros = "0.18.5" @@ -26,6 +31,7 @@ parking_lot = "0.12.5" regex.workspace = true resvg = { version = "0.45.1", default-features = false } roxmltree = "0.21.1" +rust-embed.workspace = true rustc-hash = "2.1.1" serde_json.workspace = true slotmap.workspace = true @@ -33,5 +39,17 @@ smallvec = "1.15.1" taffy = "0.9.2" vulkano.workspace = true vulkano-shaders.workspace = true -rust-embed.workspace = true -flate2 = "1.1.5" + +# `video` wgui feature +bytes = { workspace = true, optional = true } +rav1d = { + git = "https://github.com/memorysafety/rav1d.git", + rev = "2a734dc9cd52190146796fb24b29a8c7e3d2af1e", + default-features = false, + features = ["bitdepth_8"], + optional = true +} + +[features] +default = ["video"] +video = ["dep:bytes", "dep:rav1d"] diff --git a/wgui/src/components/mod.rs b/wgui/src/components/mod.rs index d91e7d71..bc00f366 100644 --- a/wgui/src/components/mod.rs +++ b/wgui/src/components/mod.rs @@ -18,6 +18,9 @@ pub mod slider; pub mod tabs; pub mod tooltip; +#[cfg(feature = "video")] +pub mod video; + pub struct RefreshData<'a> { pub layout: &'a mut Layout, } diff --git a/wgui/src/components/video.rs b/wgui/src/components/video.rs new file mode 100644 index 00000000..5d994c41 --- /dev/null +++ b/wgui/src/components/video.rs @@ -0,0 +1,75 @@ +use crate::{ + assets::AssetPath, + components::{Component, ComponentBase, ComponentTrait, RefreshData}, + drawing::Color, + layout::{WidgetID, WidgetPair}, + widget::{ + ConstructEssentials, + rectangle::{WidgetRectangle, WidgetRectangleParams}, + }, +}; +use std::{cell::RefCell, rc::Rc}; + +#[derive(Default)] +pub struct Params<'a> { + pub style: taffy::Style, + pub src: Option>, +} + +struct State {} + +struct Data { + #[allow(dead_code)] + id_container: WidgetID, +} + +#[allow(dead_code)] +pub struct ComponentVideo { + base: ComponentBase, + data: Rc, + state: Rc>, +} + +impl ComponentTrait for ComponentVideo { + fn base(&self) -> &ComponentBase { + &self.base + } + + fn base_mut(&mut self) -> &mut ComponentBase { + &mut self.base + } + + fn refresh(&self, _data: &mut RefreshData) { + // nothing to do + } +} + +impl ComponentVideo {} + +pub fn construct(ess: &mut ConstructEssentials, params: Params) -> anyhow::Result<(WidgetPair, Rc)> { + let style = params.style; + + let (root, _) = ess.layout.add_child( + ess.parent, + WidgetRectangle::create(WidgetRectangleParams { + color: Color::new(0.1, 0.1, 0.1, 1.0), + ..Default::default() + }), + style, + )?; + + let id_container = root.id; + let data = Rc::new(Data { id_container }); + + let state = Rc::new(RefCell::new(State {})); + + let base = ComponentBase { + id: root.id, + lhandles: Default::default(), + }; + + let video = Rc::new(ComponentVideo { base, data, state }); + + ess.layout.defer_component_refresh(Component(video.clone())); + Ok((root, video)) +} diff --git a/wgui/src/lib.rs b/wgui/src/lib.rs index d913f0bf..6711b1e5 100644 --- a/wgui/src/lib.rs +++ b/wgui/src/lib.rs @@ -44,6 +44,9 @@ pub mod theme; pub mod widget; pub mod windowing; +#[cfg(feature = "video")] +pub mod video_dec; + // re-exported libs pub use cosmic_text; pub use taffy; diff --git a/wgui/src/parser/component_video.rs b/wgui/src/parser/component_video.rs new file mode 100644 index 00000000..3908aa37 --- /dev/null +++ b/wgui/src/parser/component_video.rs @@ -0,0 +1,51 @@ +use crate::{ + assets::AssetPath, + components::{self, Component}, + layout::WidgetID, + parser::{ + AttribPair, ParserContext, ParserFile, get_asset_path_from_kv, + helpers::{TooltipAttribs, parse_attrib_tooltip}, + parse_children, process_component, + style::parse_style, + }, +}; + +pub fn parse_component_video<'a>( + file: &'a ParserFile, + ctx: &mut ParserContext, + node: roxmltree::Node<'a, 'a>, + parent_id: WidgetID, + attribs: &[AttribPair], + tag_name: &str, +) -> anyhow::Result { + let mut tooltip = TooltipAttribs::default(); + let mut src: Option = None; + + let style = parse_style(ctx, attribs, tag_name); + + for pair in attribs { + let (key, value) = (pair.attrib.as_ref(), pair.value.as_ref()); + match key { + "src" | "src_ext" | "src_builtin" | "src_internal" => { + let asset_path = get_asset_path_from_kv("", key, value); + + if !value.is_empty() { + src = Some(asset_path); + } + } + _ => { + parse_attrib_tooltip(ctx, tag_name, pair, &mut tooltip); + } + } + } + + let (widget, video) = components::video::construct( + &mut ctx.get_construct_essentials(parent_id), + components::video::Params { style, src }, + )?; + + process_component(ctx, Component(video), widget.id, attribs); + parse_children(file, ctx, node, widget.id)?; + + Ok(widget.id) +} diff --git a/wgui/src/parser/mod.rs b/wgui/src/parser/mod.rs index a9510ae0..f17fc256 100644 --- a/wgui/src/parser/mod.rs +++ b/wgui/src/parser/mod.rs @@ -6,6 +6,10 @@ mod component_editbox; mod component_radio_group; mod component_slider; mod component_tabs; + +#[cfg(feature = "video")] +mod component_video; + mod helpers; mod style; mod widget_div; @@ -1084,6 +1088,14 @@ fn parse_child<'a>( file, ctx, child_node, parent_id, &attribs, tag_name, )?); } + #[cfg(feature = "video")] + "Video" => { + use crate::parser::component_video::parse_component_video; + + new_widget_id = Some(parse_component_video( + file, ctx, child_node, parent_id, &attribs, tag_name, + )?); + } "Slider" => { new_widget_id = Some(parse_component_slider(ctx, parent_id, &attribs, tag_name)?); } diff --git a/wgui/src/video_dec.rs b/wgui/src/video_dec.rs new file mode 100644 index 00000000..42adf5e9 --- /dev/null +++ b/wgui/src/video_dec.rs @@ -0,0 +1,207 @@ +use bytes::{Buf, Bytes}; +use rav1d::include::dav1d::dav1d::{Dav1dContext, Dav1dSettings}; + +struct IvfReader { + file_data: Vec, + reader: Bytes, // uses &file_data ptr + pub cur_frame: u32, + pub num_frames: u32, + pub framerate: f32, + pub width: u16, + pub height: u16, +} + +// Duck IVF video container. Documentation: +// https://wiki.multimedia.cx/index.php/Duck_IVF +// Yes, it's just as simple as that. +// +// Header: +// bytes 0-3 signature: 'DKIF' +// bytes 4-5 version (should be 0) +// bytes 6-7 length of header in bytes +// bytes 8-11 codec FourCC (e.g., 'VP80') +// bytes 12-13 width in pixels +// bytes 14-15 height in pixels +// bytes 16-19 time base denominator +// bytes 20-23 time base numerator +// bytes 24-27 number of `Frame`s in file +// bytes 28-31 unused +// +// Frame: +// bytes 0-3 size of frame in bytes (not including the 12-byte header) +// bytes 4-11 64-bit presentation timestamp +// bytes 12.. frame data + +const IVF_MAGIC: [u8; 4] = [0x44, 0x4B, 0x49, 0x46]; + +impl IvfReader { + pub fn new(file_data: Vec) -> anyhow::Result { + // safety: both reader and file_data are located in the same struct + // file_data won't move at all. + let mut reader = Bytes::from_static(unsafe { std::mem::transmute::<&[u8], &'static [u8]>(&file_data) }); + + // read ivf magic + let mut header_magic: [u8; 4] = [0; 4]; + reader.try_copy_to_slice(&mut header_magic)?; + + if header_magic != IVF_MAGIC { + anyhow::bail!("invalid magic"); + } + + let header_version = reader.try_get_u16_le()?; + + if header_version != 0 { + anyhow::bail!("unsupported version"); // there's no other version than 0 + } + + let header_len = reader.try_get_u16_le()?; + if header_len != 32 { + anyhow::bail!("header length mismatching"); + } + + let mut header_fourcc: [u8; 4] = [0; 4]; + reader.try_copy_to_slice(&mut header_fourcc)?; + + let header_width = reader.try_get_u16_le()?; + let header_height = reader.try_get_u16_le()?; + let header_timebase_den = reader.try_get_u32_le()?; + let header_timebase_num = reader.try_get_u32_le()?; + let header_num_frames = reader.try_get_u32_le()?; + + let framerate = header_timebase_den as f32 / header_timebase_num as f32; + + let mut padding: [u8; 4] = [0; 4]; + reader.try_copy_to_slice(&mut padding)?; + + log::info!("IvfReader: width {header_width}, height {header_height}, framerate {framerate}"); + + Ok(IvfReader { + file_data, + reader, + cur_frame: 0, + width: header_width, + height: header_height, + framerate, + num_frames: header_num_frames, + }) + } + + // Read demuxed video packet to a chunk + pub fn read_frame(&mut self) -> anyhow::Result<&[u8]> { + let frame_size = self.reader.try_get_u32_le()? as usize; + let _frame_pts = self.reader.try_get_u64_le()?; + + if frame_size > 8 * 1024 * 1024 { + // something went really wrong + anyhow::bail!("Invalid frame size"); + } + + // SAFETY: reader.chunk() slice lifetime is the same as Self, no risk here. + let chunk_a /* 'a */ = unsafe /* it's safe */ { + let Some(chunk) = self.reader.chunk().get(0..(frame_size)) else { + anyhow::bail!("chunk read error"); + }; + std::mem::transmute::<&[u8], &'static [u8]>(chunk) + }; + + self.reader.advance(frame_size); + + self.cur_frame += 1; + + Ok(chunk_a) + } +} + +pub struct RgbFrame { + width: u16, + height: u16, + data: Vec, +} + +pub struct YuvFrame { + width: u16, + height: u16, + stride_luma: u32, + stride_chroma: u32, + data_y: Vec, + data_u: Vec, + data_v: Vec, +} + +fn clamp_u8(v: i32) -> u8 { + v.max(0).min(255) as u8 +} + +fn yuv_to_rgb(y: u8, u: u8, v: u8) -> (u8, u8, u8) { + let y_i = y as i32; + let u_i = u as i32 - 128; + let v_i = v as i32 - 128; + let c = (1192 * (y_i - 16)).max(0); + let out_r = (c + 1634 * v_i) >> 10; + let out_g = (c - 401 * u_i - 834 * v_i) >> 10; + let out_b = (c + 2066 * u_i) >> 10; + (clamp_u8(out_r), clamp_u8(out_g), clamp_u8(out_b)) +} + +impl YuvFrame { + // Simple, temporary, best-effort yuv420->rgb converter. Not performant at all, but at least it works. + // Temporary till we get native YUV support in shaders (as 3 vulkan textures). + // SAFETY: this function expects YUV420 data only. + fn to_rgb(&self) -> RgbFrame { + unsafe { + let channels = 3; // rgb + + let rgb_size = self.width as usize * self.height as usize * channels; + let mut rgb = Vec::::with_capacity(rgb_size); + rgb.set_len(rgb_size); + + let ptr_rgb = rgb.as_mut_ptr(); + let ptr_y = self.data_y.as_ptr(); + let ptr_u = self.data_u.as_ptr(); + let ptr_v = self.data_v.as_ptr(); + + let stride_luma = self.stride_luma as usize; + let stride_chroma = self.stride_chroma as usize; + let width = self.width as usize; + let height = self.height as usize; + + for y in 0..height { + for x in 0..width { + let val_y = *ptr_y.offset((y * stride_luma + x) as isize); + let val_u = *ptr_u.offset(((y / 2) * stride_chroma + (x / 2)) as isize); + let val_v = *ptr_v.offset(((y / 2) * stride_chroma + (x / 2)) as isize); + let (r, g, b) = yuv_to_rgb(val_y, val_u, val_v); + + let pos = y * (width * channels) + x * channels; + *ptr_rgb.offset((pos + 0) as isize) = r; + *ptr_rgb.offset((pos + 1) as isize) = g; + *ptr_rgb.offset((pos + 2) as isize) = b; + } + } + + RgbFrame { + width: self.width, + height: self.height, + data: rgb, + } + } + } +} + +pub struct Av1Decoder {} + +impl Av1Decoder { + pub fn new() -> Self { + Self {} + } + + pub fn read_frame(&mut self, reader: &mut IvfReader) -> anyhow::Result { + let packet = reader.read_frame()?; + + let mut decoder = rav1d::Decoder::new()?; + + todo!(); + + // rav1d::Ok(RgbFrame {}) + } +}