@@ -8,6 +8,9 @@ pub mod openvr;
|
||||
#[cfg(feature = "openxr")]
|
||||
pub mod openxr;
|
||||
|
||||
#[cfg(feature = "uidev")]
|
||||
pub mod uidev;
|
||||
|
||||
#[cfg(feature = "osc")]
|
||||
pub mod osc;
|
||||
|
||||
|
||||
241
src/backend/uidev/mod.rs
Normal file
241
src/backend/uidev/mod.rs
Normal file
@@ -0,0 +1,241 @@
|
||||
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,
|
||||
};
|
||||
|
||||
use super::overlay::OverlayRenderer;
|
||||
|
||||
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 (swapchain, images) = create_swapchain(&state.graphics, surface.clone(), config.size)?;
|
||||
|
||||
let logical_size = LogicalSize::new(config.size[0], config.size[1]);
|
||||
log::info!("Setting window size to {:?}", logical_size);
|
||||
let _ = window.request_inner_size(logical_size);
|
||||
window.set_min_inner_size(Some(logical_size));
|
||||
window.set_max_inner_size(Some(logical_size));
|
||||
window.set_resizable(false);
|
||||
window.set_title("WlxOverlay UI Preview");
|
||||
|
||||
let mut canvas = modular_canvas(&config.size, &config.elements, state)?;
|
||||
canvas.init(state)?;
|
||||
canvas.render(state).unwrap();
|
||||
let view = canvas.view().unwrap();
|
||||
let extent = view.image().extent();
|
||||
|
||||
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(
|
||||
[extent[0] as f32, extent[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()?;
|
||||
|
||||
USE_UINPUT.store(false, std::sync::atomic::Ordering::Relaxed);
|
||||
|
||||
let mut state = AppState::from_graphics(graphics.clone())?;
|
||||
let mut preview = Some(PreviewState::new(
|
||||
&mut state,
|
||||
surface.clone(),
|
||||
window.clone(),
|
||||
panel_name,
|
||||
)?);
|
||||
let mut previous_frame_end = Some(vulkano::sync::now(graphics.device.clone()).boxed());
|
||||
|
||||
let watch_path = config_io::CONFIG_ROOT_PATH.join(format!("{}.yaml", panel_name));
|
||||
let mut path_last_modified = watch_path.metadata()?.modified()?;
|
||||
|
||||
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::RedrawRequested,
|
||||
..
|
||||
} => {
|
||||
previous_frame_end.as_mut().unwrap().cleanup_finished();
|
||||
|
||||
let new_modified = watch_path.metadata().unwrap().modified().unwrap();
|
||||
if new_modified > path_last_modified {
|
||||
{
|
||||
let _ = preview.take(); // free swapchain
|
||||
}
|
||||
preview = Some(
|
||||
PreviewState::new(&mut state, surface.clone(), window.clone(), panel_name)
|
||||
.unwrap(),
|
||||
);
|
||||
path_last_modified = new_modified;
|
||||
}
|
||||
|
||||
{
|
||||
let preview = preview.as_ref().unwrap();
|
||||
|
||||
let (image_index, _, acquire_future) =
|
||||
match acquire_next_image(preview.swapchain.clone(), None)
|
||||
.map_err(Validated::unwrap)
|
||||
{
|
||||
Ok(r) => r,
|
||||
Err(VulkanError::OutOfDate) => {
|
||||
elwt.exit();
|
||||
return;
|
||||
}
|
||||
Err(e) => panic!("failed to acquire next image: {e}"),
|
||||
};
|
||||
|
||||
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();
|
||||
let _ = cmd_buf.run_ref(&preview.pass);
|
||||
cmd_buf.end_rendering().unwrap();
|
||||
|
||||
let command_buffer = cmd_buf.build().unwrap();
|
||||
let future = previous_frame_end
|
||||
.take()
|
||||
.unwrap()
|
||||
.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();
|
||||
|
||||
match future.map_err(Validated::unwrap) {
|
||||
Ok(future) => {
|
||||
previous_frame_end = Some(future.boxed());
|
||||
}
|
||||
Err(VulkanError::OutOfDate) => {
|
||||
previous_frame_end =
|
||||
Some(vulkano::sync::now(state.graphics.device.clone()).boxed());
|
||||
}
|
||||
Err(e) => {
|
||||
println!("failed to flush future: {e}");
|
||||
previous_frame_end =
|
||||
Some(vulkano::sync::now(state.graphics.device.clone()).boxed());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Event::AboutToWait => 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))
|
||||
}
|
||||
136
src/graphics.rs
136
src/graphics.rs
@@ -490,6 +490,142 @@ impl WlxGraphics {
|
||||
Ok(Arc::new(me))
|
||||
}
|
||||
|
||||
#[cfg(feature = "uidev")]
|
||||
pub fn new_window() -> anyhow::Result<(
|
||||
Arc<Self>,
|
||||
winit::event_loop::EventLoop<()>,
|
||||
Arc<winit::window::Window>,
|
||||
Arc<vulkano::swapchain::Surface>,
|
||||
)> {
|
||||
use vulkano::swapchain::Surface;
|
||||
use winit::{event_loop::EventLoop, window::WindowBuilder};
|
||||
|
||||
let event_loop = EventLoop::new().unwrap();
|
||||
let mut vk_instance_extensions = Surface::required_extensions(&event_loop).unwrap();
|
||||
vk_instance_extensions.khr_get_physical_device_properties2 = true;
|
||||
log::debug!("Instance exts for runtime: {:?}", &vk_instance_extensions);
|
||||
|
||||
let instance = Instance::new(
|
||||
get_vulkan_library().clone(),
|
||||
InstanceCreateInfo {
|
||||
flags: InstanceCreateFlags::ENUMERATE_PORTABILITY,
|
||||
enabled_extensions: vk_instance_extensions,
|
||||
..Default::default()
|
||||
},
|
||||
)?;
|
||||
|
||||
let window = Arc::new(WindowBuilder::new().build(&event_loop).unwrap());
|
||||
let surface = Surface::from_window(instance.clone(), window.clone())?;
|
||||
|
||||
let device_extensions = DeviceExtensions {
|
||||
khr_swapchain: true,
|
||||
khr_external_memory: true,
|
||||
khr_external_memory_fd: true,
|
||||
ext_external_memory_dma_buf: true,
|
||||
ext_image_drm_format_modifier: true,
|
||||
..DeviceExtensions::empty()
|
||||
};
|
||||
|
||||
log::debug!("Device exts for app: {:?}", &device_extensions);
|
||||
|
||||
let (physical_device, my_extensions, queue_family_index) = instance
|
||||
.enumerate_physical_devices()?
|
||||
.filter_map(|p| {
|
||||
if p.supported_extensions().contains(&device_extensions) {
|
||||
Some((p, device_extensions))
|
||||
} else {
|
||||
log::debug!(
|
||||
"Not using {} because it does not implement the following device extensions:",
|
||||
p.properties().device_name,
|
||||
);
|
||||
for (ext, missing) in p.supported_extensions().difference(&device_extensions) {
|
||||
if missing {
|
||||
log::debug!(" {}", ext);
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
})
|
||||
.filter_map(|(p, my_extensions)| {
|
||||
p.queue_family_properties()
|
||||
.iter()
|
||||
.enumerate()
|
||||
.position(|(i, q)| q.queue_flags.intersects(QueueFlags::GRAPHICS)
|
||||
&& p.surface_support(i as u32, &surface).unwrap_or(false))
|
||||
.map(|i| (p, my_extensions, i as u32))
|
||||
})
|
||||
.min_by_key(|(p, _, _)| match p.properties().device_type {
|
||||
PhysicalDeviceType::DiscreteGpu => 0,
|
||||
PhysicalDeviceType::IntegratedGpu => 1,
|
||||
PhysicalDeviceType::VirtualGpu => 2,
|
||||
PhysicalDeviceType::Cpu => 3,
|
||||
PhysicalDeviceType::Other => 4,
|
||||
_ => 5,
|
||||
})
|
||||
.expect("no suitable physical device found");
|
||||
|
||||
log::info!(
|
||||
"Using vkPhysicalDevice: {}",
|
||||
physical_device.properties().device_name,
|
||||
);
|
||||
|
||||
let (device, mut queues) = Device::new(
|
||||
physical_device,
|
||||
DeviceCreateInfo {
|
||||
enabled_extensions: my_extensions,
|
||||
enabled_features: Features {
|
||||
dynamic_rendering: true,
|
||||
..Features::empty()
|
||||
},
|
||||
queue_create_infos: vec![QueueCreateInfo {
|
||||
queue_family_index,
|
||||
..Default::default()
|
||||
}],
|
||||
..Default::default()
|
||||
},
|
||||
)?;
|
||||
|
||||
let native_format = device
|
||||
.physical_device()
|
||||
.surface_formats(&surface, Default::default())
|
||||
.unwrap()[0]
|
||||
.0;
|
||||
log::info!("Using surface format: {:?}", native_format);
|
||||
|
||||
let queue = queues
|
||||
.next()
|
||||
.ok_or_else(|| anyhow::anyhow!("no GPU queues available"))?;
|
||||
|
||||
let memory_allocator = memory_allocator(device.clone());
|
||||
let command_buffer_allocator = Arc::new(StandardCommandBufferAllocator::new(
|
||||
device.clone(),
|
||||
StandardCommandBufferAllocatorCreateInfo {
|
||||
secondary_buffer_count: 32,
|
||||
..Default::default()
|
||||
},
|
||||
));
|
||||
let descriptor_set_allocator = Arc::new(StandardDescriptorSetAllocator::new(
|
||||
device.clone(),
|
||||
Default::default(),
|
||||
));
|
||||
|
||||
let (quad_verts, quad_indices) = Self::default_quad(memory_allocator.clone())?;
|
||||
|
||||
let me = Self {
|
||||
instance,
|
||||
device,
|
||||
queue,
|
||||
memory_allocator,
|
||||
native_format,
|
||||
command_buffer_allocator,
|
||||
descriptor_set_allocator,
|
||||
quad_indices,
|
||||
quad_verts,
|
||||
shared_shaders: RwLock::new(HashMap::new()),
|
||||
};
|
||||
|
||||
Ok((Arc::new(me), event_loop, window, surface))
|
||||
}
|
||||
fn default_quad(
|
||||
memory_allocator: Arc<StandardMemoryAllocator>,
|
||||
) -> anyhow::Result<(Vert2Buf, IndexBuf)> {
|
||||
|
||||
@@ -120,7 +120,7 @@ pub fn modular_canvas(
|
||||
size[0] as _,
|
||||
size[1] as _,
|
||||
state.graphics.clone(),
|
||||
state.format,
|
||||
state.graphics.native_format,
|
||||
(),
|
||||
)?;
|
||||
let empty_str: Arc<str> = Arc::from("");
|
||||
|
||||
@@ -7,11 +7,18 @@ use input_linux::{
|
||||
};
|
||||
use libc::{input_event, timeval};
|
||||
use once_cell::sync::Lazy;
|
||||
use std::fs::File;
|
||||
use std::mem::transmute;
|
||||
use std::{fs::File, sync::atomic::AtomicBool};
|
||||
use strum::{EnumIter, EnumString, IntoEnumIterator};
|
||||
|
||||
pub static USE_UINPUT: AtomicBool = AtomicBool::new(true);
|
||||
|
||||
pub fn initialize() -> Box<dyn HidProvider> {
|
||||
if !USE_UINPUT.load(std::sync::atomic::Ordering::Relaxed) {
|
||||
log::info!("Uinput disabled by user.");
|
||||
return Box::new(DummyProvider {});
|
||||
}
|
||||
|
||||
if let Some(uinput) = UInputProvider::try_new() {
|
||||
log::info!("Initialized uinput.");
|
||||
return Box::new(uinput);
|
||||
|
||||
11
src/main.rs
11
src/main.rs
@@ -41,6 +41,11 @@ struct Args {
|
||||
/// Path to write logs to
|
||||
#[arg(short, long, value_name = "FILE_PATH")]
|
||||
log_to: Option<String>,
|
||||
|
||||
#[cfg(feature = "uidev")]
|
||||
/// Show a desktop window of a UI panel for development
|
||||
#[arg(short, long, value_name = "UI_NAME")]
|
||||
uidev: Option<String>,
|
||||
}
|
||||
|
||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
@@ -60,6 +65,12 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
#[cfg(feature = "uidev")]
|
||||
if let Some(panel_name) = args.uidev.as_ref() {
|
||||
crate::backend::uidev::uidev_run(panel_name.as_str())?;
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let running = Arc::new(AtomicBool::new(true));
|
||||
let _ = ctrlc::set_handler({
|
||||
let running = running.clone();
|
||||
|
||||
@@ -43,7 +43,7 @@ where
|
||||
size.x as _,
|
||||
size.y as _,
|
||||
app.graphics.clone(),
|
||||
app.format,
|
||||
app.graphics.native_format,
|
||||
data,
|
||||
)?;
|
||||
|
||||
|
||||
@@ -6,7 +6,6 @@ use idmap::IdMap;
|
||||
use rodio::{Decoder, OutputStream, OutputStreamHandle, Source};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use smallvec::{smallvec, SmallVec};
|
||||
use vulkano::format::Format;
|
||||
|
||||
use crate::{
|
||||
backend::{common::TaskContainer, input::InputState},
|
||||
@@ -24,7 +23,6 @@ pub struct AppState {
|
||||
pub session: AppSession,
|
||||
pub tasks: TaskContainer,
|
||||
pub graphics: Arc<WlxGraphics>,
|
||||
pub format: vulkano::format::Format,
|
||||
pub input_state: InputState,
|
||||
pub hid_provider: Box<dyn HidProvider>,
|
||||
pub audio: AudioOutput,
|
||||
@@ -63,7 +61,6 @@ impl AppState {
|
||||
session: AppSession::load(),
|
||||
tasks: TaskContainer::new(),
|
||||
graphics,
|
||||
format: Format::R8G8B8A8_UNORM,
|
||||
input_state: InputState::new(),
|
||||
hid_provider: crate::hid::initialize(),
|
||||
audio: AudioOutput::new(),
|
||||
|
||||
Reference in New Issue
Block a user