diff --git a/Cargo.lock b/Cargo.lock index 9b591ec8..18302e7a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2855,8 +2855,7 @@ checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de" [[package]] name = "libmonado" version = "1.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0df3183760954d877b894d42f5c6954837d8a7d7bd5f042409c4175afeaf24ea" +source = "git+https://github.com/oo8dev/libmonado-rs.git?rev=fc39940a64dea2df080a0d2c974c7d651006241f#fc39940a64dea2df080a0d2c974c7d651006241f" dependencies = [ "dlopen2", "flagset", @@ -3108,6 +3107,12 @@ dependencies = [ "pxfm", ] +[[package]] +name = "multimap" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d87ecb2933e8aeadb3e3a02b828fed80a7528047e68b4f424523a0981a3a084" + [[package]] name = "native-tls" version = "0.2.14" @@ -3905,6 +3910,17 @@ dependencies = [ "sha2", ] +[[package]] +name = "petgraph" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8701b58ea97060d5e5b155d383a69952a60943f0e6dfe30b04c287beb0b27455" +dependencies = [ + "fixedbitset", + "hashbrown 0.15.5", + "indexmap 2.12.1", +] + [[package]] name = "phf" version = "0.12.1" @@ -4167,6 +4183,64 @@ dependencies = [ "syn 2.0.113", ] +[[package]] +name = "prost" +version = "0.14.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2ea70524a2f82d518bce41317d0fae74151505651af45faf1ffbd6fd33f0568" +dependencies = [ + "bytes", + "prost-derive", +] + +[[package]] +name = "prost-build" +version = "0.14.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "343d3bd7056eda839b03204e68deff7d1b13aba7af2b2fd16890697274262ee7" +dependencies = [ + "heck 0.5.0", + "itertools 0.14.0", + "log", + "multimap", + "petgraph", + "prettyplease", + "prost", + "prost-types", + "regex", + "syn 2.0.113", + "tempfile", +] + +[[package]] +name = "prost-derive" +version = "0.14.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27c6023962132f4b30eb4c172c91ce92d933da334c59c23cddee82358ddafb0b" +dependencies = [ + "anyhow", + "itertools 0.14.0", + "proc-macro2", + "quote", + "syn 2.0.113", +] + +[[package]] +name = "prost-types" +version = "0.14.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8991c4cbdb8bc5b11f0b074ffe286c30e523de90fee5ba8132f1399f23cb3dd7" +dependencies = [ + "prost", +] + +[[package]] +name = "prost_build" +version = "0.1.0" +dependencies = [ + "prost-build", +] + [[package]] name = "pure-rust-locales" version = "0.8.2" @@ -6293,6 +6367,7 @@ dependencies = [ "mint", "openxr", "ovr_overlay", + "prost", "pure-rust-locales", "regex", "rosc", @@ -6305,6 +6380,7 @@ dependencies = [ "slotmap", "smallvec", "smithay", + "smol", "strum", "sysinfo", "thiserror 2.0.17", diff --git a/Cargo.toml b/Cargo.toml index e4200c1a..5488eca7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -30,6 +30,7 @@ members = [ "dash-frontend", "wayvr-ipc", "wayvrctl", + "scripts/prost_build", ] resolver = "3" @@ -38,6 +39,7 @@ vulkano = { git = "https://github.com/galister/vulkano.git", rev = "cf7f92867928 [workspace.dependencies] anyhow = "1.0.100" +smol = "2.0.2" glam = { version = "0.30.9", features = ["mint", "serde"] } clap = { version = "4.5.53", features = ["derive"] } xdg = "3.0.0" diff --git a/dash-frontend/Cargo.toml b/dash-frontend/Cargo.toml index eae5d91c..06f1b010 100644 --- a/dash-frontend/Cargo.toml +++ b/dash-frontend/Cargo.toml @@ -23,12 +23,12 @@ strum.workspace = true chrono = "0.4.42" keyvalues-parser = { git = "https://github.com/CosmicHorrorDev/vdf-rs.git", rev = "fc6dcbea9eb13cacb98dea40063f6f56cde6e145" } -smol = "2.0.2" +smol = { workspace = true } hyper = { version = "1.8.1", features = ["client", "http1", "http2"] } http-body-util = "0.1.3" async-native-tls = "0.5.0" smol-hyper = "0.1.1" [features] -default = ["monado" ] +default = ["monado"] monado = [] diff --git a/dash-frontend/src/frontend.rs b/dash-frontend/src/frontend.rs index b43c3b21..18f1d579 100644 --- a/dash-frontend/src/frontend.rs +++ b/dash-frontend/src/frontend.rs @@ -17,6 +17,7 @@ use wgui::{ windowing::window::{WguiWindow, WguiWindowParams, WguiWindowParamsExtra, WguiWindowPlacement}, }; use wlx_common::{ + async_executor::AsyncExecutor, audio, dash_interface::{BoxDashInterface, RecenterMode}, locale::WayVRLangProvider, @@ -29,7 +30,6 @@ use crate::{ util::{ popup_manager::{MountPopupParams, PopupManager, PopupManagerParams}, toast_manager::ToastManager, - various::AsyncExecutor, }, views, }; diff --git a/dash-frontend/src/util/cached_fetcher.rs b/dash-frontend/src/util/cached_fetcher.rs index d1a2ee33..5c7f84bc 100644 --- a/dash-frontend/src/util/cached_fetcher.rs +++ b/dash-frontend/src/util/cached_fetcher.rs @@ -1,8 +1,8 @@ use anyhow::Context; use serde::Deserialize; -use wlx_common::cache_dir; +use wlx_common::{async_executor::AsyncExecutor, cache_dir}; -use crate::util::{http_client, steam_utils::AppID, various::AsyncExecutor}; +use crate::util::{http_client, steam_utils::AppID}; pub struct CoverArt { // can be empty in case if data couldn't be fetched (use a fallback image then) diff --git a/dash-frontend/src/util/http_client.rs b/dash-frontend/src/util/http_client.rs index b6af702c..875d509e 100644 --- a/dash-frontend/src/util/http_client.rs +++ b/dash-frontend/src/util/http_client.rs @@ -13,8 +13,7 @@ use smol::{net::TcpStream, prelude::*}; use std::convert::TryInto; use std::pin::Pin; use std::task::{Context, Poll}; - -use crate::util::various::AsyncExecutor; +use wlx_common::async_executor::AsyncExecutor; pub struct HttpClientResponse { pub data: Vec, } diff --git a/dash-frontend/src/util/mod.rs b/dash-frontend/src/util/mod.rs index 67d18d3b..e5290607 100644 --- a/dash-frontend/src/util/mod.rs +++ b/dash-frontend/src/util/mod.rs @@ -4,5 +4,4 @@ pub mod pactl_wrapper; pub mod popup_manager; pub mod steam_utils; pub mod toast_manager; -pub mod various; pub mod wgui_simple; diff --git a/dash-frontend/src/views/game_cover.rs b/dash-frontend/src/views/game_cover.rs index 5221ea1d..6adc4110 100644 --- a/dash-frontend/src/views/game_cover.rs +++ b/dash-frontend/src/views/game_cover.rs @@ -25,11 +25,11 @@ use wgui::{ util::WLength, }, }; +use wlx_common::async_executor::AsyncExecutor; use crate::util::{ cached_fetcher::{self, CoverArt}, steam_utils::{self, AppID}, - various::AsyncExecutor, }; pub struct ViewCommon { diff --git a/dash-frontend/src/views/game_launcher.rs b/dash-frontend/src/views/game_launcher.rs index 2267e069..03d26d77 100644 --- a/dash-frontend/src/views/game_launcher.rs +++ b/dash-frontend/src/views/game_launcher.rs @@ -5,7 +5,6 @@ use crate::{ util::{ cached_fetcher::{self, CoverArt}, steam_utils::{self, AppID, AppManifest}, - various::AsyncExecutor, }, views::game_cover, }; @@ -19,6 +18,7 @@ use wgui::{ task::Tasks, widget::{ConstructEssentials, label::WidgetLabel}, }; +use wlx_common::async_executor::AsyncExecutor; #[derive(Clone)] enum Task { diff --git a/dash-frontend/src/views/game_list.rs b/dash-frontend/src/views/game_list.rs index 9946bdf6..5de3cf64 100644 --- a/dash-frontend/src/views/game_list.rs +++ b/dash-frontend/src/views/game_list.rs @@ -13,6 +13,7 @@ use wgui::{ label::{WidgetLabel, WidgetLabelParams}, }, }; +use wlx_common::async_executor::AsyncExecutor; use crate::{ frontend::{FrontendTask, FrontendTasks}, @@ -20,7 +21,6 @@ use crate::{ cached_fetcher::CoverArt, popup_manager::{MountPopupParams, PopupHandle}, steam_utils::{self, AppID, SteamUtils}, - various::AsyncExecutor, }, views::{self, game_cover, game_launcher}, }; diff --git a/scripts/prost_build/Cargo.toml b/scripts/prost_build/Cargo.toml new file mode 100644 index 00000000..f748fe75 --- /dev/null +++ b/scripts/prost_build/Cargo.toml @@ -0,0 +1,7 @@ +[package] +name = "prost_build" +version = "0.1.0" +edition = "2024" + +[dependencies] +prost-build = "0.14.3" diff --git a/scripts/prost_build/src/main.rs b/scripts/prost_build/src/main.rs new file mode 100644 index 00000000..12f50c39 --- /dev/null +++ b/scripts/prost_build/src/main.rs @@ -0,0 +1,5 @@ +use std::io::Result; +fn main() -> Result<()> { + prost_build::compile_protos(&["src/monado_metrics.proto"], &["src/"])?; + Ok(()) +} diff --git a/scripts/prost_build/src/monado_metrics.proto b/scripts/prost_build/src/monado_metrics.proto new file mode 100644 index 00000000..27682366 --- /dev/null +++ b/scripts/prost_build/src/monado_metrics.proto @@ -0,0 +1,174 @@ +syntax = "proto3"; + +package monado_metrics; + + +message Version +{ + uint32 major = 1; + uint32 minor = 2; +} + +message SessionFrame +{ + //! Which session this frame belongs to. + int64 session_id = 1; + + //! ID of frame. + int64 frame_id = 2; + + //! How long we thought the frame would take. + uint64 predicted_frame_time_ns = 3; + + //! When we predicted the app should wake up. + uint64 predicted_wake_up_time_ns = 4; + + //! When the client's GPU work should have completed. + uint64 predicted_gpu_done_time_ns = 5; + + //! When we predicted this frame to be shown. + uint64 predicted_display_time_ns = 6; + + //! The selected display period. + uint64 predicted_display_period_ns = 7; + + /*! + * When the app told us to display this frame, can be different + * then the predicted display time so we track that separately. + */ + uint64 display_time_ns = 8; + + //! When the predict call was made (inside of xrWaitFrame). + uint64 when_predicted_ns = 9; + + //! When the waiting thread was woken up. + uint64 when_wait_woke_ns = 10; + + //! When xrBeginFrame was called. + uint64 when_begin_ns = 11; + + //! When the layer information was delivered, (inside of xrEndFrame). + uint64 when_delivered_ns = 12; + + //! When the scheduled GPU work was completed. + uint64 when_gpu_done_ns = 13; + + //! Was this frame discarded. + bool discarded = 14; +} + +message Used +{ + //! Owning session of the frame. + int64 session_id = 1; + + //! Which session frame was used by the compositor. + int64 session_frame_id = 2; + + //! The system frame that the session was used by. + int64 system_frame_id = 3; + + //! When the frame was latched. + uint64 when_ns = 4; +} + +message SystemFrame +{ + //! ID of frame. + int64 frame_id = 1; + + //! Projected pixels to photon time. + uint64 predicted_display_time_ns = 2; + + //! Current period of displaying of frames. + uint64 predicted_display_period_ns = 3; + + //! When the compositor should hand pixels to display hardware. + uint64 desired_present_time_ns = 4; + + //! The time that the compositor should wake up. + uint64 wake_up_time_ns = 5; + uint64 present_slop_ns = 6; +} + +/*! + * Out of band info about a GPU timing from a system compositor. + */ +message SystemGpuInfo +{ + //! ID of frame. + int64 frame_id = 1; + + //! Start of GPU work. + uint64 gpu_start_ns = 2; + + //! End of GPU work. + uint64 gpu_end_ns = 3; + + //! When the information was gathered. + uint64 when_ns = 4; +} + +/*! + * Information that comes from the vulkan present timing. + */ +message SystemPresentInfo +{ + //! ID of frame. + int64 frame_id = 1; + + //! The total expected time for the compositor to complete a frame. + uint64 expected_comp_time_ns = 2; + + //! The time we predicted the compositor to wake up time. + uint64 predicted_wake_up_time_ns = 3; + + //! Predicted time for completion of the GPU work. + uint64 predicted_done_time_ns = 4; + + //! Predicted display time. + uint64 predicted_display_time_ns = 5; + + //! When was this frame predicted. + uint64 when_predict_ns = 6; + + //! When we last woke up the compositor after its equivalent of wait_frame. + uint64 when_woke_ns = 7; + + //! When the compositor started rendering a frame + uint64 when_began_ns = 8; + + //! When the compositor finished rendering a frame + uint64 when_submitted_ns = 9; + + //! When new frame timing info was last added. + uint64 when_infoed_ns = 10; + + //! When we wanted to start presenting. + uint64 desired_present_time_ns = 11; + + //! The slop used for this frame. + uint64 present_slop_ns = 12; + + //! Margin of GPU to present time. + uint64 present_margin_ns = 13; + + //! When the present time actually happened. + uint64 actual_present_time_ns = 14; + + //! The earliest we could have presented. + uint64 earliest_present_time_ns = 15; +} + + +message Record +{ + oneof record { + Version version = 1; + SessionFrame session_frame = 2; + Used used = 3; + SystemFrame system_frame = 4; + SystemGpuInfo system_gpu_info = 5; + SystemPresentInfo system_present_info = 6; + }; +} diff --git a/wayvr/Cargo.toml b/wayvr/Cargo.toml index 22c08795..916caa7c 100644 --- a/wayvr/Cargo.toml +++ b/wayvr/Cargo.toml @@ -58,7 +58,8 @@ interprocess = { version = "2.2.3" } json = { version = "0.12.4", optional = true } json5 = "1.3.0" libc = "0.2.178" -libmonado = { version = "1.6.0", optional = true } +smol = { workspace = true } +libmonado = { git = "https://github.com/oo8dev/libmonado-rs.git", rev = "fc39940a64dea2df080a0d2c974c7d651006241f", 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 } @@ -88,6 +89,7 @@ xcb = { version = "1.6.0", optional = true, features = [ "as-raw-xcb-connection", ] } xkbcommon = { version = "0.8.0" } # 0.9.0 breaks keymap import on some distros +prost = { version = "0.14.3", optional = true } [build-dependencies] regex.workspace = true @@ -95,7 +97,7 @@ regex.workspace = true [features] default = ["openvr", "openxr", "osc", "x11", "wayland"] openvr = ["dep:ovr_overlay", "dep:json"] -openxr = ["dep:openxr", "dep:libmonado"] +openxr = ["dep:openxr", "dep:libmonado", "dep:prost"] osc = ["dep:rosc"] x11 = ["dep:xcb", "wlx-capture/xshm", "xkbcommon/x11"] wayland = ["pipewire", "wlx-capture/wlr", "xkbcommon/wayland"] diff --git a/wayvr/src/backend/openxr/blocker.rs b/wayvr/src/backend/openxr/blocker.rs index 08c6497e..a99dd1f5 100644 --- a/wayvr/src/backend/openxr/blocker.rs +++ b/wayvr/src/backend/openxr/blocker.rs @@ -24,7 +24,7 @@ impl InputBlocker { } pub fn update(&mut self, app: &mut AppState) { - let Some(monado) = &mut app.monado else { + let Some(monado) = &mut app.monado_state else { return; // monado not available }; @@ -50,7 +50,7 @@ impl InputBlocker { } else { trace!("Unblocking input"); } - self.block_inputs(monado, should_block_inputs, should_block_poses); + self.block_inputs(&mut monado.ipc, should_block_inputs, should_block_poses); } self.inputs_blocked_last_frame = should_block_inputs; diff --git a/wayvr/src/backend/openxr/input.rs b/wayvr/src/backend/openxr/input.rs index 47306fac..f37c4673 100644 --- a/wayvr/src/backend/openxr/input.rs +++ b/wayvr/src/backend/openxr/input.rs @@ -272,7 +272,7 @@ impl OpenXrInputSource { } pub fn update_devices(app: &mut AppState) -> bool { - let Some(monado) = &mut app.monado else { + let Some(monado) = &mut app.monado_state else { return false; // monado not available }; @@ -297,7 +297,7 @@ impl OpenXrInputSource { let mut seen = Vec::::with_capacity(32); for (mnd_role, wlx_role) in roles { - let device = monado.device_from_role(mnd_role); + let device = monado.ipc.device_from_role(mnd_role); if let Ok(mut device) = device && !seen.contains(&device.index) { @@ -305,7 +305,7 @@ impl OpenXrInputSource { Self::update_device_battery_status(&mut device, wlx_role, &mut app.input_state); } } - if let Ok(devices) = monado.devices() { + if let Ok(devices) = monado.ipc.devices() { for mut device in devices { if !seen.contains(&device.index) { let role = if device.name_id >= 4 && device.name_id <= 8 { diff --git a/wayvr/src/backend/openxr/mod.rs b/wayvr/src/backend/openxr/mod.rs index 4d43cf94..8e7938f7 100644 --- a/wayvr/src/backend/openxr/mod.rs +++ b/wayvr/src/backend/openxr/mod.rs @@ -35,6 +35,7 @@ mod blocker; mod helpers; mod input; mod lines; +pub mod monado_state; mod overlay; mod playspace; mod skybox; @@ -85,15 +86,18 @@ pub fn openxr_run(show_by_default: bool, headless: bool) -> Result<(), BackendEr let mut delete_queue = vec![]; - app.monado_init(); + app.monado_state_init(); - let mut playspace = app.monado.as_mut().and_then(|m| { - playspace::PlayspaceMover::new(m) + let mut playspace = app.monado_state.as_mut().and_then(|m| { + playspace::PlayspaceMover::new(&mut m.ipc) .map_err(|e| log::warn!("Will not use Monado playspace mover: {e}")) .ok() }); - let mut blocker = app.monado.as_ref().map(blocker::InputBlocker::new); + let mut blocker = app + .monado_state + .as_ref() + .map(|m| blocker::InputBlocker::new(&m.ipc)); let (session, mut frame_wait, mut frame_stream) = unsafe { let raw_session = helpers::create_overlay_session( @@ -211,7 +215,7 @@ pub fn openxr_run(show_by_default: bool, headless: bool) -> Result<(), BackendEr } } - if app.monado.is_some() && next_device_update <= Instant::now() { + if app.monado_state.is_some() && next_device_update <= Instant::now() { let changed = OpenXrInputSource::update_devices(&mut app); if changed { overlays.devices_changed(&mut app)?; @@ -403,6 +407,10 @@ pub fn openxr_run(show_by_default: bool, headless: bool) -> Result<(), BackendEr futures.wait()?; // End rendering + if let Some(monado_state) = &mut app.monado_state { + monado_state.update(); + } + // Layer composition let mut layers = vec![]; if !main_session_visible && let Some(skybox) = skybox.as_mut() { @@ -499,8 +507,8 @@ pub fn openxr_run(show_by_default: bool, headless: bool) -> Result<(), BackendEr watch.config.active_state.as_mut().unwrap().transform = watch_transform; } // main_loop - if let (Some(blocker), Some(monado)) = (blocker, app.monado.as_mut()) { - blocker.unblock(monado); + if let (Some(blocker), Some(monado)) = (blocker, app.monado_state.as_mut()) { + blocker.unblock(&mut monado.ipc); } overlays.persist_layout(&mut app); diff --git a/wayvr/src/backend/openxr/monado_state.rs b/wayvr/src/backend/openxr/monado_state.rs new file mode 100644 index 00000000..293cb3d7 --- /dev/null +++ b/wayvr/src/backend/openxr/monado_state.rs @@ -0,0 +1,25 @@ +use crate::subsystem::monado_metrics::{self, metrics_fd::MonadoMetricsFd}; + +pub struct MonadoState { + pub ipc: libmonado::Monado, + pub metrics: Option, +} + +impl MonadoState { + pub fn new() -> anyhow::Result { + let mut ipc = libmonado::Monado::auto_connect().map_err(|s| anyhow::anyhow!("{s}"))?; + + let metrics = monado_metrics::metrics_fd::MonadoMetricsFd::new(&mut ipc)?; + + Ok(Self { + ipc, + metrics: Some(metrics), + }) + } + + pub fn update(&mut self) { + if let Some(metrics) = &mut self.metrics { + metrics.update(); + } + } +} diff --git a/wayvr/src/backend/openxr/playspace.rs b/wayvr/src/backend/openxr/playspace.rs index 24bd3c02..3f02d377 100644 --- a/wayvr/src/backend/openxr/playspace.rs +++ b/wayvr/src/backend/openxr/playspace.rs @@ -44,19 +44,19 @@ impl PlayspaceMover { } pub fn handle_task(&mut self, app: &mut AppState, task: PlayspaceTask) { - let Some(monado) = &mut app.monado else { + let Some(monado) = &mut app.monado_state else { return; // monado not available }; match task { PlayspaceTask::FixFloor => { - self.fix_floor(&app.input_state, monado); + self.fix_floor(&app.input_state, &mut monado.ipc); } PlayspaceTask::Reset => { - self.reset_offset(monado); + self.reset_offset(&mut monado.ipc); } PlayspaceTask::Recenter => { - self.recenter(&app.input_state, monado); + self.recenter(&app.input_state, &mut monado.ipc); } } } @@ -66,7 +66,7 @@ impl PlayspaceMover { overlays: &mut OverlayWindowManager, app: &mut AppState, ) { - let Some(monado) = &mut app.monado else { + let Some(monado) = &mut app.monado_state else { return; // monado not available }; @@ -74,7 +74,7 @@ impl PlayspaceMover { if pointer.now.space_reset { if !pointer.before.space_reset { log::info!("Space reset"); - self.reset_offset(monado); + self.reset_offset(&mut monado.ipc); } return; } @@ -111,7 +111,7 @@ impl PlayspaceMover { data.pose *= space_transform; data.hand_pose = new_hand; - apply_offset(data.pose, monado); + apply_offset(data.pose, &mut monado.ipc); self.rotate = Some(data); } else { for (i, pointer) in app.input_state.pointers.iter().enumerate() { @@ -167,7 +167,7 @@ impl PlayspaceMover { data.pose.translation += relative_pos; data.hand_pose = new_hand; - apply_offset(data.pose, monado); + apply_offset(data.pose, &mut monado.ipc); self.drag = Some(data); } else { for (i, pointer) in app.input_state.pointers.iter().enumerate() { diff --git a/wayvr/src/overlays/dashboard.rs b/wayvr/src/overlays/dashboard.rs index 3ee46f7a..a1c2cb2e 100644 --- a/wayvr/src/overlays/dashboard.rs +++ b/wayvr/src/overlays/dashboard.rs @@ -471,11 +471,11 @@ impl DashInterface for DashInterfaceLive { &mut self, app: &mut AppState, ) -> anyhow::Result> { - let Some(monado) = &mut app.monado else { + let Some(monado) = &mut app.monado_state else { return Ok(Vec::new()); // no monado available }; - let clients = monado_list_clients_filtered(monado)?; + let clients = monado_list_clients_filtered(&mut monado.ipc)?; let mut res = Vec::::new(); @@ -499,29 +499,29 @@ impl DashInterface for DashInterfaceLive { #[cfg(feature = "openxr")] fn monado_client_focus(&mut self, app: &mut AppState, name: &str) -> anyhow::Result<()> { - let Some(monado) = &mut app.monado else { + let Some(monado) = &mut app.monado_state else { return Ok(()); // no monado available }; - monado_client_focus(monado, name) + monado_client_focus(&mut monado.ipc, name) } #[cfg(feature = "openxr")] fn monado_brightness_get(&mut self, app: &mut AppState) -> Option { - let Some(monado) = &mut app.monado else { + let Some(monado) = &mut app.monado_state else { return None; }; - monado_get_brightness(monado) + monado_get_brightness(&mut monado.ipc) } #[cfg(feature = "openxr")] fn monado_brightness_set(&mut self, app: &mut AppState, brightness: f32) -> Option<()> { - let Some(monado) = &mut app.monado else { + let Some(monado) = &mut app.monado_state else { return None; }; - monado_set_brightness(monado, brightness).ok() + monado_set_brightness(&mut monado.ipc, brightness).ok() } #[cfg(not(feature = "openxr"))] diff --git a/wayvr/src/state.rs b/wayvr/src/state.rs index 5567f48f..9e8d7da2 100644 --- a/wayvr/src/state.rs +++ b/wayvr/src/state.rs @@ -9,6 +9,7 @@ use wgui::{ drawing, font_config::WguiFontConfig, gfx::WGfx, globals::WguiGlobals, parser::parse_color_hex, renderer_vk::context::SharedContext as WSharedContext, }; +use wlx_common::async_executor::AsyncExecutor; use wlx_common::locale::WayVRLangProvider; use wlx_common::{ audio, @@ -18,7 +19,10 @@ use wlx_common::{ overlays::{ToastDisplayMethod, ToastTopic}, }; +#[cfg(feature = "openxr")] +use crate::backend; use crate::backend::wayvr::WvrServerState; + #[cfg(feature = "osc")] use crate::subsystem::osc::OscSender; @@ -34,6 +38,7 @@ use crate::{ pub struct AppState { pub session: AppSession, pub tasks: TaskContainer, + pub executor: AsyncExecutor, pub gfx: Arc, pub gfx_extras: WGfxExtras, @@ -67,7 +72,7 @@ pub struct AppState { pub wvr_server: Option, #[cfg(feature = "openxr")] - pub monado: Option, + pub monado_state: Option, } #[allow(unused_mut)] @@ -152,9 +157,12 @@ impl AppState { let lang_provider = WayVRLangProvider::from_config(&session.config); + let executor = Rc::new(smol::LocalExecutor::new()); + Ok(Self { session, tasks, + executor, gfx, gfx_extras, hid_provider, @@ -184,17 +192,25 @@ impl AppState { wvr_server, #[cfg(feature = "openxr")] - monado: None, + monado_state: None, }) } #[cfg(feature = "openxr")] - pub fn monado_init(&mut self) { + pub fn monado_state_init(&mut self) { + use crate::backend::openxr::monado_state::MonadoState; + log::debug!("Connecting to Monado IPC"); - self.monado = None; // stop connection first - self.monado = libmonado::Monado::auto_connect() - .map_err(|e| log::warn!("Will not use libmonado: {e}")) - .ok(); + self.monado_state = None; // stop connection first + + match MonadoState::new() { + Ok(m) => { + self.monado_state = Some(m); + } + Err(e) => { + log::error!("Will not use libmonado: {e:?}"); + } + } } } diff --git a/wayvr/src/subsystem/mod.rs b/wayvr/src/subsystem/mod.rs index abf66273..7d3a83cf 100644 --- a/wayvr/src/subsystem/mod.rs +++ b/wayvr/src/subsystem/mod.rs @@ -5,3 +5,6 @@ pub mod notifications; #[cfg(feature = "osc")] pub mod osc; + +#[cfg(feature = "openxr")] +pub mod monado_metrics; diff --git a/wayvr/src/subsystem/monado_metrics/README.md b/wayvr/src/subsystem/monado_metrics/README.md new file mode 100644 index 00000000..84590d3f --- /dev/null +++ b/wayvr/src/subsystem/monado_metrics/README.md @@ -0,0 +1,10 @@ +`proto.rs` can be generated via prost_build tool. + +To generate a new version of protobuf file: + +``` +cd scripts/prost_build +OUT_DIR=your_directory cargo run +``` + +new protobuf files will be generated in `your_directory` directory. diff --git a/wayvr/src/subsystem/monado_metrics/metrics_fd.rs b/wayvr/src/subsystem/monado_metrics/metrics_fd.rs new file mode 100644 index 00000000..a5c4ca8d --- /dev/null +++ b/wayvr/src/subsystem/monado_metrics/metrics_fd.rs @@ -0,0 +1,52 @@ +use std::{ + io::Read, + os::{fd::AsFd, unix::net::UnixStream}, +}; + +use crate::subsystem::monado_metrics::proto; + +pub struct MonadoMetricsFd { + stream_reader: UnixStream, + stream_writer: UnixStream, +} + +impl MonadoMetricsFd { + pub fn new(monado: &mut libmonado::Monado) -> anyhow::Result { + let (stream_reader, stream_writer) = std::os::unix::net::UnixStream::pair()?; + stream_writer.set_nonblocking(true)?; + stream_reader.set_nonblocking(true)?; + + monado.push_metrics_fd(stream_writer.as_fd(), true)?; + + Ok(Self { + stream_reader, + stream_writer, + }) + } + + fn parse_message(&self, record: proto::Record) { + log::debug!("metrics message: {record:?}"); + } + + // called every frame + pub fn update(&mut self) { + let mut buf: [u8; 1024] = [0; 1024]; + + while let Ok(byte_count) = self.stream_reader.read(&mut buf) { + if byte_count == 0 { + debug_assert!(false); + break; + } + + let res: Result = prost::Message::decode_length_delimited(&buf[..]); + match res { + Ok(record) => { + self.parse_message(record); + } + Err(e) => { + log::error!("decode error: {e}"); + } + } + } + } +} diff --git a/wayvr/src/subsystem/monado_metrics/mod.rs b/wayvr/src/subsystem/monado_metrics/mod.rs new file mode 100644 index 00000000..82a86c07 --- /dev/null +++ b/wayvr/src/subsystem/monado_metrics/mod.rs @@ -0,0 +1,2 @@ +pub mod metrics_fd; +pub mod proto; diff --git a/wayvr/src/subsystem/monado_metrics/proto.rs b/wayvr/src/subsystem/monado_metrics/proto.rs new file mode 100644 index 00000000..0232dc36 --- /dev/null +++ b/wayvr/src/subsystem/monado_metrics/proto.rs @@ -0,0 +1,180 @@ +// This file is @generated by prost-build. +#[derive(Clone, Copy, PartialEq, Eq, Hash, ::prost::Message)] +pub struct Version { + #[prost(uint32, tag = "1")] + pub major: u32, + #[prost(uint32, tag = "2")] + pub minor: u32, +} +#[derive(Clone, Copy, PartialEq, Eq, Hash, ::prost::Message)] +pub struct SessionFrame { + /// ! Which session this frame belongs to. + #[prost(int64, tag = "1")] + pub session_id: i64, + /// ! ID of frame. + #[prost(int64, tag = "2")] + pub frame_id: i64, + /// ! How long we thought the frame would take. + #[prost(uint64, tag = "3")] + pub predicted_frame_time_ns: u64, + /// ! When we predicted the app should wake up. + #[prost(uint64, tag = "4")] + pub predicted_wake_up_time_ns: u64, + /// ! When the client's GPU work should have completed. + #[prost(uint64, tag = "5")] + pub predicted_gpu_done_time_ns: u64, + /// ! When we predicted this frame to be shown. + #[prost(uint64, tag = "6")] + pub predicted_display_time_ns: u64, + /// ! The selected display period. + #[prost(uint64, tag = "7")] + pub predicted_display_period_ns: u64, + /// ! + /// When the app told us to display this frame, can be different + /// then the predicted display time so we track that separately. + #[prost(uint64, tag = "8")] + pub display_time_ns: u64, + /// ! When the predict call was made (inside of xrWaitFrame). + #[prost(uint64, tag = "9")] + pub when_predicted_ns: u64, + /// ! When the waiting thread was woken up. + #[prost(uint64, tag = "10")] + pub when_wait_woke_ns: u64, + /// ! When xrBeginFrame was called. + #[prost(uint64, tag = "11")] + pub when_begin_ns: u64, + /// ! When the layer information was delivered, (inside of xrEndFrame). + #[prost(uint64, tag = "12")] + pub when_delivered_ns: u64, + /// ! When the scheduled GPU work was completed. + #[prost(uint64, tag = "13")] + pub when_gpu_done_ns: u64, + /// ! Was this frame discarded. + #[prost(bool, tag = "14")] + pub discarded: bool, +} +#[derive(Clone, Copy, PartialEq, Eq, Hash, ::prost::Message)] +pub struct Used { + /// ! Owning session of the frame. + #[prost(int64, tag = "1")] + pub session_id: i64, + /// ! Which session frame was used by the compositor. + #[prost(int64, tag = "2")] + pub session_frame_id: i64, + /// ! The system frame that the session was used by. + #[prost(int64, tag = "3")] + pub system_frame_id: i64, + /// ! When the frame was latched. + #[prost(uint64, tag = "4")] + pub when_ns: u64, +} +#[derive(Clone, Copy, PartialEq, Eq, Hash, ::prost::Message)] +pub struct SystemFrame { + /// ! ID of frame. + #[prost(int64, tag = "1")] + pub frame_id: i64, + /// ! Projected pixels to photon time. + #[prost(uint64, tag = "2")] + pub predicted_display_time_ns: u64, + /// ! Current period of displaying of frames. + #[prost(uint64, tag = "3")] + pub predicted_display_period_ns: u64, + /// ! When the compositor should hand pixels to display hardware. + #[prost(uint64, tag = "4")] + pub desired_present_time_ns: u64, + /// ! The time that the compositor should wake up. + #[prost(uint64, tag = "5")] + pub wake_up_time_ns: u64, + #[prost(uint64, tag = "6")] + pub present_slop_ns: u64, +} +/// ! +/// Out of band info about a GPU timing from a system compositor. +#[derive(Clone, Copy, PartialEq, Eq, Hash, ::prost::Message)] +pub struct SystemGpuInfo { + /// ! ID of frame. + #[prost(int64, tag = "1")] + pub frame_id: i64, + /// ! Start of GPU work. + #[prost(uint64, tag = "2")] + pub gpu_start_ns: u64, + /// ! End of GPU work. + #[prost(uint64, tag = "3")] + pub gpu_end_ns: u64, + /// ! When the information was gathered. + #[prost(uint64, tag = "4")] + pub when_ns: u64, +} +/// ! +/// Information that comes from the vulkan present timing. +#[derive(Clone, Copy, PartialEq, Eq, Hash, ::prost::Message)] +pub struct SystemPresentInfo { + /// ! ID of frame. + #[prost(int64, tag = "1")] + pub frame_id: i64, + /// ! The total expected time for the compositor to complete a frame. + #[prost(uint64, tag = "2")] + pub expected_comp_time_ns: u64, + /// ! The time we predicted the compositor to wake up time. + #[prost(uint64, tag = "3")] + pub predicted_wake_up_time_ns: u64, + /// ! Predicted time for completion of the GPU work. + #[prost(uint64, tag = "4")] + pub predicted_done_time_ns: u64, + /// ! Predicted display time. + #[prost(uint64, tag = "5")] + pub predicted_display_time_ns: u64, + /// ! When was this frame predicted. + #[prost(uint64, tag = "6")] + pub when_predict_ns: u64, + /// ! When we last woke up the compositor after its equivalent of wait_frame. + #[prost(uint64, tag = "7")] + pub when_woke_ns: u64, + /// ! When the compositor started rendering a frame + #[prost(uint64, tag = "8")] + pub when_began_ns: u64, + /// ! When the compositor finished rendering a frame + #[prost(uint64, tag = "9")] + pub when_submitted_ns: u64, + /// ! When new frame timing info was last added. + #[prost(uint64, tag = "10")] + pub when_infoed_ns: u64, + /// ! When we wanted to start presenting. + #[prost(uint64, tag = "11")] + pub desired_present_time_ns: u64, + /// ! The slop used for this frame. + #[prost(uint64, tag = "12")] + pub present_slop_ns: u64, + /// ! Margin of GPU to present time. + #[prost(uint64, tag = "13")] + pub present_margin_ns: u64, + /// ! When the present time actually happened. + #[prost(uint64, tag = "14")] + pub actual_present_time_ns: u64, + /// ! The earliest we could have presented. + #[prost(uint64, tag = "15")] + pub earliest_present_time_ns: u64, +} +#[derive(Clone, Copy, PartialEq, Eq, Hash, ::prost::Message)] +pub struct Record { + #[prost(oneof = "record::Record", tags = "1, 2, 3, 4, 5, 6")] + pub record: ::core::option::Option, +} +/// Nested message and enum types in `Record`. +pub mod record { + #[derive(Clone, Copy, PartialEq, Eq, Hash, ::prost::Oneof)] + pub enum Record { + #[prost(message, tag = "1")] + Version(super::Version), + #[prost(message, tag = "2")] + SessionFrame(super::SessionFrame), + #[prost(message, tag = "3")] + Used(super::Used), + #[prost(message, tag = "4")] + SystemFrame(super::SystemFrame), + #[prost(message, tag = "5")] + SystemGpuInfo(super::SystemGpuInfo), + #[prost(message, tag = "6")] + SystemPresentInfo(super::SystemPresentInfo), + } +} diff --git a/wlx-common/Cargo.toml b/wlx-common/Cargo.toml index e9c54ae1..ee5c9d68 100644 --- a/wlx-common/Cargo.toml +++ b/wlx-common/Cargo.toml @@ -22,7 +22,7 @@ strum.workspace = true xdg.workspace = true chrono = "0.4.42" -smol = "2.0.2" +smol = { workspace = true } wgui = { path = "../wgui/" } rodio = { version = "0.21.1", default-features = false, features = [ "playback", diff --git a/dash-frontend/src/util/various.rs b/wlx-common/src/async_executor.rs similarity index 100% rename from dash-frontend/src/util/various.rs rename to wlx-common/src/async_executor.rs diff --git a/wlx-common/src/lib.rs b/wlx-common/src/lib.rs index 698a2d29..c2c46177 100644 --- a/wlx-common/src/lib.rs +++ b/wlx-common/src/lib.rs @@ -1,4 +1,5 @@ pub mod astr_containers; +pub mod async_executor; pub mod audio; pub mod cache_dir; pub mod common;