diff --git a/src/ui/about_dialog.rs b/src/ui/about_dialog.rs index a2f855a..4e10574 100644 --- a/src/ui/about_dialog.rs +++ b/src/ui/about_dialog.rs @@ -5,7 +5,7 @@ use crate::{ }, device_prober::PhysicalXRDevice, linux_distro::LinuxDistro, - vulkaninfo::gpu_names, + vulkaninfo::VulkanInfo, xdg::XDG, }; use relm4::prelude::*; @@ -25,7 +25,7 @@ pub fn create_about_dialog() -> adw::AboutDialog { .build() } -pub fn populate_debug_info(dialog: &adw::AboutDialog) { +pub fn populate_debug_info(dialog: &adw::AboutDialog, vkinfo: Option<&VulkanInfo>) { if dialog.debug_info().len() > 0 { return; } @@ -58,9 +58,14 @@ pub fn populate_debug_info(dialog: &adw::AboutDialog) { ), format!( "GPUs: {}", - unsafe { gpu_names() } - .ok() - .map(|names| names.join(", ")) + vkinfo + .map(|i| i.gpu_names.join(", ")) + .unwrap_or("unknown".into()) + ), + format!( + "Monado Vulkan Layers: {}", + vkinfo + .map(|i| i.has_monado_vulkan_layers.to_string()) .unwrap_or("unknown".into()) ), format!("Detected XR Devices: {}", { diff --git a/src/ui/app.rs b/src/ui/app.rs index 674027e..d02b8f3 100644 --- a/src/ui/app.rs +++ b/src/ui/app.rs @@ -47,6 +47,7 @@ use crate::{ restore_runtime_entrypoint, set_runtime_entrypoint_launch_opts_from_profile, }, util::file_utils::{setcap_cap_sys_nice_eip, setcap_cap_sys_nice_eip_cmd}, + vulkaninfo::VulkanInfo, xr_devices::XRDevice, }; use adw::{prelude::*, ResponseAppearance}; @@ -86,6 +87,7 @@ pub struct App { configure_wivrn_action: gtk::gio::SimpleAction, openxr_prober_worker: Option, xrservice_ready: bool, + vkinfo: Option, } #[derive(Debug)] @@ -861,6 +863,16 @@ impl AsyncComponent for App { }; let selected_profile = config.get_selected_profile(&profiles); + let vkinfo = { + match VulkanInfo::get() { + Ok(info) => Some(info), + Err(e) => { + eprintln!("Failed to get Vulkan info: {e:#?}"); + None + } + } + }; + let mut model = App { application: init.application, app_win: root.clone(), @@ -870,6 +882,7 @@ impl AsyncComponent for App { config: config.clone(), selected_profile: selected_profile.clone(), root_win: root.clone().into(), + vkinfo: vkinfo.clone(), }) .forward(sender.input_sender(), |message| match message { MainViewOutMsg::DoStartStopXRService => Msg::DoStartStopXRService, @@ -879,6 +892,7 @@ impl AsyncComponent for App { MainViewOutMsg::SaveProfile(p) => Msg::SaveProfile(p), MainViewOutMsg::OpenLibsurviveSetup => Msg::OpenLibsurviveSetup, }), + vkinfo, debug_view: DebugView::builder() .launch(DebugViewInit { profile: selected_profile, @@ -926,8 +940,10 @@ impl AsyncComponent for App { model.about_dialog, #[strong(rename_to = app_win)] model.app_win, + #[strong(rename_to = vkinfo)] + model.vkinfo, move |_| { - populate_debug_info(&about_dialog); + populate_debug_info(&about_dialog, vkinfo.as_ref()); about_dialog.present(Some(&app_win)); } ) diff --git a/src/ui/main_view.rs b/src/ui/main_view.rs index cf66649..a152b5c 100644 --- a/src/ui/main_view.rs +++ b/src/ui/main_view.rs @@ -19,8 +19,11 @@ use crate::{ paths::{get_data_dir, get_home_dir}, profile::{LighthouseDriver, Profile, XRServiceType}, stateless_action, - util::file_utils::{get_writer, mount_has_nosuid}, - util::steamvr_utils::chaperone_info_exists, + util::{ + file_utils::{get_writer, mount_has_nosuid}, + steamvr_utils::chaperone_info_exists, + }, + vulkaninfo::VulkanInfo, xr_devices::XRDevice, }; use adw::{prelude::*, ResponseAppearance}; @@ -65,6 +68,8 @@ pub struct MainView { #[tracker::do_not_track] profile_export_action: gtk::gio::SimpleAction, xrservice_ready: bool, + #[tracker::do_not_track] + vkinfo: Option, } #[derive(Debug)] @@ -104,6 +109,7 @@ pub struct MainViewInit { pub config: Config, pub selected_profile: Profile, pub root_win: gtk::Window, + pub vkinfo: Option, } impl MainView { @@ -328,6 +334,34 @@ impl SimpleComponent for MainView { set_wrap_mode: gtk::pango::WrapMode::Word, } }, + gtk::Box { + set_orientation: gtk::Orientation::Vertical, + set_hexpand: true, + set_vexpand: false, + set_spacing: 12, + add_css_class: "card", + add_css_class: "padded", + set_visible: model + .vkinfo + .as_ref() + .is_some_and( + |i| i.has_nvidia_gpu && !i.has_monado_vulkan_layers + ), + warning_heading(), + gtk::Label { + set_label: concat!( + "An Nvidia GPU has been detected, but it ", + "seems you don't have the Monado Vulkan Layers ", + "installed on your system.\n\nInstall the ", + "Monado Vulkan Layers or your XR session will ", + "crash." + ), + add_css_class: "warning", + set_xalign: 0.0, + set_wrap: true, + set_wrap_mode: gtk::pango::WrapMode::Word, + } + }, gtk::Box { set_orientation: gtk::Orientation::Vertical, set_hexpand: true, @@ -812,6 +846,7 @@ impl SimpleComponent for MainView { xrservice_ready: false, profile_delete_action, profile_export_action, + vkinfo: init.vkinfo, tracker: 0, }; let widgets = view_output!(); diff --git a/src/vulkaninfo.rs b/src/vulkaninfo.rs index 15b820a..a8f8110 100644 --- a/src/vulkaninfo.rs +++ b/src/vulkaninfo.rs @@ -3,27 +3,62 @@ use ash::{ Entry, }; -/// # Safety -/// -/// Dlopens the vulkan library, so this is inherently unsafe. Should be fine in -/// most circumstances -pub unsafe fn gpu_names() -> anyhow::Result> { - let entry = Entry::load()?; - let instance = entry.create_instance( - &InstanceCreateInfo::default().application_info(&ApplicationInfo::default()), - None, - )?; - let names = instance - .enumerate_physical_devices()? - .into_iter() - .filter_map(|d| { - instance - .get_physical_device_properties(d) - .device_name_as_c_str() - .ok() - .map(|cs| cs.to_string_lossy().to_string()) - }) - .collect(); - instance.destroy_instance(None); - Ok(names) +#[derive(Debug, Clone)] +pub struct VulkanInfo { + pub has_nvidia_gpu: bool, + pub has_monado_vulkan_layers: bool, + pub gpu_names: Vec, +} + +const NVIDIA_VENDOR_ID: u32 = 0x10de; + +impl VulkanInfo { + /// # Safety + /// + /// Dlopens the vulkan library, so this is inherently unsafe. Should be fine in + /// most circumstances + pub fn get() -> anyhow::Result { + let entry = unsafe { Entry::load() }?; + let instance = unsafe { + entry.create_instance( + &InstanceCreateInfo::default().application_info(&ApplicationInfo::default()), + None, + ) + }?; + let mut has_nvidia_gpu = false; + let mut has_monado_vulkan_layers = false; + let gpu_names = unsafe { instance.enumerate_physical_devices() }? + .into_iter() + .filter_map(|d| { + let props = unsafe { instance.get_physical_device_properties(d) }; + if props.vendor_id == NVIDIA_VENDOR_ID { + has_nvidia_gpu = true; + } + if !has_monado_vulkan_layers { + has_monado_vulkan_layers = + unsafe { instance.enumerate_device_layer_properties(d) } + .ok() + .map(|layerprops| { + layerprops.iter().any(|lp| { + lp.layer_name_as_c_str().is_ok_and(|name| { + name.to_string_lossy() + == "VK_LAYER_MND_enable_timeline_semaphore" + }) + }) + }) + == Some(true); + } + props + .device_name_as_c_str() + .ok() + .map(|cs| cs.to_string_lossy().to_string()) + }) + .collect(); + unsafe { instance.destroy_instance(None) }; + Ok(Self { + gpu_names, + has_nvidia_gpu, + has_monado_vulkan_layers, + }) + } }