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

@@ -0,0 +1 @@
<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="white" d="M13 15h3q.825 0 1.413-.587T18 13v-2q0-.825-.587-1.412T16 9h-3zm1.5-1.5v-3H16q.2 0 .35.15t.15.35v2q0 .2-.15.35t-.35.15zm-8 1.5H10q.425 0 .713-.288T11 14v-1q0-.425-.288-.712T10 12q.425 0 .713-.288T11 11v-1q0-.425-.288-.712T10 9H6.5v1.5h3v.75h-2v1.5h2v.75h-3zM4 20q-.825 0-1.412-.587T2 18V6q0-.825.588-1.412T4 4h16q.825 0 1.413.588T22 6v12q0 .825-.587 1.413T20 20z"/></svg>

After

Width:  |  Height:  |  Size: 591 B

View File

@@ -0,0 +1 @@
<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="white" d="M5 21q-.825 0-1.412-.587T3 19v-4q0-.825.588-1.412T5 13h14q.825 0 1.413.588T21 15v4q0 .825-.587 1.413T19 21zm0-10q-.825 0-1.412-.587T3 9V5q0-.825.588-1.412T5 3h14q.825 0 1.413.588T21 5v4q0 .825-.587 1.413T19 11zm14-6H5v4h14z"/></svg>

After

Width:  |  Height:  |  Size: 453 B

View File

@@ -0,0 +1 @@
<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="white" d="M5 21q-.825 0-1.412-.587T3 19V5q0-.825.588-1.412T5 3h4q.825 0 1.413.588T11 5v14q0 .825-.587 1.413T9 21zm10 0q-.825 0-1.412-.587T13 19V5q0-.825.588-1.412T15 3h4q.825 0 1.413.588T21 5v14q0 .825-.587 1.413T19 21zm4-16h-4v14h4z"/></svg>

After

Width:  |  Height:  |  Size: 453 B

View File

@@ -0,0 +1 @@
<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="white" d="M15 21q-.825 0-1.412-.587T13 19V5q0-.825.588-1.412T15 3h4q.825 0 1.413.588T21 5v14q0 .825-.587 1.413T19 21zM5 21q-.825 0-1.412-.587T3 19V5q0-.825.588-1.412T5 3h4q.825 0 1.413.588T11 5v14q0 .825-.587 1.413T9 21zM5 5v14h4V5z"/></svg>

After

Width:  |  Height:  |  Size: 452 B

View File

@@ -0,0 +1 @@
<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="white" d="M5 11q-.825 0-1.412-.587T3 9V5q0-.825.588-1.412T5 3h14q.825 0 1.413.588T21 5v4q0 .825-.587 1.413T19 11zm0 10q-.825 0-1.412-.587T3 19v-4q0-.825.588-1.412T5 13h14q.825 0 1.413.588T21 15v4q0 .825-.587 1.413T19 21zm0-2h14v-4H5z"/></svg>

After

Width:  |  Height:  |  Size: 453 B

View File

@@ -0,0 +1 @@
<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="m2.1 2.075l19.8 19.8l-1.425 1.425l-1.1-1.1q-.5.375-1.1.588T17 23h-6.95q-.75 0-1.4-.337T7.575 21.7L1.2 12.375l.6-.575q.475-.475 1.125-.55t1.175.3L7 13.575v-3.75L.675 3.5zM21 18.15l-6-5.975V3q0-.425.288-.712T16 2t.713.288T17 3v9.175h2V5q0-.425.288-.712T20 4t.713.288T21 5zm-8-8l-2-2V2q0-.425.288-.712T12 1t.713.288T13 2zm-4-4l-2-2V4q0-.425.288-.712T8 3t.713.288T9 4z"/></svg>

After

Width:  |  Height:  |  Size: 600 B

View File

@@ -44,6 +44,7 @@
<TopButton sticky="0" id="top_pos" src="edit/anchor.svg" tooltip="EDIT_MODE.POSITIONING" press="::EditModeTab pos" /> <TopButton sticky="0" id="top_pos" src="edit/anchor.svg" tooltip="EDIT_MODE.POSITIONING" press="::EditModeTab pos" />
<TopButton sticky="0" id="top_alpha" src="edit/fade.svg" tooltip="EDIT_MODE.OPACITY" press="::EditModeTab alpha" /> <TopButton sticky="0" id="top_alpha" src="edit/fade.svg" tooltip="EDIT_MODE.OPACITY" press="::EditModeTab alpha" />
<TopButton sticky="0" id="top_curve" src="edit/curve.svg" tooltip="EDIT_MODE.ADJUST_CURVATURE" press="::EditModeTab curve" /> <TopButton sticky="0" id="top_curve" src="edit/curve.svg" tooltip="EDIT_MODE.ADJUST_CURVATURE" press="::EditModeTab curve" />
<TopButton sticky="0" id="top_stereo" src="edit/3d.svg" tooltip="EDIT_MODE.STEREO_MODE" press="::EditModeTab stereo" />
<!-- TopButton sticky="0" id="top_move" src="edit/move-all.svg" tooltip="EDIT_MODE.MOVE_PRESS_AND_DRAG" / --> <!-- TopButton sticky="0" id="top_move" src="edit/move-all.svg" tooltip="EDIT_MODE.MOVE_PRESS_AND_DRAG" / -->
<!-- TopButton sticky="0" id="top_resize" src="edit/resize.svg" tooltip="EDIT_MODE.RESIZE_PRESS_AND_DRAG" / --> <!-- TopButton sticky="0" id="top_resize" src="edit/resize.svg" tooltip="EDIT_MODE.RESIZE_PRESS_AND_DRAG" / -->
<TopButtonDanger src="edit/delete.svg" tooltip="EDIT_MODE.DELETE" press="::EditModeDeletePress" release="::EditModeDeleteRelease" /> <TopButtonDanger src="edit/delete.svg" tooltip="EDIT_MODE.DELETE" press="::EditModeDeletePress" release="::EditModeDeleteRelease" />
@@ -67,6 +68,15 @@
<Slider id="lerp_slider" width="250" height="16" min_value="0.05" max_value="1" value="1" step="0.05" /> <Slider id="lerp_slider" width="250" height="16" min_value="0.05" max_value="1" value="1" step="0.05" />
</div> </div>
</div> </div>
<div id="tab_stereo" display="none" height="100" flex_direction="column">
<div padding="8" gap="8" justify_content="center" align_items="center">
<PosButton id="stereo_none" src="edit/3d.svg" tooltip="EDIT_MODE.STEREO_NONE" press="::EditModeSetStereo none" />
<PosButton id="stereo_leftright" src="edit/3d_leftright.svg" tooltip="EDIT_MODE.STEREO_LEFTRIGHT" press="::EditModeSetStereo leftright" />
<PosButton id="stereo_rightleft" src="edit/3d_rightleft.svg" tooltip="EDIT_MODE.STEREO_RIGHTLEFT" press="::EditModeSetStereo rightleft" />
<PosButton id="stereo_topbottom" src="edit/3d_topbottom.svg" tooltip="EDIT_MODE.STEREO_TOPBOTTOM" press="::EditModeSetStereo topbottom" />
<PosButton id="stereo_bottomtop" src="edit/3d_bottomtop.svg" tooltip="EDIT_MODE.STEREO_BOTTOMTOP" press="::EditModeSetStereo bottomtop" />
</div>
</div>
<div id="tab_alpha" display="none" height="100" padding="8" gap="8" justify_content="center" align_items="center"> <div id="tab_alpha" display="none" height="100" padding="8" gap="8" justify_content="center" align_items="center">
<div width="100%" padding="8" gap="8" justify_content="center" align_items="center"> <div width="100%" padding="8" gap="8" justify_content="center" align_items="center">
<label translation="EDIT_MODE.OPACITY" /> <label translation="EDIT_MODE.OPACITY" />

View File

