372 lines
12 KiB
Rust
372 lines
12 KiB
Rust
use glam::{Affine2, Affine3A, Quat, Vec2, Vec3, vec2, vec3};
|
|
use smithay::{
|
|
desktop::PopupManager,
|
|
wayland::{compositor::with_states, shell::xdg::XdgPopupSurfaceData},
|
|
};
|
|
use std::sync::Arc;
|
|
use vulkano::{
|
|
buffer::BufferUsage, image::view::ImageView, pipeline::graphics::color_blend::AttachmentBlend,
|
|
};
|
|
use wgui::gfx::pipeline::{WGfxPipeline, WPipelineCreateInfo};
|
|
use wlx_capture::frame::MouseMeta;
|
|
use wlx_common::{
|
|
overlays::{BackendAttrib, BackendAttribValue, StereoMode},
|
|
windowing::{OverlayWindowState, Positioning},
|
|
};
|
|
|
|
use crate::{
|
|
backend::{
|
|
XrBackend,
|
|
input::{self, HoverResult},
|
|
wayvr::{self, SurfaceBufWithImage},
|
|
},
|
|
graphics::{ExtentExt, Vert2Uv, upload_quad_vertices},
|
|
overlays::screen::capture::ScreenPipeline,
|
|
state::{self, AppState},
|
|
subsystem::{hid::WheelDelta, input::KeyboardFocus},
|
|
windowing::{
|
|
backend::{
|
|
FrameMeta, OverlayBackend, OverlayEventData, RenderResources, ShouldRender,
|
|
ui_transform,
|
|
},
|
|
window::{OverlayCategory, OverlayWindowConfig},
|
|
},
|
|
};
|
|
|
|
pub fn create_wl_window_overlay(
|
|
name: Arc<str>,
|
|
app: &AppState,
|
|
window: wayvr::window::WindowHandle,
|
|
) -> anyhow::Result<OverlayWindowConfig> {
|
|
Ok(OverlayWindowConfig {
|
|
name: name.clone(),
|
|
default_state: OverlayWindowState {
|
|
grabbable: true,
|
|
interactable: true,
|
|
positioning: Positioning::Floating,
|
|
curvature: Some(0.15),
|
|
transform: Affine3A::from_scale_rotation_translation(
|
|
Vec3::ONE,
|
|
Quat::IDENTITY,
|
|
vec3(0.0, 0.0, -0.95),
|
|
),
|
|
..OverlayWindowState::default()
|
|
},
|
|
keyboard_focus: Some(KeyboardFocus::WayVR),
|
|
category: OverlayCategory::WayVR,
|
|
show_on_spawn: true,
|
|
..OverlayWindowConfig::from_backend(Box::new(WvrWindowBackend::new(name, app, window)?))
|
|
})
|
|
}
|
|
|
|
pub struct WvrWindowBackend {
|
|
name: Arc<str>,
|
|
pipeline: Option<ScreenPipeline>,
|
|
popups_pipeline: Arc<WGfxPipeline<Vert2Uv>>,
|
|
interaction_transform: Option<Affine2>,
|
|
window: wayvr::window::WindowHandle,
|
|
popups: Vec<(Arc<ImageView>, Vec2)>,
|
|
just_resumed: bool,
|
|
meta: Option<FrameMeta>,
|
|
mouse: Option<MouseMeta>,
|
|
stereo: Option<StereoMode>,
|
|
cur_image: Option<Arc<ImageView>>,
|
|
}
|
|
|
|
impl WvrWindowBackend {
|
|
fn new(
|
|
name: Arc<str>,
|
|
app: &AppState,
|
|
window: wayvr::window::WindowHandle,
|
|
) -> anyhow::Result<Self> {
|
|
let popups_pipeline = app.gfx.create_pipeline(
|
|
app.gfx_extras.shaders.get("vert_quad").unwrap(), // want panic
|
|
app.gfx_extras.shaders.get("frag_screen").unwrap(), // want panic
|
|
WPipelineCreateInfo::new(app.gfx.surface_format).use_blend(AttachmentBlend::default()),
|
|
)?;
|
|
|
|
Ok(Self {
|
|
name,
|
|
pipeline: None,
|
|
window,
|
|
popups: vec![],
|
|
popups_pipeline,
|
|
interaction_transform: None,
|
|
just_resumed: false,
|
|
meta: None,
|
|
mouse: None,
|
|
stereo: if matches!(app.xr_backend, XrBackend::OpenXR) {
|
|
Some(StereoMode::None)
|
|
} else {
|
|
None
|
|
},
|
|
cur_image: None,
|
|
})
|
|
}
|
|
}
|
|
|
|
impl OverlayBackend for WvrWindowBackend {
|
|
fn init(&mut self, _app: &mut state::AppState) -> anyhow::Result<()> {
|
|
Ok(())
|
|
}
|
|
|
|
fn pause(&mut self, _app: &mut state::AppState) -> anyhow::Result<()> {
|
|
Ok(())
|
|
}
|
|
|
|
fn resume(&mut self, _app: &mut state::AppState) -> anyhow::Result<()> {
|
|
self.just_resumed = true;
|
|
Ok(())
|
|
}
|
|
|
|
fn should_render(&mut self, app: &mut AppState) -> anyhow::Result<ShouldRender> {
|
|
let Some(toplevel) = app
|
|
.wvr_server
|
|
.as_ref()
|
|
.and_then(|sv| sv.wm.windows.get(&self.window))
|
|
.map(|win| win.toplevel.clone())
|
|
else {
|
|
log::debug!(
|
|
"{:?}: WayVR overlay without matching window entry",
|
|
self.name
|
|
);
|
|
return Ok(ShouldRender::Unable);
|
|
};
|
|
|
|
let popups = PopupManager::popups_for_surface(toplevel.wl_surface())
|
|
.filter_map(|(popup, point)| {
|
|
with_states(popup.wl_surface(), |states| {
|
|
if !states
|
|
.data_map
|
|
.get::<XdgPopupSurfaceData>()
|
|
.unwrap()
|
|
.lock()
|
|
.unwrap()
|
|
.configured
|
|
{
|
|
// not yet configured
|
|
return None;
|
|
}
|
|
|
|
if let Some(surf) = SurfaceBufWithImage::get_from_surface(states) {
|
|
Some((surf.image, vec2(point.x as _, point.y as _)))
|
|
} else {
|
|
None
|
|
}
|
|
})
|
|
})
|
|
.collect::<Vec<_>>();
|
|
|
|
with_states(toplevel.wl_surface(), |states| {
|
|
if let Some(surf) = SurfaceBufWithImage::get_from_surface(states) {
|
|
let mut meta = FrameMeta {
|
|
extent: surf.image.image().extent(),
|
|
format: surf.image.format(),
|
|
..Default::default()
|
|
};
|
|
|
|
if let Some(pipeline) = self.pipeline.as_mut() {
|
|
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.interaction_transform =
|
|
Some(ui_transform(meta.extent.extent_u32arr()));
|
|
}
|
|
} else {
|
|
let pipeline =
|
|
ScreenPipeline::new(&meta, app, self.stereo.unwrap_or(StereoMode::None))?;
|
|
meta.extent[2] = pipeline.get_depth();
|
|
self.pipeline = Some(pipeline);
|
|
self.interaction_transform = Some(ui_transform(meta.extent.extent_u32arr()));
|
|
}
|
|
|
|
let mouse = app
|
|
.wvr_server
|
|
.as_ref()
|
|
.unwrap()
|
|
.wm
|
|
.mouse
|
|
.as_ref()
|
|
.filter(|m| m.hover_window == self.window)
|
|
.map(|m| MouseMeta {
|
|
x: (m.x as f32) / (meta.extent[0] as f32),
|
|
y: (m.y as f32) / (meta.extent[1] as f32),
|
|
});
|
|
|
|
let dirty = self.mouse != mouse || self.popups != popups;
|
|
self.mouse = mouse;
|
|
self.popups = popups;
|
|
self.meta = Some(meta);
|
|
if self
|
|
.cur_image
|
|
.as_ref()
|
|
.is_none_or(|i| *i.image() != *surf.image.image())
|
|
{
|
|
log::trace!(
|
|
"{}: new {} image",
|
|
self.name,
|
|
if surf.dmabuf { "DMA-buf" } else { "SHM" }
|
|
);
|
|
self.cur_image = Some(surf.image);
|
|
Ok(ShouldRender::Should)
|
|
} else if dirty {
|
|
Ok(ShouldRender::Should)
|
|
} else {
|
|
Ok(ShouldRender::Can)
|
|
}
|
|
} else {
|
|
log::trace!("{}: no buffer for wl_surface", self.name);
|
|
Ok(ShouldRender::Unable)
|
|
}
|
|
})
|
|
}
|
|
|
|
fn render(
|
|
&mut self,
|
|
app: &mut state::AppState,
|
|
rdr: &mut RenderResources,
|
|
) -> anyhow::Result<()> {
|
|
let image = self.cur_image.as_ref().unwrap().clone();
|
|
|
|
self.pipeline
|
|
.as_mut()
|
|
.unwrap()
|
|
.render(image, self.mouse.as_ref(), app, rdr)?;
|
|
|
|
for (popup_img, point) in &self.popups {
|
|
let extentf = self.meta.as_ref().unwrap().extent.extent_f32();
|
|
let popup_extentf = popup_img.extent_f32();
|
|
let mut buf_vert = app
|
|
.gfx
|
|
.empty_buffer(BufferUsage::TRANSFER_DST | BufferUsage::VERTEX_BUFFER, 4)?;
|
|
|
|
upload_quad_vertices(
|
|
&mut buf_vert,
|
|
extentf[0],
|
|
extentf[1],
|
|
point.x,
|
|
point.y,
|
|
popup_extentf[0],
|
|
popup_extentf[1],
|
|
)?;
|
|
|
|
let set0 = self.popups_pipeline.uniform_sampler(
|
|
0,
|
|
popup_img.clone(),
|
|
app.gfx.texture_filter,
|
|
)?;
|
|
let set1 = self
|
|
.popups_pipeline
|
|
.buffer(1, self.pipeline.as_ref().unwrap().get_alpha_buf())?;
|
|
|
|
let pass = self.popups_pipeline.create_pass(
|
|
extentf,
|
|
buf_vert,
|
|
0..4,
|
|
0..1,
|
|
vec![set0, set1],
|
|
&Default::default(),
|
|
)?;
|
|
|
|
rdr.cmd_buf_single().run_ref(&pass)?;
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
fn frame_meta(&mut self) -> Option<FrameMeta> {
|
|
self.meta
|
|
}
|
|
|
|
fn notify(
|
|
&mut self,
|
|
app: &mut state::AppState,
|
|
event_data: OverlayEventData,
|
|
) -> anyhow::Result<()> {
|
|
if let OverlayEventData::IdAssigned(oid) = event_data {
|
|
let wvr_server = app.wvr_server.as_mut().unwrap(); //never None
|
|
wvr_server.overlay_added(oid, self.window);
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
fn on_hover(&mut self, app: &mut state::AppState, hit: &input::PointerHit) -> HoverResult {
|
|
if let Some(meta) = self.meta.as_ref() {
|
|
let x = (hit.uv.x * (meta.extent[0] as f32)) as u32;
|
|
let y = (hit.uv.y * (meta.extent[1] as f32)) as u32;
|
|
|
|
let wvr_server = app.wvr_server.as_mut().unwrap(); //never None
|
|
wvr_server.send_mouse_move(self.window, x, y);
|
|
}
|
|
|
|
HoverResult {
|
|
haptics: None, // haptics are handled via task
|
|
consume: true,
|
|
}
|
|
}
|
|
|
|
fn on_left(&mut self, _app: &mut state::AppState, _pointer: usize) {
|
|
// Ignore event
|
|
}
|
|
|
|
fn on_pointer(&mut self, app: &mut state::AppState, hit: &input::PointerHit, pressed: bool) {
|
|
if let Some(index) = match hit.mode {
|
|
input::PointerMode::Left => Some(wayvr::MouseIndex::Left),
|
|
input::PointerMode::Middle => Some(wayvr::MouseIndex::Center),
|
|
input::PointerMode::Right => Some(wayvr::MouseIndex::Right),
|
|
_ => {
|
|
// Unknown pointer event, ignore
|
|
None
|
|
}
|
|
} {
|
|
let wvr_server = app.wvr_server.as_mut().unwrap(); //never None
|
|
if pressed {
|
|
wvr_server.send_mouse_down(self.window, index);
|
|
} else {
|
|
wvr_server.send_mouse_up(index);
|
|
}
|
|
}
|
|
}
|
|
|
|
fn on_scroll(
|
|
&mut self,
|
|
app: &mut state::AppState,
|
|
_hit: &input::PointerHit,
|
|
delta: WheelDelta,
|
|
) {
|
|
let wvr_server = app.wvr_server.as_mut().unwrap(); //never None
|
|
wvr_server.send_mouse_scroll(delta);
|
|
}
|
|
|
|
fn get_interaction_transform(&mut self) -> Option<Affine2> {
|
|
self.interaction_transform
|
|
}
|
|
|
|
fn get_attrib(&self, attrib: BackendAttrib) -> Option<BackendAttribValue> {
|
|
match attrib {
|
|
BackendAttrib::Stereo => self.stereo.map(BackendAttribValue::Stereo),
|
|
_ => None,
|
|
}
|
|
}
|
|
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,
|
|
}
|
|
}
|
|
}
|