Files
wayvr/src/backend/uidev/mod.rs

317 lines
9.8 KiB
Rust

use std::sync::Arc;
use vulkano::{
command_buffer::CommandBufferUsage,
image::{sampler::Filter, view::ImageView, ImageUsage},
swapchain::{
acquire_next_image, Surface, Swapchain, SwapchainCreateInfo, SwapchainPresentInfo,
},
sync::GpuFuture,
Validated, VulkanError,
};
use winit::{
dpi::LogicalSize,
event::{Event, WindowEvent},
event_loop::ControlFlow,
window::Window,
};
use crate::{
config::load_custom_ui,
config_io,
graphics::{DynamicPass, DynamicPipeline, WlxGraphics, BLEND_ALPHA},
gui::{
modular::{modular_canvas, ModularData},
Canvas,
},
hid::USE_UINPUT,
state::{AppState, ScreenMeta},
};
use super::{
input::{TrackedDevice, TrackedDeviceRole},
overlay::OverlayRenderer,
};
static LAST_SIZE: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
struct PreviewState {
canvas: Canvas<(), ModularData>,
pipeline: Arc<DynamicPipeline>,
pass: DynamicPass,
swapchain: Arc<Swapchain>,
images: Vec<Arc<ImageView>>,
}
impl PreviewState {
fn new(
state: &mut AppState,
surface: Arc<Surface>,
window: Arc<Window>,
panel_name: &str,
) -> anyhow::Result<Self> {
let config = load_custom_ui(panel_name)?;
let last_size = {
let size_u64 = LAST_SIZE.load(std::sync::atomic::Ordering::Relaxed);
[size_u64 as u32, (size_u64 >> 32) as u32]
};
if last_size != config.size {
let logical_size = LogicalSize::new(config.size[0], config.size[1]);
let _ = window.request_inner_size(logical_size);
window.set_min_inner_size(Some(logical_size));
window.set_max_inner_size(Some(logical_size));
LAST_SIZE.store(
(config.size[1] as u64) << 32 | config.size[0] as u64,
std::sync::atomic::Ordering::Relaxed,
);
}
let inner_size = window.inner_size();
let swapchain_size = [inner_size.width, inner_size.height];
let (swapchain, images) =
create_swapchain(&state.graphics, surface.clone(), swapchain_size)?;
let mut canvas = modular_canvas(&config.size, &config.elements, state)?;
canvas.init(state)?;
let view = canvas.view().unwrap();
let pipeline = {
let shaders = state.graphics.shared_shaders.read().unwrap();
state.graphics.create_pipeline_dynamic(
shaders.get("vert_common").unwrap().clone(), // want panic
shaders.get("frag_sprite").unwrap().clone(), // want panic
state.graphics.native_format,
Some(BLEND_ALPHA),
)
}?;
let set0 = pipeline
.uniform_sampler(0, view.clone(), Filter::Linear)
.unwrap();
let pass = pipeline
.create_pass(
[swapchain_size[0] as f32, swapchain_size[1] as f32],
state.graphics.quad_verts.clone(),
state.graphics.quad_indices.clone(),
vec![set0],
)
.unwrap();
Ok(PreviewState {
canvas,
pipeline,
pass,
swapchain,
images,
})
}
}
pub fn uidev_run(panel_name: &str) -> anyhow::Result<()> {
let (graphics, event_loop, window, surface) = WlxGraphics::new_window()?;
window.set_resizable(false);
window.set_title("WlxOverlay UI Preview");
USE_UINPUT.store(false, std::sync::atomic::Ordering::Relaxed);
let mut state = AppState::from_graphics(graphics.clone())?;
add_dummy_devices(&mut state);
add_dummy_screens(&mut state);
let mut preview = Some(PreviewState::new(
&mut state,
surface.clone(),
window.clone(),
panel_name,
)?);
let watch_path = config_io::CONFIG_ROOT_PATH.join(format!("{}.yaml", panel_name));
let mut path_last_modified = watch_path.metadata()?.modified()?;
let mut recreate = false;
let mut last_draw = std::time::Instant::now();
event_loop.run(move |event, elwt| {
elwt.set_control_flow(ControlFlow::Poll);
match event {
Event::WindowEvent {
event: WindowEvent::CloseRequested,
..
} => {
elwt.exit();
}
Event::WindowEvent {
event: WindowEvent::Resized(_),
..
} => {
recreate = true;
}
Event::WindowEvent {
event: WindowEvent::RedrawRequested,
..
} => {
let new_modified = watch_path.metadata().unwrap().modified().unwrap();
if new_modified > path_last_modified {
recreate = true;
path_last_modified = new_modified;
}
if recreate {
drop(preview.take());
preview = Some(
PreviewState::new(&mut state, surface.clone(), window.clone(), panel_name)
.unwrap(),
);
recreate = false;
window.request_redraw();
}
{
let preview = preview.as_mut().unwrap();
let (image_index, _, acquire_future) =
match acquire_next_image(preview.swapchain.clone(), None)
.map_err(Validated::unwrap)
{
Ok(r) => r,
Err(VulkanError::OutOfDate) => {
recreate = true;
return;
}
Err(e) => panic!("failed to acquire next image: {e}"),
};
if let Err(e) = preview.canvas.render(&mut state) {
log::error!("failed to render canvas: {e}");
window.request_redraw();
};
let target = preview.images[image_index as usize].clone();
let mut cmd_buf = state
.graphics
.create_command_buffer(CommandBufferUsage::OneTimeSubmit)
.unwrap();
cmd_buf.begin_rendering(target).unwrap();
cmd_buf.run_ref(&preview.pass).unwrap();
cmd_buf.end_rendering().unwrap();
last_draw = std::time::Instant::now();
let command_buffer = cmd_buf.build().unwrap();
vulkano::sync::now(graphics.device.clone())
.join(acquire_future)
.then_execute(graphics.queue.clone(), command_buffer)
.unwrap()
.then_swapchain_present(
graphics.queue.clone(),
SwapchainPresentInfo::swapchain_image_index(
preview.swapchain.clone(),
image_index,
),
)
.then_signal_fence_and_flush()
.unwrap()
.wait(None)
.unwrap();
}
}
Event::AboutToWait => {
if last_draw.elapsed().as_millis() > 100 {
window.request_redraw();
}
}
_ => (),
}
})?;
Ok(())
}
fn create_swapchain(
graphics: &WlxGraphics,
surface: Arc<Surface>,
extent: [u32; 2],
) -> anyhow::Result<(Arc<Swapchain>, Vec<Arc<ImageView>>)> {
let surface_capabilities = graphics
.device
.physical_device()
.surface_capabilities(&surface, Default::default())
.unwrap();
let (swapchain, images) = Swapchain::new(
graphics.device.clone(),
surface.clone(),
SwapchainCreateInfo {
min_image_count: surface_capabilities.min_image_count.max(2),
image_format: graphics.native_format,
image_extent: extent,
image_usage: ImageUsage::COLOR_ATTACHMENT,
composite_alpha: surface_capabilities
.supported_composite_alpha
.into_iter()
.next()
.unwrap(),
..Default::default()
},
)?;
let image_views = images
.into_iter()
.map(|image| ImageView::new_default(image).unwrap())
.collect::<Vec<_>>();
Ok((swapchain, image_views))
}
fn add_dummy_devices(app: &mut AppState) {
app.input_state.devices.push(TrackedDevice {
role: TrackedDeviceRole::Hmd,
soc: Some(0.42),
charging: true,
});
app.input_state.devices.push(TrackedDevice {
role: TrackedDeviceRole::LeftHand,
soc: Some(0.72),
charging: false,
});
app.input_state.devices.push(TrackedDevice {
role: TrackedDeviceRole::RightHand,
soc: Some(0.73),
charging: false,
});
app.input_state.devices.push(TrackedDevice {
role: TrackedDeviceRole::Tracker,
soc: Some(0.65),
charging: false,
});
app.input_state.devices.push(TrackedDevice {
role: TrackedDeviceRole::Tracker,
soc: Some(0.67),
charging: false,
});
app.input_state.devices.push(TrackedDevice {
role: TrackedDeviceRole::Tracker,
soc: Some(0.69),
charging: false,
});
}
fn add_dummy_screens(app: &mut AppState) {
app.screens.push(ScreenMeta {
name: "HDMI-A-1".into(),
id: 0,
native_handle: 0,
});
app.screens.push(ScreenMeta {
name: "DP-2".into(),
id: 0,
native_handle: 0,
});
app.screens.push(ScreenMeta {
name: "DP-3".into(),
id: 0,
native_handle: 0,
});
}