@@ -15,6 +15,11 @@ pub mod task;
use thiserror::Error; use thiserror::Error;
pub enum XrBackend {
OpenXR,
OpenVR,
}
#[derive(Error, Debug)] #[derive(Error, Debug)]
pub enum BackendError { pub enum BackendError {
#[error("backend not supported")] #[error("backend not supported")]

View File

@@ -1,6 +1,6 @@
use std::f32::consts::PI; use std::f32::consts::PI;
use std::sync::Arc;
use std::sync::atomic::{AtomicUsize, Ordering}; use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::Arc;
use ash::vk::SubmitInfo; use ash::vk::SubmitInfo;
use glam::{Affine3A, Vec3, Vec3A, Vec4}; use glam::{Affine3A, Vec3, Vec3A, Vec4};
@@ -8,7 +8,6 @@ use idmap::IdMap;
use ovr_overlay::overlay::OverlayManager; use ovr_overlay::overlay::OverlayManager;
use ovr_overlay::sys::ETrackingUniverseOrigin; use ovr_overlay::sys::ETrackingUniverseOrigin;
use vulkano::{ use vulkano::{
VulkanObject,
command_buffer::{ command_buffer::{
CommandBufferBeginInfo, CommandBufferLevel, CommandBufferUsage, RecordingCommandBuffer, CommandBufferBeginInfo, CommandBufferLevel, CommandBufferUsage, RecordingCommandBuffer,
}, },
@@ -16,20 +15,22 @@ use vulkano::{
image::view::ImageView, image::view::ImageView,
image::{Image, ImageLayout}, image::{Image, ImageLayout},
sync::{ sync::{
AccessFlags, DependencyInfo, ImageMemoryBarrier, PipelineStages,
fence::{Fence, FenceCreateInfo}, fence::{Fence, FenceCreateInfo},
AccessFlags, DependencyInfo, ImageMemoryBarrier, PipelineStages,
}, },
VulkanObject,
}; };
use wgui::gfx::WGfx; use wgui::gfx::WGfx;
use crate::backend::input::{HoverResult, PointerHit}; use crate::backend::input::{HoverResult, PointerHit};
use crate::state::AppState; use crate::state::AppState;
use crate::subsystem::hid::WheelDelta; use crate::subsystem::hid::WheelDelta;
use crate::windowing::Z_ORDER_LINES;
use crate::windowing::backend::{ use crate::windowing::backend::{
FrameMeta, OverlayBackend, OverlayEventData, RenderResources, ShouldRender, BackendAttrib, BackendAttribValue, FrameMeta, OverlayBackend, OverlayEventData,
RenderResources, ShouldRender,
}; };
use crate::windowing::window::{OverlayWindowConfig, OverlayWindowData}; use crate::windowing::window::{OverlayWindowConfig, OverlayWindowData};
use crate::windowing::Z_ORDER_LINES;
use super::overlay::OpenVrOverlayData; use super::overlay::OpenVrOverlayData;
@@ -217,6 +218,12 @@ impl OverlayBackend for LineBackend {
fn get_interaction_transform(&mut self) -> Option<glam::Affine2> { fn get_interaction_transform(&mut self) -> Option<glam::Affine2> {
None None
} }
fn get_attrib(&self, _attrib: BackendAttrib) -> Option<BackendAttribValue> {
None
}
fn set_attrib(&mut self, _: &mut AppState, _value: BackendAttribValue) -> bool {
false
}
} }
pub fn transition_layout( pub fn transition_layout(

View File

@@ -5,40 +5,41 @@ use std::{
time::{Duration, Instant}, time::{Duration, Instant},
}; };
use anyhow::{Result, anyhow}; use anyhow::{anyhow, Result};
use ovr_overlay::{ use ovr_overlay::{
TrackedDeviceIndex,
sys::{ETrackedDeviceProperty, EVRApplicationType, EVREventType}, sys::{ETrackedDeviceProperty, EVRApplicationType, EVREventType},
TrackedDeviceIndex,
}; };
use vulkano::{Handle, VulkanObject, device::physical::PhysicalDevice}; use smallvec::smallvec;
use vulkano::{device::physical::PhysicalDevice, Handle, VulkanObject};
use wlx_common::overlays::ToastTopic; use wlx_common::overlays::ToastTopic;
use crate::{ use crate::{
RUNNING,
backend::{ backend::{
BackendError,
input::interact, input::interact,
openvr::{ openvr::{
helpers::adjust_gain, helpers::adjust_gain,
input::{OpenVrInputSource, set_action_manifest}, input::{set_action_manifest, OpenVrInputSource},
lines::LinePool, lines::LinePool,
manifest::{install_manifest, uninstall_manifest}, manifest::{install_manifest, uninstall_manifest},
overlay::OpenVrOverlayData, overlay::OpenVrOverlayData,
}, },
task::{OpenVrTask, OverlayTask, TaskType}, task::{OpenVrTask, OverlayTask, TaskType},
BackendError, XrBackend,
}, },
config::save_state, config::save_state,
graphics::{GpuFutures, init_openvr_graphics}, graphics::{init_openvr_graphics, GpuFutures},
overlays::{ overlays::{
toast::Toast, toast::Toast,
watch::{WATCH_NAME, watch_fade}, watch::{watch_fade, WATCH_NAME},
}, },
state::AppState, state::AppState,
subsystem::notifications::NotificationManager, subsystem::notifications::NotificationManager,
windowing::{ windowing::{
backend::{RenderResources, ShouldRender}, backend::{RenderResources, RenderTarget, ShouldRender, StereoMode},
manager::OverlayWindowManager, manager::OverlayWindowManager,
}, },
RUNNING,
}; };
#[cfg(feature = "wayvr")] #[cfg(feature = "wayvr")]
@@ -93,7 +94,7 @@ pub fn openvr_run(show_by_default: bool, headless: bool) -> Result<(), BackendEr
let mut app = { let mut app = {
let (gfx, gfx_extras) = init_openvr_graphics(instance_extensions, device_extensions_fn)?; let (gfx, gfx_extras) = init_openvr_graphics(instance_extensions, device_extensions_fn)?;
AppState::from_graphics(gfx, gfx_extras)? AppState::from_graphics(gfx, gfx_extras, XrBackend::OpenVR)?
}; };
if show_by_default { if show_by_default {
@@ -303,11 +304,13 @@ pub fn openvr_run(show_by_default: bool, headless: bool) -> Result<(), BackendEr
continue; continue;
}; };
let meta = o.config.backend.frame_meta().unwrap(); let meta = o.config.backend.frame_meta().unwrap();
let tgt = o.ensure_staging_image(&mut app, meta.extent)?; let tgt = RenderTarget {
views: smallvec![o.ensure_staging_image(&mut app, meta.extent)?],
};
let mut rdr = RenderResources::new(app.gfx.clone(), tgt, &meta, 1.0)?; let mut rdr = RenderResources::new(app.gfx.clone(), tgt, &meta, 1.0)?;
o.render(&mut app, &mut rdr)?; o.render(&mut app, &mut rdr)?;
o.data.image_dirty = true; o.data.image_dirty = true;
futures.execute(rdr.end()?)?; futures.execute_results(rdr.end()?)?;
} }
} }

View File

