diff --git a/Cargo.lock b/Cargo.lock index 6304733..30afec4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -744,6 +744,15 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" +[[package]] +name = "cgl" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ced0551234e87afee12411d535648dd89d2e7f34c78b753395567aff3d447ff" +dependencies = [ + "libc", +] + [[package]] name = "chacha20" version = "0.10.0" @@ -1048,6 +1057,9 @@ dependencies = [ "egui-wgpu", "egui-winit", "egui_glow", + "glow 0.14.2", + "glutin", + "glutin-winit", "image", "js-sys", "log", @@ -1489,6 +1501,63 @@ dependencies = [ "web-sys", ] +[[package]] +name = "glutin" +version = "0.32.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12124de845cacfebedff80e877bb37b5b75c34c5a4c89e47e1cdd67fb6041325" +dependencies = [ + "bitflags 2.11.1", + "cfg_aliases 0.2.1", + "cgl", + "dispatch2", + "glutin_egl_sys", + "glutin_glx_sys", + "glutin_wgl_sys", + "libloading", + "objc2 0.6.4", + "objc2-app-kit 0.3.2", + "objc2-core-foundation", + "objc2-foundation 0.3.2", + "once_cell", + "raw-window-handle", + "wayland-sys", + "windows-sys 0.52.0", + "x11-dl", +] + +[[package]] +name = "glutin-winit" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85edca7075f8fc728f28cb8fbb111a96c3b89e930574369e3e9c27eb75d3788f" +dependencies = [ + "cfg_aliases 0.2.1", + "glutin", + "raw-window-handle", + "winit", +] + +[[package]] +name = "glutin_egl_sys" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c4680ba6195f424febdc3ba46e7a42a0e58743f2edb115297b86d7f8ecc02d2" +dependencies = [ + "gl_generator", + "windows-sys 0.52.0", +] + +[[package]] +name = "glutin_glx_sys" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a7bb2938045a88b612499fbcba375a77198e01306f52272e692f8c1f3751185" +dependencies = [ + "gl_generator", + "x11-dl", +] + [[package]] name = "glutin_wgl_sys" version = "0.6.1" @@ -2366,6 +2435,7 @@ dependencies = [ "bitflags 2.11.1", "block2 0.6.2", "objc2 0.6.4", + "objc2-core-foundation", "objc2-core-graphics", "objc2-foundation 0.3.2", ] diff --git a/Cargo.toml b/Cargo.toml index c80c32e..2001232 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,12 +10,13 @@ authors = ["InannaBeloved "] readme = "README.md" [dependencies] -# Use the wgpu (Vulkan/Metal/DX) renderer instead of the default glow +# Default to the wgpu (Vulkan/Metal/DX) renderer instead of the default glow # (OpenGL) one. NVIDIA + Wayland crashes the GL surface on window resize # (Glutin EGL_BAD_ALLOC / OutOfMemory on maximize); wgpu's Vulkan path -# reconfigures the swapchain cleanly. We drop the `glow` default feature -# and keep the rest (accesskit, fonts, wayland, x11). -eframe = { version = "0.29", default-features = false, features = ["accesskit", "default_fonts", "wayland", "x11", "wgpu"] } +# reconfigures the swapchain cleanly. Keep glow compiled in as an escape +# hatch for machines where wgpu/Vulkan creates a window but never presents +# a frame (`YT_OFFLINE_RENDERER=glow` or `--renderer glow`). +eframe = { version = "0.29", default-features = false, features = ["accesskit", "default_fonts", "wayland", "x11", "wgpu", "glow"] } image = { version = "0.25", default-features = false, features = ["webp", "jpeg", "png"] } toml = "0.8" serde = { version = "1.0", features = ["derive"] } diff --git a/docs/src/troubleshooting.md b/docs/src/troubleshooting.md index df62613..1bc4b24 100644 --- a/docs/src/troubleshooting.md +++ b/docs/src/troubleshooting.md @@ -96,6 +96,27 @@ handles the resize cleanly. Make sure you have a working Vulkan driver (`vulkan-icd-loader` + your GPU's Vulkan package), which any desktop with working graphics already has. +## The desktop window opens but stays blank + +The desktop defaults to the wgpu/Vulkan renderer. On some systems with a +broken Vulkan stack, the OS can create the window while wgpu never +presents a frame. Try the OpenGL renderer: + +```bash +YT_OFFLINE_RENDERER=glow yt-offline +``` + +or: + +```bash +yt-offline --renderer glow +``` + +If that works, update/reinstall your Vulkan driver later and switch back +to the default renderer. On NVIDIA + Wayland, prefer the default wgpu +renderer when possible because OpenGL has historically crashed on window +maximize. + ## The web UI looks like an old version after an upgrade The SPA is served `Cache-Control: no-store`, so a hard reload diff --git a/src/main.rs b/src/main.rs index a7543aa..e26b3ea 100644 --- a/src/main.rs +++ b/src/main.rs @@ -36,6 +36,33 @@ mod vtt; mod web; mod ytdlp_bin; +fn renderer_from_name(requested: Option<&str>) -> eframe::Renderer { + match requested.map(|s| s.trim().to_ascii_lowercase()).as_deref() { + Some("glow" | "opengl" | "gl") => eframe::Renderer::Glow, + Some("wgpu" | "vulkan" | "vk") | None => eframe::Renderer::Wgpu, + Some(name) => { + eprintln!( + "Warning: unknown renderer '{name}' (expected 'wgpu' or 'glow'); using wgpu." + ); + eframe::Renderer::Wgpu + } + } +} + +fn renderer_from_args(args: &[String]) -> eframe::Renderer { + let requested = args + .iter() + .position(|a| a == "--renderer") + .and_then(|pos| args.get(pos + 1).map(String::as_str)); + + if requested.is_some() { + renderer_from_name(requested) + } else { + let env_renderer = std::env::var("YT_OFFLINE_RENDERER").ok(); + renderer_from_name(env_renderer.as_deref()) + } +} + fn main() -> eframe::Result<()> { let args: Vec = std::env::args().collect(); @@ -82,10 +109,11 @@ fn main() -> eframe::Result<()> { .with_inner_size([1280.0, 820.0]) .with_min_inner_size([800.0, 500.0]) .with_title("yt-offline"), - // Force the wgpu (Vulkan) renderer. The default glow/OpenGL path - // crashes on NVIDIA + Wayland when the window is maximized - // (Glutin EGL_BAD_ALLOC). wgpu reconfigures the swapchain cleanly. - renderer: eframe::Renderer::Wgpu, + // Default to wgpu (Vulkan): the glow/OpenGL path crashes on some + // NVIDIA + Wayland maximizes (Glutin EGL_BAD_ALLOC). Keep a launch + // escape hatch for systems where Vulkan/wgpu opens a blank window: + // `YT_OFFLINE_RENDERER=glow yt-offline` or `yt-offline --renderer glow`. + renderer: renderer_from_args(&args), ..Default::default() }; eframe::run_native( @@ -94,3 +122,41 @@ fn main() -> eframe::Result<()> { Box::new(|cc| Ok(Box::new(app::App::new(cc, tray)))), ) } + +#[cfg(test)] +mod tests { + #[test] + fn renderer_name_accepts_default_and_aliases() { + assert_eq!(super::renderer_from_name(None), eframe::Renderer::Wgpu); + assert_eq!( + super::renderer_from_name(Some("wgpu")), + eframe::Renderer::Wgpu + ); + assert_eq!( + super::renderer_from_name(Some("vulkan")), + eframe::Renderer::Wgpu + ); + assert_eq!( + super::renderer_from_name(Some("glow")), + eframe::Renderer::Glow + ); + assert_eq!( + super::renderer_from_name(Some("opengl")), + eframe::Renderer::Glow + ); + assert_eq!( + super::renderer_from_name(Some("unknown")), + eframe::Renderer::Wgpu + ); + } + + #[test] + fn renderer_arg_overrides_env_parser_path() { + let args = vec![ + "yt-offline".to_string(), + "--renderer".to_string(), + "gl".to_string(), + ]; + assert_eq!(super::renderer_from_args(&args), eframe::Renderer::Glow); + } +}