diff --git a/wlx-overlay-s/src/backend/mod.rs b/wlx-overlay-s/src/backend/mod.rs
index eb3cc14..f3f1c52 100644
--- a/wlx-overlay-s/src/backend/mod.rs
+++ b/wlx-overlay-s/src/backend/mod.rs
@@ -15,6 +15,11 @@ pub mod task;
use thiserror::Error;
+pub enum XrBackend {
+ OpenXR,
+ OpenVR,
+}
+
#[derive(Error, Debug)]
pub enum BackendError {
#[error("backend not supported")]
diff --git a/wlx-overlay-s/src/backend/openvr/lines.rs b/wlx-overlay-s/src/backend/openvr/lines.rs
index 3e2ddc6..38eb88d 100644
--- a/wlx-overlay-s/src/backend/openvr/lines.rs
+++ b/wlx-overlay-s/src/backend/openvr/lines.rs
@@ -1,6 +1,6 @@
use std::f32::consts::PI;
-use std::sync::Arc;
use std::sync::atomic::{AtomicUsize, Ordering};
+use std::sync::Arc;
use ash::vk::SubmitInfo;
use glam::{Affine3A, Vec3, Vec3A, Vec4};
@@ -8,7 +8,6 @@ use idmap::IdMap;
use ovr_overlay::overlay::OverlayManager;
use ovr_overlay::sys::ETrackingUniverseOrigin;
use vulkano::{
- VulkanObject,
command_buffer::{
CommandBufferBeginInfo, CommandBufferLevel, CommandBufferUsage, RecordingCommandBuffer,
},
@@ -16,20 +15,22 @@ use vulkano::{
image::view::ImageView,
image::{Image, ImageLayout},
sync::{
- AccessFlags, DependencyInfo, ImageMemoryBarrier, PipelineStages,
fence::{Fence, FenceCreateInfo},
+ AccessFlags, DependencyInfo, ImageMemoryBarrier, PipelineStages,
},
+ VulkanObject,
};
use wgui::gfx::WGfx;
use crate::backend::input::{HoverResult, PointerHit};
use crate::state::AppState;
use crate::subsystem::hid::WheelDelta;
-use crate::windowing::Z_ORDER_LINES;
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::Z_ORDER_LINES;
use super::overlay::OpenVrOverlayData;
@@ -217,6 +218,12 @@ impl OverlayBackend for LineBackend {
fn get_interaction_transform(&mut self) -> Option
{
None
}
+ fn get_attrib(&self, _attrib: BackendAttrib) -> Option {
+ None
+ }
+ fn set_attrib(&mut self, _: &mut AppState, _value: BackendAttribValue) -> bool {
+ false
+ }
}
pub fn transition_layout(
diff --git a/wlx-overlay-s/src/backend/openvr/mod.rs b/wlx-overlay-s/src/backend/openvr/mod.rs
index f800933..a245635 100644
--- a/wlx-overlay-s/src/backend/openvr/mod.rs
+++ b/wlx-overlay-s/src/backend/openvr/mod.rs
@@ -5,40 +5,41 @@ use std::{
time::{Duration, Instant},
};
-use anyhow::{Result, anyhow};
+use anyhow::{anyhow, Result};
use ovr_overlay::{
- TrackedDeviceIndex,
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 crate::{
- RUNNING,
backend::{
- BackendError,
input::interact,
openvr::{
helpers::adjust_gain,
- input::{OpenVrInputSource, set_action_manifest},
+ input::{set_action_manifest, OpenVrInputSource},
lines::LinePool,
manifest::{install_manifest, uninstall_manifest},
overlay::OpenVrOverlayData,
},
task::{OpenVrTask, OverlayTask, TaskType},
+ BackendError, XrBackend,
},
config::save_state,
- graphics::{GpuFutures, init_openvr_graphics},
+ graphics::{init_openvr_graphics, GpuFutures},
overlays::{
toast::Toast,
- watch::{WATCH_NAME, watch_fade},
+ watch::{watch_fade, WATCH_NAME},
},
state::AppState,
subsystem::notifications::NotificationManager,
windowing::{
- backend::{RenderResources, ShouldRender},
+ backend::{RenderResources, RenderTarget, ShouldRender, StereoMode},
manager::OverlayWindowManager,
},
+ RUNNING,
};
#[cfg(feature = "wayvr")]
@@ -93,7 +94,7 @@ pub fn openvr_run(show_by_default: bool, headless: bool) -> Result<(), BackendEr
let mut app = {
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 {
@@ -303,11 +304,13 @@ pub fn openvr_run(show_by_default: bool, headless: bool) -> Result<(), BackendEr
continue;
};
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)?;
o.render(&mut app, &mut rdr)?;
o.data.image_dirty = true;
- futures.execute(rdr.end()?)?;
+ futures.execute_results(rdr.end()?)?;
}
}
diff --git a/wlx-overlay-s/src/backend/openxr/lines.rs b/wlx-overlay-s/src/backend/openxr/lines.rs
index 19610ce..d1aedd4 100644
--- a/wlx-overlay-s/src/backend/openxr/lines.rs
+++ b/wlx-overlay-s/src/backend/openxr/lines.rs
@@ -1,19 +1,20 @@
use glam::{Affine3A, Vec3, Vec3A};
use idmap::IdMap;
use openxr as xr;
+use smallvec::SmallVec;
use std::{
f32::consts::PI,
sync::{
- Arc,
atomic::{AtomicUsize, Ordering},
+ Arc,
},
};
use wgui::gfx::{
- WGfx,
cmd::WGfxClearMode,
pass::WGfxPass,
pipeline::{WGfxPipeline, WPipelineCreateInfo},
+ WGfx,
};
use crate::{
@@ -27,8 +28,8 @@ use vulkano::{
};
use super::{
+ swapchain::{create_swapchain, SwapchainOpts, WlxSwapchain},
CompositionLayer, XrState,
- swapchain::{SwapchainOpts, WlxSwapchain, create_swapchain},
};
static LINE_AUTO_INCREMENT: AtomicUsize = AtomicUsize::new(1);
@@ -156,7 +157,13 @@ impl LinePool {
) -> anyhow::Result<()> {
for line in self.lines.values_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]);
@@ -167,7 +174,7 @@ impl LinePool {
cmd_buffer.run_ref(&self.pass)?;
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>(
&'a mut self,
xr: &'a XrState,
- ) -> anyhow::Result>> {
- let mut quads = Vec::new();
+ ) -> anyhow::Result; 2]>> {
+ let mut quads = SmallVec::new_const();
for line in self.lines.values_mut() {
line.swapchain.ensure_image_released()?;
@@ -186,7 +193,7 @@ impl LinePool {
if let Some(inner) = line.maybe_line.take() {
let quad = xr::CompositionLayerQuad::new()
.pose(inner.pose)
- .sub_image(line.swapchain.get_subimage())
+ .sub_image(line.swapchain.get_subimage(0))
.eye_visibility(xr::EyeVisibility::BOTH)
.space(&xr.stage)
.size(xr::Extent2Df {
diff --git a/wlx-overlay-s/src/backend/openxr/mod.rs b/wlx-overlay-s/src/backend/openxr/mod.rs
index 8ac2b2c..22f57e1 100644
--- a/wlx-overlay-s/src/backend/openxr/mod.rs
+++ b/wlx-overlay-s/src/backend/openxr/mod.rs
@@ -1,7 +1,7 @@
use std::{
collections::VecDeque,
ops::Add,
- sync::{Arc, atomic::Ordering},
+ sync::{atomic::Ordering, Arc},
time::{Duration, Instant},
};
@@ -14,25 +14,25 @@ use vulkano::{Handle, VulkanObject};
use wlx_common::overlays::ToastTopic;
use crate::{
- FRAME_COUNTER, RUNNING,
backend::{
- BackendError,
input::interact,
openxr::{lines::LinePool, overlay::OpenXrOverlayData},
task::{OverlayTask, TaskType},
+ BackendError, XrBackend,
},
config::save_state,
- graphics::{GpuFutures, init_openxr_graphics},
+ graphics::{init_openxr_graphics, GpuFutures},
overlays::{
toast::Toast,
- watch::{WATCH_NAME, watch_fade},
+ watch::{watch_fade, WATCH_NAME},
},
state::AppState,
subsystem::notifications::NotificationManager,
windowing::{
- backend::{RenderResources, ShouldRender},
+ backend::{RenderResources, RenderTarget, ShouldRender},
manager::OverlayWindowManager,
},
+ FRAME_COUNTER, RUNNING,
};
#[cfg(feature = "wayvr")]
@@ -70,7 +70,7 @@ pub fn openxr_run(show_by_default: bool, headless: bool) -> Result<(), BackendEr
let mut app = {
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 = {
@@ -403,11 +403,12 @@ pub fn openxr_run(show_by_default: bool, headless: bool) -> Result<(), BackendEr
if should_render {
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)?;
o.render(&mut app, &mut rdr)?;
o.data.last_alpha = alpha;
- futures.execute(rdr.end()?)?;
+ futures.execute_results(rdr.end()?)?;
} else if o.data.swapchain.is_none() {
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()?;
continue;
}
- let maybe_layer = o.present(&xr_state)?;
- if matches!(maybe_layer, CompositionLayer::None) {
- continue;
+ for layer in o.present(&xr_state)? {
+ layers.push((dist_sq, layer));
}
- layers.push((dist_sq, maybe_layer));
}
- for maybe_layer in lines.present(&xr_state)? {
- if matches!(maybe_layer, CompositionLayer::None) {
- continue;
- }
- layers.push((0.0, maybe_layer));
+ for layer in lines.present(&xr_state)? {
+ layers.push((0.0, layer));
}
// 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,
CompositionLayer::Cylinder(ref l) => l as &xr::CompositionLayerBase,
CompositionLayer::Equirect2(ref l) => l as &xr::CompositionLayerBase,
- CompositionLayer::None => unreachable!(),
})
.collect::>();
@@ -523,7 +518,6 @@ pub fn openxr_run(show_by_default: bool, headless: bool) -> Result<(), BackendEr
}
pub(super) enum CompositionLayer<'a> {
- None,
Quad(xr::CompositionLayerQuad<'a, xr::Vulkan>),
Cylinder(xr::CompositionLayerCylinderKHR<'a, xr::Vulkan>),
Equirect2(xr::CompositionLayerEquirect2KHR<'a, xr::Vulkan>),
diff --git a/wlx-overlay-s/src/backend/openxr/overlay.rs b/wlx-overlay-s/src/backend/openxr/overlay.rs
index 8847b07..51c238c 100644
--- a/wlx-overlay-s/src/backend/openxr/overlay.rs
+++ b/wlx-overlay-s/src/backend/openxr/overlay.rs
@@ -1,12 +1,12 @@
use glam::Vec3A;
use openxr::{self as xr, CompositionLayerFlags};
-use std::{f32::consts::PI, sync::Arc};
-use vulkano::image::view::ImageView;
+use smallvec::{smallvec, SmallVec};
+use std::f32::consts::PI;
use xr::EyeVisibility;
-use super::{CompositionLayer, XrState, helpers, swapchain::WlxSwapchain};
+use super::{helpers, swapchain::WlxSwapchain, CompositionLayer, XrState};
use crate::{
- backend::openxr::swapchain::{SwapchainOpts, create_swapchain},
+ backend::openxr::swapchain::{create_swapchain, SwapchainOpts, WlxSwapchainImage},
state::AppState,
windowing::window::OverlayWindowData,
};
@@ -26,7 +26,7 @@ impl OverlayWindowData {
app: &AppState,
xr: &'a XrState,
extent: [u32; 3],
- ) -> anyhow::Result> {
+ ) -> anyhow::Result {
if let Some(swapchain) = self.data.swapchain.as_mut()
&& swapchain.extent == extent
{
@@ -34,10 +34,11 @@ impl OverlayWindowData {
}
log::debug!(
- "{}: recreating swapchain at {}x{}",
+ "{}: recreating swapchain at {}x{}x{}",
self.config.name,
extent[0],
extent[1],
+ extent[2],
);
let mut swapchain = create_swapchain(xr, app.gfx.clone(), extent, SwapchainOpts::new())?;
let tgt = swapchain.acquire_wait_image()?;
@@ -48,21 +49,31 @@ impl OverlayWindowData {
pub(super) fn present<'a>(
&'a mut self,
xr: &'a XrState,
- ) -> anyhow::Result> {
+ ) -> anyhow::Result; 2]>> {
+ let mut layers = SmallVec::new_const();
+
let Some(swapchain) = self.data.swapchain.as_mut() else {
- log::warn!("{}: swapchain not ready", self.config.name);
- return Ok(CompositionLayer::None);
+ log::trace!("{}: no swapchain", self.config.name);
+ return Ok(layers);
};
if !swapchain.ever_acquired {
log::warn!("{}: swapchain not rendered", self.config.name);
- return Ok(CompositionLayer::None);
+ return Ok(layers);
}
swapchain.ensure_image_released()?;
// overlays without active_state don't get queued for present
let state = self.config.active_state.as_ref().unwrap();
- let sub_image = swapchain.get_subimage();
+ let 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 aspect_ratio = swapchain.extent[1] as f32 / swapchain.extent[0] as f32;
@@ -89,30 +100,35 @@ impl OverlayWindowData {
let posef = helpers::translation_rotation_to_posef(center_point, quat);
let angle = 2.0 * (scale_x / (2.0 * radius));
- let cylinder = xr::CompositionLayerCylinderKHR::new()
- .layer_flags(flags)
- .pose(posef)
- .sub_image(sub_image)
- .eye_visibility(EyeVisibility::BOTH)
- .space(&xr.stage)
- .radius(radius)
- .central_angle(angle)
- .aspect_ratio(aspect_ratio);
- Ok(CompositionLayer::Cylinder(cylinder))
+ for sub_image in sub_images {
+ let cylinder = xr::CompositionLayerCylinderKHR::new()
+ .layer_flags(flags)
+ .pose(posef)
+ .sub_image(sub_image.0)
+ .eye_visibility(sub_image.1)
+ .space(&xr.stage)
+ .radius(radius)
+ .central_angle(angle)
+ .aspect_ratio(aspect_ratio);
+ layers.push(CompositionLayer::Cylinder(cylinder))
+ }
} else {
let posef = helpers::transform_to_posef(&transform);
- let quad = xr::CompositionLayerQuad::new()
- .layer_flags(flags)
- .pose(posef)
- .sub_image(sub_image)
- .eye_visibility(EyeVisibility::BOTH)
- .space(&xr.stage)
- .size(xr::Extent2Df {
- width: scale_x,
- height: scale_y,
- });
- Ok(CompositionLayer::Quad(quad))
+ for sub_image in sub_images {
+ let quad = xr::CompositionLayerQuad::new()
+ .layer_flags(flags)
+ .pose(posef)
+ .sub_image(sub_image.0)
+ .eye_visibility(sub_image.1)
+ .space(&xr.stage)
+ .size(xr::Extent2Df {
+ width: scale_x,
+ height: scale_y,
+ });
+ layers.push(CompositionLayer::Quad(quad))
+ }
}
+ Ok(layers)
}
pub(super) fn after_input(&mut self, app: &mut AppState) -> anyhow::Result<()> {
diff --git a/wlx-overlay-s/src/backend/openxr/skybox.rs b/wlx-overlay-s/src/backend/openxr/skybox.rs
index 7601ed4..e437bbf 100644
--- a/wlx-overlay-s/src/backend/openxr/skybox.rs
+++ b/wlx-overlay-s/src/backend/openxr/skybox.rs
@@ -15,13 +15,13 @@ use wgui::gfx::{cmd::WGfxClearMode, pipeline::WPipelineCreateInfo};
use crate::{
backend::openxr::{helpers::translation_rotation_to_posef, swapchain::SwapchainOpts},
config_io,
- graphics::{ExtentExt, GpuFutures, dds::WlxCommandBufferDds},
+ graphics::{dds::WlxCommandBufferDds, ExtentExt, GpuFutures},
state::AppState,
};
use super::{
+ swapchain::{create_swapchain, WlxSwapchain},
CompositionLayer, XrState,
- swapchain::{WlxSwapchain, create_swapchain},
};
pub(super) struct Skybox {
@@ -94,7 +94,12 @@ impl Skybox {
let extent = self.view.image().extent();
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(
app.gfx_extras.shaders.get("vert_quad").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.end_rendering()?;
- futures.execute((cmd_buffer.queue.clone(), cmd_buffer.build()?))?;
+ futures.execute(cmd_buffer.queue.clone(), cmd_buffer.build()?)?;
self.sky = Some(swapchain);
Ok(())
@@ -148,7 +153,12 @@ impl Skybox {
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(
tgt.extent_f32(),
app.gfx_extras.quad_verts.clone(),
@@ -165,7 +175,7 @@ impl Skybox {
cmd_buffer.run_ref(&pass)?;
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);
Ok(())
@@ -211,7 +221,7 @@ impl Skybox {
.layer_flags(xr::CompositionLayerFlags::BLEND_TEXTURE_SOURCE_ALPHA)
.pose(pose)
.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)
.space(&xr.stage)
.central_horizontal_angle(HORIZ_ANGLE)
@@ -226,7 +236,7 @@ impl Skybox {
width: 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)
.space(&xr.stage);
diff --git a/wlx-overlay-s/src/backend/openxr/swapchain.rs b/wlx-overlay-s/src/backend/openxr/swapchain.rs
index 3e11ba9..03b6573 100644
--- a/wlx-overlay-s/src/backend/openxr/swapchain.rs
+++ b/wlx-overlay-s/src/backend/openxr/swapchain.rs
@@ -5,8 +5,12 @@ use openxr as xr;
use smallvec::SmallVec;
use vulkano::{
+ image::{
+ sys::RawImage,
+ view::{ImageView, ImageViewCreateInfo},
+ ImageCreateInfo, ImageUsage,
+ },
Handle,
- image::{ImageCreateInfo, ImageUsage, sys::RawImage, view::ImageView},
};
use wgui::gfx::WGfx;
@@ -47,7 +51,7 @@ pub(super) fn create_swapchain(
width: extent[0],
height: extent[1],
face_count: 1,
- array_size: 1,
+ array_size: extent[2],
mip_count: 1,
})?;
@@ -63,7 +67,8 @@ pub(super) fn create_swapchain(
vk_image,
ImageCreateInfo {
format: gfx.surface_format as _,
- extent,
+ extent: [extent[0], extent[1], 1],
+ array_layers: extent[2],
usage: ImageUsage::COLOR_ATTACHMENT,
..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.
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::; 4]>>>()?;
+ .collect::>>()?;
Ok(WlxSwapchain {
acquired: false,
@@ -84,16 +95,21 @@ pub(super) fn create_swapchain(
})
}
+#[derive(Default, Clone)]
+pub(super) struct WlxSwapchainImage {
+ pub views: SmallVec<[Arc; 2]>,
+}
+
pub(super) struct WlxSwapchain {
acquired: bool,
pub(super) ever_acquired: bool,
pub(super) swapchain: xr::Swapchain,
pub(super) extent: [u32; 3],
- pub(super) images: SmallVec<[Arc; 4]>,
+ pub(super) images: SmallVec<[WlxSwapchainImage; 4]>,
}
impl WlxSwapchain {
- pub(super) fn acquire_wait_image(&mut self) -> anyhow::Result> {
+ pub(super) fn acquire_wait_image(&mut self) -> anyhow::Result {
let idx = self.swapchain.acquire_image()? as usize;
self.swapchain.wait_image(xr::Duration::INFINITE)?;
self.ever_acquired = true;
@@ -109,7 +125,7 @@ impl WlxSwapchain {
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!");
xr::SwapchainSubImage::new()
.swapchain(&self.swapchain)
@@ -120,6 +136,6 @@ impl WlxSwapchain {
height: self.extent[1] as _,
},
})
- .image_array_index(0)
+ .image_array_index(array_index)
}
}
diff --git a/wlx-overlay-s/src/graphics/mod.rs b/wlx-overlay-s/src/graphics/mod.rs
index aed4fe4..b1bc94f 100644
--- a/wlx-overlay-s/src/graphics/mod.rs
+++ b/wlx-overlay-s/src/graphics/mod.rs
@@ -6,13 +6,14 @@ use std::{
sync::{Arc, OnceLock},
};
-use glam::{Vec2, vec2};
+use glam::{vec2, Vec2};
+use smallvec::SmallVec;
use vulkano::{
buffer::{BufferCreateInfo, BufferUsage},
command_buffer::{CommandBufferUsage, PrimaryAutoCommandBuffer, PrimaryCommandBufferAbstract},
image::view::ImageView,
memory::allocator::{AllocationCreateInfo, MemoryTypeFilter},
- sync::GpuFuture,
+ sync::{now, GpuFuture},
};
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 vulkano::{
- self, VulkanObject,
+ self,
buffer::{Buffer, BufferContents, IndexBuffer, Subbuffer},
device::{
- DeviceCreateInfo, DeviceExtensions, DeviceFeatures, Queue, QueueCreateInfo, QueueFlags,
physical::{PhysicalDevice, PhysicalDeviceType},
+ DeviceCreateInfo, DeviceExtensions, DeviceFeatures, Queue, QueueCreateInfo, QueueFlags,
},
format::Format,
instance::{Instance, InstanceCreateInfo, InstanceExtensions},
@@ -39,6 +40,7 @@ use vulkano::{
vertex_input::Vertex,
},
shader::ShaderModule,
+ VulkanObject,
};
use dmabuf::get_drm_formats;
@@ -634,6 +636,11 @@ fn queue_families_priorities(
}
}
+pub struct RenderResult {
+ pub queue: Arc,
+ pub cmd_buf: Arc,
+}
+
#[derive(Default)]
pub struct GpuFutures {
futures: Vec>,
@@ -642,12 +649,27 @@ pub struct GpuFutures {
impl GpuFutures {
pub fn execute(
&mut self,
- cmd: (Arc, Arc),
+ queue: Arc,
+ cmd_buf: Arc,
) -> anyhow::Result<()> {
- self.futures.push(cmd.1.execute(cmd.0)?.boxed());
-
+ self.futures.push(cmd_buf.execute(queue)?.boxed());
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<()> {
let mut it = self.futures.into_iter();
let Some(mut all) = it.next() else {
diff --git a/wlx-overlay-s/src/gui/panel/mod.rs b/wlx-overlay-s/src/gui/panel/mod.rs
index df867f6..713de2d 100644
--- a/wlx-overlay-s/src/gui/panel/mod.rs
+++ b/wlx-overlay-s/src/gui/panel/mod.rs
@@ -1,7 +1,7 @@
use std::{cell::RefCell, rc::Rc};
use button::setup_custom_button;
-use glam::{Affine2, Vec2, vec2};
+use glam::{vec2, Affine2, Vec2};
use label::setup_custom_label;
use wgui::{
assets::AssetPath,
@@ -16,7 +16,7 @@ use wgui::{
layout::{Layout, LayoutParams, WidgetID},
parser::{CustomAttribsInfoOwned, Fetchable, ParserState},
renderer_vk::context::Context as WguiContext,
- widget::{EventResult, label::WidgetLabel},
+ widget::{label::WidgetLabel, EventResult},
};
use wlx_common::timestep::Timestep;
@@ -25,7 +25,8 @@ use crate::{
state::AppState,
subsystem::hid::WheelDelta,
windowing::backend::{
- FrameMeta, OverlayBackend, OverlayEventData, RenderResources, ShouldRender, ui_transform,
+ ui_transform, BackendAttrib, BackendAttribValue, FrameMeta, OverlayBackend,
+ OverlayEventData, RenderResources, ShouldRender,
},
};
@@ -290,7 +291,7 @@ impl OverlayBackend for GuiPanel {
self.context.draw(
&globals.font_system,
&mut app.wgui_shared,
- &mut rdr.cmd_buf,
+ &mut rdr.cmd_buf_single(),
&primitives,
)?;
Ok(())
@@ -380,4 +381,10 @@ impl OverlayBackend for GuiPanel {
fn get_interaction_transform(&mut self) -> Option {
self.interaction_transform
}
+ fn get_attrib(&self, _attrib: BackendAttrib) -> Option {
+ None
+ }
+ fn set_attrib(&mut self, _app: &mut AppState, _value: BackendAttribValue) -> bool {
+ false
+ }
}
diff --git a/wlx-overlay-s/src/overlays/edit/mod.rs b/wlx-overlay-s/src/overlays/edit/mod.rs
index ae690bb..b08bc15 100644
--- a/wlx-overlay-s/src/overlays/edit/mod.rs
+++ b/wlx-overlay-s/src/overlays/edit/mod.rs
@@ -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,
+ stereo: SpriteTabHandler,
}
type EditModeWrapPanel = GuiPanel;
@@ -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 {
self.inner.frame_meta()
@@ -218,6 +240,12 @@ impl OverlayBackend for EditModeBackendWrapper {
fn get_interaction_transform(&mut self) -> Option {
self.inner.get_interaction_transform()
}
+ fn get_attrib(&self, attrib: BackendAttrib) -> Option {
+ 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 {
@@ -229,7 +257,8 @@ fn make_edit_panel(app: &mut AppState) -> anyhow::Result {
},
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 {
})
}
"::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 {
},
)?;
- 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::("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(())
diff --git a/wlx-overlay-s/src/overlays/edit/pos.rs b/wlx-overlay-s/src/overlays/edit/pos.rs
index 82a198e..eb7fadc 100644
--- a/wlx-overlay-s/src/overlays/edit/pos.rs
+++ b/wlx-overlay-s/src/overlays/edit/pos.rs
@@ -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,
- positioning: Positioning,
- has_interpolation: bool,
-}
-
#[derive(Default)]
-pub(super) struct PositioningHandler {
- top_sprite_id: WidgetID,
- interpolation_id: WidgetID,
- buttons: HashMap<&'static str, Rc>,
- active_button: Option>,
+pub struct PosTabState {
+ pos: Positioning,
+ has_lerp: bool,
}
-impl PositioningHandler {
- pub fn new(panel: &mut EditModeWrapPanel) -> anyhow::Result {
- let mut buttons = HashMap::new();
+impl From 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> {
+ 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::(id)
- .ok_or_else(|| {
- anyhow::anyhow!("Element with id=\"{sprite_id}\" must be a ")
- })?;
+ 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::(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 {
- 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",
- }
-}
diff --git a/wlx-overlay-s/src/overlays/edit/sprite_tab.rs b/wlx-overlay-s/src/overlays/edit/sprite_tab.rs
new file mode 100644
index 0000000..06fdea2
--- /dev/null
+++ b/wlx-overlay-s/src/overlays/edit/sprite_tab.rs
@@ -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 {
+ name: &'static str,
+ sprite: CustomGlyphData,
+ component: Rc,
+ state: S,
+}
+
+pub type SpriteTabHighlightChanged = dyn Fn(&mut CallbackDataCommon, &S);
+pub type SpriteTabButtonClicked = dyn Fn(&mut CallbackDataCommon, &S) -> Box;
+
+#[derive(Default)]
+pub(super) struct SpriteTabHandler {
+ top_sprite_id: WidgetID,
+ buttons: HashMap<&'static str, Rc>>,
+ active_button: Option>>,
+ on_highlight_changed: Option>>,
+ on_button_clicked: Option>>,
+}
+
+impl SpriteTabHandler
+where
+ S: SpriteTabKey,
+{
+ pub fn new(
+ panel: &mut EditModeWrapPanel,
+ prefix: &str,
+ names: &[&'static str],
+ on_button_clicked: Box>,
+ on_highlight_changed: Option>>,
+ ) -> anyhow::Result {
+ 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::(id)
+ .ok_or_else(|| {
+ anyhow::anyhow!("Element with id=\"{sprite_id}\" must be a ")
+ })?;
+
+ 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::(self.top_sprite_id)
+ {
+ sprite.params.glyph_data = Some(new.sprite.clone());
+ }
+ }
+
+ pub fn button_clicked(
+ &mut self,
+ common: &mut CallbackDataCommon,
+ key: &str,
+ ) -> Box {
+ 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);
+ }
+}
diff --git a/wlx-overlay-s/src/overlays/edit/stereo.rs b/wlx-overlay-s/src/overlays/edit/stereo.rs
new file mode 100644
index 0000000..9a9ab48
--- /dev/null
+++ b/wlx-overlay-s/src/overlays/edit/stereo.rs
@@ -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::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}")
+ }
+ }
+ }
+}
diff --git a/wlx-overlay-s/src/overlays/edit/tab.rs b/wlx-overlay-s/src/overlays/edit/tab.rs
index 8399802..67fe4ce 100644
--- a/wlx-overlay-s/src/overlays/edit/tab.rs
+++ b/wlx-overlay-s/src/overlays/edit/tab.rs
@@ -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);
diff --git a/wlx-overlay-s/src/overlays/keyboard/mod.rs b/wlx-overlay-s/src/overlays/keyboard/mod.rs
index 35d8cb8..68b64b7 100644
--- a/wlx-overlay-s/src/overlays/keyboard/mod.rs
+++ b/wlx-overlay-s/src/overlays/keyboard/mod.rs
@@ -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 {
self.panel().get_interaction_transform()
}
+ fn get_attrib(&self, _attrib: BackendAttrib) -> Option {
+ None
+ }
+ fn set_attrib(&mut self, _app: &mut AppState, _value: BackendAttribValue) -> bool {
+ false
+ }
}
struct KeyboardState {
diff --git a/wlx-overlay-s/src/overlays/mirror.rs b/wlx-overlay-s/src/overlays/mirror.rs
index 5ae91dd..f40d4c9 100644
--- a/wlx-overlay-s/src/overlays/mirror.rs
+++ b/wlx-overlay-s/src/overlays/mirror.rs
@@ -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 {
self.interaction_transform
}
+ fn get_attrib(&self, attrib: BackendAttrib) -> Option {
+ 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 {
diff --git a/wlx-overlay-s/src/overlays/screen/backend.rs b/wlx-overlay-s/src/overlays/screen/backend.rs
index 385e02b..2f1a2e2 100644
--- a/wlx-overlay-s/src/overlays/screen/backend.rs
+++ b/wlx-overlay-s/src/overlays/screen/backend.rs
@@ -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,
mouse_transform: Affine2,
interaction_transform: Option,
+ stereo: Option,
}
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 {
@@ -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 {
self.interaction_transform
}
+ #[allow(unreachable_patterns)]
+ fn get_attrib(&self, attrib: BackendAttrib) -> Option {
+ 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,
+ }
+ }
}
diff --git a/wlx-overlay-s/src/overlays/screen/capture.rs b/wlx-overlay-s/src/overlays/screen/capture.rs
index ba9e985..8a8d794 100644
--- a/wlx-overlay-s/src/overlays/screen/capture.rs
+++ b/wlx-overlay-s/src/overlays/screen/capture.rs
@@ -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,
buf_vert: Subbuffer<[Vert2Uv]>,
}
pub(super) struct ScreenPipeline {
- mouse: MousePass,
+ mouse: BufPass,
+ pass: SmallVec<[BufPass; 2]>,
pipeline: Arc>,
- pass: WGfxPass,
buf_alpha: Subbuffer<[f32]>,
extentf: [f32; 2],
+ stereo: StereoMode,
}
impl ScreenPipeline {
- pub(super) fn new(meta: &FrameMeta, app: &mut AppState) -> anyhow::Result {
+ pub(super) fn new(
+ meta: &FrameMeta,
+ app: &mut AppState,
+ stereo: StereoMode,
+ ) -> anyhow::Result {
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>,
extentf: [f32; 2],
buf_alpha: Subbuffer<[f32]>,
- ) -> anyhow::Result> {
+ ) -> anyhow::Result {
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>,
extentf: [f32; 2],
buf_alpha: Subbuffer<[f32]>,
- ) -> anyhow::Result {
+ ) -> anyhow::Result {
#[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(¤t.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,
diff --git a/wlx-overlay-s/src/overlays/wayvr.rs b/wlx-overlay-s/src/overlays/wayvr.rs
index 468589c..8cb4a3a 100644
--- a/wlx-overlay-s/src/overlays/wayvr.rs
+++ b/wlx-overlay-s/src/overlays/wayvr.rs
@@ -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 {
self.interaction_transform
}
+
+ fn get_attrib(&self, _attrib: BackendAttrib) -> Option {
+ None
+ }
+ fn set_attrib(&mut self, _app: &mut AppState, _value: BackendAttribValue) -> bool {
+ false
+ }
}
#[allow(dead_code)]
diff --git a/wlx-overlay-s/src/state.rs b/wlx-overlay-s/src/state.rs
index 68502f9..74b59f7 100644
--- a/wlx-overlay-s/src/state.rs
+++ b/wlx-overlay-s/src/state.rs
@@ -1,6 +1,6 @@
use glam::Affine3A;
use idmap::IdMap;
-use smallvec::{SmallVec, smallvec};
+use smallvec::{smallvec, SmallVec};
use std::sync::Arc;
use wgui::{
font_config::WguiFontConfig, gfx::WGfx, globals::WguiGlobals, parser::parse_color_hex,
@@ -22,7 +22,7 @@ use {
use crate::subsystem::osc::OscSender;
use crate::{
- backend::{input::InputState, task::TaskContainer},
+ backend::{input::InputState, task::TaskContainer, XrBackend},
config::load_general_config,
config_io::{self, get_config_file_path},
graphics::WGfxExtras,
@@ -51,6 +51,8 @@ pub struct AppState {
pub dbus: DbusConnector,
+ pub xr_backend: XrBackend,
+
#[cfg(feature = "osc")]
pub osc_sender: Option,
@@ -60,7 +62,11 @@ pub struct AppState {
#[allow(unused_mut)]
impl AppState {
- pub fn from_graphics(gfx: Arc, gfx_extras: WGfxExtras) -> anyhow::Result {
+ pub fn from_graphics(
+ gfx: Arc,
+ gfx_extras: WGfxExtras,
+ xr_backend: XrBackend,
+ ) -> anyhow::Result {
// insert shared resources
let mut tasks = TaskContainer::new();
@@ -133,6 +139,7 @@ impl AppState {
get_config_file_path(&theme),
)?,
dbus,
+ xr_backend,
#[cfg(feature = "osc")]
osc_sender,
diff --git a/wlx-overlay-s/src/windowing/backend.rs b/wlx-overlay-s/src/windowing/backend.rs
index 447f16e..26e2762 100644
--- a/wlx-overlay-s/src/windowing/backend.rs
+++ b/wlx-overlay-s/src/windowing/backend.rs
@@ -1,22 +1,18 @@
use glam::{Affine2, Affine3A, Vec2};
+use smallvec::SmallVec;
use std::{any::Any, sync::Arc};
-use vulkano::{
- command_buffer::{CommandBufferUsage, PrimaryAutoCommandBuffer},
- device::Queue,
- format::Format,
- image::view::ImageView,
-};
+use vulkano::{command_buffer::CommandBufferUsage, format::Format, image::view::ImageView};
use wgui::gfx::{
- WGfx,
cmd::{GfxCommandBuffer, WGfxClearMode},
+ WGfx,
};
use crate::{
backend::input::{HoverResult, PointerHit},
- graphics::ExtentExt,
+ graphics::{ExtentExt, RenderResult},
state::AppState,
subsystem::hid::WheelDelta,
- windowing::{OverlayID, window::OverlayCategory},
+ windowing::{window::OverlayCategory, OverlayID},
};
#[derive(Default, Clone, Copy)]
@@ -36,33 +32,90 @@ pub enum ShouldRender {
Unable,
}
+#[derive(Default, Debug, Clone, Copy)]
+pub enum StereoMode {
+ #[default]
+ None,
+ LeftRight,
+ RightLeft,
+ TopBottom,
+ BottomTop,
+}
+
+pub struct RenderTarget {
+ pub views: SmallVec<[Arc; 2]>,
+}
+
pub struct RenderResources {
pub alpha: f32,
- pub cmd_buf: GfxCommandBuffer,
+ pub cmd_bufs: SmallVec<[GfxCommandBuffer; 2]>,
pub extent: [u32; 2],
}
impl RenderResources {
pub fn new(
gfx: Arc,
- tgt: Arc,
+ target: RenderTarget,
meta: &FrameMeta,
alpha: f32,
) -> anyhow::Result {
- let mut cmd_buf = gfx.create_gfx_command_buffer(CommandBufferUsage::OneTimeSubmit)?;
- cmd_buf.begin_rendering(tgt, meta.clear)?;
+ let mut cmd_bufs = SmallVec::new_const();
+
+ 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 {
- cmd_buf,
+ cmd_bufs,
alpha,
extent: meta.extent.extent_u32arr(),
})
}
- pub fn end(mut self) -> anyhow::Result<(Arc, Arc)> {
- self.cmd_buf.end_rendering()?;
- Ok((self.cmd_buf.queue.clone(), self.cmd_buf.build()?))
+ pub fn cmd_buf_single(&mut self) -> &mut GfxCommandBuffer {
+ self.cmd_bufs.first_mut().unwrap() // first must always be populated
}
+
+ pub fn is_stereo(&self) -> bool {
+ self.cmd_bufs.len() > 1
+ }
+
+ pub fn end(self) -> anyhow::Result> {
+ 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 {
@@ -105,6 +158,8 @@ pub trait OverlayBackend: Any {
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 get_interaction_transform(&mut self) -> Option;
+ fn get_attrib(&self, attrib: BackendAttrib) -> Option;
+ fn set_attrib(&mut self, app: &mut AppState, value: BackendAttribValue) -> bool;
}
pub fn ui_transform(extent: [u32; 2]) -> Affine2 {
@@ -156,4 +211,10 @@ impl OverlayBackend for DummyBackend {
fn get_interaction_transform(&mut self) -> Option {
None
}
+ fn get_attrib(&self, _attrib: BackendAttrib) -> Option {
+ None
+ }
+ fn set_attrib(&mut self, _: &mut AppState, _value: BackendAttribValue) -> bool {
+ false
+ }
}
diff --git a/wlx-overlay-s/src/windowing/manager.rs b/wlx-overlay-s/src/windowing/manager.rs
index a1abe6e..cd52eb8 100644
--- a/wlx-overlay-s/src/windowing/manager.rs
+++ b/wlx-overlay-s/src/windowing/manager.rs
@@ -12,7 +12,6 @@ use wlx_common::{
};
use crate::{
- FRAME_COUNTER,
backend::task::OverlayTask,
overlays::{
anchor::create_anchor, edit::EditWrapperManager, keyboard::create_keyboard,
@@ -20,12 +19,13 @@ use crate::{
},
state::AppState,
windowing::{
- OverlayID, OverlaySelector,
backend::{OverlayEventData, OverlayMeta},
set::OverlayWindowSet,
snap_upright,
window::{OverlayCategory, OverlayWindowData},
+ OverlayID, OverlaySelector,
},
+ FRAME_COUNTER,
};
pub const MAX_OVERLAY_SETS: usize = 7;
@@ -401,6 +401,7 @@ impl OverlayWindowManager {
if enabled {
self.wrappers
.wrap_edit_mode(id, &mut overlay.config, app)
+ .inspect_err(|e| log::error!("{e:?}"))
.unwrap(); // FIXME: unwrap
} else {
self.wrappers.unwrap_edit_mode(&mut overlay.config);