rewrite built-in wayland compositor egl → vulkan
This commit is contained in:
@@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user