@@ -1,19 +1,20 @@
use glam::{Affine3A, Vec3, Vec3A}; use glam::{Affine3A, Vec3, Vec3A};
use idmap::IdMap; use idmap::IdMap;
use openxr as xr; use openxr as xr;
use smallvec::SmallVec;
use std::{ use std::{
f32::consts::PI, f32::consts::PI,
sync::{ sync::{
Arc,
atomic::{AtomicUsize, Ordering}, atomic::{AtomicUsize, Ordering},
Arc,
}, },
}; };
use wgui::gfx::{ use wgui::gfx::{
WGfx,
cmd::WGfxClearMode, cmd::WGfxClearMode,
pass::WGfxPass, pass::WGfxPass,
pipeline::{WGfxPipeline, WPipelineCreateInfo}, pipeline::{WGfxPipeline, WPipelineCreateInfo},
WGfx,
}; };
use crate::{ use crate::{
@@ -27,8 +28,8 @@ use vulkano::{
}; };
use super::{ use super::{
swapchain::{create_swapchain, SwapchainOpts, WlxSwapchain},
CompositionLayer, XrState, CompositionLayer, XrState,
swapchain::{SwapchainOpts, WlxSwapchain, create_swapchain},
}; };
static LINE_AUTO_INCREMENT: AtomicUsize = AtomicUsize::new(1); static LINE_AUTO_INCREMENT: AtomicUsize = AtomicUsize::new(1);
@@ -156,7 +157,13 @@ impl LinePool {
) -> anyhow::Result<()> { ) -> anyhow::Result<()> {
for line in self.lines.values_mut() { for line in self.lines.values_mut() {
if let Some(inner) = line.maybe_line.as_mut() { if let Some(inner) = line.maybe_line.as_mut() {
let tgt = line.swapchain.acquire_wait_image()?; let tgt = line
.swapchain
.acquire_wait_image()?
.views
.into_iter()
.next()
.unwrap();
self.buf_color.write()?[0..6].copy_from_slice(&COLORS[inner.color]); self.buf_color.write()?[0..6].copy_from_slice(&COLORS[inner.color]);
@@ -167,7 +174,7 @@ impl LinePool {
cmd_buffer.run_ref(&self.pass)?; cmd_buffer.run_ref(&self.pass)?;
cmd_buffer.end_rendering()?; cmd_buffer.end_rendering()?;
futures.execute((cmd_buffer.queue.clone(), cmd_buffer.build()?))?; futures.execute(cmd_buffer.queue.clone(), cmd_buffer.build()?)?;
} }
} }
@@ -177,8 +184,8 @@ impl LinePool {
pub(super) fn present<'a>( pub(super) fn present<'a>(
&'a mut self, &'a mut self,
xr: &'a XrState, xr: &'a XrState,
) -> anyhow::Result<Vec<CompositionLayer<'a>>> { ) -> anyhow::Result<SmallVec<[CompositionLayer<'a>; 2]>> {
let mut quads = Vec::new(); let mut quads = SmallVec::new_const();
for line in self.lines.values_mut() { for line in self.lines.values_mut() {
line.swapchain.ensure_image_released()?; line.swapchain.ensure_image_released()?;
@@ -186,7 +193,7 @@ impl LinePool {
if let Some(inner) = line.maybe_line.take() { if let Some(inner) = line.maybe_line.take() {
let quad = xr::CompositionLayerQuad::new() let quad = xr::CompositionLayerQuad::new()
.pose(inner.pose) .pose(inner.pose)
.sub_image(line.swapchain.get_subimage()) .sub_image(line.swapchain.get_subimage(0))
.eye_visibility(xr::EyeVisibility::BOTH) .eye_visibility(xr::EyeVisibility::BOTH)
.space(&xr.stage) .space(&xr.stage)
.size(xr::Extent2Df { .size(xr::Extent2Df {

View File

@@ -1,7 +1,7 @@
use std::{ use std::{
collections::VecDeque, collections::VecDeque,
ops::Add, ops::Add,
sync::{Arc, atomic::Ordering}, sync::{atomic::Ordering, Arc},
time::{Duration, Instant}, time::{Duration, Instant},
}; };
@@ -14,25 +14,25 @@ use vulkano::{Handle, VulkanObject};
use wlx_common::overlays::ToastTopic; use wlx_common::overlays::ToastTopic;
use crate::{ use crate::{
FRAME_COUNTER, RUNNING,
backend::{ backend::{
BackendError,
input::interact, input::interact,
openxr::{lines::LinePool, overlay::OpenXrOverlayData}, openxr::{lines::LinePool, overlay::OpenXrOverlayData},
task::{OverlayTask, TaskType}, task::{OverlayTask, TaskType},
BackendError, XrBackend,
}, },
config::save_state, config::save_state,
graphics::{GpuFutures, init_openxr_graphics}, graphics::{init_openxr_graphics, GpuFutures},
overlays::{ overlays::{
toast::Toast, toast::Toast,
watch::{WATCH_NAME, watch_fade}, watch::{watch_fade, WATCH_NAME},
}, },
state::AppState, state::AppState,
subsystem::notifications::NotificationManager, subsystem::notifications::NotificationManager,
windowing::{ windowing::{
backend::{RenderResources, ShouldRender}, backend::{RenderResources, RenderTarget, ShouldRender},
manager::OverlayWindowManager, manager::OverlayWindowManager,
}, },
FRAME_COUNTER, RUNNING,
}; };
#[cfg(feature = "wayvr")] #[cfg(feature = "wayvr")]
@@ -70,7 +70,7 @@ pub fn openxr_run(show_by_default: bool, headless: bool) -> Result<(), BackendEr
let mut app = { let mut app = {
let (gfx, gfx_extras) = init_openxr_graphics(xr_instance.clone(), system)?; let (gfx, gfx_extras) = init_openxr_graphics(xr_instance.clone(), system)?;
AppState::from_graphics(gfx, gfx_extras)? AppState::from_graphics(gfx, gfx_extras, XrBackend::OpenXR)?
}; };
let environment_blend_mode = { let environment_blend_mode = {
@@ -403,11 +403,12 @@ pub fn openxr_run(show_by_default: bool, headless: bool) -> Result<(), BackendEr
if should_render { if should_render {
let meta = o.config.backend.frame_meta().unwrap(); // want panic let meta = o.config.backend.frame_meta().unwrap(); // want panic
let tgt = o.ensure_swapchain_acquire(&app, &xr_state, meta.extent)?; let wsi = o.ensure_swapchain_acquire(&app, &xr_state, meta.extent)?;
let tgt = RenderTarget { views: wsi.views };
let mut rdr = RenderResources::new(app.gfx.clone(), tgt, &meta, alpha)?; let mut rdr = RenderResources::new(app.gfx.clone(), tgt, &meta, alpha)?;
o.render(&mut app, &mut rdr)?; o.render(&mut app, &mut rdr)?;
o.data.last_alpha = alpha; o.data.last_alpha = alpha;
futures.execute(rdr.end()?)?; futures.execute_results(rdr.end()?)?;
} else if o.data.swapchain.is_none() { } else if o.data.swapchain.is_none() {
continue; continue;
} }
@@ -439,18 +440,13 @@ pub fn openxr_run(show_by_default: bool, headless: bool) -> Result<(), BackendEr
o.data.swapchain.as_mut().unwrap().ensure_image_released()?; o.data.swapchain.as_mut().unwrap().ensure_image_released()?;
continue; continue;
} }
let maybe_layer = o.present(&xr_state)?; for layer in o.present(&xr_state)? {
if matches!(maybe_layer, CompositionLayer::None) { layers.push((dist_sq, layer));
continue;
} }
layers.push((dist_sq, maybe_layer));
} }
for maybe_layer in lines.present(&xr_state)? { for layer in lines.present(&xr_state)? {
if matches!(maybe_layer, CompositionLayer::None) { layers.push((0.0, layer));
continue;
}
layers.push((0.0, maybe_layer));
} }
// End layer composition // End layer composition
@@ -468,7 +464,6 @@ pub fn openxr_run(show_by_default: bool, headless: bool) -> Result<(), BackendEr
CompositionLayer::Quad(ref l) => l as &xr::CompositionLayerBase<xr::Vulkan>, CompositionLayer::Quad(ref l) => l as &xr::CompositionLayerBase<xr::Vulkan>,
CompositionLayer::Cylinder(ref l) => l as &xr::CompositionLayerBase<xr::Vulkan>, CompositionLayer::Cylinder(ref l) => l as &xr::CompositionLayerBase<xr::Vulkan>,
CompositionLayer::Equirect2(ref l) => l as &xr::CompositionLayerBase<xr::Vulkan>, CompositionLayer::Equirect2(ref l) => l as &xr::CompositionLayerBase<xr::Vulkan>,
CompositionLayer::None => unreachable!(),
}) })
.collect::<Vec<_>>(); .collect::<Vec<_>>();
@@ -523,7 +518,6 @@ pub fn openxr_run(show_by_default: bool, headless: bool) -> Result<(), BackendEr
} }
pub(super) enum CompositionLayer<'a> { pub(super) enum CompositionLayer<'a> {
None,
Quad(xr::CompositionLayerQuad<'a, xr::Vulkan>), Quad(xr::CompositionLayerQuad<'a, xr::Vulkan>),
Cylinder(xr::CompositionLayerCylinderKHR<'a, xr::Vulkan>), Cylinder(xr::CompositionLayerCylinderKHR<'a, xr::Vulkan>),
Equirect2(xr::CompositionLayerEquirect2KHR<'a, xr::Vulkan>), Equirect2(xr::CompositionLayerEquirect2KHR<'a, xr::Vulkan>),

View File

@@ -1,12 +1,12 @@
use glam::Vec3A; use glam::Vec3A;
use openxr::{self as xr, CompositionLayerFlags}; use openxr::{self as xr, CompositionLayerFlags};
use std::{f32::consts::PI, sync::Arc}; use smallvec::{smallvec, SmallVec};
use vulkano::image::view::ImageView; use std::f32::consts::PI;
use xr::EyeVisibility; use xr::EyeVisibility;
use super::{CompositionLayer, XrState, helpers, swapchain::WlxSwapchain}; use super::{helpers, swapchain::WlxSwapchain, CompositionLayer, XrState};
use crate::{ use crate::{
backend::openxr::swapchain::{SwapchainOpts, create_swapchain}, backend::openxr::swapchain::{create_swapchain, SwapchainOpts, WlxSwapchainImage},
state::AppState, state::AppState,
windowing::window::OverlayWindowData, windowing::window::OverlayWindowData,
}; };
@@ -26,7 +26,7 @@ impl OverlayWindowData<OpenXrOverlayData> {
app: &AppState, app: &AppState,
xr: &'a XrState, xr: &'a XrState,
extent: [u32; 3], extent: [u32; 3],
) -> anyhow::Result<Arc<ImageView>> { ) -> anyhow::Result<WlxSwapchainImage> {
if let Some(swapchain) = self.data.swapchain.as_mut() if let Some(swapchain) = self.data.swapchain.as_mut()
&& swapchain.extent == extent && swapchain.extent == extent
{ {
@@ -34,10 +34,11 @@ impl OverlayWindowData<OpenXrOverlayData> {
} }
log::debug!( log::debug!(
"{}: recreating swapchain at {}x{}", "{}: recreating swapchain at {}x{}x{}",
self.config.name, self.config.name,
extent[0], extent[0],
extent[1], extent[1],
extent[2],
); );
let mut swapchain = create_swapchain(xr, app.gfx.clone(), extent, SwapchainOpts::new())?; let mut swapchain = create_swapchain(xr, app.gfx.clone(), extent, SwapchainOpts::new())?;
let tgt = swapchain.acquire_wait_image()?; let tgt = swapchain.acquire_wait_image()?;
@@ -48,21 +49,31 @@ impl OverlayWindowData<OpenXrOverlayData> {
pub(super) fn present<'a>( pub(super) fn present<'a>(
&'a mut self, &'a mut self,
xr: &'a XrState, xr: &'a XrState,
) -> anyhow::Result<CompositionLayer<'a>> { ) -> anyhow::Result<SmallVec<[CompositionLayer<'a>; 2]>> {
let mut layers = SmallVec::new_const();
let Some(swapchain) = self.data.swapchain.as_mut() else { let Some(swapchain) = self.data.swapchain.as_mut() else {
log::warn!("{}: swapchain not ready", self.config.name); log::trace!("{}: no swapchain", self.config.name);
return Ok(CompositionLayer::None); return Ok(layers);
}; };
if !swapchain.ever_acquired { if !swapchain.ever_acquired {
log::warn!("{}: swapchain not rendered", self.config.name); log::warn!("{}: swapchain not rendered", self.config.name);
return Ok(CompositionLayer::None); return Ok(layers);
} }
swapchain.ensure_image_released()?; swapchain.ensure_image_released()?;
// overlays without active_state don't get queued for present // overlays without active_state don't get queued for present
let state = self.config.active_state.as_ref().unwrap(); let state = self.config.active_state.as_ref().unwrap();
let sub_image = swapchain.get_subimage(); let sub_images: SmallVec<[_; 2]> = if swapchain.extent[2] > 1 {
smallvec![
(swapchain.get_subimage(0), EyeVisibility::LEFT),
(swapchain.get_subimage(1), EyeVisibility::RIGHT),
]
} else {
smallvec![(swapchain.get_subimage(0), EyeVisibility::BOTH),]
};
let transform = state.transform * self.config.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 aspect_ratio = swapchain.extent[1] as f32 / swapchain.extent[0] as f32;
@@ -89,30 +100,35 @@ impl OverlayWindowData<OpenXrOverlayData> {
let posef = helpers::translation_rotation_to_posef(center_point, quat); let posef = helpers::translation_rotation_to_posef(center_point, quat);
let angle = 2.0 * (scale_x / (2.0 * radius)); let angle = 2.0 * (scale_x / (2.0 * radius));
let cylinder = xr::CompositionLayerCylinderKHR::new() for sub_image in sub_images {
.layer_flags(flags) let cylinder = xr::CompositionLayerCylinderKHR::new()
.pose(posef) .layer_flags(flags)
.sub_image(sub_image) .pose(posef)
.eye_visibility(EyeVisibility::BOTH) .sub_image(sub_image.0)
.space(&xr.stage) .eye_visibility(sub_image.1)
.radius(radius) .space(&xr.stage)
.central_angle(angle) .radius(radius)
.aspect_ratio(aspect_ratio); .central_angle(angle)
Ok(CompositionLayer::Cylinder(cylinder)) .aspect_ratio(aspect_ratio);
layers.push(CompositionLayer::Cylinder(cylinder))
}
} else { } else {
let posef = helpers::transform_to_posef(&transform); let posef = helpers::transform_to_posef(&transform);
let quad = xr::CompositionLayerQuad::new() for sub_image in sub_images {
.layer_flags(flags) let quad = xr::CompositionLayerQuad::new()
.pose(posef) .layer_flags(flags)
.sub_image(sub_image) .pose(posef)
.eye_visibility(EyeVisibility::BOTH) .sub_image(sub_image.0)
.space(&xr.stage) .eye_visibility(sub_image.1)
.size(xr::Extent2Df { .space(&xr.stage)
width: scale_x, .size(xr::Extent2Df {
height: scale_y, width: scale_x,
}); height: scale_y,
Ok(CompositionLayer::Quad(quad)) });
layers.push(CompositionLayer::Quad(quad))
}
} }
Ok(layers)
} }
pub(super) fn after_input(&mut self, app: &mut AppState) -> anyhow::Result<()> { pub(super) fn after_input(&mut self, app: &mut AppState) -> anyhow::Result<()> {

View File

@@ -15,13 +15,13 @@ use wgui::gfx::{cmd::WGfxClearMode, pipeline::WPipelineCreateInfo};
use crate::{ use crate::{
backend::openxr::{helpers::translation_rotation_to_posef, swapchain::SwapchainOpts}, backend::openxr::{helpers::translation_rotation_to_posef, swapchain::SwapchainOpts},
config_io, config_io,
graphics::{ExtentExt, GpuFutures, dds::WlxCommandBufferDds}, graphics::{dds::WlxCommandBufferDds, ExtentExt, GpuFutures},
state::AppState, state::AppState,
}; };
use super::{ use super::{
swapchain::{create_swapchain, WlxSwapchain},
CompositionLayer, XrState, CompositionLayer, XrState,
swapchain::{WlxSwapchain, create_swapchain},
}; };
pub(super) struct Skybox { pub(super) struct Skybox {
@@ -94,7 +94,12 @@ impl Skybox {
let extent = self.view.image().extent(); let extent = self.view.image().extent();
let mut swapchain = create_swapchain(xr, app.gfx.clone(), extent, opts)?; let mut swapchain = create_swapchain(xr, app.gfx.clone(), extent, opts)?;
let tgt = swapchain.acquire_wait_image()?; let tgt = swapchain
.acquire_wait_image()?
.views
.into_iter()
.next()
.unwrap();
let pipeline = app.gfx.create_pipeline( let pipeline = app.gfx.create_pipeline(
app.gfx_extras.shaders.get("vert_quad").unwrap(), // want panic app.gfx_extras.shaders.get("vert_quad").unwrap(), // want panic
app.gfx_extras.shaders.get("frag_srgb").unwrap(), // want panic app.gfx_extras.shaders.get("frag_srgb").unwrap(), // want panic
@@ -119,7 +124,7 @@ impl Skybox {
cmd_buffer.run_ref(&pass)?; cmd_buffer.run_ref(&pass)?;
cmd_buffer.end_rendering()?; cmd_buffer.end_rendering()?;
futures.execute((cmd_buffer.queue.clone(), cmd_buffer.build()?))?; futures.execute(cmd_buffer.queue.clone(), cmd_buffer.build()?)?;
self.sky = Some(swapchain); self.sky = Some(swapchain);
Ok(()) Ok(())
@@ -148,7 +153,12 @@ impl Skybox {
WPipelineCreateInfo::new(app.gfx.surface_format).use_blend(AttachmentBlend::alpha()), WPipelineCreateInfo::new(app.gfx.surface_format).use_blend(AttachmentBlend::alpha()),
)?; )?;
let tgt = swapchain.acquire_wait_image()?; let tgt = swapchain
.acquire_wait_image()?
.views
.into_iter()
.next()
.unwrap();
let pass = pipeline.create_pass( let pass = pipeline.create_pass(
tgt.extent_f32(), tgt.extent_f32(),
app.gfx_extras.quad_verts.clone(), app.gfx_extras.quad_verts.clone(),
@@ -165,7 +175,7 @@ impl Skybox {
cmd_buffer.run_ref(&pass)?; cmd_buffer.run_ref(&pass)?;
cmd_buffer.end_rendering()?; cmd_buffer.end_rendering()?;
futures.execute((cmd_buffer.queue.clone(), cmd_buffer.build()?))?; futures.execute(cmd_buffer.queue.clone(), cmd_buffer.build()?)?;
self.grid = Some(swapchain); self.grid = Some(swapchain);
Ok(()) Ok(())
@@ -211,7 +221,7 @@ impl Skybox {
.layer_flags(xr::CompositionLayerFlags::BLEND_TEXTURE_SOURCE_ALPHA) .layer_flags(xr::CompositionLayerFlags::BLEND_TEXTURE_SOURCE_ALPHA)
.pose(pose) .pose(pose)
.radius(10.0) .radius(10.0)
.sub_image(self.sky.as_ref().unwrap().get_subimage()) .sub_image(self.sky.as_ref().unwrap().get_subimage(0))
.eye_visibility(xr::EyeVisibility::BOTH) .eye_visibility(xr::EyeVisibility::BOTH)
.space(&xr.stage) .space(&xr.stage)
.central_horizontal_angle(HORIZ_ANGLE) .central_horizontal_angle(HORIZ_ANGLE)
@@ -226,7 +236,7 @@ impl Skybox {
width: 10.0, width: 10.0,
height: 10.0, height: 10.0,
}) })
.sub_image(self.grid.as_ref().unwrap().get_subimage()) .sub_image(self.grid.as_ref().unwrap().get_subimage(0))
.eye_visibility(xr::EyeVisibility::BOTH) .eye_visibility(xr::EyeVisibility::BOTH)
.space(&xr.stage); .space(&xr.stage);

View File

@@ -5,8 +5,12 @@ use openxr as xr;
use smallvec::SmallVec; use smallvec::SmallVec;
use vulkano::{ use vulkano::{
image::{
sys::RawImage,
view::{ImageView, ImageViewCreateInfo},
ImageCreateInfo, ImageUsage,
},
Handle, Handle,
image::{ImageCreateInfo, ImageUsage, sys::RawImage, view::ImageView},
}; };
use wgui::gfx::WGfx; use wgui::gfx::WGfx;
@@ -47,7 +51,7 @@ pub(super) fn create_swapchain(
width: extent[0], width: extent[0],
height: extent[1], height: extent[1],
face_count: 1, face_count: 1,
array_size: 1, array_size: extent[2],
mip_count: 1, mip_count: 1,
})?; })?;
@@ -63,7 +67,8 @@ pub(super) fn create_swapchain(
vk_image, vk_image,
ImageCreateInfo { ImageCreateInfo {
format: gfx.surface_format as _, format: gfx.surface_format as _,
extent, extent: [extent[0], extent[1], 1],
array_layers: extent[2],
usage: ImageUsage::COLOR_ATTACHMENT, usage: ImageUsage::COLOR_ATTACHMENT,
..Default::default() ..Default::default()
}, },
@@ -71,9 +76,15 @@ pub(super) fn create_swapchain(
}; };
// SAFETY: OpenXR guarantees that the image is a swapchain image, thus has memory backing it. // SAFETY: OpenXR guarantees that the image is a swapchain image, thus has memory backing it.
let image = Arc::new(unsafe { raw_image.assume_bound() }); let image = Arc::new(unsafe { raw_image.assume_bound() });
Ok(ImageView::new_default(image)?) let mut wsi = WlxSwapchainImage::default();
for d in 0..extent[2] {
let mut create_info = ImageViewCreateInfo::from_image(&*image);
create_info.subresource_range.array_layers = d..d + 1;
wsi.views.push(ImageView::new(image.clone(), create_info)?);
}
Ok(wsi)
}) })
.collect::<anyhow::Result<SmallVec<[Arc<ImageView>; 4]>>>()?; .collect::<anyhow::Result<SmallVec<[WlxSwapchainImage; 4]>>>()?;
Ok(WlxSwapchain { Ok(WlxSwapchain {
acquired: false, acquired: false,
@@ -84,16 +95,21 @@ pub(super) fn create_swapchain(
}) })
} }
#[derive(Default, Clone)]
pub(super) struct WlxSwapchainImage {
pub views: SmallVec<[Arc<ImageView>; 2]>,
}
pub(super) struct WlxSwapchain { pub(super) struct WlxSwapchain {
acquired: bool, acquired: bool,
pub(super) ever_acquired: bool, pub(super) ever_acquired: bool,
pub(super) swapchain: xr::Swapchain<xr::Vulkan>, pub(super) swapchain: xr::Swapchain<xr::Vulkan>,
pub(super) extent: [u32; 3], pub(super) extent: [u32; 3],
pub(super) images: SmallVec<[Arc<ImageView>; 4]>, pub(super) images: SmallVec<[WlxSwapchainImage; 4]>,
} }
impl WlxSwapchain { impl WlxSwapchain {
pub(super) fn acquire_wait_image(&mut self) -> anyhow::Result<Arc<ImageView>> { pub(super) fn acquire_wait_image(&mut self) -> anyhow::Result<WlxSwapchainImage> {
let idx = self.swapchain.acquire_image()? as usize; let idx = self.swapchain.acquire_image()? as usize;
self.swapchain.wait_image(xr::Duration::INFINITE)?; self.swapchain.wait_image(xr::Duration::INFINITE)?;
self.ever_acquired = true; self.ever_acquired = true;
@@ -109,7 +125,7 @@ impl WlxSwapchain {
Ok(()) Ok(())
} }
pub(super) fn get_subimage(&self) -> xr::SwapchainSubImage<'_, xr::Vulkan> { pub(super) fn get_subimage(&self, array_index: u32) -> xr::SwapchainSubImage<'_, xr::Vulkan> {
debug_assert!(self.ever_acquired, "swapchain was never acquired!"); debug_assert!(self.ever_acquired, "swapchain was never acquired!");
xr::SwapchainSubImage::new() xr::SwapchainSubImage::new()
.swapchain(&self.swapchain) .swapchain(&self.swapchain)
@@ -120,6 +136,6 @@ impl WlxSwapchain {
height: self.extent[1] as _, height: self.extent[1] as _,
}, },
}) })
.image_array_index(0) .image_array_index(array_index)
} }
} }

View File

@@ -6,13 +6,14 @@ use std::{
sync::{Arc, OnceLock}, sync::{Arc, OnceLock},
}; };
use glam::{Vec2, vec2}; use glam::{vec2, Vec2};
use smallvec::SmallVec;
use vulkano::{ use vulkano::{
buffer::{BufferCreateInfo, BufferUsage}, buffer::{BufferCreateInfo, BufferUsage},
command_buffer::{CommandBufferUsage, PrimaryAutoCommandBuffer, PrimaryCommandBufferAbstract}, command_buffer::{CommandBufferUsage, PrimaryAutoCommandBuffer, PrimaryCommandBufferAbstract},
image::view::ImageView, image::view::ImageView,
memory::allocator::{AllocationCreateInfo, MemoryTypeFilter}, memory::allocator::{AllocationCreateInfo, MemoryTypeFilter},
sync::GpuFuture, sync::{now, GpuFuture},
}; };
use wgui::gfx::WGfx; use wgui::gfx::WGfx;
@@ -26,11 +27,11 @@ use crate::shaders::{frag_color, frag_grid, frag_screen, frag_srgb, vert_quad};
use {ash::vk, std::os::raw::c_void}; use {ash::vk, std::os::raw::c_void};
use vulkano::{ use vulkano::{
self, VulkanObject, self,
buffer::{Buffer, BufferContents, IndexBuffer, Subbuffer}, buffer::{Buffer, BufferContents, IndexBuffer, Subbuffer},
device::{ device::{
DeviceCreateInfo, DeviceExtensions, DeviceFeatures, Queue, QueueCreateInfo, QueueFlags,
physical::{PhysicalDevice, PhysicalDeviceType}, physical::{PhysicalDevice, PhysicalDeviceType},
DeviceCreateInfo, DeviceExtensions, DeviceFeatures, Queue, QueueCreateInfo, QueueFlags,
}, },
format::Format, format::Format,
instance::{Instance, InstanceCreateInfo, InstanceExtensions}, instance::{Instance, InstanceCreateInfo, InstanceExtensions},
@@ -39,6 +40,7 @@ use vulkano::{
vertex_input::Vertex, vertex_input::Vertex,
}, },
shader::ShaderModule, shader::ShaderModule,
VulkanObject,
}; };
use dmabuf::get_drm_formats; use dmabuf::get_drm_formats;
@@ -634,6 +636,11 @@ fn queue_families_priorities(
} }
} }
pub struct RenderResult {
pub queue: Arc<Queue>,
pub cmd_buf: Arc<PrimaryAutoCommandBuffer>,
}
#[derive(Default)] #[derive(Default)]
pub struct GpuFutures { pub struct GpuFutures {
futures: Vec<Box<dyn GpuFuture>>, futures: Vec<Box<dyn GpuFuture>>,
@@ -642,12 +649,27 @@ pub struct GpuFutures {
impl GpuFutures { impl GpuFutures {
pub fn execute( pub fn execute(
&mut self, &mut self,
cmd: (Arc<Queue>, Arc<PrimaryAutoCommandBuffer>), queue: Arc<Queue>,
cmd_buf: Arc<PrimaryAutoCommandBuffer>,
) -> anyhow::Result<()> { ) -> anyhow::Result<()> {
self.futures.push(cmd.1.execute(cmd.0)?.boxed()); self.futures.push(cmd_buf.execute(queue)?.boxed());
Ok(()) Ok(())
} }
pub fn execute_results(&mut self, results: SmallVec<[RenderResult; 2]>) -> anyhow::Result<()> {
for (i, res) in results.into_iter().enumerate() {
if i == 0 {
let future = res.cmd_buf.execute(res.queue)?;
self.futures.push(Box::new(future));
} else {
let future = self.futures.pop().unwrap();
let future = future.then_execute(res.queue, res.cmd_buf)?;
self.futures.push(Box::new(future));
}
}
Ok(())
}
pub fn wait(self) -> anyhow::Result<()> { pub fn wait(self) -> anyhow::Result<()> {
let mut it = self.futures.into_iter(); let mut it = self.futures.into_iter();
let Some(mut all) = it.next() else { let Some(mut all) = it.next() else {

View File

@@ -1,7 +1,7 @@
use std::{cell::RefCell, rc::Rc}; use std::{cell::RefCell, rc::Rc};
use button::setup_custom_button; use button::setup_custom_button;
use glam::{Affine2, Vec2, vec2}; use glam::{vec2, Affine2, Vec2};
use label::setup_custom_label; use label::setup_custom_label;
use wgui::{ use wgui::{
assets::AssetPath, assets::AssetPath,
@@ -16,7 +16,7 @@ use wgui::{
layout::{Layout, LayoutParams, WidgetID}, layout::{Layout, LayoutParams, WidgetID},
parser::{CustomAttribsInfoOwned, Fetchable, ParserState}, parser::{CustomAttribsInfoOwned, Fetchable, ParserState},
renderer_vk::context::Context as WguiContext, renderer_vk::context::Context as WguiContext,
widget::{EventResult, label::WidgetLabel}, widget::{label::WidgetLabel, EventResult},
}; };
use wlx_common::timestep::Timestep; use wlx_common::timestep::Timestep;
@@ -25,7 +25,8 @@ use crate::{
state::AppState, state::AppState,
subsystem::hid::WheelDelta, subsystem::hid::WheelDelta,
windowing::backend::{ windowing::backend::{
FrameMeta, OverlayBackend, OverlayEventData, RenderResources, ShouldRender, ui_transform, ui_transform, BackendAttrib, BackendAttribValue, FrameMeta, OverlayBackend,
OverlayEventData, RenderResources, ShouldRender,
}, },
}; };
@@ -290,7 +291,7 @@ impl<S: 'static> OverlayBackend for GuiPanel<S> {
self.context.draw( self.context.draw(
&globals.font_system, &globals.font_system,
&mut app.wgui_shared, &mut app.wgui_shared,
&mut rdr.cmd_buf, &mut rdr.cmd_buf_single(),
&primitives, &primitives,
)?; )?;
Ok(()) Ok(())
@@ -380,4 +381,10 @@ impl<S: 'static> OverlayBackend for GuiPanel<S> {
fn get_interaction_transform(&mut self) -> Option<Affine2> { fn get_interaction_transform(&mut self) -> Option<Affine2> {
self.interaction_transform self.interaction_transform
} }
fn get_attrib(&self, _attrib: BackendAttrib) -> Option<BackendAttribValue> {
None
}
fn set_attrib(&mut self, _app: &mut AppState, _value: BackendAttribValue) -> bool {
false
}
} }

View File

@@ -8,6 +8,7 @@ use std::{
use glam::vec2; use glam::vec2;
use slotmap::Key; use slotmap::Key;
use smallvec::smallvec;
use wgui::{ use wgui::{
components::{button::ComponentButton, checkbox::ComponentCheckbox, slider::ComponentSlider}, components::{button::ComponentButton, checkbox::ComponentCheckbox, slider::ComponentSlider},
event::{CallbackDataCommon, EventAlterables, EventCallback}, event::{CallbackDataCommon, EventAlterables, EventCallback},
@@ -16,25 +17,35 @@ use wgui::{
}; };
use crate::{ use crate::{
attrib_value,
backend::{ backend::{
input::HoverResult, input::HoverResult,
task::{OverlayTask, TaskContainer, TaskType}, task::{OverlayTask, TaskContainer, TaskType},
}, },
gui::panel::{GuiPanel, NewGuiPanelParams, OnCustomAttribFunc, button::BUTTON_EVENTS}, gui::panel::{button::BUTTON_EVENTS, GuiPanel, NewGuiPanelParams, OnCustomAttribFunc},
overlays::edit::{ 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, state::AppState,
subsystem::hid::WheelDelta, subsystem::hid::WheelDelta,
windowing::{ windowing::{
OverlayID, OverlaySelector, backend::{
backend::{DummyBackend, OverlayBackend, OverlayEventData, RenderResources, ShouldRender}, BackendAttrib, BackendAttribValue, DummyBackend, OverlayBackend, OverlayEventData,
RenderResources, ShouldRender, StereoMode,
},
window::OverlayWindowConfig, window::OverlayWindowConfig,
OverlayID, OverlaySelector,
}, },
}; };
mod lock; mod lock;
mod pos; mod pos;
mod sprite_tab;
mod stereo;
pub mod tab; pub mod tab;
pub(super) struct LongPressButtonState { pub(super) struct LongPressButtonState {
@@ -54,7 +65,8 @@ struct EditModeState {
delete: LongPressButtonState, delete: LongPressButtonState,
tabs: ButtonPaneTabSwitcher, tabs: ButtonPaneTabSwitcher,
lock: InteractLockHandler, lock: InteractLockHandler,
pos: PositioningHandler, pos: SpriteTabHandler<PosTabState>,
stereo: SpriteTabHandler<StereoMode>,
} }
type EditModeWrapPanel = GuiPanel<EditModeState>; type EditModeWrapPanel = GuiPanel<EditModeState>;
@@ -77,7 +89,6 @@ impl EditWrapperManager {
} }
log::debug!("EditMode wrap on {}", owc.name); log::debug!("EditMode wrap on {}", owc.name);
let inner = mem::replace(&mut owc.backend, Box::new(DummyBackend {}));
let mut panel = self.panel_pool.pop(); let mut panel = self.panel_pool.pop();
if panel.is_none() { if panel.is_none() {
panel = Some(make_edit_panel(app)?); panel = Some(make_edit_panel(app)?);
@@ -85,6 +96,8 @@ impl EditWrapperManager {
let mut panel = panel.unwrap(); let mut panel = panel.unwrap();
reset_panel(&mut panel, id, owc)?; reset_panel(&mut panel, id, owc)?;
let inner = mem::replace(&mut owc.backend, Box::new(DummyBackend {}));
owc.backend = Box::new(EditModeBackendWrapper { owc.backend = Box::new(EditModeBackendWrapper {
inner: ManuallyDrop::new(inner), inner: ManuallyDrop::new(inner),
panel: ManuallyDrop::new(panel), panel: ManuallyDrop::new(panel),
@@ -178,7 +191,16 @@ impl OverlayBackend for EditModeBackendWrapper {
rdr: &mut RenderResources, rdr: &mut RenderResources,
) -> anyhow::Result<()> { ) -> anyhow::Result<()> {
self.inner.render(app, rdr)?; 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> { fn frame_meta(&mut self) -> Option<crate::windowing::backend::FrameMeta> {
self.inner.frame_meta() self.inner.frame_meta()
@@ -218,6 +240,12 @@ impl OverlayBackend for EditModeBackendWrapper {
fn get_interaction_transform(&mut self) -> Option<glam::Affine2> { fn get_interaction_transform(&mut self) -> Option<glam::Affine2> {
self.inner.get_interaction_transform() 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> { 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(), tabs: ButtonPaneTabSwitcher::default(),
lock: InteractLockHandler::default(), lock: InteractLockHandler::default(),
pos: PositioningHandler::default(), pos: SpriteTabHandler::default(),
stereo: SpriteTabHandler::default(),
}; };
let on_custom_attrib: OnCustomAttribFunc = Box::new(move |layout, attribs, _app| { 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" => { "::EditModeSetPos" => {
let pos_key = args.next().unwrap().to_owned(); let key = args.next().unwrap().to_owned();
Box::new(move |common, _data, app, state| { Box::new(move |common, _data, app, state| {
let sel = OverlaySelector::Id(*state.id.borrow()); 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 app.tasks
.enqueue(TaskType::Overlay(OverlayTask::Modify(sel, task))); .enqueue(TaskType::Overlay(OverlayTask::Modify(sel, task)));
Ok(EventResult::Consumed) 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.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_checkbox(&mut panel, "additive_box", cb_assign_additive)?;
set_up_slider(&mut panel, "lerp_slider", cb_assign_lerp)?; set_up_slider(&mut panel, "lerp_slider", cb_assign_lerp)?;
@@ -366,10 +407,29 @@ fn reset_panel(
.fetch_component_as::<ComponentCheckbox>("additive_box")?; .fetch_component_as::<ComponentCheckbox>("additive_box")?;
c.set_checked(&mut common, state.additive); 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.lock.reset(&mut common, state.interactable);
panel.state.tabs.reset(&mut common); 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)?; panel.layout.process_alterables(alterables)?;
Ok(()) Ok(())

View File

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

View File

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

View File

@@ -1,14 +1,14 @@
use std::{ use std::{
sync::{ sync::{
Arc,
atomic::{AtomicUsize, Ordering}, atomic::{AtomicUsize, Ordering},
Arc,
}, },
task::{Context, Poll}, task::{Context, Poll},
}; };
use futures::{Future, FutureExt}; use futures::{Future, FutureExt};
use glam::{Affine2, Affine3A, Quat, Vec3, vec3}; use glam::{vec3, Affine2, Affine3A, Quat, Vec3};
use wlx_capture::pipewire::{PipewireCapture, PipewireSelectScreenResult, pipewire_select_screen}; use wlx_capture::pipewire::{pipewire_select_screen, PipewireCapture, PipewireSelectScreenResult};
use wlx_common::windowing::OverlayWindowState; use wlx_common::windowing::OverlayWindowState;
use crate::{ use crate::{
@@ -19,12 +19,12 @@ use crate::{
state::{AppSession, AppState}, state::{AppSession, AppState},
subsystem::hid::WheelDelta, subsystem::hid::WheelDelta,
windowing::{ windowing::{
OverlaySelector,
backend::{ backend::{
FrameMeta, OverlayBackend, OverlayEventData, RenderResources, ShouldRender, ui_transform, BackendAttrib, BackendAttribValue, FrameMeta, OverlayBackend,
ui_transform, OverlayEventData, RenderResources, ShouldRender,
}, },
window::{OverlayCategory, OverlayWindowConfig}, window::{OverlayCategory, OverlayWindowConfig},
OverlaySelector,
}, },
}; };
@@ -152,6 +152,20 @@ impl OverlayBackend for MirrorBackend {
fn get_interaction_transform(&mut self) -> Option<Affine2> { fn get_interaction_transform(&mut self) -> Option<Affine2> {
self.interaction_transform 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> { pub fn new_mirror_name() -> Arc<str> {

View File

@@ -1,22 +1,26 @@
use std::{ use std::{
sync::{Arc, LazyLock, atomic::AtomicU64}, sync::{atomic::AtomicU64, Arc, LazyLock},
time::Instant, time::Instant,
}; };
use glam::{Affine2, Vec2, vec2}; use glam::{vec2, Affine2, Vec2};
use wlx_capture::{WlxCapture, frame::Transform}; use wlx_capture::{frame::Transform, WlxCapture};
use crate::{ use crate::{
backend::input::{HoverResult, PointerHit, PointerMode}, backend::{
input::{HoverResult, PointerHit, PointerMode},
XrBackend,
},
graphics::ExtentExt, graphics::ExtentExt,
state::AppState, state::AppState,
subsystem::hid::{MOUSE_LEFT, MOUSE_MIDDLE, MOUSE_RIGHT, WheelDelta}, subsystem::hid::{WheelDelta, MOUSE_LEFT, MOUSE_MIDDLE, MOUSE_RIGHT},
windowing::backend::{ 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.; const CURSOR_SIZE: f32 = 16. / 1440.;
@@ -42,6 +46,7 @@ pub struct ScreenBackend {
meta: Option<FrameMeta>, meta: Option<FrameMeta>,
mouse_transform: Affine2, mouse_transform: Affine2,
interaction_transform: Option<Affine2>, interaction_transform: Option<Affine2>,
stereo: Option<StereoMode>,
} }
impl ScreenBackend { impl ScreenBackend {
@@ -57,6 +62,7 @@ impl ScreenBackend {
meta: None, meta: None,
mouse_transform: Affine2::ZERO, mouse_transform: Affine2::ZERO,
interaction_transform: None, interaction_transform: None,
stereo: None,
} }
} }
@@ -103,7 +109,12 @@ impl ScreenBackend {
} }
impl OverlayBackend for 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(()) Ok(())
} }
fn should_render(&mut self, app: &mut AppState) -> anyhow::Result<ShouldRender> { 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() { 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 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 _])?; pipeline.set_extent(app, [meta.extent[0] as _, meta.extent[1] as _])?;
self.set_interaction_transform( self.set_interaction_transform(
meta.extent.extent_vec2(), meta.extent.extent_vec2(),
@@ -166,7 +181,10 @@ impl OverlayBackend for ScreenBackend {
); );
} }
} else { } 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()); 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> { fn get_interaction_transform(&mut self) -> Option<Affine2> {
self.interaction_transform 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 std::{f32::consts::PI, sync::Arc};
use glam::{Affine3A, Vec3}; use glam::{Affine3A, Vec3};
use smallvec::smallvec; use smallvec::{smallvec, SmallVec};
use vulkano::{ use vulkano::{
buffer::{BufferUsage, Subbuffer}, buffer::{BufferUsage, Subbuffer},
command_buffer::CommandBufferUsage, command_buffer::CommandBufferUsage,
device::Queue, device::Queue,
format::Format, format::Format,
image::{Image, sampler::Filter, view::ImageView}, image::{sampler::Filter, view::ImageView, Image},
pipeline::graphics::color_blend::AttachmentBlend, pipeline::graphics::color_blend::AttachmentBlend,
}; };
use wgui::gfx::{ use wgui::gfx::{
WGfx,
cmd::WGfxClearMode, cmd::WGfxClearMode,
pass::WGfxPass, pass::WGfxPass,
pipeline::{WGfxPipeline, WPipelineCreateInfo}, pipeline::{WGfxPipeline, WPipelineCreateInfo},
WGfx,
}; };
use wlx_capture::{ use wlx_capture::{
WlxCapture,
frame::{self as wlx_frame, DrmFormat, FrameFormat, MouseMeta, Transform, WlxFrame}, frame::{self as wlx_frame, DrmFormat, FrameFormat, MouseMeta, Transform, WlxFrame},
WlxCapture,
}; };
use wlx_common::config::GeneralConfig; use wlx_common::config::GeneralConfig;
use crate::{ use crate::{
graphics::{ graphics::{
Vert2Uv, dmabuf::{fourcc_to_vk, WGfxDmabuf},
dmabuf::{WGfxDmabuf, fourcc_to_vk}, upload_quad_vertices, Vert2Uv,
upload_quad_vertices,
}, },
state::AppState, state::AppState,
windowing::backend::{FrameMeta, RenderResources}, windowing::backend::{FrameMeta, RenderResources, StereoMode},
}; };
const CURSOR_SIZE: f32 = 16. / 1440.; const CURSOR_SIZE: f32 = 16. / 1440.;
struct MousePass { struct BufPass {
pass: WGfxPass<Vert2Uv>, pass: WGfxPass<Vert2Uv>,
buf_vert: Subbuffer<[Vert2Uv]>, buf_vert: Subbuffer<[Vert2Uv]>,
} }
pub(super) struct ScreenPipeline { pub(super) struct ScreenPipeline {
mouse: MousePass, mouse: BufPass,
pass: SmallVec<[BufPass; 2]>,
pipeline: Arc<WGfxPipeline<Vert2Uv>>, pipeline: Arc<WGfxPipeline<Vert2Uv>>,
pass: WGfxPass<Vert2Uv>,
buf_alpha: Subbuffer<[f32]>, buf_alpha: Subbuffer<[f32]>,
extentf: [f32; 2], extentf: [f32; 2],
stereo: StereoMode,
} }
impl ScreenPipeline { 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 extentf = [meta.extent[0] as f32, meta.extent[1] as f32];
let pipeline = app.gfx.create_pipeline( let pipeline = app.gfx.create_pipeline(
@@ -63,17 +67,61 @@ impl ScreenPipeline {
.gfx .gfx
.empty_buffer(BufferUsage::TRANSFER_DST | BufferUsage::UNIFORM_BUFFER, 1)?; .empty_buffer(BufferUsage::TRANSFER_DST | BufferUsage::UNIFORM_BUFFER, 1)?;
Ok(Self { let mut me = Self {
pass: Self::create_pass(app, pipeline.clone(), extentf, buf_alpha.clone())?, pass: smallvec![Self::create_pass(
app,
pipeline.clone(),
extentf,
buf_alpha.clone()
)?],
mouse: Self::create_mouse_pass(app, pipeline.clone(), extentf, buf_alpha.clone())?, mouse: Self::create_mouse_pass(app, pipeline.clone(), extentf, buf_alpha.clone())?,
pipeline, pipeline,
extentf, extentf,
buf_alpha, 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<()> { 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.mouse =
Self::create_mouse_pass(app, self.pipeline.clone(), extentf, self.buf_alpha.clone())?; Self::create_mouse_pass(app, self.pipeline.clone(), extentf, self.buf_alpha.clone())?;
Ok(()) Ok(())
@@ -84,21 +132,27 @@ impl ScreenPipeline {
pipeline: Arc<WGfxPipeline<Vert2Uv>>, pipeline: Arc<WGfxPipeline<Vert2Uv>>,
extentf: [f32; 2], extentf: [f32; 2],
buf_alpha: Subbuffer<[f32]>, buf_alpha: Subbuffer<[f32]>,
) -> anyhow::Result<WGfxPass<Vert2Uv>> { ) -> anyhow::Result<BufPass> {
let set0 = pipeline.uniform_sampler( let set0 = pipeline.uniform_sampler(
0, 0,
app.gfx_extras.fallback_image.clone(), app.gfx_extras.fallback_image.clone(),
app.gfx.texture_filter, app.gfx.texture_filter,
)?; )?;
let set1 = pipeline.buffer(1, buf_alpha)?; 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, extentf,
app.gfx_extras.quad_verts.clone(), buf_vert.clone(),
0..4, 0..4,
0..1, 0..1,
vec![set0, set1], vec![set0, set1],
&Default::default(), &Default::default(),
) )?;
Ok(BufPass { pass, buf_vert })
} }
fn create_mouse_pass( fn create_mouse_pass(
@@ -106,7 +160,7 @@ impl ScreenPipeline {
pipeline: Arc<WGfxPipeline<Vert2Uv>>, pipeline: Arc<WGfxPipeline<Vert2Uv>>,
extentf: [f32; 2], extentf: [f32; 2],
buf_alpha: Subbuffer<[f32]>, buf_alpha: Subbuffer<[f32]>,
) -> anyhow::Result<MousePass> { ) -> anyhow::Result<BufPass> {
#[rustfmt::skip] #[rustfmt::skip]
let mouse_bytes = [ let mouse_bytes = [
0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0xff, 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()?; cmd_xfer.build_and_execute_now()?;
Ok(MousePass { pass, buf_vert }) Ok(BufPass { pass, buf_vert })
} }
pub(super) fn render( pub(super) fn render(
@@ -150,33 +204,103 @@ impl ScreenPipeline {
rdr: &mut RenderResources, rdr: &mut RenderResources,
) -> anyhow::Result<()> { ) -> anyhow::Result<()> {
let view = ImageView::new_default(capture.image.clone())?; 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; 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() { current
let size = CURSOR_SIZE * self.extentf[1]; .pass
let half_size = size * 0.5; .update_sampler(0, view.clone(), app.gfx.texture_filter)?;
upload_quad_vertices( cmd_buf.run_ref(&current.pass)?;
&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,
)?;
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(()) 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)] #[derive(Clone)]
pub struct WlxCaptureIn { pub struct WlxCaptureIn {
name: Arc<str>, name: Arc<str>,

View File

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

View File

@@ -1,6 +1,6 @@
use glam::Affine3A; use glam::Affine3A;
use idmap::IdMap; use idmap::IdMap;
use smallvec::{SmallVec, smallvec}; use smallvec::{smallvec, SmallVec};
use std::sync::Arc; use std::sync::Arc;
use wgui::{ use wgui::{
font_config::WguiFontConfig, gfx::WGfx, globals::WguiGlobals, parser::parse_color_hex, font_config::WguiFontConfig, gfx::WGfx, globals::WguiGlobals, parser::parse_color_hex,
@@ -22,7 +22,7 @@ use {
use crate::subsystem::osc::OscSender; use crate::subsystem::osc::OscSender;
use crate::{ use crate::{
backend::{input::InputState, task::TaskContainer}, backend::{input::InputState, task::TaskContainer, XrBackend},
config::load_general_config, config::load_general_config,
config_io::{self, get_config_file_path}, config_io::{self, get_config_file_path},
graphics::WGfxExtras, graphics::WGfxExtras,
@@ -51,6 +51,8 @@ pub struct AppState {
pub dbus: DbusConnector, pub dbus: DbusConnector,
pub xr_backend: XrBackend,
#[cfg(feature = "osc")] #[cfg(feature = "osc")]
pub osc_sender: Option<OscSender>, pub osc_sender: Option<OscSender>,
@@ -60,7 +62,11 @@ pub struct AppState {
#[allow(unused_mut)] #[allow(unused_mut)]
impl AppState { impl AppState {
pub fn from_graphics(gfx: Arc<WGfx>, gfx_extras: WGfxExtras) -> anyhow::Result<Self> { pub fn from_graphics(
gfx: Arc<WGfx>,
gfx_extras: WGfxExtras,
xr_backend: XrBackend,
) -> anyhow::Result<Self> {
// insert shared resources // insert shared resources
let mut tasks = TaskContainer::new(); let mut tasks = TaskContainer::new();
@@ -133,6 +139,7 @@ impl AppState {
get_config_file_path(&theme), get_config_file_path(&theme),
)?, )?,
dbus, dbus,
xr_backend,
#[cfg(feature = "osc")] #[cfg(feature = "osc")]
osc_sender, osc_sender,

View File

@@ -1,22 +1,18 @@
use glam::{Affine2, Affine3A, Vec2}; use glam::{Affine2, Affine3A, Vec2};
use smallvec::SmallVec;
use std::{any::Any, sync::Arc}; use std::{any::Any, sync::Arc};
use vulkano::{ use vulkano::{command_buffer::CommandBufferUsage, format::Format, image::view::ImageView};
command_buffer::{CommandBufferUsage, PrimaryAutoCommandBuffer},
device::Queue,
format::Format,
image::view::ImageView,
};
use wgui::gfx::{ use wgui::gfx::{
WGfx,
cmd::{GfxCommandBuffer, WGfxClearMode}, cmd::{GfxCommandBuffer, WGfxClearMode},
WGfx,
}; };
use crate::{ use crate::{
backend::input::{HoverResult, PointerHit}, backend::input::{HoverResult, PointerHit},
graphics::ExtentExt, graphics::{ExtentExt, RenderResult},
state::AppState, state::AppState,
subsystem::hid::WheelDelta, subsystem::hid::WheelDelta,
windowing::{OverlayID, window::OverlayCategory}, windowing::{window::OverlayCategory, OverlayID},
}; };
#[derive(Default, Clone, Copy)] #[derive(Default, Clone, Copy)]
@@ -36,33 +32,90 @@ pub enum ShouldRender {
Unable, Unable,
} }
#[derive(Default, Debug, Clone, Copy)]
pub enum StereoMode {
#[default]
None,
LeftRight,
RightLeft,
TopBottom,
BottomTop,
}
pub struct RenderTarget {
pub views: SmallVec<[Arc<ImageView>; 2]>,
}
pub struct RenderResources { pub struct RenderResources {
pub alpha: f32, pub alpha: f32,
pub cmd_buf: GfxCommandBuffer, pub cmd_bufs: SmallVec<[GfxCommandBuffer; 2]>,
pub extent: [u32; 2], pub extent: [u32; 2],
} }
impl RenderResources { impl RenderResources {
pub fn new( pub fn new(
gfx: Arc<WGfx>, gfx: Arc<WGfx>,
tgt: Arc<ImageView>, target: RenderTarget,
meta: &FrameMeta, meta: &FrameMeta,
alpha: f32, alpha: f32,
) -> anyhow::Result<Self> { ) -> anyhow::Result<Self> {
let mut cmd_buf = gfx.create_gfx_command_buffer(CommandBufferUsage::OneTimeSubmit)?; let mut cmd_bufs = SmallVec::new_const();
cmd_buf.begin_rendering(tgt, meta.clear)?;
for tgt in target.views {
let mut cmd_buf = gfx.create_gfx_command_buffer(CommandBufferUsage::OneTimeSubmit)?;
cmd_buf.begin_rendering(tgt, meta.clear)?;
cmd_bufs.push(cmd_buf);
}
Ok(Self { Ok(Self {
cmd_buf, cmd_bufs,
alpha, alpha,
extent: meta.extent.extent_u32arr(), extent: meta.extent.extent_u32arr(),
}) })
} }
pub fn end(mut self) -> anyhow::Result<(Arc<Queue>, Arc<PrimaryAutoCommandBuffer>)> { pub fn cmd_buf_single(&mut self) -> &mut GfxCommandBuffer {
self.cmd_buf.end_rendering()?; self.cmd_bufs.first_mut().unwrap() // first must always be populated
Ok((self.cmd_buf.queue.clone(), self.cmd_buf.build()?))
} }
pub fn is_stereo(&self) -> bool {
self.cmd_bufs.len() > 1
}
pub fn end(self) -> anyhow::Result<SmallVec<[RenderResult; 2]>> {
let mut ret_val = SmallVec::new_const();
for mut buf in self.cmd_bufs {
buf.end_rendering()?;
ret_val.push(RenderResult {
queue: buf.queue.clone(),
cmd_buf: buf.build()?,
});
}
Ok(ret_val)
}
}
#[macro_export]
macro_rules! attrib_value {
($opt:expr, $variant:path) => {
$opt.and_then(|e| match e {
$variant(inner) => Some(inner),
_ => None,
})
};
}
#[derive(Clone, Copy)]
pub enum BackendAttrib {
Stereo,
}
#[derive(Debug, Clone)]
pub enum BackendAttribValue {
Stereo(StereoMode),
} }
pub struct OverlayMeta { pub struct OverlayMeta {
@@ -105,6 +158,8 @@ pub trait OverlayBackend: Any {
fn on_pointer(&mut self, app: &mut AppState, hit: &PointerHit, pressed: bool); fn on_pointer(&mut self, app: &mut AppState, hit: &PointerHit, pressed: bool);
fn on_scroll(&mut self, app: &mut AppState, hit: &PointerHit, delta: WheelDelta); fn on_scroll(&mut self, app: &mut AppState, hit: &PointerHit, delta: WheelDelta);
fn get_interaction_transform(&mut self) -> Option<Affine2>; fn get_interaction_transform(&mut self) -> Option<Affine2>;
fn get_attrib(&self, attrib: BackendAttrib) -> Option<BackendAttribValue>;
fn set_attrib(&mut self, app: &mut AppState, value: BackendAttribValue) -> bool;
} }
pub fn ui_transform(extent: [u32; 2]) -> Affine2 { pub fn ui_transform(extent: [u32; 2]) -> Affine2 {
@@ -156,4 +211,10 @@ impl OverlayBackend for DummyBackend {
fn get_interaction_transform(&mut self) -> Option<glam::Affine2> { fn get_interaction_transform(&mut self) -> Option<glam::Affine2> {
None None
} }
fn get_attrib(&self, _attrib: BackendAttrib) -> Option<BackendAttribValue> {
None
}
fn set_attrib(&mut self, _: &mut AppState, _value: BackendAttribValue) -> bool {
false
}
} }

View File

@@ -12,7 +12,6 @@ use wlx_common::{
}; };
use crate::{ use crate::{
FRAME_COUNTER,
backend::task::OverlayTask, backend::task::OverlayTask,
overlays::{ overlays::{
anchor::create_anchor, edit::EditWrapperManager, keyboard::create_keyboard, anchor::create_anchor, edit::EditWrapperManager, keyboard::create_keyboard,
@@ -20,12 +19,13 @@ use crate::{
}, },
state::AppState, state::AppState,
windowing::{ windowing::{
OverlayID, OverlaySelector,
backend::{OverlayEventData, OverlayMeta}, backend::{OverlayEventData, OverlayMeta},
set::OverlayWindowSet, set::OverlayWindowSet,
snap_upright, snap_upright,
window::{OverlayCategory, OverlayWindowData}, window::{OverlayCategory, OverlayWindowData},
OverlayID, OverlaySelector,
}, },
FRAME_COUNTER,
}; };
pub const MAX_OVERLAY_SETS: usize = 7; pub const MAX_OVERLAY_SETS: usize = 7;
@@ -401,6 +401,7 @@ impl<T> OverlayWindowManager<T> {
if enabled { if enabled {
self.wrappers self.wrappers
.wrap_edit_mode(id, &mut overlay.config, app) .wrap_edit_mode(id, &mut overlay.config, app)
.inspect_err(|e| log::error!("{e:?}"))
.unwrap(); // FIXME: unwrap .unwrap(); // FIXME: unwrap
} else { } else {
self.wrappers.unwrap_edit_mode(&mut overlay.config); self.wrappers.unwrap_edit_mode(&mut overlay.config);