refactor overlay windowing

This commit is contained in:
galister
2025-10-05 18:49:59 +09:00
parent 40cc27f7b0
commit aa64310d96
46 changed files with 1329 additions and 1342 deletions

View File

@@ -1,210 +0,0 @@
use std::sync::{Arc, LazyLock};
#[cfg(feature = "openxr")]
use openxr as xr;
use glam::{Affine3A, Vec3, Vec3A};
use slotmap::HopSlotMap;
use thiserror::Error;
use crate::{
config::AStrSetExt,
overlays::{
anchor::create_anchor,
keyboard::{builder::create_keyboard, KEYBOARD_NAME},
screen::create_screens,
watch::{create_watch, WATCH_NAME},
},
state::AppState,
};
use super::overlay::{OverlayData, OverlayID};
#[derive(Error, Debug)]
pub enum BackendError {
#[error("backend not supported")]
NotSupported,
#[cfg(feature = "openxr")]
#[error("OpenXR Error: {0:?}")]
OpenXrError(#[from] xr::sys::Result),
#[error("Shutdown")]
Shutdown,
#[error("Restart")]
Restart,
#[error("Fatal: {0:?}")]
Fatal(#[from] anyhow::Error),
}
pub struct OverlayContainer<T>
where
T: Default,
{
overlays: HopSlotMap<OverlayID, OverlayData<T>>,
}
impl<T> OverlayContainer<T>
where
T: Default,
{
pub fn new(app: &mut AppState, headless: bool) -> anyhow::Result<Self> {
let mut overlays = HopSlotMap::with_key();
let mut show_screens = app.session.config.show_screens.clone();
let mut maybe_keymap = None;
if headless {
log::info!("Running in headless mode; keyboard will be en-US");
} else {
match create_screens(app) {
Ok((data, keymap)) => {
if show_screens.is_empty()
&& let Some((_, s, _)) = data.screens.first()
{
show_screens.arc_set(s.name.clone());
}
for (meta, mut state, backend) in data.screens {
if show_screens.arc_get(state.name.as_ref()) {
state.show_hide = true;
}
overlays.insert(OverlayData::<T> {
state,
..OverlayData::from_backend(backend)
});
app.screens.push(meta);
}
maybe_keymap = keymap;
}
Err(e) => log::error!("Unable to initialize screens: {e:?}"),
}
}
let anchor = create_anchor(app)?;
overlays.insert(anchor);
let mut watch = create_watch::<T>(app)?;
watch.state.want_visible = true;
overlays.insert(watch);
let mut keyboard = create_keyboard(app, maybe_keymap)?;
keyboard.state.show_hide = show_screens.arc_get(KEYBOARD_NAME);
keyboard.state.want_visible = false;
overlays.insert(keyboard);
Ok(Self { overlays })
}
pub fn mut_by_selector(&mut self, selector: &OverlaySelector) -> Option<&mut OverlayData<T>> {
match selector {
OverlaySelector::Id(id) => self.mut_by_id(*id),
OverlaySelector::Name(name) => self.mut_by_name(name),
}
}
pub fn remove_by_selector(&mut self, selector: &OverlaySelector) -> Option<OverlayData<T>> {
match selector {
OverlaySelector::Id(id) => self.overlays.remove(*id),
OverlaySelector::Name(name) => {
self.lookup(name).and_then(|id| self.overlays.remove(id))
}
}
}
pub fn get_by_id(&mut self, id: OverlayID) -> Option<&OverlayData<T>> {
self.overlays.get(id)
}
pub fn mut_by_id(&mut self, id: OverlayID) -> Option<&mut OverlayData<T>> {
self.overlays.get_mut(id)
}
pub fn get_by_name<'a>(&'a mut self, name: &str) -> Option<&'a OverlayData<T>> {
self.overlays.values().find(|o| *o.state.name == *name)
}
pub fn mut_by_name<'a>(&'a mut self, name: &str) -> Option<&'a mut OverlayData<T>> {
self.overlays.values_mut().find(|o| *o.state.name == *name)
}
pub fn iter(&self) -> impl Iterator<Item = (OverlayID, &'_ OverlayData<T>)> {
self.overlays.iter()
}
pub fn iter_mut(&mut self) -> impl Iterator<Item = (OverlayID, &'_ mut OverlayData<T>)> {
self.overlays.iter_mut()
}
pub fn values(&self) -> impl Iterator<Item = &'_ OverlayData<T>> {
self.overlays.values()
}
pub fn values_mut(&mut self) -> impl Iterator<Item = &'_ mut OverlayData<T>> {
self.overlays.values_mut()
}
pub fn lookup(&self, name: &str) -> Option<OverlayID> {
self.overlays
.iter()
.find(|(_, v)| v.state.name.as_ref() == name)
.map(|(k, _)| k)
}
pub fn add(&mut self, overlay: OverlayData<T>) -> OverlayID {
self.overlays.insert(overlay)
}
pub fn show_hide(&mut self, app: &mut AppState) {
let any_shown = self
.overlays
.values()
.any(|o| o.state.show_hide && o.state.want_visible);
if !any_shown {
static ANCHOR_LOCAL: LazyLock<Affine3A> =
LazyLock::new(|| Affine3A::from_translation(Vec3::NEG_Z));
let hmd = snap_upright(app.input_state.hmd, Vec3A::Y);
app.anchor = hmd * *ANCHOR_LOCAL;
}
self.overlays.values_mut().for_each(|o| {
if o.state.show_hide {
o.state.want_visible = !any_shown;
if o.state.want_visible
&& app.session.config.realign_on_showhide
&& o.state.recenter
{
o.state.reset(app, false);
}
}
// toggle watch back on if it was hidden
if !any_shown && *o.state.name == *WATCH_NAME {
o.state.reset(app, true);
}
});
}
}
#[derive(Clone, Debug)]
pub enum OverlaySelector {
Id(OverlayID),
Name(Arc<str>),
}
pub fn snap_upright(transform: Affine3A, up_dir: Vec3A) -> Affine3A {
if transform.x_axis.dot(up_dir).abs() < 0.2 {
let scale = transform.x_axis.length();
let col_z = transform.z_axis.normalize();
let col_y = up_dir;
let col_x = col_y.cross(col_z);
let col_y = col_z.cross(col_x).normalize();
let col_x = col_x.normalize();
Affine3A::from_cols(
col_x * scale,
col_y * scale,
col_z * scale,
transform.translation,
)
} else {
transform
}
}

View File

