mirror of https://github.com/wayvr-org/wayvr.git
Merge pull request #445 from wayvr-org/feat-monado-timings
feat: Monado debug timings graph
This commit is contained in:
commit
543f157895
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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 = []
|
||||
|
|
|
|||
|
|
@ -0,0 +1,3 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24"><!-- Icon from Material Symbols by Google - https://github.com/google/material-design-icons/blob/master/LICENSE -->
|
||||
<path fill="currentColor" d="M12 19q1.65 0 2.825-1.175T16 15v-4q0-1.65-1.175-2.825T12 7T9.175 8.175T8 11v4q0 1.65 1.175 2.825T12 19m-2-3h4v-2h-4zm0-4h4v-2h-4zm2 9q-1.625 0-3.012-.8T6.8 18H4v-2h2.1q-.075-.5-.088-1T6 14H4v-2h2q0-.5.012-1t.088-1H4V8h2.8q.35-.575.788-1.075T8.6 6.05L7 4.4L8.4 3l2.15 2.15q.7-.225 1.425-.225t1.425.225L15.6 3L17 4.4l-1.65 1.65q.575.375 1.038.862T17.2 8H20v2h-2.1q.075.5.088 1T18 12h2v2h-2q0 .5-.013 1t-.087 1H20v2h-2.8q-.8 1.4-2.187 2.2T12 21" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 682 B |
|
|
@ -11,10 +11,10 @@
|
|||
gap="8"
|
||||
round="8" />
|
||||
|
||||
<!-- src, text, translation -->
|
||||
<!-- src_builtin, text, translation -->
|
||||
<template name="GroupBoxTitle">
|
||||
<div flex_direction="row" align_items="center" gap="8">
|
||||
<sprite src="${src}" src_builtin="${src_builtin}" width="24" height="24" />
|
||||
<sprite src_builtin="${src_builtin}" width="24" height="24" />
|
||||
<label text="${text}" translation="${translation}" weight="bold" size="18" />
|
||||
</div>
|
||||
<rectangle color="#FFFFFF44" width="100%" height="2" />
|
||||
|
|
|
|||
|
|
@ -1,37 +1,14 @@
|
|||
<layout>
|
||||
<include src="../t_group_box.xml" />
|
||||
|
||||
<!-- key: str, value: str -->
|
||||
<template name="BoolFlag">
|
||||
<div flex_direction="row" gap="4">
|
||||
<label text="${key}" />
|
||||
<label weight="bold" text="${value}" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- name, checked, flag_* -->
|
||||
<template name="Cell">
|
||||
<rectangle macro="group_box">
|
||||
<CheckBox id="checkbox" text="${name}" checked="${checked}" />
|
||||
<div flex_direction="row" gap="8">
|
||||
<BoolFlag key="Active:" value="${flag_active}" />
|
||||
<BoolFlag key="Focused:" value="${flag_focused}" />
|
||||
<BoolFlag key="IO active:" value="${flag_io_active}" />
|
||||
<BoolFlag key="Overlay:" value="${flag_overlay}" />
|
||||
<BoolFlag key="Primary:" value="${flag_primary}" />
|
||||
<BoolFlag key="Visible:" value="${flag_visible}" />
|
||||
</div>
|
||||
</rectangle>
|
||||
</template>
|
||||
|
||||
<elements>
|
||||
<label translation="DISPLAY_BRIGHTNESS" />
|
||||
<Slider id="slider_brightness" width="300" height="24" min_value="0" max_value="140" />
|
||||
|
||||
<label translation="PROCESS_LIST" />
|
||||
<div id="list_parent" flex_direction="column" gap="8">
|
||||
<!-- filled at runtime -->
|
||||
<div gap="4">
|
||||
<Tabs id="tabs">
|
||||
<Tab name="general_settings" translation="GENERAL_SETTINGS" sprite_src_builtin="dashboard/settings.svg" />
|
||||
<Tab name="process_list" translation="PROCESS_LIST" sprite_src_builtin="dashboard/cpu.svg" />
|
||||
<Tab name="debug_timings" translation="DEBUG_INFO" sprite_src_builtin="dashboard/not_a_bug.svg" />
|
||||
</Tabs>
|
||||
<div gap="4" id="content" width="100%">
|
||||
<!-- filled-in at runtime -->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</elements>
|
||||
</layout>
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
<layout>
|
||||
<include src="../t_group_box.xml" />
|
||||
|
||||
<!-- name, limit_min, limit_max -->
|
||||
<template name="DebugGraph">
|
||||
<rectangle flex_direction="column" align_items="center" gap="8" border="2" color="#ffffff11" color2="#ffffff22" gradient="vertical" border_color="#ffffff33" round="4" padding="4">
|
||||
<label text="${name}" size="9" weight="bold" />
|
||||
<BarGraph id="graph" width="180" height="70" unit="ms" limit_min="${limit_min}" limit_max="${limit_max}" capacity="50" />
|
||||
</rectangle>
|
||||
</template>
|
||||
|
||||
<!-- text -->
|
||||
<template name="SessionButton">
|
||||
<Button id="button" text="${text}" />
|
||||
</template>
|
||||
|
||||
<elements>
|
||||
<rectangle macro="group_box">
|
||||
<GroupBoxTitle src_builtin="dashboard/apps.svg" text="Sessions" />
|
||||
<div id="session_list_parent" flex_wrap="wrap" flex_direction="row" gap="8" />
|
||||
<GroupBoxTitle src_builtin="dashboard/not_a_bug.svg" text="Debug timings" />
|
||||
<div id="timings_parent" flex_wrap="wrap" flex_direction="row" gap="8">
|
||||
<label text="Please select which session you want to monitor" />
|
||||
</div>
|
||||
</rectangle>
|
||||
</elements>
|
||||
</layout>
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
<layout>
|
||||
<include src="../t_group_box.xml" />
|
||||
|
||||
<elements>
|
||||
<rectangle macro="group_box">
|
||||
<GroupBoxTitle src_builtin="dashboard/settings.svg" translation="GENERAL_SETTINGS" />
|
||||
|
||||
<label translation="DISPLAY_BRIGHTNESS" />
|
||||
<Slider id="slider_brightness" width="300" height="24" min_value="0" max_value="140" />
|
||||
</rectangle>
|
||||
</elements>
|
||||
</layout>
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
<layout>
|
||||
<include src="../t_group_box.xml" />
|
||||
|
||||
<!-- key: str, value: str -->
|
||||
<template name="BoolFlag">
|
||||
<div flex_direction="row" gap="4">
|
||||
<label text="${key}" />
|
||||
<label weight="bold" text="${value}" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- name, checked, flag_* -->
|
||||
<template name="Cell">
|
||||
<rectangle macro="group_box">
|
||||
<CheckBox id="checkbox" text="${name}" checked="${checked}" />
|
||||
<div flex_direction="row" gap="8" flex_wrap="wrap">
|
||||
<BoolFlag key="Active:" value="${flag_active}" />
|
||||
<BoolFlag key="Focused:" value="${flag_focused}" />
|
||||
<BoolFlag key="IO active:" value="${flag_io_active}" />
|
||||
<BoolFlag key="Overlay:" value="${flag_overlay}" />
|
||||
<BoolFlag key="Primary:" value="${flag_primary}" />
|
||||
<BoolFlag key="Visible:" value="${flag_visible}" />
|
||||
</div>
|
||||
</rectangle>
|
||||
</template>
|
||||
|
||||
<elements>
|
||||
<rectangle macro="group_box">
|
||||
<GroupBoxTitle src_builtin="dashboard/cpu.svg" translation="PROCESS_LIST" />
|
||||
<div id="list_parent" flex_direction="column" gap="8">
|
||||
<!-- filled at runtime -->
|
||||
</div>
|
||||
</rectangle>
|
||||
</elements>
|
||||
</layout>
|
||||
|
|
@ -54,7 +54,9 @@
|
|||
<Tab name="autostart_apps" translation="APP_SETTINGS.AUTOSTART_APPS" sprite_src_builtin="dashboard/apps.svg" />
|
||||
<Tab name="troubleshooting" translation="APP_SETTINGS.TROUBLESHOOTING" sprite_src_builtin="dashboard/cpu.svg" />
|
||||
</Tabs>
|
||||
<div flex_wrap="wrap" justify_content="stretch" gap="4" id="settings_root" width="100%" />
|
||||
<div flex_wrap="wrap" justify_content="stretch" gap="4" id="settings_root" width="100%">
|
||||
<!-- filled-in at runtime -->
|
||||
</div>
|
||||
</div>
|
||||
</elements>
|
||||
</layout>
|
||||
|
|
@ -1,16 +1,16 @@
|
|||
<layout>
|
||||
<include src="../t_group_box.xml" />
|
||||
<include src_builtin="../t_group_box.xml" />
|
||||
|
||||
<!-- device_name, device_icon -->
|
||||
<template name="DeviceSlider">
|
||||
<rectangle macro="group_box">
|
||||
<div width="100%" align_items="center" justify_content="center" gap="8">
|
||||
<sprite src="${device_icon}" width="16" height="16" />
|
||||
<sprite src_builtin="${device_icon}" width="16" height="16" />
|
||||
<label text="${device_name}" margin_right="8" size="12" weight="bold" />
|
||||
</div>
|
||||
<div width="100%" align_items="center">
|
||||
<CheckBox id="checkbox" />
|
||||
<Button sprite_src="${volume_icon}" id="btn_mute" width="32" />
|
||||
<Button sprite_src_builtin="${volume_icon}" id="btn_mute" width="32" />
|
||||
<Slider id="slider" flex_grow="1" height="16" min_value="0" max_value="150" margin_left="8" />
|
||||
</div>
|
||||
</rectangle>
|
||||
|
|
@ -41,7 +41,7 @@
|
|||
flex_grow="1"
|
||||
id="${id}"
|
||||
translation="${translation}"
|
||||
sprite_src="${src}">
|
||||
sprite_src_builtin="${src}">
|
||||
</Button>
|
||||
</template>
|
||||
|
||||
|
|
@ -67,4 +67,4 @@
|
|||
</div>
|
||||
</div>
|
||||
</elements>
|
||||
</layout>
|
||||
</layout>
|
||||
|
|
|
|||
|
|
@ -161,5 +161,6 @@
|
|||
"PROCESS": {
|
||||
"STOP": "Stopp",
|
||||
"FORCE_KILL": "Erzwinge Beenden"
|
||||
}
|
||||
},
|
||||
"DEBUG_INFO": "Debug-Informationen"
|
||||
}
|
||||
|
|
@ -133,6 +133,7 @@
|
|||
"VOLUME": "Volume"
|
||||
},
|
||||
"CLOSE_WINDOW": "Close window",
|
||||
"DEBUG_INFO": "Debug info",
|
||||
"DISPLAY_BRIGHTNESS": "Display brightness",
|
||||
"FAILED_TO_LAUNCH_APPLICATION": "Failed to launch a application:",
|
||||
"GAME_LAUNCHED": "Game launched",
|
||||
|
|
|
|||
|
|
@ -161,5 +161,6 @@
|
|||
"PROCESS": {
|
||||
"STOP": "Detener",
|
||||
"FORCE_KILL": "Forzar cierre"
|
||||
}
|
||||
},
|
||||
"DEBUG_INFO": "Información de depuración"
|
||||
}
|
||||
|
|
@ -161,5 +161,6 @@
|
|||
"PROCESS": {
|
||||
"STOP": "Interrompi",
|
||||
"FORCE_KILL": "Uccidi forzatamente"
|
||||
}
|
||||
},
|
||||
"DEBUG_INFO": "Informazioni di debug"
|
||||
}
|
||||
|
|
@ -161,5 +161,6 @@
|
|||
"PROCESS": {
|
||||
"STOP": "停止",
|
||||
"FORCE_KILL": "強制終了"
|
||||
}
|
||||
},
|
||||
"DEBUG_INFO": "デバッグ情報"
|
||||
}
|
||||
|
|
@ -161,5 +161,6 @@
|
|||
"PROCESS": {
|
||||
"STOP": "Zatrzymaj",
|
||||
"FORCE_KILL": "Wymuś zakończenie"
|
||||
}
|
||||
},
|
||||
"DEBUG_INFO": "Informacje debugowania"
|
||||
}
|
||||
|
|
@ -161,5 +161,6 @@
|
|||
"PROCESS": {
|
||||
"STOP": "停止",
|
||||
"FORCE_KILL": "强制关闭"
|
||||
}
|
||||
},
|
||||
"DEBUG_INFO": "调试信息"
|
||||
}
|
||||
|
|
@ -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,
|
||||
};
|
||||
|
|
@ -45,7 +45,7 @@ pub type FrontendTasks = Tasks<FrontendTask>;
|
|||
|
||||
pub struct Frontend<T> {
|
||||
pub layout: Layout,
|
||||
globals: WguiGlobals,
|
||||
pub globals: WguiGlobals,
|
||||
|
||||
pub interface: BoxDashInterface<T>,
|
||||
|
||||
|
|
|
|||
|
|
@ -286,7 +286,7 @@ impl AppList {
|
|||
let mut params = HashMap::<Rc<str>, Rc<str>>::new();
|
||||
params.insert("text".into(), category_name.into());
|
||||
|
||||
parser_state.parse_template(
|
||||
parser_state.realize_template(
|
||||
doc_params,
|
||||
"CategoryText",
|
||||
&mut frontend.layout,
|
||||
|
|
@ -318,7 +318,7 @@ impl AppList {
|
|||
);
|
||||
params.insert("name".into(), entry.app_name.clone());
|
||||
|
||||
let data = parser_state.parse_template(
|
||||
let data = parser_state.realize_template(
|
||||
doc_params,
|
||||
"AppEntry",
|
||||
&mut frontend.layout,
|
||||
|
|
|
|||
|
|
@ -2,24 +2,119 @@ use std::{collections::HashMap, marker::PhantomData, rc::Rc};
|
|||
|
||||
use wgui::{
|
||||
assets::AssetPath,
|
||||
components::{checkbox::ComponentCheckbox, slider::ComponentSlider},
|
||||
components::{
|
||||
bar_graph::{ComponentBarGraph, ValueCell},
|
||||
button::ComponentButton,
|
||||
checkbox::ComponentCheckbox,
|
||||
slider::ComponentSlider,
|
||||
tabs::ComponentTabs,
|
||||
},
|
||||
drawing::Color,
|
||||
globals::WguiGlobals,
|
||||
layout::WidgetID,
|
||||
layout::{Layout, WidgetID},
|
||||
parser::{self, Fetchable, ParseDocumentParams, ParserState},
|
||||
task::Tasks,
|
||||
};
|
||||
use wlx_common::dash_interface;
|
||||
use wlx_common::dash_interface::{self, MonadoDumpSessionFrame};
|
||||
|
||||
use crate::{
|
||||
frontend::Frontend,
|
||||
tab::{Tab, TabType},
|
||||
};
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Clone)]
|
||||
enum TabNameEnum {
|
||||
GeneralSettings,
|
||||
ProcessList,
|
||||
DebugTimings,
|
||||
}
|
||||
|
||||
impl TabNameEnum {
|
||||
fn from_string(s: &str) -> Option<Self> {
|
||||
match s {
|
||||
"general_settings" => Some(TabNameEnum::GeneralSettings),
|
||||
"process_list" => Some(TabNameEnum::ProcessList),
|
||||
"debug_timings" => Some(TabNameEnum::DebugTimings),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum Task {
|
||||
Refresh,
|
||||
FocusClient(String),
|
||||
SetBrightness(f32),
|
||||
SetTab(TabNameEnum),
|
||||
|
||||
// `ProcessList` tab
|
||||
ProcessListRefresh,
|
||||
ProcessListFocusClient(String),
|
||||
|
||||
// `DebugTimings` tab
|
||||
DebugTimingsRefreshSessionList,
|
||||
DebugTimingsSetSessionId(i64),
|
||||
}
|
||||
|
||||
struct SubtabProcessList {
|
||||
id_list_parent: WidgetID,
|
||||
state: ParserState,
|
||||
cells: Vec<parser::ParserData>,
|
||||
}
|
||||
|
||||
struct SubtabGeneralSettings {
|
||||
#[allow(dead_code)]
|
||||
state: ParserState,
|
||||
}
|
||||
|
||||
struct DebugGraph {
|
||||
graph: Rc<ComponentBarGraph>,
|
||||
}
|
||||
|
||||
struct DebugSessionList {
|
||||
#[allow(dead_code)]
|
||||
buttons: Vec<Rc<ComponentButton>>,
|
||||
}
|
||||
|
||||
struct TimingsSession {
|
||||
resolved_name: Option<String>,
|
||||
last_frame: MonadoDumpSessionFrame,
|
||||
}
|
||||
|
||||
struct Graphs {
|
||||
predicted_display_time: DebugGraph,
|
||||
predicted_frame_time: DebugGraph,
|
||||
predicted_wake_up_time: DebugGraph,
|
||||
predicted_gpu_done_time: DebugGraph,
|
||||
predicted_display_period: DebugGraph,
|
||||
display_time: DebugGraph,
|
||||
when_predicted: DebugGraph,
|
||||
when_wait_woke: DebugGraph,
|
||||
when_begin: DebugGraph,
|
||||
when_delivered: DebugGraph,
|
||||
when_gpu_done: DebugGraph,
|
||||
}
|
||||
|
||||
type SessionsMap = HashMap<i64 /* session id */, TimingsSession>;
|
||||
|
||||
struct SubtabDebugTimings {
|
||||
#[allow(dead_code)]
|
||||
state: ParserState,
|
||||
|
||||
graphs: Option<Graphs>,
|
||||
session_list: DebugSessionList,
|
||||
selected_session_id: Option<i64>,
|
||||
|
||||
id_sessions_list_parent: WidgetID,
|
||||
id_timings_parent: WidgetID,
|
||||
|
||||
sessions: SessionsMap,
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[allow(clippy::large_enum_variant)]
|
||||
enum Subtab {
|
||||
Empty,
|
||||
GeneralSettings(SubtabGeneralSettings),
|
||||
ProcessList(SubtabProcessList),
|
||||
DebugTimings(SubtabDebugTimings),
|
||||
}
|
||||
|
||||
pub struct TabMonado<T> {
|
||||
|
|
@ -29,10 +124,8 @@ pub struct TabMonado<T> {
|
|||
|
||||
marker: PhantomData<T>,
|
||||
|
||||
globals: WguiGlobals,
|
||||
id_list_parent: WidgetID,
|
||||
|
||||
cells: Vec<parser::ParserData>,
|
||||
id_content: WidgetID,
|
||||
subtab: Subtab,
|
||||
|
||||
ticks: u32,
|
||||
}
|
||||
|
|
@ -45,15 +138,64 @@ impl<T> Tab<T> for TabMonado<T> {
|
|||
fn update(&mut self, frontend: &mut Frontend<T>, _time_ms: u32, data: &mut T) -> anyhow::Result<()> {
|
||||
for task in self.tasks.drain() {
|
||||
match task {
|
||||
Task::Refresh => self.refresh(frontend, data)?,
|
||||
Task::FocusClient(name) => self.focus_client(frontend, data, name)?,
|
||||
Task::ProcessListRefresh => {
|
||||
if let Subtab::ProcessList(process_list) = &mut self.subtab {
|
||||
process_list.refresh(frontend, data, &self.tasks)?;
|
||||
}
|
||||
}
|
||||
Task::ProcessListFocusClient(client_name) => {
|
||||
if let Subtab::ProcessList(process_list) = &mut self.subtab {
|
||||
process_list.focus_client(frontend, data, client_name, &self.tasks)?;
|
||||
}
|
||||
}
|
||||
Task::DebugTimingsRefreshSessionList => {
|
||||
if let Subtab::DebugTimings(tab) = &mut self.subtab {
|
||||
tab.refresh_session_list(&mut frontend.layout, &self.tasks)?;
|
||||
}
|
||||
}
|
||||
Task::DebugTimingsSetSessionId(session_id) => {
|
||||
if let Subtab::DebugTimings(tab) = &mut self.subtab {
|
||||
tab.set_session_id(&mut frontend.layout, session_id)?;
|
||||
}
|
||||
}
|
||||
Task::SetBrightness(brightness) => self.set_brightness(frontend, data, brightness),
|
||||
Task::SetTab(tab) => {
|
||||
frontend.layout.remove_children(self.id_content);
|
||||
match tab {
|
||||
TabNameEnum::GeneralSettings => {
|
||||
self.subtab = Subtab::GeneralSettings(SubtabGeneralSettings::new(
|
||||
self.id_content,
|
||||
frontend,
|
||||
data,
|
||||
&self.tasks,
|
||||
)?)
|
||||
}
|
||||
TabNameEnum::ProcessList => {
|
||||
self.tasks.push(Task::ProcessListRefresh);
|
||||
self.subtab = Subtab::ProcessList(SubtabProcessList::new(self.id_content, frontend)?)
|
||||
}
|
||||
TabNameEnum::DebugTimings => {
|
||||
self.subtab = Subtab::DebugTimings(SubtabDebugTimings::new(self.id_content, frontend, &self.tasks)?)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// every few seconds
|
||||
if self.ticks.is_multiple_of(500) {
|
||||
self.tasks.push(Task::Refresh);
|
||||
match &mut self.subtab {
|
||||
Subtab::Empty => {}
|
||||
Subtab::GeneralSettings(_) => {}
|
||||
Subtab::ProcessList(_) => {
|
||||
// every few seconds
|
||||
if let Subtab::ProcessList(_) = &self.subtab
|
||||
&& self.ticks.is_multiple_of(500)
|
||||
{
|
||||
self.tasks.push(Task::ProcessListRefresh);
|
||||
}
|
||||
}
|
||||
Subtab::DebugTimings(timings) => {
|
||||
timings.update(&self.tasks, data, frontend)?;
|
||||
}
|
||||
}
|
||||
|
||||
self.ticks += 1;
|
||||
|
|
@ -62,7 +204,7 @@ impl<T> Tab<T> for TabMonado<T> {
|
|||
}
|
||||
}
|
||||
|
||||
fn doc_params(globals: &'_ WguiGlobals) -> ParseDocumentParams<'_> {
|
||||
fn doc_params_monado(globals: &'_ WguiGlobals) -> ParseDocumentParams<'_> {
|
||||
ParseDocumentParams {
|
||||
globals: globals.clone(),
|
||||
path: AssetPath::BuiltIn("gui/tab/monado.xml"),
|
||||
|
|
@ -70,6 +212,30 @@ fn doc_params(globals: &'_ WguiGlobals) -> ParseDocumentParams<'_> {
|
|||
}
|
||||
}
|
||||
|
||||
fn doc_params_tab_process_list(globals: &'_ WguiGlobals) -> ParseDocumentParams<'_> {
|
||||
ParseDocumentParams {
|
||||
globals: globals.clone(),
|
||||
path: AssetPath::BuiltIn("gui/tab/monado_tab_process_list.xml"),
|
||||
extra: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
fn doc_params_tab_general_settings(globals: &'_ WguiGlobals) -> ParseDocumentParams<'_> {
|
||||
ParseDocumentParams {
|
||||
globals: globals.clone(),
|
||||
path: AssetPath::BuiltIn("gui/tab/monado_tab_general_settings.xml"),
|
||||
extra: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
fn doc_params_tab_debug_timings(globals: &'_ WguiGlobals) -> ParseDocumentParams<'_> {
|
||||
ParseDocumentParams {
|
||||
globals: globals.clone(),
|
||||
path: AssetPath::BuiltIn("gui/tab/monado_tab_debug_timings.xml"),
|
||||
extra: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
fn yesno(n: bool) -> &'static str {
|
||||
match n {
|
||||
true => "yes",
|
||||
|
|
@ -77,29 +243,320 @@ fn yesno(n: bool) -> &'static str {
|
|||
}
|
||||
}
|
||||
|
||||
impl<T> TabMonado<T> {
|
||||
pub fn new(frontend: &mut Frontend<T>, parent_id: WidgetID) -> anyhow::Result<Self> {
|
||||
let globals = frontend.layout.state.globals.clone();
|
||||
let state = wgui::parser::parse_from_assets(&doc_params(&globals), &mut frontend.layout, parent_id)?;
|
||||
impl SubtabGeneralSettings {
|
||||
fn new<T>(
|
||||
parent_id: WidgetID,
|
||||
frontend: &mut Frontend<T>,
|
||||
data: &mut T,
|
||||
tasks: &Tasks<Task>,
|
||||
) -> anyhow::Result<Self> {
|
||||
let state = wgui::parser::parse_from_assets(
|
||||
&doc_params_tab_general_settings(&frontend.globals),
|
||||
&mut frontend.layout,
|
||||
parent_id,
|
||||
)?;
|
||||
|
||||
let id_list_parent = state.get_widget_id("list_parent")?;
|
||||
// get brightness
|
||||
let slider_brightness = state.fetch_component_as::<ComponentSlider>("slider_brightness")?;
|
||||
if let Some(brightness) = frontend.interface.monado_brightness_get(data) {
|
||||
let mut c = frontend.layout.start_common();
|
||||
slider_brightness.set_value(&mut c.common(), brightness * 100.0);
|
||||
c.finish()?;
|
||||
|
||||
let tasks = Tasks::<Task>::new();
|
||||
slider_brightness.on_value_changed({
|
||||
let tasks = tasks.clone();
|
||||
Box::new(move |_common, e| {
|
||||
tasks.push(Task::SetBrightness(e.value / 100.0));
|
||||
Ok(())
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
tasks.push(Task::Refresh);
|
||||
Ok(Self { state })
|
||||
}
|
||||
}
|
||||
|
||||
fn mount_sessions_list(
|
||||
state: &mut ParserState,
|
||||
layout: &mut Layout,
|
||||
tasks: &Tasks<Task>,
|
||||
id_parent: WidgetID,
|
||||
sessions: &SessionsMap,
|
||||
) -> anyhow::Result<DebugSessionList> {
|
||||
let mut buttons = Vec::new();
|
||||
let globals = layout.state.globals.clone();
|
||||
layout.remove_children(id_parent);
|
||||
|
||||
for (session_id, session) in sessions {
|
||||
let mut params = HashMap::new();
|
||||
|
||||
params.insert(
|
||||
Rc::from("text"),
|
||||
Rc::from(format!(
|
||||
"{} (ID {})",
|
||||
session.resolved_name.as_ref().map_or("Unknown", |s| s.as_str()),
|
||||
session_id,
|
||||
)),
|
||||
);
|
||||
|
||||
let data = state.realize_template(
|
||||
&doc_params_tab_debug_timings(&globals),
|
||||
"SessionButton",
|
||||
layout,
|
||||
id_parent,
|
||||
params,
|
||||
)?;
|
||||
|
||||
let button = data.fetch_component_as::<ComponentButton>("button")?;
|
||||
|
||||
button.on_click({
|
||||
let tasks = tasks.clone();
|
||||
let session_id = *session_id;
|
||||
Rc::new(move |_, _| {
|
||||
tasks.push(Task::DebugTimingsSetSessionId(session_id));
|
||||
Ok(())
|
||||
})
|
||||
});
|
||||
|
||||
buttons.push(button);
|
||||
}
|
||||
|
||||
Ok(DebugSessionList { buttons })
|
||||
}
|
||||
|
||||
fn mount_graph(
|
||||
state: &mut ParserState,
|
||||
layout: &mut Layout,
|
||||
id_parent: WidgetID,
|
||||
name: &'static str,
|
||||
limits: (f32, f32),
|
||||
) -> anyhow::Result<DebugGraph> {
|
||||
let globals = layout.state.globals.clone();
|
||||
let mut params = HashMap::new();
|
||||
params.insert(Rc::from("name"), Rc::from(name));
|
||||
params.insert(Rc::from("limit_min"), Rc::from(limits.0.to_string()));
|
||||
params.insert(Rc::from("limit_max"), Rc::from(limits.1.to_string()));
|
||||
|
||||
let data = state.realize_template(
|
||||
&doc_params_tab_debug_timings(&globals),
|
||||
"DebugGraph",
|
||||
layout,
|
||||
id_parent,
|
||||
params,
|
||||
)?;
|
||||
|
||||
let graph = data.fetch_component_as::<ComponentBarGraph>("graph")?;
|
||||
Ok(DebugGraph { graph })
|
||||
}
|
||||
|
||||
fn ns_to_ms(ns: i64) -> f32 {
|
||||
(ns / 1000) as f32 / 1000.0
|
||||
}
|
||||
|
||||
impl SubtabDebugTimings {
|
||||
fn new<T>(parent_id: WidgetID, frontend: &mut Frontend<T>, tasks: &Tasks<Task>) -> anyhow::Result<Self> {
|
||||
let mut state = wgui::parser::parse_from_assets(
|
||||
&doc_params_tab_debug_timings(&frontend.globals),
|
||||
&mut frontend.layout,
|
||||
parent_id,
|
||||
)?;
|
||||
|
||||
let id_timings_parent = state.get_widget_id("timings_parent")?;
|
||||
let id_sessions_list_parent = state.get_widget_id("session_list_parent")?;
|
||||
|
||||
let sessions = Default::default();
|
||||
|
||||
let session_list = mount_sessions_list(
|
||||
&mut state,
|
||||
&mut frontend.layout,
|
||||
tasks,
|
||||
id_sessions_list_parent,
|
||||
&sessions,
|
||||
)?;
|
||||
|
||||
Ok(Self {
|
||||
state,
|
||||
marker: PhantomData,
|
||||
graphs: None,
|
||||
session_list,
|
||||
id_sessions_list_parent,
|
||||
id_timings_parent,
|
||||
sessions,
|
||||
selected_session_id: None,
|
||||
})
|
||||
}
|
||||
|
||||
fn set_session_id(&mut self, layout: &mut Layout, session_id: i64) -> anyhow::Result<()> {
|
||||
layout.remove_children(self.id_timings_parent);
|
||||
|
||||
let mut graph = |name: &'static str, limits: (f32, f32)| -> anyhow::Result<DebugGraph> {
|
||||
mount_graph(&mut self.state, layout, self.id_timings_parent, name, limits)
|
||||
};
|
||||
|
||||
// populate graphs
|
||||
self.graphs = Some(Graphs {
|
||||
predicted_display_time: graph("Predicted display time", (0.0, 30.0))?,
|
||||
predicted_frame_time: graph("Predicted frame time", (0.0, 30.0))?,
|
||||
predicted_wake_up_time: graph("Predicted wake-up time", (0.0, 30.0))?,
|
||||
predicted_gpu_done_time: graph("Predicted GPU done time", (0.0, 30.0))?,
|
||||
predicted_display_period: graph("Predicted display period", (0.0, 30.0))?,
|
||||
display_time: graph("Display time", (0.0, 30.0))?,
|
||||
when_predicted: graph("When predicted", (0.0, 30.0))?,
|
||||
when_wait_woke: graph("When wait woke", (0.0, 30.0))?,
|
||||
when_begin: graph("When begin", (0.0, 30.0))?,
|
||||
when_delivered: graph("When delivered", (0.0, 30.0))?,
|
||||
when_gpu_done: graph("When GPU done", (0.0, 30.0))?,
|
||||
});
|
||||
|
||||
self.selected_session_id = Some(session_id);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn refresh_session_list(&mut self, layout: &mut Layout, tasks: &Tasks<Task>) -> anyhow::Result<()> {
|
||||
self.session_list = mount_sessions_list(
|
||||
&mut self.state,
|
||||
layout,
|
||||
tasks,
|
||||
globals,
|
||||
self.id_sessions_list_parent,
|
||||
&self.sessions,
|
||||
)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn update<T>(&mut self, tasks: &Tasks<Task>, data: &mut T, frontend: &mut Frontend<T>) -> anyhow::Result<()> {
|
||||
if !frontend.interface.monado_metrics_set_enabled(data, true) {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let frames = frontend.interface.monado_metrics_dump_session_frames(data);
|
||||
if frames.is_empty() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let col_green = Color::new(0.0, 1.0, 0.0, 1.0);
|
||||
|
||||
for frame in frames {
|
||||
//log::info!("{:?}", frame);
|
||||
|
||||
match self.sessions.get_mut(&frame.session_id) {
|
||||
Some(session) => {
|
||||
if let Some(graphs) = &mut self.graphs
|
||||
&& let Some(selected_session_id) = self.selected_session_id
|
||||
&& selected_session_id == frame.session_id
|
||||
{
|
||||
let predicted_display_time = ns_to_ms(session.last_frame.predicted_display_time_ns as i64);
|
||||
let predicted_frame_time = ns_to_ms(frame.predicted_frame_time_ns as i64);
|
||||
let predicted_wake_up_time =
|
||||
ns_to_ms(frame.predicted_wake_up_time_ns as i64 - session.last_frame.predicted_wake_up_time_ns as i64);
|
||||
let predicted_gpu_done_time =
|
||||
ns_to_ms(frame.predicted_gpu_done_time_ns as i64 - session.last_frame.predicted_gpu_done_time_ns as i64);
|
||||
let predicted_display_period = ns_to_ms(session.last_frame.predicted_display_period_ns as i64); // 6.944 ms for 144Hz
|
||||
let display_time = ns_to_ms(frame.display_time_ns as i64 - session.last_frame.display_time_ns as i64);
|
||||
let when_predicted = ns_to_ms(frame.when_predicted_ns as i64 - session.last_frame.when_predicted_ns as i64);
|
||||
let when_wait_woke = ns_to_ms(frame.when_wait_woke_ns as i64 - session.last_frame.when_wait_woke_ns as i64);
|
||||
let when_begin = ns_to_ms(frame.when_begin_ns as i64 - session.last_frame.when_begin_ns as i64);
|
||||
let when_delivered = ns_to_ms(frame.when_delivered_ns as i64 - session.last_frame.when_delivered_ns as i64);
|
||||
let when_gpu_done = ns_to_ms(frame.when_gpu_done_ns as i64 - session.last_frame.when_gpu_done_ns as i64);
|
||||
|
||||
graphs.predicted_display_time.graph.push_value(ValueCell {
|
||||
value: predicted_display_time,
|
||||
color: col_green,
|
||||
});
|
||||
|
||||
graphs.predicted_frame_time.graph.push_value(ValueCell {
|
||||
value: predicted_frame_time,
|
||||
color: col_green,
|
||||
});
|
||||
|
||||
graphs.predicted_wake_up_time.graph.push_value(ValueCell {
|
||||
value: predicted_wake_up_time,
|
||||
color: col_green,
|
||||
});
|
||||
|
||||
graphs.predicted_gpu_done_time.graph.push_value(ValueCell {
|
||||
value: predicted_gpu_done_time,
|
||||
color: col_green,
|
||||
});
|
||||
|
||||
graphs.predicted_display_period.graph.push_value(ValueCell {
|
||||
value: predicted_display_period,
|
||||
color: col_green,
|
||||
});
|
||||
|
||||
graphs.display_time.graph.push_value(ValueCell {
|
||||
value: display_time,
|
||||
color: col_green,
|
||||
});
|
||||
|
||||
graphs.when_predicted.graph.push_value(ValueCell {
|
||||
value: when_predicted,
|
||||
color: col_green,
|
||||
});
|
||||
|
||||
graphs.when_wait_woke.graph.push_value(ValueCell {
|
||||
value: when_wait_woke,
|
||||
color: col_green,
|
||||
});
|
||||
|
||||
graphs.when_begin.graph.push_value(ValueCell {
|
||||
value: when_begin,
|
||||
color: col_green,
|
||||
});
|
||||
|
||||
graphs.when_delivered.graph.push_value(ValueCell {
|
||||
value: when_delivered,
|
||||
color: col_green,
|
||||
});
|
||||
|
||||
graphs.when_gpu_done.graph.push_value(ValueCell {
|
||||
value: when_gpu_done,
|
||||
color: col_green,
|
||||
});
|
||||
}
|
||||
|
||||
session.last_frame = frame;
|
||||
}
|
||||
None => {
|
||||
self.sessions.insert(
|
||||
frame.session_id,
|
||||
TimingsSession {
|
||||
last_frame: frame,
|
||||
resolved_name: None, /* TODO! find client ID from session ID */
|
||||
},
|
||||
);
|
||||
tasks.push(Task::DebugTimingsRefreshSessionList);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
frontend.layout.mark_redraw();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl SubtabProcessList {
|
||||
fn new<T>(parent_id: WidgetID, frontend: &mut Frontend<T>) -> anyhow::Result<Self> {
|
||||
let state = wgui::parser::parse_from_assets(
|
||||
&doc_params_tab_process_list(&frontend.globals),
|
||||
&mut frontend.layout,
|
||||
parent_id,
|
||||
)?;
|
||||
let id_list_parent = state.get_widget_id("list_parent")?;
|
||||
|
||||
Ok(Self {
|
||||
state,
|
||||
id_list_parent,
|
||||
ticks: 0,
|
||||
cells: Vec::new(),
|
||||
})
|
||||
}
|
||||
|
||||
fn mount_client(&mut self, frontend: &mut Frontend<T>, client: &dash_interface::MonadoClient) -> anyhow::Result<()> {
|
||||
fn mount_client(
|
||||
&mut self,
|
||||
layout: &mut Layout,
|
||||
client: &dash_interface::MonadoClient,
|
||||
tasks: &Tasks<Task>,
|
||||
) -> anyhow::Result<()> {
|
||||
let mut par = HashMap::<Rc<str>, Rc<str>>::new();
|
||||
par.insert(
|
||||
"checked".into(),
|
||||
|
|
@ -109,7 +566,10 @@ impl<T> TabMonado<T> {
|
|||
Rc::from("0")
|
||||
},
|
||||
);
|
||||
par.insert("name".into(), client.name.clone().into());
|
||||
par.insert(
|
||||
"name".into(),
|
||||
format!("{} (Client ID: {})", client.name, client.id).into(),
|
||||
);
|
||||
par.insert("flag_active".into(), yesno(client.is_active).into());
|
||||
par.insert("flag_focused".into(), yesno(client.is_focused).into());
|
||||
par.insert("flag_io_active".into(), yesno(client.is_io_active).into());
|
||||
|
|
@ -117,21 +577,23 @@ impl<T> TabMonado<T> {
|
|||
par.insert("flag_primary".into(), yesno(client.is_primary).into());
|
||||
par.insert("flag_visible".into(), yesno(client.is_visible).into());
|
||||
|
||||
let state_cell = self.state.parse_template(
|
||||
&doc_params(&self.globals),
|
||||
let globals = layout.state.globals.clone();
|
||||
|
||||
let state_cell = self.state.realize_template(
|
||||
&doc_params_tab_process_list(&globals),
|
||||
"Cell",
|
||||
&mut frontend.layout,
|
||||
layout,
|
||||
self.id_list_parent,
|
||||
par,
|
||||
)?;
|
||||
|
||||
let checkbox = state_cell.fetch_component_as::<ComponentCheckbox>("checkbox")?;
|
||||
checkbox.on_toggle({
|
||||
let tasks = self.tasks.clone();
|
||||
let tasks = tasks.clone();
|
||||
let client_name = client.name.clone();
|
||||
Box::new(move |_common, e| {
|
||||
if e.checked {
|
||||
tasks.push(Task::FocusClient(client_name.clone()));
|
||||
tasks.push(Task::ProcessListFocusClient(client_name.clone()));
|
||||
}
|
||||
Ok(())
|
||||
})
|
||||
|
|
@ -142,41 +604,63 @@ impl<T> TabMonado<T> {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
fn refresh(&mut self, frontend: &mut Frontend<T>, data: &mut T) -> anyhow::Result<()> {
|
||||
fn focus_client<T>(
|
||||
&mut self,
|
||||
frontend: &mut Frontend<T>,
|
||||
data: &mut T,
|
||||
name: String,
|
||||
tasks: &Tasks<Task>,
|
||||
) -> anyhow::Result<()> {
|
||||
frontend.interface.monado_client_focus(data, &name)?;
|
||||
tasks.push(Task::ProcessListRefresh);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn refresh<T>(&mut self, frontend: &mut Frontend<T>, data: &mut T, tasks: &Tasks<Task>) -> anyhow::Result<()> {
|
||||
log::debug!("refreshing monado client list");
|
||||
|
||||
let clients = frontend.interface.monado_client_list(data)?;
|
||||
let clients = frontend.interface.monado_client_list(data, true)?;
|
||||
|
||||
frontend.layout.remove_children(self.id_list_parent);
|
||||
self.cells.clear();
|
||||
|
||||
for client in clients {
|
||||
self.mount_client(frontend, &client)?;
|
||||
}
|
||||
|
||||
// get brightness
|
||||
let slider_brightness = self.state.fetch_component_as::<ComponentSlider>("slider_brightness")?;
|
||||
if let Some(brightness) = frontend.interface.monado_brightness_get(data) {
|
||||
let mut c = frontend.layout.start_common();
|
||||
slider_brightness.set_value(&mut c.common(), brightness * 100.0);
|
||||
c.finish()?;
|
||||
|
||||
slider_brightness.on_value_changed({
|
||||
let tasks = self.tasks.clone();
|
||||
Box::new(move |_common, e| {
|
||||
tasks.push(Task::SetBrightness(e.value / 100.0));
|
||||
Ok(())
|
||||
})
|
||||
});
|
||||
self.mount_client(&mut frontend.layout, &client, tasks)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn focus_client(&mut self, frontend: &mut Frontend<T>, data: &mut T, name: String) -> anyhow::Result<()> {
|
||||
frontend.interface.monado_client_focus(data, &name)?;
|
||||
self.tasks.push(Task::Refresh);
|
||||
Ok(())
|
||||
impl<T> TabMonado<T> {
|
||||
pub fn new(frontend: &mut Frontend<T>, parent_id: WidgetID) -> anyhow::Result<Self> {
|
||||
let globals = frontend.layout.state.globals.clone();
|
||||
let state = wgui::parser::parse_from_assets(&doc_params_monado(&globals), &mut frontend.layout, parent_id)?;
|
||||
let id_content = state.get_widget_id("content")?;
|
||||
let tabs = state.fetch_component_as::<ComponentTabs>("tabs")?;
|
||||
|
||||
let tasks = Tasks::<Task>::new();
|
||||
|
||||
tabs.on_select({
|
||||
let tasks = tasks.clone();
|
||||
Rc::new(move |_common, evt| {
|
||||
if let Some(tab) = TabNameEnum::from_string(&evt.name) {
|
||||
tasks.push(Task::SetTab(tab));
|
||||
}
|
||||
Ok(())
|
||||
})
|
||||
});
|
||||
|
||||
tasks.push(Task::SetTab(TabNameEnum::ProcessList));
|
||||
|
||||
Ok(Self {
|
||||
state,
|
||||
marker: PhantomData,
|
||||
tasks,
|
||||
id_content,
|
||||
ticks: 0,
|
||||
subtab: Subtab::Empty,
|
||||
})
|
||||
}
|
||||
|
||||
fn set_brightness(&mut self, frontend: &mut Frontend<T>, data: &mut T, brightness: f32) {
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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<u8>,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -750,7 +750,7 @@ impl View {
|
|||
|
||||
let data = self
|
||||
.state
|
||||
.parse_template(&doc_params(&self.globals), "Card", params.layout, self.id_devices, par)?;
|
||||
.realize_template(&doc_params(&self.globals), "Card", params.layout, self.id_devices, par)?;
|
||||
|
||||
let btn_card = data.fetch_component_as::<ComponentButton>("btn_card")?;
|
||||
btn_card.on_click({
|
||||
|
|
@ -764,7 +764,6 @@ impl View {
|
|||
})
|
||||
});
|
||||
|
||||
log::info!("mount card TODO: {}", params.card.name);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
|
@ -794,7 +793,7 @@ impl View {
|
|||
},
|
||||
);
|
||||
|
||||
let data = self.state.parse_template(
|
||||
let data = self.state.realize_template(
|
||||
&doc_params(&self.globals),
|
||||
"DeviceSlider",
|
||||
params.layout,
|
||||
|
|
@ -941,7 +940,7 @@ impl View {
|
|||
layout.remove_children(self.id_devices);
|
||||
|
||||
{
|
||||
let data = self.state.parse_template(
|
||||
let data = self.state.realize_template(
|
||||
&doc_params(&self.globals),
|
||||
"SelectAudioProfileText",
|
||||
layout,
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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},
|
||||
};
|
||||
|
|
|
|||
|
|
@ -127,7 +127,7 @@ impl View {
|
|||
for game in games {
|
||||
let game_name = View::extract_name_from_appid(&game.app_id, &self.installed_games);
|
||||
|
||||
let t = self.state.parse_template(
|
||||
let t = self.state.realize_template(
|
||||
&doc_params(layout.state.globals.clone()),
|
||||
"RunningGameCell",
|
||||
layout,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,7 @@
|
|||
[package]
|
||||
name = "prost_build"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
prost-build = "0.14.3"
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
use std::io::Result;
|
||||
fn main() -> Result<()> {
|
||||
prost_build::compile_protos(&["src/monado_metrics.proto"], &["src/"])?;
|
||||
Ok(())
|
||||
}
|
||||
|
|
@ -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;
|
||||
};
|
||||
}
|
||||
|
|
@ -9,11 +9,11 @@ repository = "https://github.com/wlx-team/wayvr"
|
|||
keywords = [
|
||||
"linux",
|
||||
"openvr",
|
||||
"openxr",
|
||||
"x11",
|
||||
"wayland",
|
||||
"openvr-overlay",
|
||||
"openxr",
|
||||
"openxr-overlay",
|
||||
"wayland",
|
||||
"x11",
|
||||
]
|
||||
categories = ["games"]
|
||||
|
||||
|
|
@ -32,20 +32,19 @@ glam = { workspace = true, features = ["mint", "serde"] }
|
|||
idmap = { workspace = true, features = ["serde"] }
|
||||
idmap-derive.workspace = true
|
||||
log.workspace = true
|
||||
rust-embed.workspace = true
|
||||
regex.workspace = true
|
||||
slotmap.workspace = true
|
||||
rust-embed.workspace = true
|
||||
serde = { workspace = true, features = ["rc"] }
|
||||
serde_json.workspace = true
|
||||
slotmap.workspace = true
|
||||
strum.workspace = true
|
||||
vulkano.workspace = true
|
||||
vulkano-shaders.workspace = true
|
||||
xdg.workspace = true
|
||||
|
||||
ash = "^0.38.0" # must match vulkano
|
||||
ash = "^0.38.0" # must match vulkano
|
||||
bytes = { version = "1.11.1" }
|
||||
chrono = { version = "0.4.42", features = [ "unstable-locales"]}
|
||||
pure-rust-locales = "0.8.2"
|
||||
chrono = { version = "0.4.42", features = ["unstable-locales"] }
|
||||
chrono-tz = "0.10.4"
|
||||
config = "0.15.19"
|
||||
dbus = { version = "0.9.9" }
|
||||
|
|
@ -58,14 +57,16 @@ 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 }
|
||||
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 }
|
||||
ovr_overlay = { features = [
|
||||
ovr_overlay = { git = "https://github.com/galister/ovr_overlay_oyasumi", rev = "e477bd2a9e04293ea68c1e7529ef2cb131f32acc", features = [
|
||||
"ovr_input",
|
||||
"ovr_system",
|
||||
], git = "https://github.com/galister/ovr_overlay_oyasumi", rev = "e477bd2a9e04293ea68c1e7529ef2cb131f32acc", optional = true }
|
||||
], optional = true }
|
||||
prost = { version = "0.14.3", optional = true }
|
||||
pure-rust-locales = "0.8.2"
|
||||
rosc = { version = "0.11.4", optional = true }
|
||||
serde_json5 = "0.2.1"
|
||||
serde_yaml = "0.9.34"
|
||||
|
|
@ -74,32 +75,34 @@ smallvec = "1.15.1"
|
|||
smithay = { version = "0.7.0", default-features = false, features = [
|
||||
"backend_vulkan",
|
||||
"desktop",
|
||||
"xwayland",
|
||||
"wayland_frontend",
|
||||
"xwayland",
|
||||
] }
|
||||
smol = { workspace = true }
|
||||
sysinfo = { version = "0.37" }
|
||||
thiserror = "2.0"
|
||||
tracing-subscriber = { version = "0.3.22", features = ["env-filter"] }
|
||||
tracing = "0.1.43"
|
||||
uuid = { version = "1.19.0", features = ["v4", "fast-rng"] }
|
||||
tracing-subscriber = { version = "0.3.22", features = ["env-filter"] }
|
||||
uuid = { version = "1.19.0", features = ["fast-rng", "v4"] }
|
||||
wayland-client = { workspace = true }
|
||||
winit = { version = "0.30.12", optional = true }
|
||||
xcb = { version = "1.6.0", optional = true, features = [
|
||||
xcb = { version = "1.6.0", features = [
|
||||
"as-raw-xcb-connection",
|
||||
] }
|
||||
xkbcommon = { version = "0.8.0" } # 0.9.0 breaks keymap import on some distros
|
||||
], optional = true }
|
||||
xkbcommon = { version = "0.8.0" } # 0.9.0 breaks keymap import on some distros
|
||||
|
||||
[build-dependencies]
|
||||
regex.workspace = true
|
||||
|
||||
[features]
|
||||
default = ["openvr", "openxr", "osc", "x11", "wayland"]
|
||||
openvr = ["dep:ovr_overlay", "dep:json"]
|
||||
openxr = ["dep:openxr", "dep:libmonado"]
|
||||
default = ["openvr", "openxr", "osc", "wayland", "x11"]
|
||||
as-raw-xcb-connection = []
|
||||
feat-monado-metrics = []
|
||||
openvr = ["dep:json", "dep:ovr_overlay"]
|
||||
openxr = ["dep:libmonado", "dep:openxr", "dep:prost"]
|
||||
osc = ["dep:rosc"]
|
||||
x11 = ["dep:xcb", "wlx-capture/xshm", "xkbcommon/x11"]
|
||||
wayland = ["pipewire", "wlx-capture/wlr", "xkbcommon/wayland"]
|
||||
pipewire = ["wlx-capture/pipewire"]
|
||||
uidev = ["dep:winit"]
|
||||
wayland = ["pipewire", "wlx-capture/wlr", "xkbcommon/wayland"]
|
||||
x11 = ["dep:xcb", "wlx-capture/xshm", "xkbcommon/x11"]
|
||||
xcb = ["dep:xcb"]
|
||||
as-raw-xcb-connection = []
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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::<u32>::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 {
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -0,0 +1,58 @@
|
|||
#[cfg(feature = "feat-monado-metrics")]
|
||||
use crate::subsystem::monado_metrics::{self, metrics_fd::MonadoMetricsFd};
|
||||
|
||||
pub struct MonadoState {
|
||||
pub ipc: libmonado::Monado,
|
||||
|
||||
#[cfg(feature = "feat-monado-metrics")]
|
||||
pub metrics: Option<MonadoMetricsFd>,
|
||||
}
|
||||
|
||||
impl MonadoState {
|
||||
pub fn new() -> anyhow::Result<Self> {
|
||||
let ipc = libmonado::Monado::auto_connect().map_err(|s| anyhow::anyhow!("{s}"))?;
|
||||
let res = Self {
|
||||
ipc,
|
||||
#[cfg(feature = "feat-monado-metrics")]
|
||||
metrics: None,
|
||||
};
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
pub fn update(&mut self) {
|
||||
#[cfg(feature = "feat-monado-metrics")]
|
||||
if let Some(metrics) = &mut self.metrics {
|
||||
metrics.update();
|
||||
|
||||
if metrics.is_full() {
|
||||
let _ = self.set_metrics_enabled(false); // disable metrics if they aren't used
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_metrics_enabled(&mut self, enabled: bool) -> anyhow::Result<()> {
|
||||
#[cfg(feature = "feat-monado-metrics")]
|
||||
{
|
||||
if enabled {
|
||||
if self.metrics.is_none() {
|
||||
log::info!("Starting Monado metrics");
|
||||
self.metrics = Some(monado_metrics::metrics_fd::MonadoMetricsFd::new(
|
||||
&mut self.ipc,
|
||||
)?);
|
||||
}
|
||||
} else {
|
||||
if self.metrics.is_some() {
|
||||
log::info!("Stopping Monado metrics");
|
||||
}
|
||||
self.metrics = None;
|
||||
}
|
||||
}
|
||||
#[cfg(not(feature = "feat-monado-metrics"))]
|
||||
{
|
||||
#[allow(path_statements)]
|
||||
enabled;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
|
@ -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<OpenXrOverlayData>,
|
||||
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() {
|
||||
|
|
|
|||
|
|
@ -470,20 +470,27 @@ impl DashInterface<AppState> for DashInterfaceLive {
|
|||
fn monado_client_list(
|
||||
&mut self,
|
||||
app: &mut AppState,
|
||||
filtered: bool,
|
||||
) -> anyhow::Result<Vec<dash_interface::MonadoClient>> {
|
||||
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 = if filtered {
|
||||
monado_list_clients_filtered(&mut monado.ipc)?
|
||||
} else {
|
||||
monado.ipc.clients()?.into_iter().collect()
|
||||
};
|
||||
|
||||
let mut res = Vec::<dash_interface::MonadoClient>::new();
|
||||
|
||||
for mut client in clients {
|
||||
let client_id = client.id();
|
||||
let name = client.name()?;
|
||||
let state = client.state()?;
|
||||
|
||||
res.push(dash_interface::MonadoClient {
|
||||
id: client_id as i64,
|
||||
name,
|
||||
is_primary: state.contains(libmonado::ClientState::ClientPrimaryApp),
|
||||
is_active: state.contains(libmonado::ClientState::ClientSessionActive),
|
||||
|
|
@ -499,35 +506,117 @@ impl DashInterface<AppState> 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<f32> {
|
||||
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(feature = "openxr")]
|
||||
fn monado_metrics_set_enabled(&mut self, app: &mut AppState, enabled: bool) -> bool {
|
||||
#[cfg(feature = "feat-monado-metrics")]
|
||||
{
|
||||
let Some(monado) = &mut app.monado_state else {
|
||||
return false;
|
||||
};
|
||||
|
||||
if let Err(e) = monado.set_metrics_enabled(enabled) {
|
||||
log::error!("failed to enable metrics: {e:?}");
|
||||
return false;
|
||||
}
|
||||
true
|
||||
}
|
||||
#[cfg(not(feature = "feat-monado-metrics"))]
|
||||
#[allow(path_statements)]
|
||||
{
|
||||
app;
|
||||
enabled;
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "openxr")]
|
||||
#[allow(clippy::match_same_arms)]
|
||||
fn monado_metrics_dump_session_frames(
|
||||
&mut self,
|
||||
app: &mut AppState,
|
||||
) -> Vec<dash_interface::MonadoDumpSessionFrame> {
|
||||
#[cfg(feature = "feat-monado-metrics")]
|
||||
{
|
||||
let Some(monado) = &mut app.monado_state else {
|
||||
return Vec::new();
|
||||
};
|
||||
let Some(metrics) = &mut monado.metrics else {
|
||||
return Vec::new(); // metrics not enabled or not available
|
||||
};
|
||||
|
||||
metrics
|
||||
.dump_records()
|
||||
.iter()
|
||||
.filter_map(|record| {
|
||||
use crate::subsystem::monado_metrics::proto::record;
|
||||
let record = record.record?;
|
||||
match record {
|
||||
record::Record::SessionFrame(sframe) => {
|
||||
// map it to our struct
|
||||
Some(dash_interface::MonadoDumpSessionFrame {
|
||||
session_id: sframe.session_id,
|
||||
frame_id: sframe.frame_id,
|
||||
predicted_frame_time_ns: sframe.predicted_frame_time_ns,
|
||||
predicted_wake_up_time_ns: sframe.predicted_wake_up_time_ns,
|
||||
predicted_gpu_done_time_ns: sframe.predicted_gpu_done_time_ns,
|
||||
predicted_display_time_ns: sframe.predicted_display_time_ns,
|
||||
predicted_display_period_ns: sframe.predicted_display_period_ns,
|
||||
display_time_ns: sframe.display_time_ns,
|
||||
when_predicted_ns: sframe.when_predicted_ns,
|
||||
when_wait_woke_ns: sframe.when_wait_woke_ns,
|
||||
when_begin_ns: sframe.when_begin_ns,
|
||||
when_delivered_ns: sframe.when_delivered_ns,
|
||||
when_gpu_done_ns: sframe.when_gpu_done_ns,
|
||||
discarded: sframe.discarded,
|
||||
})
|
||||
}
|
||||
record::Record::Version(_) => None,
|
||||
record::Record::Used(_) => None,
|
||||
record::Record::SystemFrame(_) => None,
|
||||
record::Record::SystemGpuInfo(_) => None,
|
||||
record::Record::SystemPresentInfo(_) => None,
|
||||
}
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
#[cfg(not(feature = "feat-monado-metrics"))]
|
||||
#[allow(path_statements)]
|
||||
{
|
||||
app;
|
||||
Vec::new()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "openxr"))]
|
||||
fn monado_client_list(
|
||||
&mut self,
|
||||
_: &mut AppState,
|
||||
_filtered: bool,
|
||||
) -> anyhow::Result<Vec<dash_interface::MonadoClient>> {
|
||||
anyhow::bail!("Not supported in this build.")
|
||||
}
|
||||
|
|
@ -543,6 +632,20 @@ impl DashInterface<AppState> for DashInterfaceLive {
|
|||
fn monado_brightness_set(&mut self, _: &mut AppState, _: f32) -> Option<()> {
|
||||
None
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "openxr"))]
|
||||
fn monado_metrics_set_enabled(&mut self, _: &mut AppState, _enabled: bool) -> bool {
|
||||
// not supported in this build
|
||||
false
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "openxr"))]
|
||||
fn monado_metrics_dump_session_frames(
|
||||
&mut self,
|
||||
_: &mut AppState,
|
||||
) -> Vec<dash_interface::MonadoDumpSessionFrame> {
|
||||
Vec::new() // not supported in this build
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "openxr")]
|
||||
|
|
|
|||
|
|
@ -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<WGfx>,
|
||||
pub gfx_extras: WGfxExtras,
|
||||
|
|
@ -67,7 +72,7 @@ pub struct AppState {
|
|||
pub wvr_server: Option<WvrServerState>,
|
||||
|
||||
#[cfg(feature = "openxr")]
|
||||
pub monado: Option<libmonado::Monado>,
|
||||
pub monado_state: Option<backend::openxr::monado_state::MonadoState>,
|
||||
}
|
||||
|
||||
#[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:?}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -5,3 +5,7 @@ pub mod notifications;
|
|||
|
||||
#[cfg(feature = "osc")]
|
||||
pub mod osc;
|
||||
|
||||
#[cfg(feature = "openxr")]
|
||||
#[cfg(feature = "feat-monado-metrics")]
|
||||
pub mod monado_metrics;
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
@ -0,0 +1,70 @@
|
|||
use std::{
|
||||
collections::VecDeque,
|
||||
io::Read,
|
||||
os::{fd::AsFd, unix::net::UnixStream},
|
||||
};
|
||||
|
||||
use crate::subsystem::monado_metrics::proto;
|
||||
|
||||
pub struct MonadoMetricsFd {
|
||||
stream_reader: UnixStream,
|
||||
stream_writer: UnixStream,
|
||||
|
||||
records: VecDeque<proto::Record>,
|
||||
}
|
||||
|
||||
const RECORD_QUEUE_SIZE: usize = 500;
|
||||
|
||||
impl MonadoMetricsFd {
|
||||
pub fn new(monado: &mut libmonado::Monado) -> anyhow::Result<Self> {
|
||||
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,
|
||||
records: VecDeque::new(),
|
||||
})
|
||||
}
|
||||
|
||||
fn parse_message(&mut self, record: proto::Record) {
|
||||
self.records.push_back(record);
|
||||
if self.records.len() >= RECORD_QUEUE_SIZE {
|
||||
self.records.pop_front();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn dump_records(&mut self) -> Vec<proto::Record> {
|
||||
let records = std::mem::take(&mut self.records);
|
||||
records.into_iter().collect()
|
||||
}
|
||||
|
||||
pub fn is_full(&self) -> bool {
|
||||
self.records.len() >= RECORD_QUEUE_SIZE - 1
|
||||
}
|
||||
|
||||
// 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<proto::Record, _> = prost::Message::decode_length_delimited(&buf[..]);
|
||||
match res {
|
||||
Ok(record) => {
|
||||
self.parse_message(record);
|
||||
}
|
||||
Err(e) => {
|
||||
log::error!("decode error: {e}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
pub mod metrics_fd;
|
||||
pub mod proto;
|
||||
|
|
@ -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<record::Record>,
|
||||
}
|
||||
/// 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),
|
||||
}
|
||||
}
|
||||
|
|
@ -6,7 +6,7 @@
|
|||
|
||||
## [Built-in components](#components)
|
||||
|
||||
[Button](#button-component), [Slider](#slider-component), [CheckBox](#checkbox-component), [Tabs](#tabs-component) ([Tab](#tab-component)), [EditBox](#editbox-component)
|
||||
[Button](#button-component), [Slider](#slider-component), [CheckBox](#checkbox-component), [Tabs](#tabs-component) ([Tab](#tab-component)), [EditBox](#editbox-component), [BarGraph](#bargraph-component)
|
||||
|
||||
## [Examples](#examples)
|
||||
|
||||
|
|
@ -448,6 +448,30 @@ _Image path (see [sprite](#sprite-widget)) for src descriptions_
|
|||
|
||||
_Initial text content_
|
||||
|
||||
## BarGraph component
|
||||
|
||||
### `<BarGraph>`
|
||||
|
||||
### A bar graph widget
|
||||
|
||||
#### Parameters
|
||||
|
||||
`limit_min`: **float**
|
||||
|
||||
_Minimum limit value_
|
||||
|
||||
`limit_max`: **float**
|
||||
|
||||
_Maximum limit value_
|
||||
|
||||
`unit`: **string** (default: "")
|
||||
|
||||
_Unit type, for example "%" or "ms"_
|
||||
|
||||
`capacity`: **int** (default: 50)
|
||||
|
||||
_Value count_
|
||||
|
||||
---
|
||||
|
||||
# Examples
|
||||
|
|
|
|||
|
|
@ -0,0 +1,237 @@
|
|||
use std::{cell::RefCell, collections::VecDeque, rc::Rc};
|
||||
|
||||
use glam::Vec2;
|
||||
use taffy::{
|
||||
FlexDirection, JustifyContent,
|
||||
prelude::{auto, length, percent},
|
||||
};
|
||||
|
||||
use crate::{
|
||||
components::{Component, ComponentBase, ComponentTrait, RefreshData},
|
||||
drawing::{self, GradientMode, PrimitiveExtent, RenderPrimitive},
|
||||
event::CallbackDataCommon,
|
||||
i18n::Translation,
|
||||
layout::{WidgetID, WidgetPair},
|
||||
renderer_vk::text::{FontWeight, HorizontalAlign, TextStyle},
|
||||
widget::{
|
||||
ConstructEssentials,
|
||||
custom_draw::{WidgetCustomDraw, WidgetCustomDrawParams},
|
||||
div::WidgetDiv,
|
||||
label::{WidgetLabel, WidgetLabelParams},
|
||||
rectangle::{WidgetRectangle, WidgetRectangleParams},
|
||||
util::WLength,
|
||||
},
|
||||
};
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct Params {
|
||||
pub style: taffy::Style,
|
||||
pub limits: (f32, f32),
|
||||
pub unit: String,
|
||||
pub capacity: u32,
|
||||
}
|
||||
|
||||
pub struct ValueCell {
|
||||
pub value: f32,
|
||||
pub color: drawing::Color,
|
||||
}
|
||||
|
||||
struct State {
|
||||
limits: (f32, f32), /* min - max */
|
||||
values: VecDeque<ValueCell>,
|
||||
}
|
||||
|
||||
#[allow(clippy::struct_field_names)]
|
||||
struct Data {
|
||||
#[allow(dead_code)]
|
||||
id_root: WidgetID,
|
||||
|
||||
id_label_val_min: WidgetID,
|
||||
id_label_val_max: WidgetID,
|
||||
|
||||
unit: String,
|
||||
capacity: u32,
|
||||
}
|
||||
|
||||
pub struct ComponentBarGraph {
|
||||
base: ComponentBase,
|
||||
data: Rc<Data>,
|
||||
state: Rc<RefCell<State>>,
|
||||
}
|
||||
|
||||
impl ComponentTrait for ComponentBarGraph {
|
||||
fn base(&self) -> &ComponentBase {
|
||||
&self.base
|
||||
}
|
||||
|
||||
fn base_mut(&mut self) -> &mut ComponentBase {
|
||||
&mut self.base
|
||||
}
|
||||
|
||||
fn refresh(&self, data: &mut RefreshData) {
|
||||
let state = self.state.borrow();
|
||||
self.update_limits_text(&state, data.common);
|
||||
}
|
||||
}
|
||||
|
||||
impl ComponentBarGraph {
|
||||
fn update_limits_text(&self, state: &State, c: &mut CallbackDataCommon) -> Option<()> {
|
||||
let mut label_val_min = c.state.widgets.get_as::<WidgetLabel>(self.data.id_label_val_min)?;
|
||||
let mut label_val_max = c.state.widgets.get_as::<WidgetLabel>(self.data.id_label_val_max)?;
|
||||
|
||||
label_val_min.set_text(
|
||||
c,
|
||||
Translation::from_raw_text_string(format!("{}{}", state.limits.0, self.data.unit)),
|
||||
);
|
||||
label_val_max.set_text(
|
||||
c,
|
||||
Translation::from_raw_text_string(format!("{}{}", state.limits.1, self.data.unit)),
|
||||
);
|
||||
|
||||
Some(())
|
||||
}
|
||||
|
||||
pub fn set_limits(&self, c: &mut CallbackDataCommon, limits: (f32, f32)) {
|
||||
let mut state = self.state.borrow_mut();
|
||||
state.limits = limits;
|
||||
self.update_limits_text(&state, c);
|
||||
}
|
||||
|
||||
pub fn push_value(&self, cell: ValueCell) {
|
||||
let mut state = self.state.borrow_mut();
|
||||
if state.values.len() > self.data.capacity as usize {
|
||||
state.values.pop_front();
|
||||
}
|
||||
state.values.push_back(cell);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn construct(
|
||||
ess: &mut ConstructEssentials,
|
||||
mut params: Params,
|
||||
) -> anyhow::Result<(WidgetPair, Rc<ComponentBarGraph>)> {
|
||||
params.style.flex_direction = FlexDirection::Row;
|
||||
params.style.gap = length(4.0);
|
||||
|
||||
// override style
|
||||
let (root, _) = ess.layout.add_child(ess.parent, WidgetDiv::create(), params.style)?;
|
||||
|
||||
let (vertical_texts, _) = ess.layout.add_child(
|
||||
root.id,
|
||||
WidgetDiv::create(),
|
||||
taffy::Style {
|
||||
justify_content: Some(JustifyContent::SpaceBetween),
|
||||
flex_direction: FlexDirection::Column,
|
||||
size: taffy::Size {
|
||||
width: auto(),
|
||||
height: percent(1.0),
|
||||
},
|
||||
..Default::default()
|
||||
},
|
||||
)?;
|
||||
|
||||
let (rect, _) = ess.layout.add_child(
|
||||
root.id,
|
||||
WidgetRectangle::create(WidgetRectangleParams {
|
||||
border: 2.0,
|
||||
border_color: drawing::Color::new(1.0, 1.0, 1.0, 0.5),
|
||||
round: WLength::Units(3.0),
|
||||
gradient: GradientMode::Vertical,
|
||||
color: drawing::Color::new(0.0, 0.0, 0.0, 0.6),
|
||||
..Default::default()
|
||||
}),
|
||||
taffy::Style {
|
||||
position: taffy::Position::Relative,
|
||||
size: taffy::Size {
|
||||
width: percent(1.0),
|
||||
height: percent(1.0),
|
||||
},
|
||||
..Default::default()
|
||||
},
|
||||
)?;
|
||||
|
||||
let state = Rc::new(RefCell::new(State {
|
||||
limits: params.limits,
|
||||
values: VecDeque::new(),
|
||||
}));
|
||||
|
||||
let (_, _) = ess.layout.add_child(
|
||||
rect.id,
|
||||
WidgetCustomDraw::create(WidgetCustomDrawParams {
|
||||
func: {
|
||||
let state = state.clone();
|
||||
Box::new(move |info| {
|
||||
let state = state.borrow();
|
||||
let (limit_min, limit_max) = state.limits;
|
||||
|
||||
let box_width = info.boundary.width();
|
||||
let box_height = info.boundary.height();
|
||||
|
||||
let bar_width = box_width / state.values.len() as f32;
|
||||
|
||||
for (idx, cell) in state.values.iter().enumerate() {
|
||||
let norm_value = ((cell.value - limit_min) / (limit_max - limit_min)).clamp(0.0, 1.0);
|
||||
let bar_height = norm_value * box_height;
|
||||
let bar_x = bar_width * idx as f32;
|
||||
let bar_y = box_height - bar_height;
|
||||
|
||||
info.primitives.push(RenderPrimitive::Rectangle(
|
||||
PrimitiveExtent {
|
||||
boundary: drawing::Boundary {
|
||||
pos: Vec2::new(bar_x, bar_y),
|
||||
size: Vec2::new(bar_width, bar_height),
|
||||
},
|
||||
transform: info.transform.transform,
|
||||
},
|
||||
drawing::Rectangle {
|
||||
color: cell.color,
|
||||
..Default::default()
|
||||
},
|
||||
));
|
||||
}
|
||||
})
|
||||
},
|
||||
}),
|
||||
taffy::Style {
|
||||
size: taffy::Size {
|
||||
width: percent(1.0),
|
||||
height: percent(1.0),
|
||||
},
|
||||
..Default::default()
|
||||
},
|
||||
)?;
|
||||
|
||||
let label_params = WidgetLabelParams {
|
||||
style: TextStyle {
|
||||
align: Some(HorizontalAlign::Right),
|
||||
weight: Some(FontWeight::Bold),
|
||||
size: Some(11.0),
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let label = WidgetLabel::create(&mut ess.layout.state, label_params.clone());
|
||||
let (label_val_max, _) = ess.layout.add_child(vertical_texts.id, label, Default::default())?;
|
||||
|
||||
let label = WidgetLabel::create(&mut ess.layout.state, label_params);
|
||||
let (label_val_min, _) = ess.layout.add_child(vertical_texts.id, label, Default::default())?;
|
||||
|
||||
let data = Rc::new(Data {
|
||||
id_root: root.id,
|
||||
id_label_val_min: label_val_min.id,
|
||||
id_label_val_max: label_val_max.id,
|
||||
unit: params.unit,
|
||||
capacity: params.capacity,
|
||||
});
|
||||
|
||||
let base = ComponentBase {
|
||||
id: root.id,
|
||||
lhandles: Vec::new(),
|
||||
};
|
||||
|
||||
let bar_graph = Rc::new(ComponentBarGraph { base, data, state });
|
||||
|
||||
ess.layout.defer_component_refresh(Component(bar_graph.clone()));
|
||||
Ok((root, bar_graph))
|
||||
}
|
||||
|
|
@ -7,6 +7,7 @@ use crate::{
|
|||
layout::WidgetID,
|
||||
};
|
||||
|
||||
pub mod bar_graph;
|
||||
pub mod button;
|
||||
pub mod checkbox;
|
||||
pub mod editbox;
|
||||
|
|
|
|||
|
|
@ -225,11 +225,13 @@ pub struct ImagePrimitive {
|
|||
pub round_units: u8,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct PrimitiveExtent {
|
||||
pub(super) boundary: Boundary,
|
||||
pub(super) transform: Mat4,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub enum RenderPrimitive {
|
||||
NewPass,
|
||||
Rectangle(PrimitiveExtent, Rectangle),
|
||||
|
|
|
|||
|
|
@ -377,7 +377,7 @@ impl Layout {
|
|||
self.registered_components_to_refresh.insert(*node_id, component.weak());
|
||||
}
|
||||
|
||||
/// Convenience function to avoid repeated `WidgetID` → `WidgetState` lookups.
|
||||
/// Convenience function to avoid repeated `WidgetID` → `WidgetState` look-ups.
|
||||
pub fn add_event_listener<U1: 'static, U2: 'static>(
|
||||
&self,
|
||||
widget_id: WidgetID,
|
||||
|
|
@ -883,6 +883,7 @@ impl Layout {
|
|||
widget::WidgetType::Label => drawing::Color::new(0.4, 1.0, 0.0, 1.0),
|
||||
widget::WidgetType::Sprite => drawing::Color::new(0.0, 0.8, 1.0, 1.0),
|
||||
widget::WidgetType::Rectangle => drawing::Color::new(1.0, 0.5, 0.2, 1.0),
|
||||
widget::WidgetType::CustomDraw => drawing::Color::new(1.0, 1.0, 1.0, 1.0),
|
||||
};
|
||||
|
||||
let line = format!(
|
||||
|
|
|
|||
|
|
@ -0,0 +1,56 @@
|
|||
use crate::{
|
||||
components::{Component, bar_graph},
|
||||
layout::WidgetID,
|
||||
parser::{AttribPair, ParserContext, process_component, style::parse_style},
|
||||
widget::ConstructEssentials,
|
||||
};
|
||||
|
||||
pub fn parse_component_bar_graph(
|
||||
ctx: &mut ParserContext,
|
||||
parent_id: WidgetID,
|
||||
attribs: &[AttribPair],
|
||||
tag_name: &str,
|
||||
) -> anyhow::Result<WidgetID> {
|
||||
let style = parse_style(ctx, attribs, tag_name);
|
||||
let mut limit_min = 0.0;
|
||||
let mut capacity = 50;
|
||||
let mut limit_max = 100.0;
|
||||
let mut unit = String::new();
|
||||
|
||||
for pair in attribs {
|
||||
let (key, value) = (pair.attrib.as_ref(), pair.value.as_ref());
|
||||
#[allow(clippy::single_match)]
|
||||
match key {
|
||||
"capacity" => {
|
||||
ctx.parse_check_i32(tag_name, key, value, &mut capacity);
|
||||
}
|
||||
"limit_min" => {
|
||||
ctx.parse_check_f32(tag_name, key, value, &mut limit_min);
|
||||
}
|
||||
"limit_max" => {
|
||||
ctx.parse_check_f32(tag_name, key, value, &mut limit_max);
|
||||
}
|
||||
"unit" => {
|
||||
unit = value.to_string();
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
let (widget, component) = bar_graph::construct(
|
||||
&mut ConstructEssentials {
|
||||
layout: ctx.layout,
|
||||
parent: parent_id,
|
||||
},
|
||||
bar_graph::Params {
|
||||
style,
|
||||
limits: (limit_min, limit_max),
|
||||
unit,
|
||||
capacity: capacity.try_into().unwrap_or(50),
|
||||
},
|
||||
)?;
|
||||
|
||||
process_component(ctx, Component(component), widget.id, attribs);
|
||||
|
||||
Ok(widget.id)
|
||||
}
|
||||
|
|
@ -1,3 +1,4 @@
|
|||
mod component_bar_graph;
|
||||
mod component_button;
|
||||
mod component_checkbox;
|
||||
mod component_editbox;
|
||||
|
|
@ -21,6 +22,7 @@ use crate::{
|
|||
layout::{Layout, LayoutParams, LayoutState, Widget, WidgetID, WidgetMap, WidgetPair},
|
||||
log::LogErr,
|
||||
parser::{
|
||||
component_bar_graph::parse_component_bar_graph,
|
||||
component_button::parse_component_button,
|
||||
component_checkbox::{CheckboxKind, parse_component_checkbox},
|
||||
component_editbox::parse_component_editbox,
|
||||
|
|
@ -201,7 +203,6 @@ impl Fetchable for ParserData {
|
|||
let casted = widget
|
||||
.get_as::<T>()
|
||||
.ok_or_else(|| anyhow::anyhow!("fetch_widget_as({id}): failed to cast"))?;
|
||||
|
||||
Ok(casted)
|
||||
}
|
||||
}
|
||||
|
|
@ -217,16 +218,40 @@ pub struct ParserState {
|
|||
}
|
||||
|
||||
impl ParserState {
|
||||
/// This function is suitable in cases if you don't want to pollute main parser state with dynamic IDs
|
||||
/// Use `instantiate_template` instead unless you want to handle `components` results yourself.
|
||||
/// Make sure not to drop them if you want to have your listener handles valid
|
||||
pub fn parse_template(
|
||||
/// Parse named <template> tag and process it.
|
||||
/// Preferred method of parsing templates. Same as `parse_template_only`,
|
||||
/// but it keeps components data in this `ParserState` object for you.
|
||||
/// The result can be safely dropped, all required event listeners and components
|
||||
/// will be kept intact in this `ParserState`.
|
||||
/// Resulting ParserData::components Vec will be left empty (they are moved into this `ParserState::data`)
|
||||
pub fn realize_template(
|
||||
&mut self,
|
||||
doc_params: &ParseDocumentParams,
|
||||
template_name: &str,
|
||||
layout: &mut Layout,
|
||||
widget_id: WidgetID,
|
||||
template_parameters: HashMap<Rc<str>, Rc<str>>,
|
||||
) -> anyhow::Result<ParserData> {
|
||||
let mut parser_data =
|
||||
self.parse_template_only(doc_params, template_name, layout, widget_id, template_parameters)?;
|
||||
// Collect components contained in this freshly-parsed template
|
||||
self.data.components.append(&mut parser_data.components);
|
||||
Ok(parser_data)
|
||||
}
|
||||
|
||||
/// Parse named <template> tag and process it.
|
||||
/// Semi-internal - This function is suitable in cases if you don't want to pollute
|
||||
/// the main parser state state with dynamic IDs (this won't propagate components!)
|
||||
/// Use `realize_template` (or in some rare cases: `instantiate_template`) instead unless you want to handle `components` results yourself.
|
||||
/// Make sure not to drop resulting ParserData if you want to have your listener handles valid
|
||||
/// (they are contained in components). Use `realize_template` instead if you don't want to think about it.
|
||||
pub fn parse_template_only(
|
||||
&self,
|
||||
doc_params: &ParseDocumentParams,
|
||||
template_name: &str,
|
||||
layout: &mut Layout,
|
||||
widget_id: WidgetID,
|
||||
template_parameters: HashMap<Rc<str>, Rc<str>>,
|
||||
) -> anyhow::Result<ParserData> {
|
||||
let Some(template) = self.data.templates.get(template_name) else {
|
||||
anyhow::bail!(
|
||||
|
|
@ -252,7 +277,13 @@ impl ParserState {
|
|||
Ok(ctx.data_local)
|
||||
}
|
||||
|
||||
/// Instantiate template by saving all the results into the main `ParserState`
|
||||
/// Parse named <template> tag and process it.
|
||||
/// Instantiate template by saving all the results into the main `ParserState`.
|
||||
/// Be aware you this function will save ALL parsed IDs and other metadata
|
||||
/// into your main ParserState context (deep move).
|
||||
/// You shouldn't instantiate the same template twice, to prevent ID name clash.
|
||||
/// Consider using `parse_template_only` or `realize_template` instead if you want
|
||||
/// to instantiate more than a single template of the same type.
|
||||
pub fn instantiate_template(
|
||||
&mut self,
|
||||
doc_params: &ParseDocumentParams,
|
||||
|
|
@ -261,7 +292,7 @@ impl ParserState {
|
|||
widget_id: WidgetID,
|
||||
template_parameters: HashMap<Rc<str>, Rc<str>>,
|
||||
) -> anyhow::Result<()> {
|
||||
let mut data_local = self.parse_template(doc_params, template_name, layout, widget_id, template_parameters)?;
|
||||
let mut data_local = self.parse_template_only(doc_params, template_name, layout, widget_id, template_parameters)?;
|
||||
|
||||
self.data.take_results_from(&mut data_local);
|
||||
Ok(())
|
||||
|
|
@ -1056,6 +1087,7 @@ fn parse_child<'a>(
|
|||
)?);
|
||||
}
|
||||
"EditBox" => new_widget_id = Some(parse_component_editbox(ctx, parent_id, &attribs, tag_name)?),
|
||||
"BarGraph" => new_widget_id = Some(parse_component_bar_graph(ctx, parent_id, &attribs, tag_name)?),
|
||||
"Tabs" => {
|
||||
new_widget_id = Some(parse_component_tabs(ctx, child_node, parent_id, &attribs, tag_name)?);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -187,7 +187,7 @@ impl InnerAtlas {
|
|||
// Grow each dimension by a factor of 2. The growth factor was chosen to match the growth
|
||||
// factor of `Vec`.`
|
||||
let new_size = (self.size * GROWTH_FACTOR).min(self.max_texture_dimension_2d);
|
||||
log::info!("Grow {:?} atlas {} → {new_size}", self.kind, self.size);
|
||||
log::debug!("Grow {:?} atlas {} → {new_size}", self.kind, self.size);
|
||||
|
||||
self.packer.grow(size2(new_size as i32, new_size as i32));
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,65 @@
|
|||
use slotmap::Key;
|
||||
|
||||
use crate::{
|
||||
drawing::{self},
|
||||
layout::WidgetID,
|
||||
stack,
|
||||
widget::WidgetStateFlags,
|
||||
};
|
||||
|
||||
use super::{WidgetObj, WidgetState};
|
||||
|
||||
pub struct CustomDrawArgs<'a> {
|
||||
pub boundary: &'a drawing::Boundary,
|
||||
pub transform: &'a stack::Transform,
|
||||
pub primitives: &'a mut Vec<drawing::RenderPrimitive>,
|
||||
}
|
||||
|
||||
pub struct WidgetCustomDrawParams {
|
||||
pub func: Box<dyn Fn(CustomDrawArgs)>,
|
||||
}
|
||||
|
||||
// FIXME: bring up a better name for this
|
||||
pub struct WidgetCustomDraw {
|
||||
pub params: WidgetCustomDrawParams,
|
||||
id: WidgetID,
|
||||
}
|
||||
|
||||
impl WidgetCustomDraw {
|
||||
pub fn create(params: WidgetCustomDrawParams) -> WidgetState {
|
||||
WidgetState::new(
|
||||
WidgetStateFlags::default(),
|
||||
Box::new(Self {
|
||||
params,
|
||||
id: WidgetID::null(),
|
||||
}),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl WidgetObj for WidgetCustomDraw {
|
||||
fn draw(&mut self, state: &mut super::DrawState, _params: &super::DrawParams) {
|
||||
let boundary = drawing::Boundary::construct_relative(state.transform_stack);
|
||||
(*self.params.func)(CustomDrawArgs {
|
||||
primitives: state.primitives,
|
||||
boundary: &boundary,
|
||||
transform: state.transform_stack.get(),
|
||||
});
|
||||
}
|
||||
|
||||
fn get_id(&self) -> WidgetID {
|
||||
self.id
|
||||
}
|
||||
|
||||
fn set_id(&mut self, id: WidgetID) {
|
||||
self.id = id;
|
||||
}
|
||||
|
||||
fn get_type(&self) -> super::WidgetType {
|
||||
super::WidgetType::Rectangle
|
||||
}
|
||||
|
||||
fn debug_print(&self) -> String {
|
||||
String::default()
|
||||
}
|
||||
}
|
||||
|
|
@ -17,7 +17,7 @@ use crate::{
|
|||
|
||||
use super::{WidgetObj, WidgetState};
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct WidgetLabelParams {
|
||||
pub content: Translation,
|
||||
pub style: TextStyle,
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ use crate::{
|
|||
stack::{ScissorStack, TransformStack},
|
||||
};
|
||||
|
||||
pub mod custom_draw;
|
||||
pub mod div;
|
||||
pub mod image;
|
||||
pub mod label;
|
||||
|
|
@ -152,6 +153,7 @@ pub enum WidgetType {
|
|||
Label,
|
||||
Sprite,
|
||||
Rectangle,
|
||||
CustomDraw,
|
||||
}
|
||||
|
||||
impl WidgetType {
|
||||
|
|
@ -161,6 +163,7 @@ impl WidgetType {
|
|||
WidgetType::Label => "label",
|
||||
WidgetType::Sprite => "sprite",
|
||||
WidgetType::Rectangle => "rectangle",
|
||||
WidgetType::CustomDraw => "custom_draw",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -117,7 +117,7 @@ impl ContextMenu {
|
|||
par.insert(Rc::from("tooltip_str"), tooltip.generate(&mut globals.i18n()));
|
||||
}
|
||||
|
||||
let mut data_cell = inner_parser.parse_template(&doc_params, "Cell", layout, id_buttons, par)?;
|
||||
let mut data_cell = inner_parser.realize_template(&doc_params, "Cell", layout, id_buttons, par)?;
|
||||
|
||||
let button = data_cell.fetch_component_as::<ComponentButton>("button")?;
|
||||
let button_id = button.base().get_id();
|
||||
|
|
@ -136,7 +136,7 @@ impl ContextMenu {
|
|||
}
|
||||
|
||||
if idx < cells.len() - 1 {
|
||||
inner_parser.parse_template(&doc_params, "Separator", layout, id_buttons, Default::default())?;
|
||||
inner_parser.realize_template(&doc_params, "Separator", layout, id_buttons, Default::default())?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ use crate::{config::GeneralConfig, desktop_finder::DesktopFinder};
|
|||
|
||||
#[derive(Clone)]
|
||||
pub struct MonadoClient {
|
||||
pub id: i64,
|
||||
pub name: String,
|
||||
pub is_primary: bool,
|
||||
pub is_active: bool,
|
||||
|
|
@ -16,6 +17,25 @@ pub struct MonadoClient {
|
|||
pub is_io_active: bool,
|
||||
}
|
||||
|
||||
// see `wayvr/src/subsystem/monado_metrics/proto.rs` for documentation
|
||||
#[derive(Debug)]
|
||||
pub struct MonadoDumpSessionFrame {
|
||||
pub session_id: i64,
|
||||
pub frame_id: i64,
|
||||
pub predicted_frame_time_ns: u64,
|
||||
pub predicted_wake_up_time_ns: u64,
|
||||
pub predicted_gpu_done_time_ns: u64,
|
||||
pub predicted_display_time_ns: u64,
|
||||
pub predicted_display_period_ns: u64,
|
||||
pub display_time_ns: u64,
|
||||
pub when_predicted_ns: u64,
|
||||
pub when_wait_woke_ns: u64,
|
||||
pub when_begin_ns: u64,
|
||||
pub when_delivered_ns: u64,
|
||||
pub when_gpu_done_ns: u64,
|
||||
pub discarded: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub enum RecenterMode {
|
||||
FixFloor,
|
||||
|
|
@ -36,10 +56,12 @@ pub trait DashInterface<T> {
|
|||
) -> anyhow::Result<WvrProcessHandle>;
|
||||
fn process_list(&mut self, data: &mut T) -> anyhow::Result<Vec<WvrProcess>>;
|
||||
fn process_terminate(&mut self, data: &mut T, handle: WvrProcessHandle) -> anyhow::Result<()>;
|
||||
fn monado_client_list(&mut self, data: &mut T) -> anyhow::Result<Vec<MonadoClient>>;
|
||||
fn monado_client_list(&mut self, data: &mut T, filtered: bool) -> anyhow::Result<Vec<MonadoClient>>;
|
||||
fn monado_client_focus(&mut self, data: &mut T, name: &str) -> anyhow::Result<()>;
|
||||
fn monado_brightness_get(&mut self, data: &mut T) -> Option<f32>;
|
||||
fn monado_brightness_set(&mut self, data: &mut T, brightness: f32) -> Option<()>;
|
||||
fn monado_metrics_set_enabled(&mut self, data: &mut T, enabled: bool) -> bool;
|
||||
fn monado_metrics_dump_session_frames(&mut self, data: &mut T) -> Vec<MonadoDumpSessionFrame>;
|
||||
fn recenter_playspace(&mut self, data: &mut T, mode: RecenterMode) -> anyhow::Result<()>;
|
||||
fn desktop_finder<'a>(&'a mut self, data: &'a mut T) -> &'a mut DesktopFinder;
|
||||
fn general_config<'a>(&'a mut self, data: &'a mut T) -> &'a mut GeneralConfig;
|
||||
|
|
|
|||
|
|
@ -63,6 +63,8 @@ pub struct DashInterfaceEmulated {
|
|||
general_config: GeneralConfig,
|
||||
monado_clients: Vec<dash_interface::MonadoClient>,
|
||||
brightness: f32,
|
||||
debug_frame_id: u64,
|
||||
debug_counter: u64,
|
||||
}
|
||||
|
||||
impl DashInterfaceEmulated {
|
||||
|
|
@ -86,6 +88,7 @@ impl DashInterfaceEmulated {
|
|||
|
||||
let monado_clients = vec![
|
||||
dash_interface::MonadoClient {
|
||||
id: 1,
|
||||
name: String::from("The Best VR Game 3000"),
|
||||
is_active: true,
|
||||
is_focused: true,
|
||||
|
|
@ -95,6 +98,7 @@ impl DashInterfaceEmulated {
|
|||
is_visible: true,
|
||||
},
|
||||
dash_interface::MonadoClient {
|
||||
id: 2,
|
||||
name: String::from("Second app"),
|
||||
is_active: true,
|
||||
is_focused: false,
|
||||
|
|
@ -104,6 +108,7 @@ impl DashInterfaceEmulated {
|
|||
is_visible: true,
|
||||
},
|
||||
dash_interface::MonadoClient {
|
||||
id: 3,
|
||||
name: String::from("Third app"),
|
||||
is_active: true,
|
||||
is_focused: false,
|
||||
|
|
@ -121,6 +126,8 @@ impl DashInterfaceEmulated {
|
|||
general_config,
|
||||
monado_clients,
|
||||
brightness: 1.0,
|
||||
debug_counter: 0,
|
||||
debug_frame_id: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -229,7 +236,11 @@ impl DashInterface<()> for DashInterfaceEmulated {
|
|||
|
||||
fn toggle_dashboard(&mut self, _data: &mut ()) {}
|
||||
|
||||
fn monado_client_list(&mut self, _data: &mut ()) -> anyhow::Result<Vec<dash_interface::MonadoClient>> {
|
||||
fn monado_client_list(
|
||||
&mut self,
|
||||
_data: &mut (),
|
||||
_filtered: bool,
|
||||
) -> anyhow::Result<Vec<dash_interface::MonadoClient>> {
|
||||
Ok(self.monado_clients.clone())
|
||||
}
|
||||
|
||||
|
|
@ -256,4 +267,32 @@ impl DashInterface<()> for DashInterfaceEmulated {
|
|||
self.brightness = brightness;
|
||||
Some(())
|
||||
}
|
||||
|
||||
fn monado_metrics_set_enabled(&mut self, _: &mut (), _enabled: bool) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn monado_metrics_dump_session_frames(&mut self, _: &mut ()) -> Vec<dash_interface::MonadoDumpSessionFrame> {
|
||||
// simulate """""typical""""" wired hmd timing graphs
|
||||
self.debug_frame_id += 1;
|
||||
self.debug_counter ^= self.debug_frame_id * 383;
|
||||
self.debug_counter ^= 0xdeadbeefbaddcafe;
|
||||
self.debug_counter >>= 1;
|
||||
vec![dash_interface::MonadoDumpSessionFrame {
|
||||
session_id: 123,
|
||||
frame_id: self.debug_frame_id as _,
|
||||
predicted_frame_time_ns: (((self.debug_frame_id) % 15) * 1000 * 1000) as _,
|
||||
predicted_wake_up_time_ns: (((self.debug_frame_id * 2) % 16) * 1000 * 1000) as _,
|
||||
predicted_gpu_done_time_ns: (((self.debug_frame_id * 3) % 17) * 1000 * 1000) as _,
|
||||
predicted_display_time_ns: (((self.debug_frame_id * 4) % 14) * 1000 * 1000) as _,
|
||||
predicted_display_period_ns: (((self.debug_frame_id * 5) % 13) * 1000 * 1000) as _,
|
||||
display_time_ns: (((self.debug_frame_id * 7) % 12) * 1000 * 1000) as _,
|
||||
when_predicted_ns: (((self.debug_frame_id * 53) % 11) * 1000 * 1000) as _,
|
||||
when_wait_woke_ns: (((self.debug_frame_id * 23) % 10) * 1000 * 1000) as _,
|
||||
when_begin_ns: (((self.debug_frame_id * 13) % 9) * 1000 * 1000) as _,
|
||||
when_delivered_ns: (((self.debug_frame_id * 11) % 8) * 1000 * 1000) as _,
|
||||
when_gpu_done_ns: (((self.debug_frame_id * 12) % 7) * 1000 * 1000) as _,
|
||||
discarded: self.debug_frame_id.is_multiple_of(5),
|
||||
}]
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
pub mod astr_containers;
|
||||
pub mod async_executor;
|
||||
pub mod audio;
|
||||
pub mod cache_dir;
|
||||
pub mod common;
|
||||
|
|
|
|||
Loading…
Reference in New Issue