rewrite built-in wayland compositor egl → vulkan

This commit is contained in:
galister
2025-12-25 21:26:38 +09:00
parent 3b6acb3673
commit 40dc33410d
34 changed files with 923 additions and 3051 deletions

View File

@@ -222,9 +222,13 @@ impl OverlayBackend for ScreenBackend {
fn render(&mut self, app: &mut AppState, rdr: &mut RenderResources) -> anyhow::Result<()> {
// want panic; must be some if should_render was not Unable
let capture = self.cur_frame.as_ref().unwrap();
let image = capture.image.clone();
// want panic; must be Some if cur_frame is also Some
self.pipeline.as_mut().unwrap().render(&capture, app, rdr)?;
self.pipeline
.as_mut()
.unwrap()
.render(image, capture.mouse.as_ref(), app, rdr)?;
self.capture.request_new_frame();
Ok(())
}

View File

@@ -17,8 +17,8 @@ use wgui::gfx::{
pipeline::{WGfxPipeline, WPipelineCreateInfo},
};
use wlx_capture::{
WlxCapture,
frame::{self as wlx_frame, DrmFormat, FrameFormat, MouseMeta, Transform, WlxFrame},
DrmFormat, WlxCapture,
frame::{self as wlx_frame, FrameFormat, MouseMeta, WlxFrame},
};
use wlx_common::{config::GeneralConfig, overlays::StereoMode};
@@ -39,7 +39,8 @@ struct BufPass {
buf_vert: Subbuffer<[Vert2Uv]>,
}
pub(super) struct ScreenPipeline {
/// A render pipeline that supports mouse + stereo
pub struct ScreenPipeline {
mouse: BufPass,
pass: SmallVec<[BufPass; 2]>,
pipeline: Arc<WGfxPipeline<Vert2Uv>>,
@@ -49,11 +50,7 @@ pub(super) struct ScreenPipeline {
}
impl ScreenPipeline {
pub(super) fn new(
meta: &FrameMeta,
app: &mut AppState,
stereo: StereoMode,
) -> anyhow::Result<Self> {
pub fn new(meta: &FrameMeta, app: &mut AppState, stereo: StereoMode) -> anyhow::Result<Self> {
let extentf = [meta.extent[0] as f32, meta.extent[1] as f32];
let pipeline = app.gfx.create_pipeline(
@@ -198,13 +195,13 @@ impl ScreenPipeline {
Ok(BufPass { pass, buf_vert })
}
pub(super) fn render(
pub fn render(
&mut self,
capture: &WlxCaptureOut,
image: Arc<ImageView>,
mouse: Option<&MouseMeta>,
app: &mut AppState,
rdr: &mut RenderResources,
) -> anyhow::Result<()> {
let view = ImageView::new_default(capture.image.clone())?;
self.buf_alpha.write()?[0] = rdr.alpha;
for (eye, cmd_buf) in rdr.cmd_bufs.iter_mut().enumerate() {
@@ -212,11 +209,11 @@ impl ScreenPipeline {
current
.pass
.update_sampler(0, view.clone(), app.gfx.texture_filter)?;
.update_sampler(0, image.clone(), app.gfx.texture_filter)?;
cmd_buf.run_ref(&current.pass)?;
if let Some(mouse) = capture.mouse.as_ref() {
if let Some(mouse) = mouse.as_ref() {
let size = CURSOR_SIZE * self.extentf[1];
let half_size = size * 0.5;
@@ -325,10 +322,10 @@ impl WlxCaptureIn {
}
#[derive(Clone)]
pub struct WlxCaptureOut {
image: Arc<Image>,
format: FrameFormat,
mouse: Option<MouseMeta>,
pub(super) struct WlxCaptureOut {
pub(super) image: Arc<ImageView>,
pub(super) format: FrameFormat,
pub(super) mouse: Option<MouseMeta>,
}
impl WlxCaptureOut {
@@ -340,10 +337,6 @@ impl WlxCaptureOut {
format: self.image.format(),
}
}
pub(super) const fn get_transform(&self) -> Transform {
self.format.transform
}
}
fn upload_image(
@@ -390,7 +383,7 @@ pub(super) fn receive_callback(me: &WlxCaptureIn, frame: WlxFrame) -> Option<Wlx
let format = frame.format;
match me.gfx.dmabuf_texture(frame) {
Ok(image) => Some(WlxCaptureOut {
image,
image: ImageView::new_default(image).ok()?,
format,
mouse: None,
}),
@@ -406,7 +399,7 @@ pub(super) fn receive_callback(me: &WlxCaptureIn, frame: WlxFrame) -> Option<Wlx
return None;
};
let format = match fourcc_to_vk(frame.format.fourcc) {
let format = match fourcc_to_vk(frame.format.drm_format.code) {
Ok(x) => x,
Err(e) => {
log::error!("{}: {}", me.name, e);
@@ -439,7 +432,7 @@ pub(super) fn receive_callback(me: &WlxCaptureIn, frame: WlxFrame) -> Option<Wlx
}?;
Some(WlxCaptureOut {
image,
image: ImageView::new_default(image).ok()?,
format: frame.format,
mouse: None,
})
@@ -447,7 +440,7 @@ pub(super) fn receive_callback(me: &WlxCaptureIn, frame: WlxFrame) -> Option<Wlx
WlxFrame::MemPtr(frame) => {
log::trace!("{}: New MemPtr frame", me.name);
let format = match fourcc_to_vk(frame.format.fourcc) {
let format = match fourcc_to_vk(frame.format.drm_format.code) {
Ok(x) => x,
Err(e) => {
log::error!("{}: {}", me.name, e);
@@ -459,7 +452,7 @@ pub(super) fn receive_callback(me: &WlxCaptureIn, frame: WlxFrame) -> Option<Wlx
let image = upload_image(me, frame.format.width, frame.format.height, format, data)?;
Some(WlxCaptureOut {
image,
image: ImageView::new_default(image).ok()?,
format: frame.format,
mouse: frame.mouse,
})

View File

@@ -14,7 +14,7 @@ use crate::{
};
pub mod backend;
mod capture;
pub mod capture;
#[cfg(feature = "wayland")]
pub mod mirror;
#[cfg(feature = "pipewire")]

View File

@@ -1,575 +1,176 @@
use anyhow::Context;
use glam::{Affine2, Affine3A, Quat, Vec3, vec3};
use smallvec::smallvec;
use smithay::wayland::compositor::with_states;
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},
};
use wayvr_ipc::packet_server::{PacketServer, WvrStateChanged};
use wgui::gfx::{
WGfx,
pass::WGfxPass,
pipeline::{WGfxPipeline, WPipelineCreateInfo},
};
use wlx_capture::frame::{DmabufFrame, FourCC, FrameFormat, FramePlane};
use wlx_common::overlays::{BackendAttrib, BackendAttribValue};
use wlx_common::windowing::OverlayWindowState;
use vulkano::image::view::ImageView;
use wgui::gfx::WGfx;
use wlx_common::overlays::{BackendAttrib, BackendAttribValue, StereoMode};
use crate::{
backend::{
XrBackend,
input::{self, HoverResult},
task::{OverlayTask, TaskType},
wayvr::{self, WayVR, WayVRAction, WayVRDisplayClickAction, display},
wayvr::{self, SurfaceBufWithImage, WayVR, window::WindowManager},
},
config_wayvr,
graphics::{Vert2Uv, dmabuf::WGfxDmabuf},
ipc::{event_queue::SyncEventQueue, ipc_server, signal::WayVRSignal},
graphics::{ExtentExt, WGfxExtras},
ipc::{event_queue::SyncEventQueue, signal::WayVRSignal},
overlays::screen::capture::ScreenPipeline,
state::{self, AppState},
subsystem::{hid::WheelDelta, input::KeyboardFocus},
subsystem::hid::WheelDelta,
windowing::{
OverlayID, OverlaySelector, Z_ORDER_DASHBOARD,
OverlayID,
backend::{
FrameMeta, OverlayBackend, OverlayEventData, RenderResources, ShouldRender,
ui_transform,
},
manager::OverlayWindowManager,
window::{OverlayCategory, OverlayWindowConfig, OverlayWindowData},
},
};
use super::toast::error_toast;
// Hard-coded for now
const DASHBOARD_WIDTH: u16 = 1920;
const DASHBOARD_HEIGHT: u16 = 1080;
const DASHBOARD_DISPLAY_NAME: &str = "_DASHBOARD";
pub struct WayVRContext {
wayvr: Rc<RefCell<WayVRData>>,
display: wayvr::display::DisplayHandle,
}
impl WayVRContext {
pub const fn new(wvr: Rc<RefCell<WayVRData>>, display: wayvr::display::DisplayHandle) -> Self {
Self {
wayvr: wvr,
display,
}
}
}
pub struct OverlayToCreate {
pub conf_display: config_wayvr::WayVRDisplay,
pub disp_handle: display::DisplayHandle,
}
pub struct WayVRData {
pub display_handle_map: HashMap<display::DisplayHandle, OverlayID>,
pub overlays_to_create: Vec<OverlayToCreate>,
pub dashboard_executed: bool,
pub window_handle_map: HashMap<wayvr::window::WindowHandle, OverlayID>,
pub data: WayVR,
pub pending_haptics: Option<input::Haptics>,
}
impl WayVRData {
pub fn new(
gfx: Arc<WGfx>,
gfx_extras: &WGfxExtras,
config: wayvr::Config,
signals: SyncEventQueue<WayVRSignal>,
) -> anyhow::Result<Self> {
Ok(Self {
display_handle_map: HashMap::default(),
data: WayVR::new(config, signals)?,
overlays_to_create: Vec::new(),
dashboard_executed: false,
pending_haptics: None,
window_handle_map: HashMap::default(),
data: WayVR::new(gfx, &gfx_extras, config, signals)?,
})
}
pub fn get_unique_display_name(&self, mut candidate: String) -> String {
let mut num = 0;
while !self
.data
.state
.displays
.vec
.iter()
.flatten()
.any(|d| d.obj.name == candidate)
{
if num > 0 {
candidate = format!("{candidate} ({num})");
}
num += 1;
}
candidate
}
fn set_overlay_display_handle(&mut self, id: OverlayID, disp_handle: display::DisplayHandle) {
self.display_handle_map.insert(disp_handle, id);
let display = self.data.state.displays.get_mut(&disp_handle).unwrap(); // Never fails
display.overlay_id = Some(id);
}
}
struct ImageData {
vk_image: Arc<Image>,
vk_image_view: Arc<ImageView>,
}
pub struct WayVRBackend {
pipeline: Arc<WGfxPipeline<Vert2Uv>>,
pass: WGfxPass<Vert2Uv>,
buf_alpha: Subbuffer<[f32]>,
image: Option<ImageData>,
context: Rc<RefCell<WayVRContext>>,
graphics: Arc<WGfx>,
resolution: [u16; 2],
name: Arc<str>,
pipeline: Option<ScreenPipeline>,
mouse_transform: Affine2,
interaction_transform: Option<Affine2>,
window: wayvr::window::WindowHandle,
wayvr: Rc<RefCell<WayVRData>>,
wm: Rc<RefCell<WindowManager>>,
just_resumed: bool,
meta: Option<FrameMeta>,
stereo: Option<StereoMode>,
cur_image: Option<Arc<ImageView>>,
}
impl WayVRBackend {
pub fn new(
app: &state::AppState,
wvr: Rc<RefCell<WayVRData>>,
display: wayvr::display::DisplayHandle,
resolution: [u16; 2],
name: Arc<str>,
xr_backend: XrBackend,
wayvr: Rc<RefCell<WayVRData>>,
window: wayvr::window::WindowHandle,
) -> anyhow::Result<Self> {
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
WPipelineCreateInfo::new(app.gfx.surface_format)
.use_updatable_descriptors(smallvec![0]),
)?;
let buf_alpha = app
.gfx
.empty_buffer(BufferUsage::TRANSFER_DST | BufferUsage::UNIFORM_BUFFER, 1)?;
let set0 = pipeline.uniform_sampler(
0,
app.gfx_extras.fallback_image.clone(),
app.gfx.texture_filter,
)?;
let set1 = pipeline.buffer(1, buf_alpha.clone())?;
let pass = pipeline.create_pass(
[resolution[0] as _, resolution[1] as _],
app.gfx_extras.quad_verts.clone(),
0..4,
0..1,
vec![set0, set1],
&Default::default(),
)?;
let wm = wayvr.borrow().data.state.wm.clone();
Ok(Self {
pipeline,
pass,
buf_alpha,
context: Rc::new(RefCell::new(WayVRContext::new(wvr, display))),
graphics: app.gfx.clone(),
image: None,
resolution,
name,
pipeline: None,
wayvr,
wm,
window,
mouse_transform: Affine2::IDENTITY,
interaction_transform: Some(ui_transform([resolution[0] as _, resolution[1] as _])), //TODO:dynamic
interaction_transform: None,
just_resumed: false,
meta: None,
stereo: if matches!(xr_backend, XrBackend::OpenXR) {
Some(StereoMode::None)
} else {
None
},
cur_image: None,
})
}
}
pub fn get_or_create_display_by_name(
app: &mut AppState,
wayvr: &mut WayVRData,
disp_name: &str,
) -> anyhow::Result<display::DisplayHandle> {
let disp_handle =
if let Some(disp) = WayVR::get_display_by_name(&wayvr.data.state.displays, disp_name) {
disp
} else {
let conf_display = app
.session
.wayvr_config
.get_display(disp_name)
.ok_or_else(|| anyhow::anyhow!("Cannot find display named \"{disp_name}\""))?
.clone();
let disp_handle = wayvr.data.state.create_display(
conf_display.width,
conf_display.height,
disp_name,
conf_display.primary.unwrap_or(false),
)?;
wayvr.overlays_to_create.push(OverlayToCreate {
conf_display,
disp_handle,
});
disp_handle
};
Ok(disp_handle)
}
pub fn executable_exists_in_path(command: &str) -> bool {
let Ok(path) = std::env::var("PATH") else {
return false; // very unlikely to happen
};
for dir in path.split(':') {
let exec_path = std::path::PathBuf::from(dir).join(command);
if exec_path.exists() && exec_path.is_file() {
return true; // executable found
}
}
false
}
fn toggle_dashboard<O>(
app: &mut AppState,
overlays: &mut OverlayWindowManager<O>,
wayvr: &mut WayVRData,
) -> anyhow::Result<()>
where
O: Default,
{
let Some(conf_dash) = app.session.wayvr_config.dashboard.clone() else {
anyhow::bail!("Dashboard is not configured");
};
if !wayvr.dashboard_executed && !executable_exists_in_path(&conf_dash.exec) {
anyhow::bail!("Executable \"{}\" not found", &conf_dash.exec);
}
let (newly_created, disp_handle) = wayvr.data.state.get_or_create_dashboard_display(
DASHBOARD_WIDTH,
DASHBOARD_HEIGHT,
DASHBOARD_DISPLAY_NAME,
)?;
if newly_created {
log::info!("Creating dashboard overlay");
let overlay = OverlayWindowData::from_config(OverlayWindowConfig {
default_state: OverlayWindowState {
interactable: true,
grabbable: true,
curvature: Some(0.15),
transform: Affine3A::from_scale_rotation_translation(
Vec3::ONE * 2.0,
Quat::IDENTITY,
vec3(0.0, -0.35, -1.75),
),
..OverlayWindowState::default()
},
z_order: Z_ORDER_DASHBOARD,
show_on_spawn: true,
global: true,
..create_overlay(
app,
DASHBOARD_DISPLAY_NAME,
OverlayToCreate {
disp_handle,
conf_display: config_wayvr::WayVRDisplay {
attach_to: None,
width: DASHBOARD_WIDTH,
height: DASHBOARD_HEIGHT,
scale: None,
rotation: None,
pos: None,
primary: None,
},
},
)?
});
let overlay_id = overlays.add(overlay, app);
wayvr.set_overlay_display_handle(overlay_id, disp_handle);
let args_vec = &conf_dash
.args
.as_ref()
.map_or_else(Vec::new, |args| ipc_server::gen_args_vec(args.as_str()));
let env_vec = &conf_dash
.env
.as_ref()
.map_or_else(Vec::new, |env| ipc_server::gen_env_vec(env));
let mut userdata = HashMap::new();
userdata.insert(String::from("type"), String::from("dashboard"));
// Start dashboard specified in the WayVR config
let _process_handle_unused = wayvr.data.state.spawn_process(
disp_handle,
&conf_dash.exec,
args_vec,
env_vec,
conf_dash.working_dir.as_deref(),
userdata,
)?;
wayvr.dashboard_executed = true;
return Ok(());
}
let display = wayvr.data.state.displays.get(&disp_handle).unwrap(); // safe
let Some(overlay_id) = display.overlay_id else {
anyhow::bail!("Overlay ID not set for dashboard display");
};
let cur_visibility = !display.visible;
app.ipc_server
.broadcast(PacketServer::WvrStateChanged(if cur_visibility {
WvrStateChanged::DashboardShown
} else {
WvrStateChanged::DashboardHidden
}));
app.tasks.enqueue(TaskType::Overlay(OverlayTask::Modify(
OverlaySelector::Id(overlay_id),
Box::new(move |app, o| {
o.toggle(app);
}),
)));
Ok(())
}
fn create_overlay(
app: &mut AppState,
name: &str,
cell: OverlayToCreate,
) -> anyhow::Result<OverlayWindowConfig> {
let conf_display = &cell.conf_display;
let disp_handle = cell.disp_handle;
let mut overlay = create_wayvr_display_overlay(
app,
conf_display.width,
conf_display.height,
disp_handle,
conf_display.scale.unwrap_or(1.0),
name,
)?;
if let Some(attach_to) = &conf_display.attach_to {
overlay.default_state.positioning = attach_to.get_positioning();
}
let rot = conf_display
.rotation
.as_ref()
.map_or(glam::Quat::IDENTITY, |rot| {
glam::Quat::from_axis_angle(Vec3::from_slice(&rot.axis), f32::to_radians(rot.angle))
});
let pos = conf_display
.pos
.as_ref()
.map_or(Vec3::NEG_Z, |pos| Vec3::from_slice(pos));
overlay.default_state.transform = Affine3A::from_rotation_translation(rot, pos);
Ok(overlay)
}
pub fn create_queued_displays<O>(
app: &mut AppState,
data: &mut WayVRData,
overlays: &mut OverlayWindowManager<O>,
) -> anyhow::Result<()>
where
O: Default,
{
let overlays_to_create = std::mem::take(&mut data.overlays_to_create);
for cell in overlays_to_create {
let Some(disp) = data.data.state.displays.get(&cell.disp_handle) else {
continue; // this shouldn't happen
};
let name = disp.name.clone();
let disp_handle = cell.disp_handle;
let overlay = OverlayWindowData::from_config(create_overlay(app, name.as_str(), cell)?);
let overlay_id = overlays.add(overlay, app); // Insert freshly created WayVR overlay into wlx stack
data.set_overlay_display_handle(overlay_id, disp_handle);
}
Ok(())
}
impl WayVRBackend {
fn ensure_software_data(
&mut self,
data: &wayvr::egl_data::RenderSoftwarePixelsData,
) -> anyhow::Result<()> {
let mut upload = self
.graphics
.create_xfer_command_buffer(CommandBufferUsage::OneTimeSubmit)?;
let tex = upload.upload_image(
u32::from(data.width),
u32::from(data.height),
Format::R8G8B8A8_UNORM,
&data.data,
)?;
// FIXME: can we use _buffers_ here?
upload.build_and_execute_now()?;
//buffers.push(upload.build()?);
self.image = Some(ImageData {
vk_image: tex.clone(),
vk_image_view: ImageView::new_default(tex).unwrap(),
});
Ok(())
}
fn ensure_dmabuf_data(
&mut self,
data: &wayvr::egl_data::RenderDMAbufData,
) -> anyhow::Result<()> {
if self.image.is_some() {
return Ok(()); // already initialized and automatically updated due to direct zero-copy textue access
}
// First init
let mut planes = [FramePlane::default(); 4];
planes[0].fd = Some(data.fd);
planes[0].offset = data.offset as u32;
planes[0].stride = data.stride;
let ctx = self.context.borrow_mut();
let wayvr = ctx.wayvr.borrow_mut();
let Some(disp) = wayvr.data.state.displays.get(&ctx.display) else {
anyhow::bail!("Failed to fetch WayVR display")
};
let frame = DmabufFrame {
format: FrameFormat {
width: u32::from(disp.width),
height: u32::from(disp.height),
fourcc: FourCC {
value: data.mod_info.fourcc,
},
modifier: data.mod_info.modifiers[0], /* possibly not proper? */
..Default::default()
},
num_planes: 1,
planes,
..Default::default()
};
drop(wayvr);
let layouts: Vec<SubresourceLayout> = vec![SubresourceLayout {
offset: data.offset as _,
size: 0,
row_pitch: data.stride as _,
array_pitch: None,
depth_pitch: None,
}];
let tex = self.graphics.dmabuf_texture_ex(
frame,
ImageTiling::DrmFormatModifier,
layouts,
&data.mod_info.modifiers,
)?;
self.image = Some(ImageData {
vk_image: tex.clone(),
vk_image_view: ImageView::new_default(tex).unwrap(),
});
Ok(())
}
}
impl OverlayBackend for WayVRBackend {
fn init(&mut self, _app: &mut state::AppState) -> anyhow::Result<()> {
Ok(())
}
fn pause(&mut self, _app: &mut state::AppState) -> anyhow::Result<()> {
let ctx = self.context.borrow_mut();
let wayvr = &mut ctx.wayvr.borrow_mut().data;
wayvr.state.set_display_visible(ctx.display, false);
Ok(())
}
fn resume(&mut self, _app: &mut state::AppState) -> anyhow::Result<()> {
let ctx = self.context.borrow_mut();
let wayvr = &mut ctx.wayvr.borrow_mut().data;
wayvr.state.set_display_visible(ctx.display, true);
self.just_resumed = true;
Ok(())
}
fn should_render(&mut self, _app: &mut AppState) -> anyhow::Result<ShouldRender> {
let ctx = self.context.borrow();
let mut wayvr = ctx.wayvr.borrow_mut();
let redrawn = match wayvr.data.render_display(ctx.display) {
Ok(r) => r,
Err(e) => {
log::error!("render_display failed: {e}");
return Ok(ShouldRender::Unable);
}
fn should_render(&mut self, app: &mut AppState) -> anyhow::Result<ShouldRender> {
let wm = self.wm.borrow();
let Some(window) = wm.windows.get(&self.window) else {
log::debug!(
"{:?}: WayVR overlay without matching window entry",
self.name
);
return Ok(ShouldRender::Unable);
};
if redrawn {
Ok(ShouldRender::Should)
} else {
Ok(ShouldRender::Can)
}
with_states(window.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()));
}
self.meta = Some(meta);
if self
.cur_image
.as_ref()
.is_none_or(|i| *i.image() != *surf.image.image())
{
self.cur_image = Some(surf.image);
Ok(ShouldRender::Should)
} else {
Ok(ShouldRender::Can)
}
} else {
Ok(ShouldRender::Unable)
}
})
}
fn render(
&mut self,
_app: &mut state::AppState,
app: &mut state::AppState,
rdr: &mut RenderResources,
) -> anyhow::Result<()> {
let ctx = self.context.borrow();
let wayvr = ctx.wayvr.borrow_mut();
let mouse = None; //TODO: mouse cursor
let image = self.cur_image.as_ref().unwrap().clone();
let data = wayvr
.data
.state
.get_render_data(ctx.display)
.context("Failed to fetch render data")?
.clone();
self.pipeline
.as_mut()
.unwrap()
.render(image, mouse, app, rdr)?;
drop(wayvr);
drop(ctx);
match data {
wayvr::egl_data::RenderData::Dmabuf(data) => {
self.ensure_dmabuf_data(&data)?;
}
wayvr::egl_data::RenderData::Software(data) => {
if let Some(new_frame) = &data {
self.ensure_software_data(new_frame)?;
}
}
}
let image = self.image.as_ref().unwrap();
self.pass
.update_sampler(0, image.vk_image_view.clone(), self.graphics.texture_filter)?;
self.buf_alpha.write()?[0] = rdr.alpha;
rdr.cmd_buf_single().run_ref(&self.pass)?;
Ok(())
}
fn frame_meta(&mut self) -> Option<FrameMeta> {
Some(FrameMeta {
extent: [self.resolution[0] as u32, self.resolution[1] as u32, 1],
..Default::default()
})
self.meta
}
fn notify(
@@ -581,24 +182,17 @@ impl OverlayBackend for WayVRBackend {
}
fn on_hover(&mut self, _app: &mut state::AppState, hit: &input::PointerHit) -> HoverResult {
let ctx = self.context.borrow();
let wayvr = &mut ctx.wayvr.borrow_mut();
if let Some(disp) = wayvr.data.state.displays.get(&ctx.display) {
if let Some(window) = self.wm.borrow().windows.get(&self.window) {
let pos = self.mouse_transform.transform_point2(hit.uv);
let x = ((pos.x * f32::from(disp.width)) as i32).max(0);
let y = ((pos.y * f32::from(disp.height)) as i32).max(0);
let x = ((pos.x * (window.size_x as f32)) as u32).max(0);
let y = ((pos.y * (window.size_y as f32)) as u32).max(0);
let ctx = self.context.borrow();
wayvr
.data
.state
.send_mouse_move(ctx.display, x as u32, y as u32);
let wayvr = &mut self.wayvr.borrow_mut().data;
wayvr.state.send_mouse_move(self.window, x, y);
}
HoverResult {
haptics: wayvr.pending_haptics.take(),
haptics: None, // haptics are handled via task
consume: true,
}
}
@@ -617,10 +211,9 @@ impl OverlayBackend for WayVRBackend {
None
}
} {
let ctx = self.context.borrow();
let wayvr = &mut ctx.wayvr.borrow_mut().data;
let wayvr = &mut self.wayvr.borrow_mut().data;
if pressed {
wayvr.state.send_mouse_down(ctx.display, index);
wayvr.state.send_mouse_down(self.window, index);
} else {
wayvr.state.send_mouse_up(index);
}
@@ -633,233 +226,34 @@ impl OverlayBackend for WayVRBackend {
_hit: &input::PointerHit,
delta: WheelDelta,
) {
let ctx = self.context.borrow();
ctx.wayvr.borrow_mut().data.state.send_mouse_scroll(delta);
self.wayvr.borrow_mut().data.state.send_mouse_scroll(delta);
}
fn get_interaction_transform(&mut self) -> Option<Affine2> {
self.interaction_transform
}
fn get_attrib(&self, _attrib: BackendAttrib) -> Option<BackendAttribValue> {
None
}
fn set_attrib(&mut self, _app: &mut AppState, _value: BackendAttribValue) -> bool {
false
}
}
#[allow(dead_code)]
pub fn create_wayvr_display_overlay(
app: &mut state::AppState,
display_width: u16,
display_height: u16,
display_handle: wayvr::display::DisplayHandle,
display_scale: f32,
name: &str,
) -> anyhow::Result<OverlayWindowConfig> {
let wayland_server = app
.wayland_server
.as_ref()
.map(|r| r.clone())
.context("wayland_server unavailable")?;
let backend = Box::new(WayVRBackend::new(
app,
wayland_server,
display_handle,
[display_width, display_height],
)?);
let category = if name == DASHBOARD_DISPLAY_NAME {
OverlayCategory::Dashboard
} else {
OverlayCategory::WayVR
};
Ok(OverlayWindowConfig {
name: format!("WVR-{name}").into(),
keyboard_focus: Some(KeyboardFocus::WayVR),
category,
default_state: OverlayWindowState {
interactable: true,
grabbable: true,
transform: Affine3A::from_scale_rotation_translation(
Vec3::ONE * display_scale,
Quat::IDENTITY,
vec3(0.0, -0.1, -1.0),
),
..OverlayWindowState::default()
},
..OverlayWindowConfig::from_backend(backend)
})
}
fn show_display<O>(
wayvr: &mut WayVRData,
overlays: &mut OverlayWindowManager<O>,
app: &mut AppState,
display_name: &str,
) where
O: Default,
{
if let Some(display) = WayVR::get_display_by_name(&wayvr.data.state.displays, display_name) {
if let Some(overlay_id) = wayvr.display_handle_map.get(&display)
&& let Some(overlay) = overlays.mut_by_id(*overlay_id)
{
overlay.config.activate(app);
}
wayvr.data.state.set_display_visible(display, true);
}
}
fn action_app_click<O>(
app: &mut AppState,
overlays: &mut OverlayWindowManager<O>,
catalog_name: &Arc<str>,
app_name: &Arc<str>,
) -> anyhow::Result<()>
where
O: Default,
{
let wayland_server = app
.wayland_server
.as_ref()
.map(|r| r.clone())
.context("wayland_server unavailable")?;
let catalog = app
.session
.wayvr_config
.get_catalog(catalog_name)
.ok_or_else(|| anyhow::anyhow!("Failed to get catalog \"{catalog_name}\""))?
.clone();
if let Some(app_entry) = catalog.get_app(app_name) {
let mut wayvr = wayland_server.borrow_mut();
let disp_handle = get_or_create_display_by_name(
app,
&mut wayvr,
&app_entry.target_display.to_lowercase(),
)?;
let args_vec = &app_entry
.args
.as_ref()
.map_or_else(Vec::new, |args| ipc_server::gen_args_vec(args.as_str()));
let env_vec = &app_entry
.env
.as_ref()
.map_or_else(Vec::new, |env| ipc_server::gen_env_vec(env));
// Terminate existing process if required
if let Some(process_handle) =
wayvr
.data
.state
.process_query(disp_handle, &app_entry.exec, args_vec, env_vec)
{
// Terminate process
wayvr.data.terminate_process(process_handle);
} else {
// Spawn process
wayvr.data.state.spawn_process(
disp_handle,
&app_entry.exec,
args_vec,
env_vec,
None,
HashMap::default(),
)?;
show_display::<O>(&mut wayvr, overlays, app, app_entry.target_display.as_str());
fn get_attrib(&self, attrib: BackendAttrib) -> Option<BackendAttribValue> {
match attrib {
BackendAttrib::Stereo => self.stereo.map(|s| BackendAttribValue::Stereo(s)),
_ => None,
}
}
Ok(())
}
pub fn action_display_click<O>(
app: &mut AppState,
overlays: &mut OverlayWindowManager<O>,
display_name: &Arc<str>,
action: &WayVRDisplayClickAction,
) -> anyhow::Result<()>
where
O: Default,
{
let wayland_server = app
.wayland_server
.clone()
.context("wayland_server unavailable")?;
let mut wayvr = wayland_server.borrow_mut();
let Some(handle) = WayVR::get_display_by_name(&wayvr.data.state.displays, display_name) else {
return Ok(());
};
let Some(display) = wayvr.data.state.displays.get_mut(&handle) else {
return Ok(());
};
let Some(overlay_id) = display.overlay_id else {
return Ok(());
};
let Some(overlay) = overlays.mut_by_id(overlay_id) else {
return Ok(());
};
match action {
WayVRDisplayClickAction::ToggleVisibility => {
// Toggle visibility
overlay.config.toggle(app);
}
WayVRDisplayClickAction::Reset => {
// Show it at the front
overlay.config.reset(app, true);
}
}
Ok(())
}
pub fn wayvr_action<O>(
app: &mut AppState,
overlays: &mut OverlayWindowManager<O>,
action: &WayVRAction,
) where
O: Default,
{
match action {
WayVRAction::AppClick {
catalog_name,
app_name,
} => {
if let Err(e) = action_app_click(app, overlays, catalog_name, app_name) {
// Happens if something went wrong with initialization
// or input exec path is invalid. Do nothing, just print an error
error_toast(app, "action_app_click failed", e);
}
}
WayVRAction::DisplayClick {
display_name,
action,
} => {
if let Err(e) = action_display_click::<O>(app, overlays, display_name, action) {
error_toast(app, "action_display_click failed", e);
}
}
WayVRAction::ToggleDashboard => {
if let Some(wayland_server) = app.wayland_server.as_ref().map(|r| r.clone())
&& let Err(e) =
toggle_dashboard::<O>(app, overlays, &mut wayland_server.borrow_mut())
{
error_toast(app, "toggle_dashboard failed", e);
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,
}
}
}