sbs 3d support

This commit is contained in:
galister
2025-12-16 23:12:35 +09:00
parent 9f5c0b9049
commit c41c0b9b59
30 changed files with 929 additions and 360 deletions

View File

@@ -8,6 +8,7 @@ use std::{
use glam::vec2;
use slotmap::Key;
use smallvec::smallvec;
use wgui::{
components::{button::ComponentButton, checkbox::ComponentCheckbox, slider::ComponentSlider},
event::{CallbackDataCommon, EventAlterables, EventCallback},
@@ -16,25 +17,35 @@ use wgui::{
};
use crate::{
attrib_value,
backend::{
input::HoverResult,
task::{OverlayTask, TaskContainer, TaskType},
},
gui::panel::{GuiPanel, NewGuiPanelParams, OnCustomAttribFunc, button::BUTTON_EVENTS},
gui::panel::{button::BUTTON_EVENTS, GuiPanel, NewGuiPanelParams, OnCustomAttribFunc},
overlays::edit::{
lock::InteractLockHandler, pos::PositioningHandler, tab::ButtonPaneTabSwitcher,
lock::InteractLockHandler,
pos::{new_pos_tab_handler, PosTabState},
sprite_tab::SpriteTabHandler,
stereo::new_stereo_tab_handler,
tab::ButtonPaneTabSwitcher,
},
state::AppState,
subsystem::hid::WheelDelta,
windowing::{
OverlayID, OverlaySelector,
backend::{DummyBackend, OverlayBackend, OverlayEventData, RenderResources, ShouldRender},
backend::{
BackendAttrib, BackendAttribValue, DummyBackend, OverlayBackend, OverlayEventData,
RenderResources, ShouldRender, StereoMode,
},
window::OverlayWindowConfig,
OverlayID, OverlaySelector,
},
};
mod lock;
mod pos;
mod sprite_tab;
mod stereo;
pub mod tab;
pub(super) struct LongPressButtonState {
@@ -54,7 +65,8 @@ struct EditModeState {
delete: LongPressButtonState,
tabs: ButtonPaneTabSwitcher,
lock: InteractLockHandler,
pos: PositioningHandler,
pos: SpriteTabHandler<PosTabState>,
stereo: SpriteTabHandler<StereoMode>,
}
type EditModeWrapPanel = GuiPanel<EditModeState>;
@@ -77,7 +89,6 @@ impl EditWrapperManager {
}
log::debug!("EditMode wrap on {}", owc.name);
let inner = mem::replace(&mut owc.backend, Box::new(DummyBackend {}));
let mut panel = self.panel_pool.pop();
if panel.is_none() {
panel = Some(make_edit_panel(app)?);
@@ -85,6 +96,8 @@ impl EditWrapperManager {
let mut panel = panel.unwrap();
reset_panel(&mut panel, id, owc)?;
let inner = mem::replace(&mut owc.backend, Box::new(DummyBackend {}));
owc.backend = Box::new(EditModeBackendWrapper {
inner: ManuallyDrop::new(inner),
panel: ManuallyDrop::new(panel),
@@ -178,7 +191,16 @@ impl OverlayBackend for EditModeBackendWrapper {
rdr: &mut RenderResources,
) -> anyhow::Result<()> {
self.inner.render(app, rdr)?;
self.panel.render(app, rdr)
self.panel.render(app, rdr)?;
// `GuiPanel` is not stereo-aware, so just render the same pass twice
if rdr.cmd_bufs.len() > 1 {
rdr.cmd_bufs.reverse();
self.panel.render(app, rdr)?;
rdr.cmd_bufs.reverse();
}
Ok(())
}
fn frame_meta(&mut self) -> Option<crate::windowing::backend::FrameMeta> {
self.inner.frame_meta()
@@ -218,6 +240,12 @@ impl OverlayBackend for EditModeBackendWrapper {
fn get_interaction_transform(&mut self) -> Option<glam::Affine2> {
self.inner.get_interaction_transform()
}
fn get_attrib(&self, attrib: BackendAttrib) -> Option<BackendAttribValue> {
self.inner.get_attrib(attrib)
}
fn set_attrib(&mut self, app: &mut AppState, value: BackendAttribValue) -> bool {
self.inner.set_attrib(app, value)
}
}
fn make_edit_panel(app: &mut AppState) -> anyhow::Result<EditModeWrapPanel> {
@@ -229,7 +257,8 @@ fn make_edit_panel(app: &mut AppState) -> anyhow::Result<EditModeWrapPanel> {
},
tabs: ButtonPaneTabSwitcher::default(),
lock: InteractLockHandler::default(),
pos: PositioningHandler::default(),
pos: SpriteTabHandler::default(),
stereo: SpriteTabHandler::default(),
};
let on_custom_attrib: OnCustomAttribFunc = Box::new(move |layout, attribs, _app| {
@@ -270,10 +299,20 @@ fn make_edit_panel(app: &mut AppState) -> anyhow::Result<EditModeWrapPanel> {
})
}
"::EditModeSetPos" => {
let pos_key = args.next().unwrap().to_owned();
let key = args.next().unwrap().to_owned();
Box::new(move |common, _data, app, state| {
let sel = OverlaySelector::Id(*state.id.borrow());
let task = state.pos.pos_button_clicked(common, &pos_key);
let task = state.pos.button_clicked(common, &key);
app.tasks
.enqueue(TaskType::Overlay(OverlayTask::Modify(sel, task)));
Ok(EventResult::Consumed)
})
}
"::EditModeSetStereo" => {
let key = args.next().unwrap().to_owned();
Box::new(move |common, _data, app, state| {
let sel = OverlaySelector::Id(*state.id.borrow());
let task = state.stereo.button_clicked(common, &key);
app.tasks
.enqueue(TaskType::Overlay(OverlayTask::Modify(sel, task)));
Ok(EventResult::Consumed)
@@ -315,9 +354,11 @@ fn make_edit_panel(app: &mut AppState) -> anyhow::Result<EditModeWrapPanel> {
},
)?;
panel.state.pos = PositioningHandler::new(&mut panel)?;
panel.state.pos = new_pos_tab_handler(&mut panel)?;
panel.state.stereo = new_stereo_tab_handler(&mut panel)?;
panel.state.lock = InteractLockHandler::new(&mut panel)?;
panel.state.tabs = ButtonPaneTabSwitcher::new(&mut panel, &["none", "pos", "alpha", "curve"])?;
panel.state.tabs =
ButtonPaneTabSwitcher::new(&mut panel, &["none", "pos", "alpha", "curve", "stereo"])?;
set_up_checkbox(&mut panel, "additive_box", cb_assign_additive)?;
set_up_slider(&mut panel, "lerp_slider", cb_assign_lerp)?;
@@ -366,10 +407,29 @@ fn reset_panel(
.fetch_component_as::<ComponentCheckbox>("additive_box")?;
c.set_checked(&mut common, state.additive);
panel.state.pos.reset(&mut common, state.positioning);
panel
.state
.pos
.reset(&mut common, &state.positioning.into());
panel.state.lock.reset(&mut common, state.interactable);
panel.state.tabs.reset(&mut common);
if let Some(stereo) = attrib_value!(
owc.backend.get_attrib(BackendAttrib::Stereo),
BackendAttribValue::Stereo
) {
panel
.state
.tabs
.set_tab_visible(&mut common, "stereo", true);
panel.state.stereo.reset(&mut common, &stereo);
} else {
panel
.state
.tabs
.set_tab_visible(&mut common, "stereo", false);
}
panel.layout.process_alterables(alterables)?;
Ok(())

View File

@@ -1,174 +1,116 @@
use std::{collections::HashMap, rc::Rc};
use wgui::{
components::button::ComponentButton,
event::{CallbackDataCommon, StyleSetRequest},
layout::WidgetID,
parser::Fetchable,
renderer_vk::text::custom_glyph::CustomGlyphData,
taffy,
widget::sprite::WidgetSprite,
};
use wgui::{event::StyleSetRequest, parser::Fetchable, taffy};
use wlx_common::{common::LeftRight, windowing::Positioning};
use crate::{
backend::task::ModifyOverlayTask, overlays::edit::EditModeWrapPanel, windowing::window,
overlays::edit::{
sprite_tab::{SpriteTabHandler, SpriteTabKey},
EditModeWrapPanel,
},
windowing::window,
};
static POS_NAMES: [&str; 6] = ["static", "anchored", "floating", "hmd", "hand_l", "hand_r"];
struct PosButtonState {
name: &'static str,
sprite: CustomGlyphData,
component: Rc<ComponentButton>,
positioning: Positioning,
has_interpolation: bool,
}
#[derive(Default)]
pub(super) struct PositioningHandler {
top_sprite_id: WidgetID,
interpolation_id: WidgetID,
buttons: HashMap<&'static str, Rc<PosButtonState>>,
active_button: Option<Rc<PosButtonState>>,
pub struct PosTabState {
pos: Positioning,
has_lerp: bool,
}
impl PositioningHandler {
pub fn new(panel: &mut EditModeWrapPanel) -> anyhow::Result<Self> {
let mut buttons = HashMap::new();
impl From<Positioning> for PosTabState {
fn from(value: Positioning) -> Self {
Self {
pos: value,
has_lerp: false,
}
}
}
for name in &POS_NAMES {
let button_id = format!("pos_{name}");
let component = panel.parser_state.fetch_component_as(&button_id)?;
pub fn new_pos_tab_handler(
panel: &mut EditModeWrapPanel,
) -> anyhow::Result<SpriteTabHandler<PosTabState>> {
let interpolation_id = panel.parser_state.get_widget_id("pos_interpolation")?;
let sprite_id = format!("{button_id}_sprite");
let id = panel.parser_state.get_widget_id(&sprite_id)?;
let sprite_w = panel
.layout
.state
.widgets
.get_as::<WidgetSprite>(id)
.ok_or_else(|| {
anyhow::anyhow!("Element with id=\"{sprite_id}\" must be a <sprite>")
})?;
SpriteTabHandler::new(
panel,
"pos",
&POS_NAMES,
Box::new(|_common, state| {
let positioning = state.pos;
Box::new(move |app, owc| {
let state = owc.active_state.as_mut().unwrap(); //want panic
state.positioning = positioning;
window::save_transform(state, app);
})
}),
Some(Box::new(move |common, state| {
let interpolation_disp = if state.has_lerp {
taffy::Display::Flex
} else {
taffy::Display::None
};
let sprite = sprite_w.params.glyph_data.clone().ok_or_else(|| {
anyhow::anyhow!("Element with id=\"{sprite_id}\" must have a valid src!")
})?;
let (positioning, has_interpolation) = key_to_pos(name);
buttons.insert(
*name,
Rc::new(PosButtonState {
name,
sprite,
component,
positioning,
has_interpolation,
}),
common.alterables.set_style(
interpolation_id,
StyleSetRequest::Display(interpolation_disp),
);
}
let top_sprite_id = panel.parser_state.get_widget_id("top_pos_sprite")?;
let interpolation_id = panel.parser_state.get_widget_id("pos_interpolation")?;
Ok(Self {
buttons,
active_button: None,
top_sprite_id,
interpolation_id,
})
}
fn change_highlight(&mut self, common: &mut CallbackDataCommon, key: &str) {
if let Some(old) = self.active_button.take() {
old.component.set_sticky_state(common, false);
}
let new = self.buttons.get_mut(key).unwrap();
new.component.set_sticky_state(common, true);
self.active_button = Some(new.clone());
let interpolation_disp = if new.has_interpolation {
taffy::Display::Flex
} else {
taffy::Display::None
};
common.alterables.set_style(
self.interpolation_id,
StyleSetRequest::Display(interpolation_disp),
);
// change top sprite
if let Some(mut sprite) = common
.state
.widgets
.get_as::<WidgetSprite>(self.top_sprite_id)
{
sprite.params.glyph_data = Some(new.sprite.clone());
}
}
pub fn pos_button_clicked(
&mut self,
common: &mut CallbackDataCommon,
key: &str,
) -> Box<ModifyOverlayTask> {
self.change_highlight(common, key);
let (pos, _) = key_to_pos(key);
Box::new(move |app, owc| {
let state = owc.active_state.as_mut().unwrap(); //want panic
state.positioning = pos;
window::save_transform(state, app);
})
}
pub fn reset(&mut self, common: &mut CallbackDataCommon, pos: Positioning) {
let key = pos_to_key(pos);
self.change_highlight(common, key);
}
})),
)
}
fn key_to_pos(key: &str) -> (Positioning, bool) {
match key {
"static" => (Positioning::Static, false),
"anchored" => (Positioning::Anchored, false),
"floating" => (Positioning::Floating, false),
"hmd" => (Positioning::FollowHead { lerp: 1.0 }, true),
"hand_l" => (
impl SpriteTabKey for PosTabState {
fn to_tab_key(&self) -> &'static str {
match self.pos {
Positioning::Static => "static",
Positioning::Anchored => "anchored",
Positioning::Floating => "floating",
Positioning::FollowHead { .. } => "hmd",
Positioning::FollowHand {
hand: LeftRight::Left,
lerp: 1.0,
},
true,
),
"hand_r" => (
..
} => "hand_l",
Positioning::FollowHand {
hand: LeftRight::Right,
lerp: 1.0,
..
} => "hand_r",
}
}
fn from_tab_key(key: &str) -> Self {
match key {
"static" => PosTabState {
pos: Positioning::Static,
has_lerp: false,
},
true,
),
_ => {
panic!("cannot translate to positioning: {key}")
"anchored" => PosTabState {
pos: Positioning::Anchored,
has_lerp: false,
},
"floating" => PosTabState {
pos: Positioning::Floating,
has_lerp: false,
},
"hmd" => PosTabState {
pos: Positioning::FollowHead { lerp: 1.0 },
has_lerp: true,
},
"hand_l" => PosTabState {
pos: Positioning::FollowHand {
hand: LeftRight::Left,
lerp: 1.0,
},
has_lerp: true,
},
"hand_r" => PosTabState {
pos: Positioning::FollowHand {
hand: LeftRight::Right,
lerp: 1.0,
},
has_lerp: true,
},
_ => {
panic!("cannot translate to positioning: {key}")
}
}
}
}
const fn pos_to_key(pos: Positioning) -> &'static str {
match pos {
Positioning::Static => "static",
Positioning::Anchored => "anchored",
Positioning::Floating => "floating",
Positioning::FollowHead { .. } => "hmd",
Positioning::FollowHand {
hand: LeftRight::Left,
..
} => "hand_l",
Positioning::FollowHand {
hand: LeftRight::Right,
..
} => "hand_r",
}
}

View File

@@ -0,0 +1,129 @@
use std::{collections::HashMap, rc::Rc};
use wgui::{
components::button::ComponentButton, event::CallbackDataCommon, layout::WidgetID,
parser::Fetchable, renderer_vk::text::custom_glyph::CustomGlyphData,
widget::sprite::WidgetSprite,
};
use crate::{backend::task::ModifyOverlayTask, overlays::edit::EditModeWrapPanel};
pub trait SpriteTabKey {
fn to_tab_key(&self) -> &'static str;
fn from_tab_key(key: &str) -> Self;
}
struct SpriteTabButtonState<S> {
name: &'static str,
sprite: CustomGlyphData,
component: Rc<ComponentButton>,
state: S,
}
pub type SpriteTabHighlightChanged<S> = dyn Fn(&mut CallbackDataCommon, &S);
pub type SpriteTabButtonClicked<S> = dyn Fn(&mut CallbackDataCommon, &S) -> Box<ModifyOverlayTask>;
#[derive(Default)]
pub(super) struct SpriteTabHandler<S> {
top_sprite_id: WidgetID,
buttons: HashMap<&'static str, Rc<SpriteTabButtonState<S>>>,
active_button: Option<Rc<SpriteTabButtonState<S>>>,
on_highlight_changed: Option<Box<SpriteTabHighlightChanged<S>>>,
on_button_clicked: Option<Box<SpriteTabButtonClicked<S>>>,
}
impl<S> SpriteTabHandler<S>
where
S: SpriteTabKey,
{
pub fn new(
panel: &mut EditModeWrapPanel,
prefix: &str,
names: &[&'static str],
on_button_clicked: Box<SpriteTabButtonClicked<S>>,
on_highlight_changed: Option<Box<SpriteTabHighlightChanged<S>>>,
) -> anyhow::Result<Self> {
let mut buttons = HashMap::new();
for name in names {
let button_id = format!("{prefix}_{name}");
let component = panel.parser_state.fetch_component_as(&button_id)?;
let sprite_id = format!("{button_id}_sprite");
let id = panel.parser_state.get_widget_id(&sprite_id)?;
let sprite_w = panel
.layout
.state
.widgets
.get_as::<WidgetSprite>(id)
.ok_or_else(|| {
anyhow::anyhow!("Element with id=\"{sprite_id}\" must be a <sprite>")
})?;
let sprite = sprite_w.params.glyph_data.clone().ok_or_else(|| {
anyhow::anyhow!("Element with id=\"{sprite_id}\" must have a valid src!")
})?;
let state = S::from_tab_key(name);
buttons.insert(
*name,
Rc::new(SpriteTabButtonState {
name,
sprite,
component,
state,
}),
);
}
let top_sprite_id = panel
.parser_state
.get_widget_id(&format!("top_{prefix}_sprite"))?;
Ok(Self {
buttons,
active_button: None,
top_sprite_id,
on_highlight_changed,
on_button_clicked: Some(on_button_clicked),
})
}
fn change_highlight(&mut self, common: &mut CallbackDataCommon, key: &str) {
if let Some(old) = self.active_button.take() {
old.component.set_sticky_state(common, false);
}
let new = self.buttons.get_mut(key).unwrap();
new.component.set_sticky_state(common, true);
self.active_button = Some(new.clone());
if let Some(highlight_changed) = self.on_highlight_changed.as_ref() {
highlight_changed(common, &new.state);
}
// change top sprite
if let Some(mut sprite) = common
.state
.widgets
.get_as::<WidgetSprite>(self.top_sprite_id)
{
sprite.params.glyph_data = Some(new.sprite.clone());
}
}
pub fn button_clicked(
&mut self,
common: &mut CallbackDataCommon,
key: &str,
) -> Box<ModifyOverlayTask> {
self.change_highlight(common, key);
let state = S::from_tab_key(key);
self.on_button_clicked.as_ref().unwrap()(common, &state)
}
pub fn reset(&mut self, common: &mut CallbackDataCommon, state: &S) {
let key = state.to_tab_key();
self.change_highlight(common, key);
}
}

View File

@@ -0,0 +1,52 @@
use crate::{
overlays::edit::{
sprite_tab::{SpriteTabHandler, SpriteTabKey},
EditModeWrapPanel,
},
windowing::backend::{BackendAttribValue, StereoMode},
};
static STEREO_NAMES: [&str; 5] = ["none", "leftright", "rightleft", "topbottom", "bottomtop"];
pub fn new_stereo_tab_handler(
panel: &mut EditModeWrapPanel,
) -> anyhow::Result<SpriteTabHandler<StereoMode>> {
SpriteTabHandler::new(
panel,
"stereo",
&STEREO_NAMES,
Box::new(|_common, state| {
let stereo = state.clone();
Box::new(move |app, owc| {
owc.backend
.set_attrib(app, BackendAttribValue::Stereo(stereo));
})
}),
None,
)
}
impl SpriteTabKey for StereoMode {
fn to_tab_key(&self) -> &'static str {
match self {
StereoMode::None => "none",
StereoMode::LeftRight => "leftright",
StereoMode::RightLeft => "rightleft",
StereoMode::TopBottom => "topbottom",
StereoMode::BottomTop => "bottomtop",
}
}
fn from_tab_key(key: &str) -> Self {
match key {
"none" => StereoMode::None,
"leftright" => StereoMode::LeftRight,
"rightleft" => StereoMode::RightLeft,
"topbottom" => StereoMode::TopBottom,
"bottomtop" => StereoMode::BottomTop,
_ => {
panic!("cannot translate to positioning: {key}")
}
}
}
}

View File

@@ -67,6 +67,22 @@ impl ButtonPaneTabSwitcher {
self.active_tab = Some(data);
}
pub fn set_tab_visible(&mut self, common: &mut CallbackDataCommon, tab: &str, visible: bool) {
let Some(data) = self.tabs[tab].button.as_ref() else {
return;
};
let display = if visible {
taffy::Display::Flex
} else {
taffy::Display::None
};
common
.alterables
.set_style(data.get_rect(), StyleSetRequest::Display(display));
}
pub fn reset(&mut self, common: &mut CallbackDataCommon) {
if let Some(data) = self.active_tab.take() {
set_tab_active(common, &data, false);

View File

@@ -6,24 +6,27 @@ use std::{
};
use crate::{
KEYMAP_CHANGE,
backend::input::{HoverResult, PointerHit},
gui::panel::GuiPanel,
overlays::keyboard::{builder::create_keyboard_panel, layout::AltModifier},
state::AppState,
subsystem::hid::{
ALT, CTRL, KeyModifier, META, SHIFT, SUPER, VirtualKey, WheelDelta, XkbKeymap,
get_keymap_wl, get_keymap_x11,
get_keymap_wl, get_keymap_x11, KeyModifier, VirtualKey, WheelDelta, XkbKeymap, ALT, CTRL,
META, SHIFT, SUPER,
},
windowing::{
backend::{FrameMeta, OverlayBackend, OverlayEventData, RenderResources, ShouldRender},
backend::{
BackendAttrib, BackendAttribValue, FrameMeta, OverlayBackend, OverlayEventData,
RenderResources, ShouldRender,
},
window::OverlayWindowConfig,
},
KEYMAP_CHANGE,
};
use anyhow::Context;
use glam::{Affine3A, Quat, Vec3, vec3};
use glam::{vec3, Affine3A, Quat, Vec3};
use regex::Regex;
use slotmap::{SlotMap, new_key_type};
use slotmap::{new_key_type, SlotMap};
use wgui::{
drawing,
event::{InternalStateChangeEvent, MouseButton, MouseButtonIndex},
@@ -279,6 +282,12 @@ impl OverlayBackend for KeyboardBackend {
fn get_interaction_transform(&mut self) -> Option<glam::Affine2> {
self.panel().get_interaction_transform()
}
fn get_attrib(&self, _attrib: BackendAttrib) -> Option<BackendAttribValue> {
None
}
fn set_attrib(&mut self, _app: &mut AppState, _value: BackendAttribValue) -> bool {
false
}
}
struct KeyboardState {

View File

@@ -1,14 +1,14 @@
use std::{
sync::{
Arc,
atomic::{AtomicUsize, Ordering},
Arc,
},
task::{Context, Poll},
};
use futures::{Future, FutureExt};
use glam::{Affine2, Affine3A, Quat, Vec3, vec3};
use wlx_capture::pipewire::{PipewireCapture, PipewireSelectScreenResult, pipewire_select_screen};
use glam::{vec3, Affine2, Affine3A, Quat, Vec3};
use wlx_capture::pipewire::{pipewire_select_screen, PipewireCapture, PipewireSelectScreenResult};
use wlx_common::windowing::OverlayWindowState;
use crate::{
@@ -19,12 +19,12 @@ use crate::{
state::{AppSession, AppState},
subsystem::hid::WheelDelta,
windowing::{
OverlaySelector,
backend::{
FrameMeta, OverlayBackend, OverlayEventData, RenderResources, ShouldRender,
ui_transform,
ui_transform, BackendAttrib, BackendAttribValue, FrameMeta, OverlayBackend,
OverlayEventData, RenderResources, ShouldRender,
},
window::{OverlayCategory, OverlayWindowConfig},
OverlaySelector,
},
};
@@ -152,6 +152,20 @@ impl OverlayBackend for MirrorBackend {
fn get_interaction_transform(&mut self) -> Option<Affine2> {
self.interaction_transform
}
fn get_attrib(&self, attrib: BackendAttrib) -> Option<BackendAttribValue> {
if let Some(renderer) = self.renderer.as_ref() {
renderer.get_attrib(attrib)
} else {
None
}
}
fn set_attrib(&mut self, app: &mut AppState, value: BackendAttribValue) -> bool {
if let Some(renderer) = self.renderer.as_mut() {
renderer.set_attrib(app, value)
} else {
false
}
}
}
pub fn new_mirror_name() -> Arc<str> {

View File

@@ -1,22 +1,26 @@
use std::{
sync::{Arc, LazyLock, atomic::AtomicU64},
sync::{atomic::AtomicU64, Arc, LazyLock},
time::Instant,
};
use glam::{Affine2, Vec2, vec2};
use wlx_capture::{WlxCapture, frame::Transform};
use glam::{vec2, Affine2, Vec2};
use wlx_capture::{frame::Transform, WlxCapture};
use crate::{
backend::input::{HoverResult, PointerHit, PointerMode},
backend::{
input::{HoverResult, PointerHit, PointerMode},
XrBackend,
},
graphics::ExtentExt,
state::AppState,
subsystem::hid::{MOUSE_LEFT, MOUSE_MIDDLE, MOUSE_RIGHT, WheelDelta},
subsystem::hid::{WheelDelta, MOUSE_LEFT, MOUSE_MIDDLE, MOUSE_RIGHT},
windowing::backend::{
FrameMeta, OverlayBackend, OverlayEventData, RenderResources, ShouldRender,
BackendAttrib, BackendAttribValue, FrameMeta, OverlayBackend, OverlayEventData,
RenderResources, ShouldRender, StereoMode,
},
};
use super::capture::{ScreenPipeline, WlxCaptureIn, WlxCaptureOut, receive_callback};
use super::capture::{receive_callback, ScreenPipeline, WlxCaptureIn, WlxCaptureOut};
const CURSOR_SIZE: f32 = 16. / 1440.;
@@ -42,6 +46,7 @@ pub struct ScreenBackend {
meta: Option<FrameMeta>,
mouse_transform: Affine2,
interaction_transform: Option<Affine2>,
stereo: Option<StereoMode>,
}
impl ScreenBackend {
@@ -57,6 +62,7 @@ impl ScreenBackend {
meta: None,
mouse_transform: Affine2::ZERO,
interaction_transform: None,
stereo: None,
}
}
@@ -103,7 +109,12 @@ impl ScreenBackend {
}
impl OverlayBackend for ScreenBackend {
fn init(&mut self, _app: &mut AppState) -> anyhow::Result<()> {
fn init(&mut self, app: &mut AppState) -> anyhow::Result<()> {
self.stereo = if matches!(app.xr_backend, XrBackend::OpenXR) {
Some(StereoMode::None)
} else {
None
};
Ok(())
}
fn should_render(&mut self, app: &mut AppState) -> anyhow::Result<ShouldRender> {
@@ -155,10 +166,14 @@ impl OverlayBackend for ScreenBackend {
}
if let Some(frame) = self.capture.receive() {
let meta = frame.get_frame_meta(&app.session.config);
let mut meta = frame.get_frame_meta(&app.session.config);
if let Some(pipeline) = self.pipeline.as_mut() {
if self.meta.is_some_and(|old| old.extent != meta.extent) {
meta.extent[2] = pipeline.get_depth();
if self
.meta
.is_some_and(|old| old.extent[..2] != meta.extent[..2])
{
pipeline.set_extent(app, [meta.extent[0] as _, meta.extent[1] as _])?;
self.set_interaction_transform(
meta.extent.extent_vec2(),
@@ -166,7 +181,10 @@ impl OverlayBackend for ScreenBackend {
);
}
} else {
self.pipeline = Some(ScreenPipeline::new(&meta, app)?);
let pipeline =
ScreenPipeline::new(&meta, app, self.stereo.unwrap_or(StereoMode::None))?;
meta.extent[2] = pipeline.get_depth();
self.pipeline = Some(pipeline);
self.set_interaction_transform(meta.extent.extent_vec2(), frame.get_transform());
}
@@ -259,4 +277,29 @@ impl OverlayBackend for ScreenBackend {
fn get_interaction_transform(&mut self) -> Option<Affine2> {
self.interaction_transform
}
#[allow(unreachable_patterns)]
fn get_attrib(&self, attrib: BackendAttrib) -> Option<BackendAttribValue> {
match attrib {
BackendAttrib::Stereo => self.stereo.map(|s| BackendAttribValue::Stereo(s)),
_ => None,
}
}
#[allow(unreachable_patterns)]
fn set_attrib(&mut self, app: &mut AppState, value: BackendAttribValue) -> bool {
match value {
BackendAttribValue::Stereo(new) => {
if let Some(stereo) = self.stereo.as_mut() {
log::debug!("{}: stereo: {stereo:?} → {new:?}", self.name);
*stereo = new;
if let Some(pipeline) = self.pipeline.as_mut() {
pipeline.set_stereo(app, new).unwrap(); // only panics if gfx is dead
}
true
} else {
false
}
}
_ => false,
}
}
}

View File

@@ -1,54 +1,58 @@
use std::{f32::consts::PI, sync::Arc};
use glam::{Affine3A, Vec3};
use smallvec::smallvec;
use smallvec::{smallvec, SmallVec};
use vulkano::{
buffer::{BufferUsage, Subbuffer},
command_buffer::CommandBufferUsage,
device::Queue,
format::Format,
image::{Image, sampler::Filter, view::ImageView},
image::{sampler::Filter, view::ImageView, Image},
pipeline::graphics::color_blend::AttachmentBlend,
};
use wgui::gfx::{
WGfx,
cmd::WGfxClearMode,
pass::WGfxPass,
pipeline::{WGfxPipeline, WPipelineCreateInfo},
WGfx,
};
use wlx_capture::{
WlxCapture,
frame::{self as wlx_frame, DrmFormat, FrameFormat, MouseMeta, Transform, WlxFrame},
WlxCapture,
};
use wlx_common::config::GeneralConfig;
use crate::{
graphics::{
Vert2Uv,
dmabuf::{WGfxDmabuf, fourcc_to_vk},
upload_quad_vertices,
dmabuf::{fourcc_to_vk, WGfxDmabuf},
upload_quad_vertices, Vert2Uv,
},
state::AppState,
windowing::backend::{FrameMeta, RenderResources},
windowing::backend::{FrameMeta, RenderResources, StereoMode},
};
const CURSOR_SIZE: f32 = 16. / 1440.;
struct MousePass {
struct BufPass {
pass: WGfxPass<Vert2Uv>,
buf_vert: Subbuffer<[Vert2Uv]>,
}
pub(super) struct ScreenPipeline {
mouse: MousePass,
mouse: BufPass,
pass: SmallVec<[BufPass; 2]>,
pipeline: Arc<WGfxPipeline<Vert2Uv>>,
pass: WGfxPass<Vert2Uv>,
buf_alpha: Subbuffer<[f32]>,
extentf: [f32; 2],
stereo: StereoMode,
}
impl ScreenPipeline {
pub(super) fn new(meta: &FrameMeta, app: &mut AppState) -> anyhow::Result<Self> {
pub(super) fn new(
meta: &FrameMeta,
app: &mut AppState,
stereo: StereoMode,
) -> anyhow::Result<Self> {
let extentf = [meta.extent[0] as f32, meta.extent[1] as f32];
let pipeline = app.gfx.create_pipeline(
@@ -63,17 +67,61 @@ impl ScreenPipeline {
.gfx
.empty_buffer(BufferUsage::TRANSFER_DST | BufferUsage::UNIFORM_BUFFER, 1)?;
Ok(Self {
pass: Self::create_pass(app, pipeline.clone(), extentf, buf_alpha.clone())?,
let mut me = Self {
pass: smallvec![Self::create_pass(
app,
pipeline.clone(),
extentf,
buf_alpha.clone()
)?],
mouse: Self::create_mouse_pass(app, pipeline.clone(), extentf, buf_alpha.clone())?,
pipeline,
extentf,
buf_alpha,
})
stereo,
};
me.set_stereo(app, stereo)?;
Ok(me)
}
pub fn set_stereo(&mut self, app: &mut AppState, stereo: StereoMode) -> anyhow::Result<()> {
let depth = if matches!(stereo, StereoMode::None) {
1
} else {
2
};
if self.pass.len() < depth {
self.pass.push(Self::create_pass(
app,
self.pipeline.clone(),
self.extentf,
self.buf_alpha.clone(),
)?);
}
if self.pass.len() > depth {
self.pass.pop();
}
for (eye, current) in self.pass.iter_mut().enumerate() {
let verts = stereo_mode_to_verts(stereo, eye);
current.buf_vert.write()?.copy_from_slice(&verts);
}
Ok(())
}
pub fn get_depth(&self) -> u32 {
self.pass.len() as _
}
pub fn set_extent(&mut self, app: &mut AppState, extentf: [f32; 2]) -> anyhow::Result<()> {
self.pass = Self::create_pass(app, self.pipeline.clone(), extentf, self.buf_alpha.clone())?;
for (eye, pass) in self.pass.iter_mut().enumerate() {
*pass = Self::create_pass(app, self.pipeline.clone(), extentf, self.buf_alpha.clone())?;
let verts = stereo_mode_to_verts(self.stereo, eye);
pass.buf_vert.write()?.copy_from_slice(&verts);
}
self.mouse =
Self::create_mouse_pass(app, self.pipeline.clone(), extentf, self.buf_alpha.clone())?;
Ok(())
@@ -84,21 +132,27 @@ impl ScreenPipeline {
pipeline: Arc<WGfxPipeline<Vert2Uv>>,
extentf: [f32; 2],
buf_alpha: Subbuffer<[f32]>,
) -> anyhow::Result<WGfxPass<Vert2Uv>> {
) -> anyhow::Result<BufPass> {
let set0 = pipeline.uniform_sampler(
0,
app.gfx_extras.fallback_image.clone(),
app.gfx.texture_filter,
)?;
let set1 = pipeline.buffer(1, buf_alpha)?;
pipeline.create_pass(
let buf_vert = app
.gfx
.empty_buffer(BufferUsage::TRANSFER_DST | BufferUsage::VERTEX_BUFFER, 4)?;
let pass = pipeline.create_pass(
extentf,
app.gfx_extras.quad_verts.clone(),
buf_vert.clone(),
0..4,
0..1,
vec![set0, set1],
&Default::default(),
)
)?;
Ok(BufPass { pass, buf_vert })
}
fn create_mouse_pass(
@@ -106,7 +160,7 @@ impl ScreenPipeline {
pipeline: Arc<WGfxPipeline<Vert2Uv>>,
extentf: [f32; 2],
buf_alpha: Subbuffer<[f32]>,
) -> anyhow::Result<MousePass> {
) -> anyhow::Result<BufPass> {
#[rustfmt::skip]
let mouse_bytes = [
0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0xff,
@@ -140,7 +194,7 @@ impl ScreenPipeline {
)?;
cmd_xfer.build_and_execute_now()?;
Ok(MousePass { pass, buf_vert })
Ok(BufPass { pass, buf_vert })
}
pub(super) fn render(
@@ -150,33 +204,103 @@ impl ScreenPipeline {
rdr: &mut RenderResources,
) -> anyhow::Result<()> {
let view = ImageView::new_default(capture.image.clone())?;
self.pass.update_sampler(0, view, app.gfx.texture_filter)?;
self.buf_alpha.write()?[0] = rdr.alpha;
rdr.cmd_buf.run_ref(&self.pass)?;
for (eye, cmd_buf) in rdr.cmd_bufs.iter_mut().enumerate() {
let current = &mut self.pass[eye];
if let Some(mouse) = capture.mouse.as_ref() {
let size = CURSOR_SIZE * self.extentf[1];
let half_size = size * 0.5;
current
.pass
.update_sampler(0, view.clone(), app.gfx.texture_filter)?;
upload_quad_vertices(
&mut self.mouse.buf_vert,
self.extentf[0],
self.extentf[1],
mouse.x.mul_add(self.extentf[0], -half_size),
mouse.y.mul_add(self.extentf[1], -half_size),
size,
size,
)?;
cmd_buf.run_ref(&current.pass)?;
rdr.cmd_buf.run_ref(&self.mouse.pass)?;
if let Some(mouse) = capture.mouse.as_ref() {
let size = CURSOR_SIZE * self.extentf[1];
let half_size = size * 0.5;
upload_quad_vertices(
&mut self.mouse.buf_vert,
self.extentf[0],
self.extentf[1],
mouse.x.mul_add(self.extentf[0], -half_size),
mouse.y.mul_add(self.extentf[1], -half_size),
size,
size,
)?;
cmd_buf.run_ref(&self.mouse.pass)?;
}
}
Ok(())
}
}
fn stereo_mode_to_verts(stereo: StereoMode, array_index: usize) -> [Vert2Uv; 4] {
let eye = match stereo {
StereoMode::RightLeft | StereoMode::BottomTop => (1 - array_index) as f32,
_ => array_index as f32,
};
match stereo {
StereoMode::None => [
Vert2Uv {
in_pos: [0., 0.],
in_uv: [0., 0.],
},
Vert2Uv {
in_pos: [1., 0.],
in_uv: [1., 0.],
},
Vert2Uv {
in_pos: [0., 1.],
in_uv: [0., 1.],
},
Vert2Uv {
in_pos: [1., 1.],
in_uv: [1., 1.],
},
],
StereoMode::LeftRight | StereoMode::RightLeft => [
Vert2Uv {
in_pos: [0., 0.],
in_uv: [eye * 0.5, 0.],
},
Vert2Uv {
in_pos: [1., 0.],
in_uv: [0.5 + eye * 0.5, 0.],
},
Vert2Uv {
in_pos: [0., 1.],
in_uv: [eye * 0.5, 1.],
},
Vert2Uv {
in_pos: [1., 1.],
in_uv: [0.5 + eye * 0.5, 1.],
},
],
StereoMode::TopBottom | StereoMode::BottomTop => [
Vert2Uv {
in_pos: [0., 0.],
in_uv: [0., eye * 0.5],
},
Vert2Uv {
in_pos: [1., 0.],
in_uv: [1., eye * 0.5],
},
Vert2Uv {
in_pos: [0., 1.],
in_uv: [0., 0.5 + eye * 0.5],
},
Vert2Uv {
in_pos: [1., 1.],
in_uv: [1., 0.5 + eye * 0.5],
},
],
}
}
#[derive(Clone)]
pub struct WlxCaptureIn {
name: Arc<str>,

View File

@@ -1,18 +1,18 @@
use anyhow::Context;
use glam::{Affine2, Affine3A, Quat, Vec3, vec3};
use glam::{vec3, Affine2, Affine3A, Quat, Vec3};
use smallvec::smallvec;
use std::{cell::RefCell, collections::HashMap, rc::Rc, sync::Arc};
use vulkano::{
buffer::{BufferUsage, Subbuffer},
command_buffer::CommandBufferUsage,
format::Format,
image::{Image, ImageTiling, SubresourceLayout, view::ImageView},
image::{view::ImageView, Image, ImageTiling, SubresourceLayout},
};
use wayvr_ipc::packet_server::{self, PacketServer, WvrStateChanged};
use wgui::gfx::{
WGfx,
pass::WGfxPass,
pipeline::{WGfxPipeline, WPipelineCreateInfo},
WGfx,
};
use wlx_capture::frame::{DmabufFrame, FourCC, FrameFormat, FramePlane};
use wlx_common::windowing::OverlayWindowState;
@@ -22,22 +22,23 @@ use crate::{
input::{self, HoverResult},
task::{OverlayTask, TaskType},
wayvr::{
self, WayVR, WayVRAction, WayVRDisplayClickAction, display,
self, display,
server_ipc::{gen_args_vec, gen_env_vec},
WayVR, WayVRAction, WayVRDisplayClickAction,
},
},
config_wayvr,
graphics::{Vert2Uv, dmabuf::WGfxDmabuf},
graphics::{dmabuf::WGfxDmabuf, Vert2Uv},
state::{self, AppState},
subsystem::{hid::WheelDelta, input::KeyboardFocus},
windowing::{
OverlayID, OverlaySelector, Z_ORDER_DASHBOARD,
backend::{
FrameMeta, OverlayBackend, OverlayEventData, RenderResources, ShouldRender,
ui_transform,
ui_transform, BackendAttrib, BackendAttribValue, FrameMeta, OverlayBackend,
OverlayEventData, RenderResources, ShouldRender,
},
manager::OverlayWindowManager,
window::{OverlayCategory, OverlayWindowConfig, OverlayWindowData},
OverlayID, OverlaySelector, Z_ORDER_DASHBOARD,
},
};
@@ -703,7 +704,7 @@ impl OverlayBackend for WayVRBackend {
self.pass
.update_sampler(0, image.vk_image_view.clone(), self.graphics.texture_filter)?;
self.buf_alpha.write()?[0] = rdr.alpha;
rdr.cmd_buf.run_ref(&self.pass)?;
rdr.cmd_buf_single().run_ref(&self.pass)?;
Ok(())
}
@@ -782,6 +783,13 @@ impl OverlayBackend for WayVRBackend {
fn get_interaction_transform(&mut self) -> Option<Affine2> {
self.interaction_transform
}
fn get_attrib(&self, _attrib: BackendAttrib) -> Option<BackendAttribValue> {
None
}
fn set_attrib(&mut self, _app: &mut AppState, _value: BackendAttribValue) -> bool {
false
}
}
#[allow(dead_code)]