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, pass: DynamicPass, swapchain: Arc, images: Vec>, } impl PreviewState { fn new( state: &mut AppState, surface: Arc, window: Arc, panel_name: &str, ) -> anyhow::Result { 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, extent: [u32; 2], ) -> anyhow::Result<(Arc, Vec>)> { 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::>(); 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, }); }