@@ -2,20 +2,18 @@ use std::f32::consts::PI;
use std::process::{Child, Command};
use std::{collections::VecDeque, time::Instant};
use glam::{Affine3A, Vec2, Vec3, Vec3A, Vec3Swizzles};
use glam::{Affine3A, Vec2, Vec3A, Vec3Swizzles};
use smallvec::{smallvec, SmallVec};
use crate::backend::common::OverlaySelector;
use crate::backend::overlay::Positioning;
use crate::config::AStrMapExt;
use crate::overlays::anchor::ANCHOR_NAME;
use crate::state::{AppSession, AppState};
use crate::subsystem::input::KeyboardFocus;
use crate::windowing::manager::OverlayWindowManager;
use crate::windowing::window::{OverlayWindowData, OverlayWindowState, Positioning};
use crate::windowing::{OverlayID, OverlaySelector};
use super::overlay::{OverlayID, OverlayState};
use super::task::{TaskContainer, TaskType};
use super::{common::OverlayContainer, overlay::OverlayData};
pub struct TrackedDevice {
pub soc: Option<f32>,
@@ -282,17 +280,17 @@ pub enum PointerMode {
Special,
}
fn update_focus(focus: &mut KeyboardFocus, state: &OverlayState) {
if let Some(f) = &state.keyboard_focus
fn update_focus(focus: &mut KeyboardFocus, overlay_keyboard_focus: &Option<KeyboardFocus>) {
if let Some(f) = &overlay_keyboard_focus
&& *focus != *f
{
log::info!("Setting keyboard focus to {:?}", *f);
log::debug!("Setting keyboard focus to {:?}", *f);
*focus = *f;
}
}
pub fn interact<O>(
overlays: &mut OverlayContainer<O>,
overlays: &mut OverlayWindowManager<O>,
app: &mut AppState,
) -> [(f32, Option<Haptics>); 2]
where
@@ -312,7 +310,7 @@ where
#[allow(clippy::too_many_lines, clippy::cognitive_complexity)]
fn interact_hand<O>(
idx: usize,
overlays: &mut OverlayContainer<O>,
overlays: &mut OverlayWindowManager<O>,
app: &mut AppState,
) -> (f32, Option<Haptics>)
where
@@ -332,7 +330,7 @@ where
let Some(mut hit) = pointer.get_nearest_hit(overlays) else {
if let Some(hovered_id) = pointer.interaction.hovered_id.take() {
if let Some(hovered) = overlays.mut_by_id(hovered_id) {
hovered.backend.on_left(app, idx);
hovered.config.backend.on_left(app, idx);
}
pointer = &mut app.input_state.pointers[idx];
pointer.interaction.hovered_id = None;
@@ -348,7 +346,7 @@ where
mode: pointer.interaction.mode,
..Default::default()
};
clicked.backend.on_pointer(app, &hit, false);
clicked.config.backend.on_pointer(app, &hit, false);
}
return (0.0, None); // no hit
};
@@ -360,7 +358,7 @@ where
if Some(pointer.idx) == old_hovered.primary_pointer {
old_hovered.primary_pointer = None;
}
old_hovered.backend.on_left(app, idx);
old_hovered.config.backend.on_left(app, idx);
pointer = &mut app.input_state.pointers[idx];
}
let Some(hovered) = overlays.mut_by_id(hit.overlay) else {
@@ -381,11 +379,17 @@ where
}
#[cfg(debug_assertions)]
log::trace!("Hit: {} {:?}", hovered.state.name, hit);
log::trace!("Hit: {} {:?}", hovered.config.name, hit);
if pointer.now.grab && !pointer.before.grab && hovered.state.grabbable {
update_focus(&mut app.hid_provider.keyboard_focus, &hovered.state);
pointer.start_grab(hit.overlay, hovered, &mut app.tasks);
let hovered_state = hovered.config.active_state.as_mut().unwrap();
if pointer.now.grab && !pointer.before.grab && hovered_state.grabbable {
update_focus(
&mut app.hid_provider.keyboard_focus,
&hovered.config.keyboard_focus,
);
pointer.start_grab(hit.overlay, hovered_state, &mut app.tasks);
log::debug!("Hand {}: grabbed {}", hit.pointer, hovered.config.name);
return (
hit.dist,
Some(Haptics {
@@ -398,7 +402,7 @@ where
// Pass mouse motion events only if not scrolling
// (allows scrolling on all Chromium-based applications)
let haptics = hovered.backend.on_hover(app, &hit);
let haptics = hovered.config.backend.on_hover(app, &hit);
pointer = &mut app.input_state.pointers[idx];
@@ -414,55 +418,66 @@ where
.frame_meta()
.is_some_and(|e| e.extent[0] >= e.extent[1]);
// re-borrow
let hovered_state = hovered.config.active_state.as_mut().unwrap();
if can_curve {
let cur = hovered.state.curvature.unwrap_or(0.0);
let cur = hovered_state.curvature.unwrap_or(0.0);
let new = scroll_y.mul_add(-0.01, cur).min(0.5);
if new <= f32::EPSILON {
hovered.state.curvature = None;
hovered_state.curvature = None;
} else {
hovered.state.curvature = Some(new);
hovered_state.curvature = Some(new);
}
} else {
hovered.state.curvature = None;
hovered_state.curvature = None;
}
} else {
hovered.backend.on_scroll(app, &hit, scroll_y, scroll_x);
hovered
.config
.backend
.on_scroll(app, &hit, scroll_y, scroll_x);
}
pointer = &mut app.input_state.pointers[idx];
}
if pointer.now.click && !pointer.before.click {
pointer.interaction.clicked_id = Some(hit.overlay);
update_focus(&mut app.hid_provider.keyboard_focus, &hovered.state);
hovered.backend.on_pointer(app, &hit, true);
update_focus(
&mut app.hid_provider.keyboard_focus,
&hovered.config.keyboard_focus,
);
hovered.config.backend.on_pointer(app, &hit, true);
} else if !pointer.now.click && pointer.before.click {
if let Some(clicked_id) = pointer.interaction.clicked_id.take() {
if let Some(clicked) = overlays.mut_by_id(clicked_id) {
clicked.backend.on_pointer(app, &hit, false);
clicked.config.backend.on_pointer(app, &hit, false);
}
} else {
hovered.backend.on_pointer(app, &hit, false);
hovered.config.backend.on_pointer(app, &hit, false);
}
}
(hit.dist, haptics)
}
impl Pointer {
fn get_nearest_hit<O>(&mut self, overlays: &mut OverlayContainer<O>) -> Option<PointerHit>
fn get_nearest_hit<O>(&mut self, overlays: &mut OverlayWindowManager<O>) -> Option<PointerHit>
where
O: Default,
{
let mut hits: SmallVec<[RayHit; 8]> = smallvec!();
for (id, overlay) in overlays.iter() {
if !overlay.state.want_visible || !overlay.state.interactable {
let Some(overlay_state) = overlay.config.active_state.as_ref() else {
continue;
};
if !overlay_state.interactable {
continue;
}
if let Some(hit) = self.ray_test(
id,
&overlay.state.transform,
overlay.state.curvature.as_ref(),
&overlay_state.transform,
overlay_state.curvature.as_ref(),
) {
if hit.dist.is_infinite() || hit.dist.is_nan() {
continue;
@@ -477,6 +492,7 @@ impl Pointer {
let overlay = overlays.mut_by_id(hit.overlay).unwrap(); // safe because we just got the id from the overlay
let Some(uv) = overlay
.config
.backend
.as_mut()
.get_interaction_transform()
@@ -502,26 +518,24 @@ impl Pointer {
None
}
fn start_grab<O>(
fn start_grab(
&mut self,
id: OverlayID,
overlay: &mut OverlayData<O>,
state: &mut OverlayWindowState,
tasks: &mut TaskContainer,
) where
O: Default,
{
) {
let offset = self
.pose
.inverse()
.transform_point3a(overlay.state.transform.translation);
.transform_point3a(state.transform.translation);
self.interaction.grabbed = Some(GrabData {
offset,
grabbed_id: id,
old_curvature: overlay.state.curvature,
old_curvature: state.curvature,
grab_all: matches!(self.interaction.mode, PointerMode::Right),
});
overlay.state.positioning = match overlay.state.positioning {
state.positioning = match state.positioning {
Positioning::FollowHand { hand, lerp } => Positioning::FollowHandPaused { hand, lerp },
Positioning::FollowHead { lerp } => Positioning::FollowHeadPaused { lerp },
x => x,
@@ -531,29 +545,26 @@ impl Pointer {
tasks.enqueue(TaskType::Overlay(
OverlaySelector::Name(ANCHOR_NAME.clone()),
Box::new(|app, o| {
o.transform = app.anchor
* Affine3A::from_scale_rotation_translation(
Vec3::ONE * o.spawn_scale,
o.spawn_rotation,
o.spawn_point.into(),
);
o.dirty = true;
o.want_visible = true;
o.saved_transform = Some(app.anchor);
o.activate(app);
}),
));
log::info!("Hand {}: grabbed {}", self.idx, overlay.state.name);
}
fn handle_grabbed<O>(idx: usize, overlay: &mut OverlayData<O>, app: &mut AppState)
fn handle_grabbed<O>(idx: usize, overlay: &mut OverlayWindowData<O>, app: &mut AppState)
where
O: Default,
{
let mut pointer = &mut app.input_state.pointers[idx];
let Some(overlay_state) = overlay.config.active_state.as_mut() else {
return;
};
let pointer = &mut app.input_state.pointers[idx];
if pointer.now.grab {
if let Some(grab_data) = pointer.interaction.grabbed.as_mut() {
if pointer.now.click {
pointer.interaction.mode = PointerMode::Special;
let cur_scale = overlay.state.transform.x_axis.length();
let cur_scale = overlay_state.transform.x_axis.length();
if cur_scale < 0.1 && pointer.now.scroll_y > 0.0 {
return;
}
@@ -561,24 +572,22 @@ impl Pointer {
return;
}
overlay.state.transform.matrix3 = overlay
.state
overlay_state.transform.matrix3 = overlay_state
.transform
.matrix3
.mul_scalar(0.025f32.mul_add(-pointer.now.scroll_y, 1.0));
} else if app.session.config.allow_sliding && pointer.now.scroll_y.is_finite() {
grab_data.offset.z -= pointer.now.scroll_y * 0.05;
}
overlay.state.transform.translation =
overlay_state.transform.translation =
pointer.pose.transform_point3a(grab_data.offset);
overlay.state.realign(&app.input_state.hmd);
overlay.state.dirty = true;
overlay.config.realign(&app.input_state.hmd);
} else {
log::error!("Grabbed overlay does not exist");
pointer.interaction.grabbed = None;
}
} else {
overlay.state.positioning = match overlay.state.positioning {
overlay_state.positioning = match overlay_state.positioning {
Positioning::FollowHandPaused { hand, lerp } => {
Positioning::FollowHand { hand, lerp }
}
@@ -586,41 +595,17 @@ impl Pointer {
x => x,
};
let save_success = overlay.state.save_transform(app);
// re-borrow
pointer = &mut app.input_state.pointers[idx];
if save_success {
if let Some(grab_data) = pointer.interaction.grabbed.as_ref()
&& overlay.state.curvature != grab_data.old_curvature
{
if let Some(val) = overlay.state.curvature {
app.session
.config
.curve_values
.arc_set(overlay.state.name.clone(), val);
} else {
let ref_name = overlay.state.name.as_ref();
app.session.config.curve_values.arc_rm(ref_name);
}
}
app.session.config.transform_values.arc_set(
overlay.state.name.clone(),
overlay.state.saved_transform.unwrap(), // safe
);
}
pointer.interaction.grabbed = None;
overlay.config.save_transform(app);
// Hide anchor
app.tasks.enqueue(TaskType::Overlay(
OverlaySelector::Name(ANCHOR_NAME.clone()),
Box::new(|_app, o| {
o.want_visible = false;
o.deactivate();
}),
));
log::info!("Hand {}: dropped {}", idx, overlay.state.name);
log::debug!("Hand {}: dropped {}", idx, overlay.config.name);
}
}

View File

@@ -1,4 +1,3 @@
pub mod common;
pub mod input;
#[cfg(feature = "openvr")]
@@ -10,7 +9,23 @@ pub mod openxr;
#[cfg(feature = "wayvr")]
pub mod wayvr;
pub mod overlay;
pub mod set;
pub mod task;
use thiserror::Error;
#[derive(Error, Debug)]
pub enum BackendError {
#[error("backend not supported")]
NotSupported,
#[cfg(feature = "openxr")]
#[error("OpenXR Error: {0:?}")]
OpenXrError(#[from] ::openxr::sys::Result),
#[error("Shutdown")]
Shutdown,
#[error("Restart")]
Restart,
#[error("Fatal: {0:?}")]
Fatal(#[from] anyhow::Error),
}

View File

@@ -4,7 +4,7 @@ use glam::Affine3A;
use ovr_overlay::{pose::Matrix3x4, settings::SettingsManager, sys::HmdMatrix34_t};
use thiserror::Error;
use crate::backend::{common::BackendError, task::ColorChannel};
use crate::backend::{BackendError, task::ColorChannel};
pub trait Affine3AConvert {
fn from_affine(affine: &Affine3A) -> Self;

View File

@@ -1,6 +1,6 @@
use std::f32::consts::PI;
use std::sync::Arc;
use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::Arc;
use ash::vk::SubmitInfo;
use glam::{Affine3A, Vec3, Vec3A, Vec4};
@@ -8,7 +8,6 @@ use idmap::IdMap;
use ovr_overlay::overlay::OverlayManager;
use ovr_overlay::sys::ETrackingUniverseOrigin;
use vulkano::{
VulkanObject,
command_buffer::{
CommandBufferBeginInfo, CommandBufferLevel, CommandBufferUsage, RecordingCommandBuffer,
},
@@ -16,25 +15,26 @@ use vulkano::{
image::view::ImageView,
image::{Image, ImageLayout},
sync::{
AccessFlags, DependencyInfo, ImageMemoryBarrier, PipelineStages,
fence::{Fence, FenceCreateInfo},
AccessFlags, DependencyInfo, ImageMemoryBarrier, PipelineStages,
},
VulkanObject,
};
use wgui::gfx::WGfx;
use crate::backend::input::{Haptics, PointerHit};
use crate::backend::overlay::{
FrameMeta, OverlayBackend, OverlayData, OverlayState, ShouldRender, Z_ORDER_LINES,
};
use crate::graphics::CommandBuffers;
use crate::state::AppState;
use crate::windowing::backend::{FrameMeta, OverlayBackend, ShouldRender};
use crate::windowing::window::{OverlayWindowConfig, OverlayWindowData, OverlayWindowState};
use crate::windowing::Z_ORDER_LINES;
use super::overlay::OpenVrOverlayData;
static LINE_AUTO_INCREMENT: AtomicUsize = AtomicUsize::new(1);
pub(super) struct LinePool {
lines: IdMap<usize, OverlayData<OpenVrOverlayData>>,
lines: IdMap<usize, OverlayWindowData<OpenVrOverlayData>>,
view: Arc<ImageView>,
colors: [Vec4; 5],
}
@@ -75,12 +75,7 @@ impl LinePool {
pub fn allocate(&mut self) -> usize {
let id = LINE_AUTO_INCREMENT.fetch_add(1, Ordering::Relaxed);
let mut data = OverlayData::<OpenVrOverlayData> {
state: OverlayState {
name: Arc::from(format!("wlx-line{id}")),
show_hide: true,
..Default::default()
},
let data = OverlayWindowData::<OpenVrOverlayData> {
data: OpenVrOverlayData {
width: 0.002,
override_width: true,
@@ -88,12 +83,17 @@ impl LinePool {
image_dirty: true,
..Default::default()
},
..OverlayData::from_backend(Box::new(LineBackend {
view: self.view.clone(),
}))
..OverlayWindowData::from_config(OverlayWindowConfig {
name: Arc::from(format!("wlx-line{id}")),
default_state: OverlayWindowState {
..Default::default()
},
z_order: Z_ORDER_LINES,
..OverlayWindowConfig::from_backend(Box::new(LineBackend {
view: self.view.clone(),
}))
})
};
data.state.z_order = Z_ORDER_LINES;
data.state.dirty = true;
self.lines.insert(id, data);
id
@@ -137,8 +137,8 @@ impl LinePool {
fn draw_transform(&mut self, id: usize, transform: Affine3A, color: Vec4) {
if let Some(data) = self.lines.get_mut(id) {
data.state.want_visible = true;
data.state.transform = transform;
data.config.default_state.alpha = 1.0;
data.config.default_state.transform = transform;
data.data.color = color;
} else {
log::warn!("Line {id} does not exist");
@@ -153,10 +153,10 @@ impl LinePool {
) -> anyhow::Result<()> {
for data in self.lines.values_mut() {
data.after_input(overlay, app)?;
if data.state.want_visible {
if data.state.dirty {
if data.config.default_state.alpha > 0.01 {
if data.config.dirty {
data.upload_texture(overlay, &app.gfx);
data.state.dirty = false;
data.config.dirty = false;
}
data.upload_transform(universe.clone(), overlay);
@@ -168,7 +168,7 @@ impl LinePool {
pub fn mark_dirty(&mut self) {
for data in self.lines.values_mut() {
data.state.dirty = true;
data.config.dirty = true;
}
}
}

View File

@@ -17,7 +17,6 @@ use vulkano::{device::physical::PhysicalDevice, Handle, VulkanObject};
use crate::{
backend::{
common::{BackendError, OverlayContainer},
input::interact,
openvr::{
helpers::adjust_gain,
@@ -26,8 +25,8 @@ use crate::{
manifest::{install_manifest, uninstall_manifest},
overlay::OpenVrOverlayData,
},
overlay::{OverlayData, ShouldRender},
task::{SystemTask, TaskType},
BackendError,
},
graphics::{init_openvr_graphics, CommandBuffers},
overlays::{
@@ -36,6 +35,7 @@ use crate::{
},
state::AppState,
subsystem::notifications::NotificationManager,
windowing::{backend::ShouldRender, manager::OverlayWindowManager, window::OverlayWindowData},
};
#[cfg(feature = "wayvr")]
@@ -94,13 +94,13 @@ pub fn openvr_run(
names.iter().map(std::string::String::as_str).collect()
};
let mut state = {
let mut app = {
let (gfx, gfx_extras) = init_openvr_graphics(instance_extensions, device_extensions_fn)?;
AppState::from_graphics(gfx, gfx_extras)?
};
if show_by_default {
state.tasks.enqueue_at(
app.tasks.enqueue_at(
TaskType::System(SystemTask::ShowHide),
Instant::now().add(Duration::from_secs(1)),
);
@@ -110,13 +110,13 @@ pub fn openvr_run(
TrackedDeviceIndex::HMD,
ETrackedDeviceProperty::Prop_UserIpdMeters_Float,
) {
state.input_state.ipd = (ipd * 1000.0).round();
log::info!("IPD: {:.0} mm", state.input_state.ipd);
app.input_state.ipd = (ipd * 1000.0).round();
log::info!("IPD: {:.0} mm", app.input_state.ipd);
}
let _ = install_manifest(&mut app_mgr);
let mut overlays = OverlayContainer::<OpenVrOverlayData>::new(&mut state, headless)?;
let mut overlays = OverlayWindowManager::<OpenVrOverlayData>::new(&mut app, headless)?;
let mut notifications = NotificationManager::new();
notifications.run_dbus();
notifications.run_udp();
@@ -139,7 +139,7 @@ pub fn openvr_run(
log::info!("HMD running @ {refresh_rate} Hz");
let watch_id = overlays.get_by_name(WATCH_NAME).unwrap().state.id; // want panic
let watch_id = overlays.lookup(WATCH_NAME).unwrap(); // want panic
// want at least half refresh rate
let frame_timeout = 2 * (1000.0 / refresh_rate).floor() as u32;
@@ -147,7 +147,7 @@ pub fn openvr_run(
let mut next_device_update = Instant::now();
let mut due_tasks = VecDeque::with_capacity(4);
let mut lines = LinePool::new(state.gfx.clone())?;
let mut lines = LinePool::new(app.gfx.clone())?;
let pointer_lines = [lines.allocate(), lines.allocate()];
'main_loop: loop {
@@ -183,12 +183,12 @@ pub fn openvr_run(
ETrackedDeviceProperty::Prop_UserIpdMeters_Float,
) {
let ipd = (ipd * 1000.0).round();
if (ipd - state.input_state.ipd).abs() > 0.05 {
log::info!("IPD: {:.1} mm -> {:.1} mm", state.input_state.ipd, ipd);
if (ipd - app.input_state.ipd).abs() > 0.05 {
log::info!("IPD: {:.1} mm -> {:.1} mm", app.input_state.ipd, ipd);
Toast::new(ToastTopic::IpdChange, "IPD".into(), format!("{ipd:.1} mm"))
.submit(&mut state);
.submit(&mut app);
}
state.input_state.ipd = ipd;
app.input_state.ipd = ipd;
}
}
_ => {}
@@ -196,19 +196,19 @@ pub fn openvr_run(
}
if next_device_update <= Instant::now() {
input_source.update_devices(&mut system_mgr, &mut state);
input_source.update_devices(&mut system_mgr, &mut app);
next_device_update = Instant::now() + Duration::from_secs(30);
}
notifications.submit_pending(&mut state);
notifications.submit_pending(&mut app);
state.tasks.retrieve_due(&mut due_tasks);
app.tasks.retrieve_due(&mut due_tasks);
while let Some(task) = due_tasks.pop_front() {
match task {
TaskType::Overlay(sel, f) => {
if let Some(o) = overlays.mut_by_selector(&sel) {
f(&mut state, &mut o.state);
f(&mut app, &mut o.config);
} else {
log::warn!("Overlay not found for task: {sel:?}");
}
@@ -218,19 +218,21 @@ pub fn openvr_run(
continue;
};
let Some((mut state, backend)) = f(&mut state) else {
let Some(overlay_config) = f(&mut app) else {
continue;
};
state.birthframe = cur_frame;
overlays.add(OverlayData {
state,
..OverlayData::from_backend(backend)
});
overlays.add(
OverlayWindowData {
birthframe: cur_frame,
..OverlayWindowData::from_config(overlay_config)
},
&mut app,
);
}
TaskType::DropOverlay(sel) => {
if let Some(o) = overlays.mut_by_selector(&sel)
&& o.state.birthframe < cur_frame
&& o.birthframe < cur_frame
{
o.destroy(&mut overlay_mgr);
overlays.remove_by_selector(&sel);
@@ -241,91 +243,89 @@ pub fn openvr_run(
let _ = adjust_gain(&mut settings_mgr, channel, value);
}
SystemTask::FixFloor => {
playspace.fix_floor(&mut chaperone_mgr, &state.input_state);
playspace.fix_floor(&mut chaperone_mgr, &app.input_state);
}
SystemTask::ResetPlayspace => {
playspace.reset_offset(&mut chaperone_mgr, &state.input_state);
playspace.reset_offset(&mut chaperone_mgr, &app.input_state);
}
SystemTask::ShowHide => {
overlays.show_hide(&mut state);
overlays.show_hide(&mut app);
}
},
TaskType::ToggleSet(set) => {
overlays.switch_or_toggle_set(&mut app, set);
}
#[cfg(feature = "wayvr")]
TaskType::WayVR(action) => {
wayvr_action(&mut state, &mut overlays, &action);
wayvr_action(&mut app, &mut overlays, &action);
}
}
}
let universe = playspace.get_universe();
state.input_state.pre_update();
input_source.update(
universe.clone(),
&mut input_mgr,
&mut system_mgr,
&mut state,
);
state.input_state.post_update(&state.session);
app.input_state.pre_update();
input_source.update(universe.clone(), &mut input_mgr, &mut system_mgr, &mut app);
app.input_state.post_update(&app.session);
if state
if app
.input_state
.pointers
.iter()
.any(|p| p.now.show_hide && !p.before.show_hide)
{
lines.mark_dirty(); // workaround to prevent lines from not showing
overlays.show_hide(&mut state);
overlays.show_hide(&mut app);
}
#[cfg(feature = "wayvr")]
if state
if app
.input_state
.pointers
.iter()
.any(|p| p.now.toggle_dashboard && !p.before.toggle_dashboard)
{
wayvr_action(&mut state, &mut overlays, &WayVRAction::ToggleDashboard);
wayvr_action(&mut app, &mut overlays, &WayVRAction::ToggleDashboard);
}
overlays
.values_mut()
.for_each(|o| o.state.auto_movement(&mut state));
.for_each(|o| o.config.auto_movement(&mut app));
watch_fade(&mut state, overlays.mut_by_id(watch_id).unwrap()); // want panic
playspace.update(&mut chaperone_mgr, &mut overlays, &state);
watch_fade(&mut app, overlays.mut_by_id(watch_id).unwrap()); // want panic
playspace.update(&mut chaperone_mgr, &mut overlays, &app);
let lengths_haptics = interact(&mut overlays, &mut state);
let lengths_haptics = interact(&mut overlays, &mut app);
for (idx, (len, haptics)) in lengths_haptics.iter().enumerate() {
lines.draw_from(
pointer_lines[idx],
state.input_state.pointers[idx].pose,
app.input_state.pointers[idx].pose,
*len,
state.input_state.pointers[idx].interaction.mode as usize + 1,
&state.input_state.hmd,
app.input_state.pointers[idx].interaction.mode as usize + 1,
&app.input_state.hmd,
);
if let Some(haptics) = haptics {
input_source.haptics(&mut input_mgr, idx, haptics);
}
}
state.hid_provider.inner.commit();
app.hid_provider.inner.commit();
let mut buffers = CommandBuffers::default();
lines.update(universe.clone(), &mut overlay_mgr, &mut state)?;
lines.update(universe.clone(), &mut overlay_mgr, &mut app)?;
for o in overlays.values_mut() {
o.after_input(&mut overlay_mgr, &mut state)?;
o.after_input(&mut overlay_mgr, &mut app)?;
}
#[cfg(feature = "osc")]
if let Some(ref mut sender) = state.osc_sender {
let _ = sender.send_params(&overlays, &state.input_state.devices);
if let Some(ref mut sender) = app.osc_sender {
let _ = sender.send_params(&overlays, &app.input_state.devices);
}
#[cfg(feature = "wayvr")]
if let Err(e) =
crate::overlays::wayvr::tick_events::<OpenVrOverlayData>(&mut state, &mut overlays)
crate::overlays::wayvr::tick_events::<OpenVrOverlayData>(&mut app, &mut overlays)
{
log::error!("WayVR tick_events failed: {e:?}");
}
@@ -333,15 +333,15 @@ pub fn openvr_run(
log::trace!("Rendering frame");
for o in overlays.values_mut() {
if o.state.want_visible {
let ShouldRender::Should = o.should_render(&mut state)? else {
if o.config.active_state.is_some() {
let ShouldRender::Should = o.should_render(&mut app)? else {
continue;
};
if !o.ensure_image_allocated(&mut state)? {
if !o.ensure_image_allocated(&mut app)? {
continue;
}
o.data.image_dirty = o.render(
&mut state,
&mut app,
o.data.image_view.as_ref().unwrap().clone(),
&mut buffers,
1.0, // alpha is instead set using OVR API
@@ -351,7 +351,7 @@ pub fn openvr_run(
log::trace!("Rendering overlays");
if let Some(mut future) = buffers.execute_now(state.gfx.queue_gfx.clone())? {
if let Some(mut future) = buffers.execute_now(app.gfx.queue_gfx.clone())? {
if let Err(e) = future.flush() {
return Err(BackendError::Fatal(e.into()));
}
@@ -360,10 +360,10 @@ pub fn openvr_run(
overlays
.values_mut()
.for_each(|o| o.after_render(universe.clone(), &mut overlay_mgr, &state.gfx));
.for_each(|o| o.after_render(universe.clone(), &mut overlay_mgr, &app.gfx));
#[cfg(feature = "wayvr")]
if let Some(wayvr) = &state.wayvr {
if let Some(wayvr) = &app.wayvr {
wayvr.borrow_mut().data.tick_finish()?;
}

View File

@@ -13,7 +13,7 @@ use vulkano::{
};
use wgui::gfx::WGfx;
use crate::{backend::overlay::OverlayData, state::AppState};
use crate::{state::AppState, windowing::window::OverlayWindowData};
use super::helpers::Affine3AConvert;
@@ -28,13 +28,13 @@ pub(super) struct OpenVrOverlayData {
pub(super) image_dirty: bool,
}
impl OverlayData<OpenVrOverlayData> {
impl OverlayWindowData<OpenVrOverlayData> {
pub(super) fn initialize(
&mut self,
overlay: &mut OverlayManager,
app: &mut AppState,
) -> anyhow::Result<OverlayHandle> {
let key = format!("wlx-{}", self.state.name);
let key = format!("wlx-{}", self.config.name);
log::debug!("Create overlay with key: {}", &key);
let handle = match overlay.create_overlay(&key, &key) {
Ok(handle) => handle,
@@ -42,7 +42,7 @@ impl OverlayData<OpenVrOverlayData> {
panic!("Failed to create overlay: {e}");
}
};
log::debug!("{}: initialize", self.state.name);
log::debug!("{}: initialize", self.config.name);
self.data.handle = Some(handle);
self.data.color = Vec4::ONE;
@@ -66,7 +66,7 @@ impl OverlayData<OpenVrOverlayData> {
if self.data.image_view.is_some() {
return Ok(true);
}
let Some(meta) = self.backend.frame_meta() else {
let Some(meta) = self.config.backend.frame_meta() else {
return Ok(false);
};
let image = app.gfx.new_image(
@@ -84,9 +84,16 @@ impl OverlayData<OpenVrOverlayData> {
overlay: &mut OverlayManager,
app: &mut AppState,
) -> anyhow::Result<()> {
if self.state.want_visible && !self.data.visible {
let want_visible = self
.config
.active_state
.as_ref()
.map(|x| x.alpha > 0.05)
.unwrap_or(false);
if want_visible && !self.data.visible {
self.show_internal(overlay, app)?;
} else if !self.state.want_visible && self.data.visible {
} else if !want_visible && self.data.visible {
self.hide_internal(overlay, app)?;
}
Ok(())
@@ -99,12 +106,12 @@ impl OverlayData<OpenVrOverlayData> {
graphics: &WGfx,
) {
if self.data.visible {
if self.state.dirty {
if self.config.dirty {
self.upload_curvature(overlay);
self.upload_transform(universe, overlay);
self.upload_alpha(overlay);
self.state.dirty = false;
self.config.dirty = false;
}
self.upload_texture(overlay, graphics);
}
@@ -119,12 +126,12 @@ impl OverlayData<OpenVrOverlayData> {
Some(handle) => handle,
None => self.initialize(overlay, app)?,
};
log::debug!("{}: show", self.state.name);
log::debug!("{}: show", self.config.name);
if let Err(e) = overlay.set_visibility(handle, true) {
log::error!("{}: Failed to show overlay: {}", self.state.name, e);
log::error!("{}: Failed to show overlay: {}", self.config.name, e);
}
self.data.visible = true;
self.backend.resume(app)
self.config.backend.resume(app)
}
fn hide_internal(
@@ -135,27 +142,30 @@ impl OverlayData<OpenVrOverlayData> {
let Some(handle) = self.data.handle else {
return Ok(());
};
log::debug!("{}: hide", self.state.name);
log::debug!("{}: hide", self.config.name);
if let Err(e) = overlay.set_visibility(handle, false) {
log::error!("{}: Failed to hide overlay: {}", self.state.name, e);
log::error!("{}: Failed to hide overlay: {}", self.config.name, e);
}
self.data.visible = false;
self.backend.pause(app)
self.config.backend.pause(app)
}
pub(super) fn upload_alpha(&self, overlay: &mut OverlayManager) {
let Some(handle) = self.data.handle else {
log::debug!("{}: No overlay handle", self.state.name);
log::debug!("{}: No overlay handle", self.config.name);
return;
};
if let Err(e) = overlay.set_opacity(handle, self.state.alpha) {
log::error!("{}: Failed to set overlay alpha: {}", self.state.name, e);
let Some(state) = self.config.active_state.as_ref() else {
return;
};
if let Err(e) = overlay.set_opacity(handle, state.alpha) {
log::error!("{}: Failed to set overlay alpha: {}", self.config.name, e);
}
}
pub(super) fn upload_color(&self, overlay: &mut OverlayManager) {
let Some(handle) = self.data.handle else {
log::debug!("{}: No overlay handle", self.state.name);
log::debug!("{}: No overlay handle", self.config.name);
return;
};
if let Err(e) = overlay.set_tint(
@@ -167,29 +177,37 @@ impl OverlayData<OpenVrOverlayData> {
a: self.data.color.w,
},
) {
log::error!("{}: Failed to set overlay tint: {}", self.state.name, e);
log::error!("{}: Failed to set overlay tint: {}", self.config.name, e);
}
}
fn upload_width(&self, overlay: &mut OverlayManager) {
let Some(handle) = self.data.handle else {
log::debug!("{}: No overlay handle", self.state.name);
log::debug!("{}: No overlay handle", self.config.name);
return;
};
if let Err(e) = overlay.set_width(handle, self.data.width) {
log::error!("{}: Failed to set overlay width: {}", self.state.name, e);
log::error!("{}: Failed to set overlay width: {}", self.config.name, e);
}
}
fn upload_curvature(&self, overlay: &mut OverlayManager) {
let Some(handle) = self.data.handle else {
log::debug!("{}: No overlay handle", self.state.name);
log::debug!("{}: No overlay handle", self.config.name);
return;
};
if let Err(e) = overlay.set_curvature(handle, self.state.curvature.unwrap_or(0.0)) {
if let Err(e) = overlay.set_curvature(
handle,
self.config
.active_state
.as_ref()
.unwrap()
.curvature
.unwrap_or(0.0),
) {
log::error!(
"{}: Failed to set overlay curvature: {}",
self.state.name,
self.config.name,
e
);
}
@@ -197,11 +215,11 @@ impl OverlayData<OpenVrOverlayData> {
fn upload_sort_order(&self, overlay: &mut OverlayManager) {
let Some(handle) = self.data.handle else {
log::debug!("{}: No overlay handle", self.state.name);
log::debug!("{}: No overlay handle", self.config.name);
return;
};
if let Err(e) = overlay.set_sort_order(handle, self.state.z_order) {
log::error!("{}: Failed to set overlay z order: {}", self.state.name, e);
if let Err(e) = overlay.set_sort_order(handle, self.config.z_order) {
log::error!("{}: Failed to set overlay z order: {}", self.config.name, e);
}
}
@@ -211,12 +229,16 @@ impl OverlayData<OpenVrOverlayData> {
overlay: &mut OverlayManager,
) {
let Some(handle) = self.data.handle else {
log::debug!("{}: No overlay handle", self.state.name);
log::debug!("{}: No overlay handle", self.config.name);
return;
};
let Some(state) = self.config.active_state.as_ref() else {
return;
};
let effective = self.state.transform
let effective = state.transform
* self
.config
.backend
.frame_meta()
.map(|f| f.transform)
@@ -227,7 +249,7 @@ impl OverlayData<OpenVrOverlayData> {
if let Err(e) = overlay.set_transform_absolute(handle, universe, &transform) {
log::error!(
"{}: Failed to set overlay transform: {}",
self.state.name,
self.config.name,
e
);
}
@@ -235,12 +257,12 @@ impl OverlayData<OpenVrOverlayData> {
pub(super) fn upload_texture(&mut self, overlay: &mut OverlayManager, graphics: &WGfx) {
let Some(handle) = self.data.handle else {
log::debug!("{}: No overlay handle", self.state.name);
log::debug!("{}: No overlay handle", self.config.name);
return;
};
let Some(view) = self.data.image_view.as_ref() else {
log::debug!("{}: Not rendered", self.state.name);
log::debug!("{}: Not rendered", self.config.name);
return;
};
@@ -254,7 +276,7 @@ impl OverlayData<OpenVrOverlayData> {
if !self.data.override_width {
let new_width = ((dimensions[0] as f32) / (dimensions[1] as f32)).min(1.0);
if (new_width - self.data.width).abs() > f32::EPSILON {
log::info!("{}: New width {}", self.state.name, new_width);
log::info!("{}: New width {}", self.config.name, new_width);
self.data.width = new_width;
self.upload_width(overlay);
}
@@ -277,22 +299,22 @@ impl OverlayData<OpenVrOverlayData> {
};
log::trace!(
"{}: UploadTex {:?}, {}x{}, {:?}",
self.state.name,
self.config.name,
format,
texture.m_nWidth,
texture.m_nHeight,
image.usage()
);
if let Err(e) = overlay.set_image_vulkan(handle, &mut texture) {
log::error!("{}: Failed to set overlay texture: {}", self.state.name, e);
log::error!("{}: Failed to set overlay texture: {}", self.config.name, e);
}
}
pub(super) fn destroy(&mut self, overlay: &mut OverlayManager) {
if let Some(handle) = self.data.handle {
log::debug!("{}: destroy", self.state.name);
log::debug!("{}: destroy", self.config.name);
if let Err(e) = overlay.destroy_overlay(handle) {
log::error!("{}: Failed to destroy overlay: {}", self.state.name, e);
log::error!("{}: Failed to destroy overlay: {}", self.config.name, e);
}
}
}

View File

@@ -6,8 +6,7 @@ use ovr_overlay::{
};
use crate::{
backend::{common::OverlayContainer, input::InputState},
state::AppState,
backend::input::InputState, state::AppState, windowing::manager::OverlayWindowManager,
};
use super::{helpers::Affine3AConvert, overlay::OpenVrOverlayData};
@@ -37,7 +36,7 @@ impl PlayspaceMover {
pub fn update(
&mut self,
chaperone_mgr: &mut ChaperoneSetupManager,
overlays: &mut OverlayContainer<OpenVrOverlayData>,
overlays: &mut OverlayWindowManager<OpenVrOverlayData>,
state: &AppState,
) {
let universe = self.universe.clone();
@@ -68,14 +67,6 @@ impl PlayspaceMover {
overlay_transform.translation = offset;
space_transform.translation = offset;
overlays.values_mut().for_each(|overlay| {
if overlay.state.grabbable {
overlay.state.dirty = true;
overlay.state.transform.translation =
overlay_transform.transform_point3a(overlay.state.transform.translation);
}
});
data.pose *= space_transform;
data.hand_pose = new_hand;
@@ -126,9 +117,12 @@ impl PlayspaceMover {
let overlay_offset = data.pose.inverse().transform_vector3a(relative_pos) * -1.0;
overlays.values_mut().for_each(|overlay| {
if overlay.state.grabbable {
overlay.state.dirty = true;
overlay.state.transform.translation += overlay_offset;
let Some(state) = overlay.config.active_state.as_mut() else {
return;
};
if state.positioning.moves_with_space() {
state.transform.translation += overlay_offset;
overlay.config.dirty = true;
}
});

View File

@@ -1,7 +1,7 @@
use libmonado::{ClientState, Monado};
use log::{info, warn};
use crate::{backend::overlay::OverlayID, state::AppState};
use crate::{state::AppState, windowing::OverlayID};
pub(super) struct InputBlocker {
hovered_last_frame: bool,

View File

@@ -17,11 +17,10 @@ use vulkano::{Handle, VulkanObject};
use crate::{
backend::{
common::{BackendError, OverlayContainer},
input::interact,
openxr::{lines::LinePool, overlay::OpenXrOverlayData},
overlay::{OverlayData, ShouldRender},
task::{SystemTask, TaskType},
BackendError,
},
graphics::{init_openxr_graphics, CommandBuffers},
overlays::{
@@ -30,6 +29,7 @@ use crate::{
},
state::AppState,
subsystem::notifications::NotificationManager,
windowing::{backend::ShouldRender, manager::OverlayWindowManager, window::OverlayWindowData},
};
#[cfg(feature = "wayvr")]
@@ -94,7 +94,7 @@ pub fn openxr_run(
);
}
let mut overlays = OverlayContainer::<OpenXrOverlayData>::new(&mut app, headless)?;
let mut overlays = OverlayWindowManager::<OpenXrOverlayData>::new(&mut app, headless)?;
let mut lines = LinePool::new(&app)?;
let mut notifications = NotificationManager::new();
@@ -336,7 +336,7 @@ pub fn openxr_run(
overlays
.values_mut()
.for_each(|o| o.state.auto_movement(&mut app));
.for_each(|o| o.config.auto_movement(&mut app));
let lengths_haptics = interact(&mut overlays, &mut app);
for (idx, (len, haptics)) in lengths_haptics.iter().enumerate() {
@@ -355,10 +355,11 @@ pub fn openxr_run(
app.hid_provider.inner.commit();
let watch = overlays.mut_by_id(watch_id).unwrap(); // want panic
let watch_transform = watch.state.transform;
if !watch.state.want_visible {
watch.state.want_visible = true;
watch.state.transform = Affine3A::from_scale(Vec3 {
let watch_state = watch.config.active_state.as_mut().unwrap();
let watch_transform = watch_state.transform;
if watch_state.alpha < 0.05 {
//FIXME: Temporary workaround for Monado bug
watch_state.transform = Affine3A::from_scale(Vec3 {
x: 0.001,
y: 0.001,
z: 0.001,
@@ -381,9 +382,9 @@ pub fn openxr_run(
for o in overlays.values_mut() {
o.data.cur_visible = false;
if !o.state.want_visible {
let Some(alpha) = o.config.active_state.as_ref().map(|x| x.alpha) else {
continue;
}
};
if !o.data.init {
o.init(&mut app)?;
@@ -392,7 +393,7 @@ pub fn openxr_run(
let should_render = match o.should_render(&mut app)? {
ShouldRender::Should => true,
ShouldRender::Can => (o.data.last_alpha - o.state.alpha).abs() > f32::EPSILON,
ShouldRender::Can => (o.data.last_alpha - alpha).abs() > f32::EPSILON,
ShouldRender::Unable => false, //try show old image if exists
};
@@ -401,11 +402,11 @@ pub fn openxr_run(
continue;
}
let tgt = o.data.swapchain.as_mut().unwrap().acquire_wait_image()?; // want
if !o.render(&mut app, tgt, &mut buffers, o.state.alpha)? {
if !o.render(&mut app, tgt, &mut buffers, alpha)? {
o.data.swapchain.as_mut().unwrap().ensure_image_released()?; // want
continue;
}
o.data.last_alpha = o.state.alpha;
o.data.last_alpha = alpha;
} else if o.data.swapchain.is_none() {
continue;
}
@@ -435,9 +436,11 @@ pub fn openxr_run(
if !o.data.cur_visible {
continue;
}
let dist_sq = (app.input_state.hmd.translation - o.state.transform.translation)
// unwrap: above if only passes if active_state is some
let active_state = o.config.active_state.as_ref().unwrap();
let dist_sq = (app.input_state.hmd.translation - active_state.transform.translation)
.length_squared()
+ (100f32 - o.state.z_order as f32);
+ (100f32 - o.config.z_order as f32);
if !dist_sq.is_normal() {
o.data.swapchain.as_mut().unwrap().ensure_image_released()?;
continue;
@@ -489,7 +492,7 @@ pub fn openxr_run(
match task {
TaskType::Overlay(sel, f) => {
if let Some(o) = overlays.mut_by_selector(&sel) {
f(&mut app, &mut o.state);
f(&mut app, &mut o.config);
} else {
log::warn!("Overlay not found for task: {sel:?}");
}
@@ -498,22 +501,22 @@ pub fn openxr_run(
let None = overlays.mut_by_selector(&sel) else {
continue;
};
let Some((mut overlay_state, overlay_backend)) = f(&mut app) else {
let Some(overlay_config) = f(&mut app) else {
continue;
};
overlay_state.birthframe = cur_frame;
overlays.add(OverlayData {
state: overlay_state,
..OverlayData::from_backend(overlay_backend)
});
overlays.add(
OverlayWindowData {
birthframe: cur_frame,
..OverlayWindowData::from_config(overlay_config)
},
&mut app,
);
}
TaskType::DropOverlay(sel) => {
if let Some(o) = overlays.mut_by_selector(&sel)
&& o.state.birthframe < cur_frame
&& o.birthframe < cur_frame
{
log::debug!("{}: destroy", o.state.name);
log::debug!("{}: destroy", o.config.name);
if let Some(o) = overlays.remove_by_selector(&sel) {
// set for deletion after all images are done showing
delete_queue.push((o, cur_frame + 5));
@@ -539,6 +542,9 @@ pub fn openxr_run(
}
_ => {}
},
TaskType::ToggleSet(set) => {
overlays.switch_or_toggle_set(&mut app, set);
}
#[cfg(feature = "wayvr")]
TaskType::WayVR(action) => {
wayvr_action(&mut app, &mut overlays, &action);
@@ -548,8 +554,9 @@ pub fn openxr_run(
delete_queue.retain(|(_, frame)| *frame > cur_frame);
//FIXME: Temporary workaround for Monado bug
let watch = overlays.mut_by_id(watch_id).unwrap(); // want panic
watch.state.transform = watch_transform;
watch.config.active_state.as_mut().unwrap().transform = watch_transform;
}
Ok(())

View File

@@ -5,11 +5,9 @@ use xr::EyeVisibility;
use super::{CompositionLayer, XrState, helpers, swapchain::WlxSwapchain};
use crate::{
backend::{
openxr::swapchain::{SwapchainOpts, create_swapchain},
overlay::OverlayData,
},
backend::openxr::swapchain::{SwapchainOpts, create_swapchain},
state::AppState,
windowing::window::OverlayWindowData,
};
#[derive(Default)]
@@ -21,7 +19,7 @@ pub struct OpenXrOverlayData {
pub(super) last_alpha: f32,
}
impl OverlayData<OpenXrOverlayData> {
impl OverlayWindowData<OpenXrOverlayData> {
pub(super) fn ensure_swapchain<'a>(
&'a mut self,
app: &AppState,
@@ -30,7 +28,7 @@ impl OverlayData<OpenXrOverlayData> {
let Some(meta) = self.frame_meta() else {
log::warn!(
"{}: swapchain cannot be created due to missing metadata",
self.state.name
self.config.name
);
return Ok(false);
};
@@ -46,7 +44,7 @@ impl OverlayData<OpenXrOverlayData> {
log::debug!(
"{}: recreating swapchain at {}x{}",
self.state.name,
self.config.name,
meta.extent[0],
meta.extent[1],
);
@@ -64,17 +62,20 @@ impl OverlayData<OpenXrOverlayData> {
xr: &'a XrState,
) -> anyhow::Result<CompositionLayer<'a>> {
let Some(swapchain) = self.data.swapchain.as_mut() else {
log::warn!("{}: swapchain not ready", self.state.name);
log::warn!("{}: swapchain not ready", self.config.name);
return Ok(CompositionLayer::None);
};
if !swapchain.ever_acquired {
log::warn!("{}: swapchain not rendered", self.state.name);
log::warn!("{}: swapchain not rendered", self.config.name);
return Ok(CompositionLayer::None);
}
swapchain.ensure_image_released()?;
// overlays without active_state don't get queued for present
let state = self.config.active_state.as_ref().unwrap();
let sub_image = swapchain.get_subimage();
let transform = self.state.transform * self.backend.frame_meta().unwrap().transform; // contract
let transform = state.transform * self.config.backend.frame_meta().unwrap().transform; // contract
let aspect_ratio = swapchain.extent[1] as f32 / swapchain.extent[0] as f32;
let (scale_x, scale_y) = if aspect_ratio < 1.0 {
@@ -85,7 +86,7 @@ impl OverlayData<OpenXrOverlayData> {
(major / aspect_ratio, major)
};
if let Some(curvature) = self.state.curvature {
if let Some(curvature) = state.curvature {
let radius = scale_x / (2.0 * PI * curvature);
let quat = helpers::transform_to_norm_quat(&transform);
let center_point = transform.translation + quat.mul_vec3a(Vec3A::Z * radius);
@@ -120,14 +121,21 @@ impl OverlayData<OpenXrOverlayData> {
}
pub(super) fn after_input(&mut self, app: &mut AppState) -> anyhow::Result<()> {
if self.data.last_visible != self.state.want_visible {
if self.state.want_visible {
self.backend.resume(app)?;
let want_visible = self
.config
.active_state
.as_ref()
.map(|x| x.alpha > 0.05)
.unwrap_or(false);
if self.data.last_visible != want_visible {
if want_visible {
self.config.backend.resume(app)?;
} else {
self.backend.pause(app)?;
self.config.backend.pause(app)?;
}
}
self.data.last_visible = self.state.want_visible;
self.data.last_visible = want_visible;
Ok(())
}
}

View File

@@ -2,8 +2,7 @@ use glam::{Affine3A, Quat, Vec3A};
use libmonado::{Monado, Pose, ReferenceSpaceType};
use crate::{
backend::{common::OverlayContainer, input::InputState},
state::AppState,
backend::input::InputState, state::AppState, windowing::manager::OverlayWindowManager,
};
use super::overlay::OpenXrOverlayData;
@@ -44,7 +43,7 @@ impl PlayspaceMover {
pub fn update(
&mut self,
overlays: &mut OverlayContainer<OpenXrOverlayData>,
overlays: &mut OverlayWindowManager<OpenXrOverlayData>,
state: &AppState,
monado: &mut Monado,
) {
@@ -129,10 +128,13 @@ impl PlayspaceMover {
let overlay_offset = data.pose.inverse().transform_vector3a(relative_pos) * -1.0;
overlays.values_mut().for_each(|overlay| {
if overlay.state.grabbable {
overlay.state.dirty = true;
overlay.state.transform.translation += overlay_offset;
let Some(state) = overlay.config.active_state.as_mut() else {
return;
};
if state.positioning.moves_with_space() {
state.transform.translation += overlay_offset;
}
overlay.config.dirty = true;
});
data.pose.translation += relative_pos;

View File

@@ -1,358 +0,0 @@
use std::{
f32::consts::PI,
sync::{atomic::AtomicUsize, Arc},
};
use glam::{Affine2, Affine3A, Mat3A, Quat, Vec2, Vec3, Vec3A};
use slotmap::new_key_type;
use vulkano::{format::Format, image::view::ImageView};
use crate::{
config::AStrMapExt, graphics::CommandBuffers, state::AppState, subsystem::input::KeyboardFocus,
};
use super::{
common::snap_upright,
input::{Haptics, PointerHit},
};
static OVERLAY_AUTO_INCREMENT: AtomicUsize = AtomicUsize::new(0);
new_key_type! {
pub struct OverlayID;
}
pub const Z_ORDER_TOAST: u32 = 70;
pub const Z_ORDER_LINES: u32 = 69;
pub const Z_ORDER_WATCH: u32 = 68;
pub const Z_ORDER_ANCHOR: u32 = 67;
pub const Z_ORDER_DEFAULT: u32 = 0;
pub const Z_ORDER_DASHBOARD: u32 = Z_ORDER_DEFAULT;
pub struct OverlayState {
pub name: Arc<str>,
pub want_visible: bool,
pub show_hide: bool,
pub grabbable: bool,
pub interactable: bool,
pub recenter: bool,
pub keyboard_focus: Option<KeyboardFocus>,
pub dirty: bool,
pub alpha: f32,
pub z_order: u32,
pub transform: Affine3A,
pub spawn_scale: f32, // aka width
pub spawn_point: Vec3A,
pub spawn_rotation: Quat,
pub saved_transform: Option<Affine3A>,
pub positioning: Positioning,
pub curvature: Option<f32>,
pub birthframe: usize,
}
impl Default for OverlayState {
fn default() -> Self {
Self {
name: Arc::from(""),
want_visible: false,
show_hide: false,
grabbable: false,
recenter: false,
interactable: false,
keyboard_focus: None,
dirty: true,
alpha: 1.0,
z_order: Z_ORDER_DEFAULT,
positioning: Positioning::Floating,
curvature: None,
spawn_scale: 1.0,
spawn_point: Vec3A::NEG_Z,
spawn_rotation: Quat::IDENTITY,
saved_transform: None,
transform: Affine3A::IDENTITY,
birthframe: 0,
}
}
}
pub struct OverlayData<T> {
pub state: OverlayState,
pub backend: Box<dyn OverlayBackend>,
pub primary_pointer: Option<usize>,
pub data: T,
}
impl<T> OverlayData<T>
where
T: Default,
{
pub fn from_backend(backend: Box<dyn OverlayBackend>) -> Self {
Self {
state: OverlayState::default(),
backend,
primary_pointer: None,
data: T::default(),
}
}
}
impl OverlayState {
fn get_transform(&self) -> Affine3A {
self.saved_transform.unwrap_or_else(|| {
Affine3A::from_scale_rotation_translation(
Vec3::ONE * self.spawn_scale,
self.spawn_rotation,
self.spawn_point.into(),
)
})
}
pub fn auto_movement(&mut self, app: &mut AppState) {
let (target_transform, lerp) = match self.positioning {
Positioning::FollowHead { lerp } => (app.input_state.hmd * self.get_transform(), lerp),
Positioning::FollowHand { hand, lerp } => (
app.input_state.pointers[hand].pose * self.get_transform(),
lerp,
),
_ => return,
};
self.transform = match lerp {
1.0 => target_transform,
lerp => {
let scale = target_transform.matrix3.x_axis.length();
let rot_from = Quat::from_mat3a(&self.transform.matrix3.div_scalar(scale));
let rot_to = Quat::from_mat3a(&target_transform.matrix3.div_scalar(scale));
let rotation = rot_from.slerp(rot_to, lerp);
let translation = self
.transform
.translation
.slerp(target_transform.translation, lerp);
Affine3A::from_scale_rotation_translation(
Vec3::ONE * scale,
rotation,
translation.into(),
)
}
};
self.dirty = true;
}
pub fn reset(&mut self, app: &mut AppState, hard_reset: bool) {
let parent_transform = match self.positioning {
Positioning::Floating
| Positioning::FollowHead { .. }
| Positioning::FollowHeadPaused { .. } => app.input_state.hmd,
Positioning::FollowHand { hand, .. } | Positioning::FollowHandPaused { hand, .. } => {
app.input_state.pointers[hand].pose
}
Positioning::Anchored => app.anchor,
Positioning::FollowOverlay { .. } | Positioning::Static => return,
};
if hard_reset {
self.saved_transform = None;
}
self.transform = parent_transform * self.get_transform();
if self.grabbable && hard_reset {
self.realign(&app.input_state.hmd);
}
self.dirty = true;
}
pub fn save_transform(&mut self, app: &mut AppState) -> bool {
let parent_transform = match self.positioning {
Positioning::Floating => snap_upright(app.input_state.hmd, Vec3A::Y),
Positioning::FollowHead { .. } | Positioning::FollowHeadPaused { .. } => {
app.input_state.hmd
}
Positioning::FollowHand { hand, .. } | Positioning::FollowHandPaused { hand, .. } => {
app.input_state.pointers[hand].pose
}
Positioning::Anchored => snap_upright(app.anchor, Vec3A::Y),
Positioning::FollowOverlay { .. } | Positioning::Static => return false,
};
self.saved_transform = Some(parent_transform.inverse() * self.transform);
true
}
pub fn realign(&mut self, hmd: &Affine3A) {
let to_hmd = hmd.translation - self.transform.translation;
let up_dir: Vec3A;
if hmd.x_axis.dot(Vec3A::Y).abs() > 0.2 {
// Snap upright
up_dir = hmd.y_axis;
} else {
let dot = to_hmd.normalize().dot(hmd.z_axis);
let z_dist = to_hmd.length();
let y_dist = (self.transform.translation.y - hmd.translation.y).abs();
let x_angle = (y_dist / z_dist).asin();
if dot < -f32::EPSILON {
// facing down
let up_point = hmd.translation + z_dist / x_angle.cos() * Vec3A::Y;
up_dir = (up_point - self.transform.translation).normalize();
} else if dot > f32::EPSILON {
// facing up
let dn_point = hmd.translation + z_dist / x_angle.cos() * Vec3A::NEG_Y;
up_dir = (self.transform.translation - dn_point).normalize();
} else {
// perfectly upright
up_dir = Vec3A::Y;
}
}
let scale = self.transform.x_axis.length();
let col_z = (self.transform.translation - hmd.translation).normalize();
let col_y = up_dir;
let col_x = col_y.cross(col_z);
let col_y = col_z.cross(col_x).normalize();
let col_x = col_x.normalize();
let rot = Mat3A::from_quat(self.spawn_rotation)
* Mat3A::from_quat(Quat::from_axis_angle(Vec3::Y, PI));
self.transform.matrix3 = Mat3A::from_cols(col_x, col_y, col_z).mul_scalar(scale) * rot;
}
}
impl<T> OverlayData<T>
where
T: Default,
{
pub fn init(&mut self, app: &mut AppState) -> anyhow::Result<()> {
if self.state.curvature.is_none() {
self.state.curvature = app
.session
.config
.curve_values
.arc_get(self.state.name.as_ref())
.copied();
}
if matches!(
self.state.positioning,
Positioning::Floating | Positioning::Anchored
) {
let hard_reset = if let Some(transform) = app
.session
.config
.transform_values
.arc_get(self.state.name.as_ref())
{
self.state.saved_transform = Some(*transform);
false
} else {
true
};
self.state.reset(app, hard_reset);
}
self.backend.init(app)
}
pub fn should_render(&mut self, app: &mut AppState) -> anyhow::Result<ShouldRender> {
self.backend.should_render(app)
}
pub fn render(
&mut self,
app: &mut AppState,
tgt: Arc<ImageView>,
buf: &mut CommandBuffers,
alpha: f32,
) -> anyhow::Result<bool> {
self.backend.render(app, tgt, buf, alpha)
}
pub fn frame_meta(&mut self) -> Option<FrameMeta> {
self.backend.frame_meta()
}
}
#[derive(Default, Clone, Copy)]
pub struct FrameMeta {
pub extent: [u32; 3],
pub transform: Affine3A,
pub format: Format,
}
pub enum ShouldRender {
/// The overlay is dirty and needs to be rendered.
Should,
/// The overlay is not dirty but is ready to be rendered.
Can,
/// The overlay is not ready to be rendered.
Unable,
}
pub trait OverlayBackend {
/// Called once, before the first frame is rendered
fn init(&mut self, app: &mut AppState) -> anyhow::Result<()>;
fn pause(&mut self, app: &mut AppState) -> anyhow::Result<()>;
fn resume(&mut self, app: &mut AppState) -> anyhow::Result<()>;
/// Called when the presentation layer is ready to present a new frame
fn should_render(&mut self, app: &mut AppState) -> anyhow::Result<ShouldRender>;
/// Called when the contents need to be rendered to the swapchain
fn render(
&mut self,
app: &mut AppState,
tgt: Arc<ImageView>,
buf: &mut CommandBuffers,
alpha: f32,
) -> anyhow::Result<bool>;
/// Called to retrieve the effective extent of the image
/// Used for creating swapchains.
///
/// Must be true if should_render was also true on the same frame.
fn frame_meta(&mut self) -> Option<FrameMeta>;
fn on_hover(&mut self, app: &mut AppState, hit: &PointerHit) -> Option<Haptics>;
fn on_left(&mut self, app: &mut AppState, pointer: usize);
fn on_pointer(&mut self, app: &mut AppState, hit: &PointerHit, pressed: bool);
fn on_scroll(&mut self, app: &mut AppState, hit: &PointerHit, delta_y: f32, delta_x: f32);
fn get_interaction_transform(&mut self) -> Option<Affine2>;
}
#[derive(Clone, Copy, Debug, Default)]
pub enum Positioning {
/// Stays in place, recenters relative to HMD
#[default]
Floating,
/// Stays in place, recenters relative to anchor
Anchored,
/// Following HMD
FollowHead { lerp: f32 },
/// Normally follows HMD, but paused due to interaction
FollowHeadPaused { lerp: f32 },
/// Following hand
FollowHand { hand: usize, lerp: f32 },
/// Normally follows hand, but paused due to interaction
FollowHandPaused { hand: usize, lerp: f32 },
/// Follow another overlay
FollowOverlay { id: usize },
/// Stays in place, no recentering
Static,
}
pub fn ui_transform(extent: [u32; 2]) -> Affine2 {
let aspect = extent[0] as f32 / extent[1] as f32;
let scale = if aspect < 1.0 {
Vec2 {
x: 1.0 / aspect,
y: -1.0,
}
} else {
Vec2 { x: 1.0, y: -aspect }
};
let center = Vec2 { x: 0.5, y: 0.5 };
Affine2::from_scale_angle_translation(scale, 0.0, center)
}

View File

@@ -1,9 +1 @@
use glam::Affine3A;
use std::sync::Arc;
pub struct OverlaySetItem {
name: Arc<str>,
transform: Affine3A,
}
pub struct OverlaySet {}

View File

@@ -7,16 +7,14 @@ use std::{
use serde::Deserialize;
use crate::state::AppState;
use crate::{
state::AppState,
windowing::{window::OverlayWindowConfig, OverlaySelector},
};
#[cfg(feature = "wayvr")]
use crate::backend::wayvr::WayVRAction;
use super::{
common::OverlaySelector,
overlay::{OverlayBackend, OverlayState},
};
static TASK_AUTO_INCREMENT: AtomicUsize = AtomicUsize::new(0);
struct AppTask {
@@ -52,14 +50,14 @@ pub enum SystemTask {
ShowHide,
}
pub type OverlayTask = dyn FnOnce(&mut AppState, &mut OverlayState) + Send;
pub type CreateOverlayTask =
dyn FnOnce(&mut AppState) -> Option<(OverlayState, Box<dyn OverlayBackend>)> + Send;
pub type OverlayTask = dyn FnOnce(&mut AppState, &mut OverlayWindowConfig) + Send;
pub type CreateOverlayTask = dyn FnOnce(&mut AppState) -> Option<OverlayWindowConfig> + Send;
pub enum TaskType {
Overlay(OverlaySelector, Box<OverlayTask>),
CreateOverlay(OverlaySelector, Box<CreateOverlayTask>),
DropOverlay(OverlaySelector),
ToggleSet(usize),
System(SystemTask),
#[cfg(feature = "wayvr")]
WayVR(WayVRAction),

View File

@@ -2,13 +2,13 @@ use std::{cell::RefCell, rc::Rc, sync::Arc};
use smithay::{
backend::renderer::{
element::{
surface::{render_elements_from_surface_tree, WaylandSurfaceRenderElement},
Kind,
},
gles::{ffi, GlesRenderer, GlesTexture},
utils::draw_render_elements,
Bind, Color32F, Frame, Renderer,
element::{
Kind,
surface::{WaylandSurfaceRenderElement, render_elements_from_surface_tree},
},
gles::{GlesRenderer, GlesTexture, ffi},
utils::draw_render_elements,
},
input,
utils::{Logical, Point, Rectangle, Size, Transform},
@@ -16,14 +16,11 @@ use smithay::{
};
use wayvr_ipc::packet_server;
use crate::{
backend::{overlay::OverlayID, wayvr::time::get_millis},
gen_id,
};
use crate::{backend::wayvr::time::get_millis, gen_id, windowing::OverlayID};
use super::{
client::WayVRCompositor, comp::send_frames_surface_tree, egl_data, event_queue::SyncEventQueue,
process, smithay_wrapper, time, window, BlitMethod, WayVRSignal,
BlitMethod, WayVRSignal, client::WayVRCompositor, comp::send_frames_surface_tree, egl_data,
event_queue::SyncEventQueue, process, smithay_wrapper, time, window,
};
fn generate_auth_key() -> String {

View File

@@ -87,7 +87,7 @@ pub enum WayVRSignal {
packet_server::WvrDisplayWindowLayout,
),
BroadcastStateChanged(packet_server::WvrStateChanged),
DropOverlay(super::overlay::OverlayID),
DropOverlay(crate::windowing::OverlayID),
Haptics(super::input::Haptics),
}