From cf29682e66fdf49297e761aa17d0a75c5a732741 Mon Sep 17 00:00:00 2001 From: galister <22305755+galister@users.noreply.github.com> Date: Sun, 6 Apr 2025 22:12:56 +0900 Subject: [PATCH] rework rendering backend --- Cargo.lock | 10 +- Cargo.toml | 2 +- src/backend/input.rs | 2 +- src/backend/openvr/lines.rs | 23 +- src/backend/openvr/mod.rs | 25 +- src/backend/openvr/overlay.rs | 37 ++- src/backend/openxr/input.rs | 2 +- src/backend/openxr/lines.rs | 104 ++++--- src/backend/openxr/mod.rs | 96 +++++-- src/backend/openxr/overlay.rs | 88 +++--- src/backend/openxr/skybox.rs | 182 ++++++++---- src/backend/openxr/swapchain.rs | 158 ++--------- src/backend/overlay.rs | 86 ++++-- src/backend/uidev/mod.rs | 56 +--- src/config_io.rs | 6 +- src/graphics/mod.rs | 419 +++++----------------------- src/gui/canvas/control.rs | 18 +- src/gui/canvas/mod.rs | 266 +++++++++--------- src/gui/font.rs | 38 ++- src/gui/modular/label.rs | 2 +- src/overlays/keyboard.rs | 25 +- src/overlays/mirror.rs | 36 ++- src/overlays/screen.rs | 476 ++++++++++++++++++-------------- src/overlays/toast.rs | 7 +- src/overlays/wayvr.rs | 79 ++++-- src/shaders/mod.rs | 36 ++- src/state.rs | 37 ++- 27 files changed, 1135 insertions(+), 1181 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b7799b9..54d0ad7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -263,16 +263,16 @@ dependencies = [ [[package]] name = "ashpd" -version = "0.10.2" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e9c39d707614dbcc6bed00015539f488d8e3fe3e66ed60961efc0c90f4b380b3" +checksum = "6cbdf310d77fd3aaee6ea2093db7011dc2d35d2eb3481e5607f1f8d942ed99df" dependencies = [ "async-fs", "async-net", "enumflags2", "futures-channel", "futures-util", - "rand 0.8.5", + "rand 0.9.0", "serde", "serde_repr", "url", @@ -5165,8 +5165,8 @@ dependencies = [ [[package]] name = "wlx-capture" -version = "0.4.2" -source = "git+https://github.com/galister/wlx-capture?tag=v0.4.2#514a81c0cfd900edb89ae4ec33f927848a49eae3" +version = "0.5.0" +source = "git+https://github.com/galister/wlx-capture?tag=v0.5.0#fc77da75dcf8c9d59cdeea384da21bdab685d18b" dependencies = [ "ashpd", "drm-fourcc", diff --git a/Cargo.toml b/Cargo.toml index 7e9fcf7..5898597 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -54,7 +54,7 @@ sysinfo = { version = "0.32.0" } thiserror = "2.0.3" vulkano = { git = "https://github.com/vulkano-rs/vulkano", rev = "94f50f1" } vulkano-shaders = { git = "https://github.com/vulkano-rs/vulkano", rev = "94f50f1" } -wlx-capture = { git = "https://github.com/galister/wlx-capture", tag = "v0.4.2", default-features = false } +wlx-capture = { git = "https://github.com/galister/wlx-capture", tag = "v0.5.0", default-features = false } libmonado = { git = "https://github.com/technobaboo/libmonado-rs", rev = "256895b18c8f9368174fad8a6232ff07764eeacb", optional = true } winit = { version = "0.30.0", optional = true } xdg = "2.5.2" diff --git a/src/backend/input.rs b/src/backend/input.rs index 1306eb5..cbb34e2 100644 --- a/src/backend/input.rs +++ b/src/backend/input.rs @@ -429,7 +429,7 @@ where .is_some_and(|x| x.grabbed_id == hit.overlay) { let can_curve = hovered - .frame_transform() + .frame_meta() .is_some_and(|e| e.extent[0] >= e.extent[1]); if can_curve { diff --git a/src/backend/openvr/lines.rs b/src/backend/openvr/lines.rs index 9b0b406..62412b7 100644 --- a/src/backend/openvr/lines.rs +++ b/src/backend/openvr/lines.rs @@ -12,9 +12,10 @@ use vulkano::image::view::ImageView; use vulkano::image::ImageLayout; use crate::backend::overlay::{ - FrameTransform, OverlayData, OverlayRenderer, OverlayState, SplitOverlayBackend, Z_ORDER_LINES, + FrameMeta, OverlayData, OverlayRenderer, OverlayState, ShouldRender, SplitOverlayBackend, + Z_ORDER_LINES, }; -use crate::graphics::WlxGraphics; +use crate::graphics::{CommandBuffers, WlxGraphics}; use crate::state::AppState; use super::overlay::OpenVrOverlayData; @@ -185,14 +186,20 @@ impl OverlayRenderer for StaticRenderer { fn resume(&mut self, _app: &mut AppState) -> anyhow::Result<()> { Ok(()) } - fn render(&mut self, _app: &mut AppState) -> anyhow::Result<()> { - Ok(()) + fn should_render(&mut self, _app: &mut AppState) -> anyhow::Result { + Ok(ShouldRender::Unable) } - fn view(&mut self) -> Option> { - Some(self.view.clone()) + fn render( + &mut self, + _app: &mut AppState, + _tgt: Arc, + _buf: &mut CommandBuffers, + _alpha: f32, + ) -> anyhow::Result { + Ok(false) } - fn frame_transform(&mut self) -> Option { - Some(FrameTransform { + fn frame_meta(&mut self) -> Option { + Some(FrameMeta { extent: self.view.image().extent(), ..Default::default() }) diff --git a/src/backend/openvr/mod.rs b/src/backend/openvr/mod.rs index e74cb96..6a4bb58 100644 --- a/src/backend/openvr/mod.rs +++ b/src/backend/openvr/mod.rs @@ -31,10 +31,10 @@ use crate::{ manifest::{install_manifest, uninstall_manifest}, overlay::OpenVrOverlayData, }, - overlay::OverlayData, + overlay::{OverlayData, ShouldRender}, task::{SystemTask, TaskType}, }, - graphics::WlxGraphics, + graphics::{CommandBuffers, WlxGraphics}, overlays::{ toast::{Toast, ToastTopic}, watch::{watch_fade, WATCH_NAME}, @@ -321,6 +321,7 @@ pub fn openvr_run(running: Arc, show_by_default: bool) -> Result<(), } state.hid_provider.commit(); + let mut buffers = CommandBuffers::default(); lines.update(universe.clone(), &mut overlay_mgr, &mut state)?; @@ -344,12 +345,30 @@ pub fn openvr_run(running: Arc, show_by_default: bool) -> Result<(), for o in overlays.iter_mut() { if o.state.want_visible { - o.render(&mut state)?; + let ShouldRender::Should = o.should_render(&mut state)? else { + continue; + }; + if !o.ensure_image_allocated(&mut state)? { + continue; + } + o.data.image_dirty = o.render( + &mut state, + o.data.image_view.as_ref().unwrap().clone(), + &mut buffers, + 1.0, // alpha is instead set using OVR API + )?; } } log::trace!("Rendering overlays"); + if let Some(mut future) = buffers.execute_now(state.graphics.queue.clone())? { + if let Err(e) = future.flush() { + return Err(BackendError::Fatal(e.into())); + }; + future.cleanup_finished(); + } + overlays .iter_mut() .for_each(|o| o.after_render(universe.clone(), &mut overlay_mgr, &state.graphics)); diff --git a/src/backend/openvr/overlay.rs b/src/backend/openvr/overlay.rs index c78e2a1..0de9f81 100644 --- a/src/backend/openvr/overlay.rs +++ b/src/backend/openvr/overlay.rs @@ -1,4 +1,5 @@ use core::f32; +use std::sync::Arc; use glam::Vec4; use ovr_overlay::{ @@ -6,7 +7,7 @@ use ovr_overlay::{ pose::Matrix3x4, sys::{ETrackingUniverseOrigin, VRVulkanTextureData_t}, }; -use vulkano::{Handle, VulkanObject}; +use vulkano::{image::view::ImageView, Handle, VulkanObject}; use crate::{ backend::overlay::{OverlayData, RelativeTo}, @@ -19,12 +20,13 @@ use super::helpers::Affine3AConvert; #[derive(Default)] pub(super) struct OpenVrOverlayData { pub(super) handle: Option, - pub(super) last_image: Option, pub(super) visible: bool, pub(super) color: Vec4, pub(crate) width: f32, pub(super) override_width: bool, pub(super) relative_to: RelativeTo, + pub(super) image_view: Option>, + pub(super) image_dirty: bool, } impl OverlayData { @@ -61,6 +63,20 @@ impl OverlayData { Ok(handle) } + pub(super) fn ensure_image_allocated(&mut self, app: &mut AppState) -> anyhow::Result { + if self.data.image_view.is_some() { + return Ok(true); + } + let Some(meta) = self.backend.frame_meta() else { + return Ok(false); + }; + let image = app + .graphics + .render_texture(meta.extent[0], meta.extent[1], meta.format)?; + self.data.image_view = Some(ImageView::new_default(image)?); + Ok(true) + } + pub(super) fn after_input( &mut self, overlay: &mut OverlayManager, @@ -200,7 +216,7 @@ impl OverlayData { let effective = self.state.transform * self .backend - .frame_transform() + .frame_meta() .map(|f| f.transform) .unwrap_or_default(); @@ -221,21 +237,17 @@ impl OverlayData { return; }; - let Some(view) = self.backend.view() else { + let Some(view) = self.data.image_view.as_ref() else { log::debug!("{}: Not rendered", self.state.name); return; }; - let image = view.image().clone(); - - let raw_image = image.handle().as_raw(); - - if let Some(last_image) = self.data.last_image { - if last_image == raw_image { - return; - } + if !self.data.image_dirty { + return; } + self.data.image_dirty = false; + let image = view.image().clone(); let dimensions = image.extent(); if !self.data.override_width { let new_width = ((dimensions[0] as f32) / (dimensions[1] as f32)).min(1.0); @@ -246,6 +258,7 @@ impl OverlayData { } } + let raw_image = image.handle().as_raw(); let format = image.format(); let mut texture = VRVulkanTextureData_t { diff --git a/src/backend/openxr/input.rs b/src/backend/openxr/input.rs index a1d1871..36fa482 100644 --- a/src/backend/openxr/input.rs +++ b/src/backend/openxr/input.rs @@ -475,7 +475,7 @@ fn is_bool(maybe_type_str: &Option) -> bool { .as_ref() .unwrap() // want panic .split('/') - .last() + .next_back() .map(|last| matches!(last, "click" | "touch")) .unwrap_or(false) } diff --git a/src/backend/openxr/lines.rs b/src/backend/openxr/lines.rs index 173182b..17b2fd0 100644 --- a/src/backend/openxr/lines.rs +++ b/src/backend/openxr/lines.rs @@ -9,55 +9,53 @@ use std::{ }, }; -use vulkano::{command_buffer::CommandBufferUsage, format::Format, image::view::ImageView}; +use vulkano::command_buffer::CommandBufferUsage; use crate::{ backend::openxr::helpers, - graphics::{WlxCommandBuffer, WlxGraphics}, + graphics::{CommandBuffers, WlxGraphics, WlxPipeline, SWAPCHAIN_FORMAT}, }; use super::{ - swapchain::{create_swapchain_render_data, SwapchainOpts, SwapchainRenderData}, + swapchain::{create_swapchain, SwapchainOpts, WlxSwapchain}, CompositionLayer, XrState, }; static LINE_AUTO_INCREMENT: AtomicUsize = AtomicUsize::new(1); pub(super) const LINE_WIDTH: f32 = 0.002; +// TODO customizable colors +static COLORS: [[f32; 6]; 5] = { + [ + [1., 1., 1., 1., 0., 0.], + [0., 0.375, 0.5, 1., 0., 0.], + [0.69, 0.188, 0., 1., 0., 0.], + [0.375, 0., 0.5, 1., 0., 0.], + [1., 0., 0., 1., 0., 0.], + ] +}; + pub(super) struct LinePool { lines: IdMap, - colors: Vec>, + pipeline: Arc, } impl LinePool { pub(super) fn new(graphics: Arc) -> anyhow::Result { - let mut command_buffer = - graphics.create_command_buffer(CommandBufferUsage::OneTimeSubmit)?; + let Ok(shaders) = graphics.shared_shaders.read() else { + anyhow::bail!("Failed to lock shared shaders for reading"); + }; - // TODO customizable colors - let colors = [ - [0xff, 0xff, 0xff, 0xff], - [0x00, 0x60, 0x80, 0xff], - [0xB0, 0x30, 0x00, 0xff], - [0x60, 0x00, 0x80, 0xff], - [0xff, 0x00, 0x00, 0xff], - ]; - - let views: anyhow::Result>> = colors - .into_iter() - .map( - |color| match command_buffer.texture2d_raw(1, 1, Format::R8G8B8A8_UNORM, &color) { - Ok(tex) => ImageView::new_default(tex).map_err(|e| anyhow::anyhow!(e)), - Err(e) => Err(e), - }, - ) - .collect(); - - command_buffer.build_and_execute_now()?; + let pipeline = graphics.create_pipeline( + shaders.get("vert_common").unwrap().clone(), // want panic + shaders.get("frag_color").unwrap().clone(), // want panic + SWAPCHAIN_FORMAT, + None, + )?; Ok(LinePool { lines: IdMap::new(), - colors: views?, + pipeline, }) } @@ -68,8 +66,7 @@ impl LinePool { ) -> anyhow::Result { let id = LINE_AUTO_INCREMENT.fetch_add(1, Ordering::Relaxed); - let srd = - create_swapchain_render_data(xr, graphics, [1, 1, 1], SwapchainOpts::new().srgb())?; + let srd = create_swapchain(xr, graphics, [1, 1, 1], SwapchainOpts::new())?; self.lines.insert( id, LineContainer { @@ -92,7 +89,7 @@ impl LinePool { return; } - debug_assert!(color < self.colors.len()); + debug_assert!(color < COLORS.len()); let Some(line) = self.lines.get_mut(id) else { log::warn!("Line {} not found", id); @@ -125,28 +122,55 @@ impl LinePool { let posef = helpers::transform_to_posef(&transform); line.maybe_line = Some(Line { - view: self.colors[color].clone(), + color, pose: posef, length: len, }); } - pub(super) fn present_xr<'a>( + pub(super) fn render( + &mut self, + graphics: Arc, + buf: &mut CommandBuffers, + ) -> anyhow::Result<()> { + for line in self.lines.values_mut() { + if let Some(inner) = line.maybe_line.as_mut() { + let tgt = line.swapchain.acquire_wait_image()?; + + let set0 = self + .pipeline + .uniform_buffer(0, COLORS[inner.color].to_vec())?; + + let pass = self + .pipeline + .create_pass_for_target(tgt.clone(), vec![set0])?; + + let mut cmd_buffer = + graphics.create_command_buffer(CommandBufferUsage::OneTimeSubmit)?; + cmd_buffer.begin_rendering(tgt)?; + cmd_buffer.run_ref(&pass)?; + cmd_buffer.end_rendering()?; + + buf.push(cmd_buffer.build()?); + } + } + + Ok(()) + } + + pub(super) fn present<'a>( &'a mut self, xr: &'a XrState, - command_buffer: &mut WlxCommandBuffer, ) -> anyhow::Result>> { let mut quads = Vec::new(); for line in self.lines.values_mut() { + line.swapchain.ensure_image_released()?; + if let Some(inner) = line.maybe_line.take() { let quad = xr::CompositionLayerQuad::new() .pose(inner.pose) - .sub_image(line.swapchain.acquire_present_release( - command_buffer, - inner.view, - 1.0, - )?) + .sub_image(line.swapchain.get_subimage()) .eye_visibility(xr::EyeVisibility::BOTH) .space(&xr.stage) .size(xr::Extent2Df { @@ -171,12 +195,12 @@ impl LinePool { } pub(super) struct Line { - pub(super) view: Arc, + pub(super) color: usize, pub(super) pose: xr::Posef, pub(super) length: f32, } struct LineContainer { - swapchain: SwapchainRenderData, + swapchain: WlxSwapchain, maybe_line: Option, } diff --git a/src/backend/openxr/mod.rs b/src/backend/openxr/mod.rs index 7386264..b6a45ac 100644 --- a/src/backend/openxr/mod.rs +++ b/src/backend/openxr/mod.rs @@ -12,7 +12,7 @@ use glam::{Affine3A, Vec3}; use libmonado::Monado; use openxr as xr; use skybox::create_skybox; -use vulkano::{command_buffer::CommandBufferUsage, Handle, VulkanObject}; +use vulkano::{Handle, VulkanObject}; use crate::{ backend::{ @@ -20,10 +20,10 @@ use crate::{ input::interact, notifications::NotificationManager, openxr::{lines::LinePool, overlay::OpenXrOverlayData}, - overlay::OverlayData, + overlay::{OverlayData, ShouldRender}, task::{SystemTask, TaskType}, }, - graphics::WlxGraphics, + graphics::{CommandBuffers, WlxGraphics}, overlays::{ toast::{Toast, ToastTopic}, watch::{watch_fade, WATCH_NAME}, @@ -364,23 +364,6 @@ pub fn openxr_run(running: Arc, show_by_default: bool) -> Result<(), }); } - let mut layers = vec![]; - let mut command_buffer = app_state - .graphics - .create_command_buffer(CommandBufferUsage::OneTimeSubmit)?; - - if !main_session_visible { - if let Some(skybox) = skybox.as_mut() { - for (idx, layer) in skybox - .present_xr(&xr_state, app_state.input_state.hmd, &mut command_buffer)? - .into_iter() - .enumerate() - { - layers.push((200.0 - 50.0 * (idx as f32), layer)); - } - } - } - #[cfg(feature = "wayvr")] if let Err(e) = crate::overlays::wayvr::tick_events::(&mut app_state, &mut overlays) @@ -388,7 +371,17 @@ pub fn openxr_run(running: Arc, show_by_default: bool) -> Result<(), log::error!("WayVR tick_events failed: {:?}", e); } + // Begin rendering + let mut buffers = CommandBuffers::default(); + + if !main_session_visible { + if let Some(skybox) = skybox.as_mut() { + skybox.render(&xr_state, &app_state, &mut buffers)?; + } + } + for o in overlays.iter_mut() { + o.data.cur_visible = false; if !o.state.want_visible { continue; } @@ -398,37 +391,85 @@ pub fn openxr_run(running: Arc, show_by_default: bool) -> Result<(), o.data.init = true; } - o.render(&mut app_state)?; + let should_render = match o.should_render(&mut app_state)? { + ShouldRender::Should => true, + ShouldRender::Can => o.data.last_alpha != o.state.alpha, + ShouldRender::Unable => false, //try show old image if exists + }; + if should_render { + if !o.ensure_swapchain(&app_state, &xr_state)? { + continue; + } + let tgt = o.data.swapchain.as_mut().unwrap().acquire_wait_image()?; // want + if !o.render(&mut app_state, tgt, &mut buffers, o.state.alpha)? { + o.data.swapchain.as_mut().unwrap().ensure_image_released()?; // want + continue; + } + o.data.last_alpha = o.state.alpha; + } else if o.data.swapchain.is_none() { + continue; + } + o.data.cur_visible = true; + } + + lines.render(app_state.graphics.clone(), &mut buffers)?; + + let future = buffers.execute_now(app_state.graphics.queue.clone())?; + if let Some(mut future) = future { + if let Err(e) = future.flush() { + return Err(BackendError::Fatal(e.into())); + }; + future.cleanup_finished(); + } + // End rendering + + // Layer composition + let mut layers = vec![]; + if !main_session_visible { + if let Some(skybox) = skybox.as_mut() { + for (idx, layer) in skybox + .present(&xr_state, &app_state)? + .into_iter() + .enumerate() + { + layers.push((200.0 - 50.0 * (idx as f32), layer)); + } + } + } + + for o in overlays.iter_mut() { + if !o.data.cur_visible { + continue; + } let dist_sq = (app_state.input_state.hmd.translation - o.state.transform.translation) .length_squared() + (100f32 - o.state.z_order as f32); - if !dist_sq.is_normal() { + o.data.swapchain.as_mut().unwrap().ensure_image_released()?; continue; } - - let maybe_layer = o.present_xr(&xr_state, &mut command_buffer)?; + let maybe_layer = o.present(&xr_state)?; if let CompositionLayer::None = maybe_layer { continue; } layers.push((dist_sq, maybe_layer)); } - for maybe_layer in lines.present_xr(&xr_state, &mut command_buffer)? { + for maybe_layer in lines.present(&xr_state)? { if let CompositionLayer::None = maybe_layer { continue; } layers.push((0.0, maybe_layer)); } + // End layer composition #[cfg(feature = "wayvr")] if let Some(wayvr) = &app_state.wayvr { wayvr.borrow_mut().data.tick_finish()?; } - command_buffer.build_and_execute_now()?; - + // Begin layer submit layers.sort_by(|a, b| b.0.total_cmp(&a.0)); let frame_ref = layers @@ -446,6 +487,7 @@ pub fn openxr_run(running: Arc, show_by_default: bool) -> Result<(), environment_blend_mode, &frame_ref, )?; + // End layer submit let removed_overlays = overlays.update(&mut app_state)?; for o in removed_overlays { diff --git a/src/backend/openxr/overlay.rs b/src/backend/openxr/overlay.rs index 9679d62..6135c9d 100644 --- a/src/backend/openxr/overlay.rs +++ b/src/backend/openxr/overlay.rs @@ -1,74 +1,72 @@ use glam::Vec3A; use openxr::{self as xr, CompositionLayerFlags}; -use std::{f32::consts::PI, sync::Arc}; +use std::f32::consts::PI; use xr::EyeVisibility; -use super::{helpers, swapchain::SwapchainRenderData, CompositionLayer, XrState}; +use super::{helpers, swapchain::WlxSwapchain, CompositionLayer, XrState}; use crate::{ backend::{ - openxr::swapchain::{create_swapchain_render_data, SwapchainOpts}, + openxr::swapchain::{create_swapchain, SwapchainOpts}, overlay::OverlayData, }, - graphics::WlxCommandBuffer, state::AppState, }; -use vulkano::image::view::ImageView; #[derive(Default)] pub struct OpenXrOverlayData { - last_view: Option>, last_visible: bool, - pub(super) swapchain: Option, + pub(super) swapchain: Option, pub(super) init: bool, + pub(super) cur_visible: bool, + pub(super) last_alpha: f32, } impl OverlayData { - pub(super) fn present_xr<'a>( + pub(super) fn ensure_swapchain<'a>( &'a mut self, + app: &AppState, xr: &'a XrState, - command_buffer: &mut WlxCommandBuffer, - ) -> anyhow::Result> { - if let Some(new_view) = self.view() { - self.data.last_view = Some(new_view); + ) -> anyhow::Result { + if self.data.swapchain.is_some() { + return Ok(true); } - let my_view = if let Some(view) = self.data.last_view.as_ref() { - view.clone() - } else { - log::warn!("{}: Will not show - image not ready", self.state.name); + let Some(meta) = self.frame_meta() else { + log::warn!( + "{}: swapchain cannot be created due to missing metadata", + self.state.name + ); + return Ok(false); + }; + + let extent = meta.extent; + self.data.swapchain = Some(create_swapchain( + xr, + app.graphics.clone(), + extent, + SwapchainOpts::new(), + )?); + Ok(true) + } + + pub(super) fn present<'a>( + &'a mut self, + xr: &'a XrState, + ) -> anyhow::Result> { + let Some(swapchain) = self.data.swapchain.as_mut() else { + log::warn!("{}: swapchain not ready", self.state.name); return Ok(CompositionLayer::None); }; + if !swapchain.ever_acquired { + log::warn!("{}: swapchain not rendered", self.state.name); + return Ok(CompositionLayer::None); + } + swapchain.ensure_image_released()?; - let frame_transform = self.frame_transform().unwrap(); // want panic - let extent = frame_transform.extent; + let sub_image = swapchain.get_subimage(); + let transform = self.state.transform * self.backend.frame_meta().unwrap().transform; // contract - let data = match self.data.swapchain { - Some(ref mut data) => data, - None => { - let srd = create_swapchain_render_data( - xr, - command_buffer.graphics.clone(), - extent, - SwapchainOpts::new(), - )?; - log::debug!( - "{}: Created swapchain {}x{}, {} images, {} MB", - self.state.name, - extent[0], - extent[1], - srd.images.len(), - extent[0] * extent[1] * 4 * srd.images.len() as u32 / 1024 / 1024 - ); - self.data.swapchain = Some(srd); - self.data.swapchain.as_mut().unwrap() //safe - } - }; - - let sub_image = data.acquire_present_release(command_buffer, my_view, self.state.alpha)?; - - let transform = self.state.transform * frame_transform.transform; - - let aspect_ratio = extent[1] as f32 / extent[0] as f32; + let aspect_ratio = swapchain.extent[1] as f32 / swapchain.extent[0] as f32; let (scale_x, scale_y) = if aspect_ratio < 1.0 { let major = transform.matrix3.col(0).length(); (major, major * aspect_ratio) diff --git a/src/backend/openxr/skybox.rs b/src/backend/openxr/skybox.rs index 9346507..8629ecb 100644 --- a/src/backend/openxr/skybox.rs +++ b/src/backend/openxr/skybox.rs @@ -1,25 +1,29 @@ use std::{f32::consts::PI, fs::File, sync::Arc}; -use glam::{Affine3A, Quat, Vec3A}; +use glam::{Quat, Vec3A}; use once_cell::sync::Lazy; use openxr::{self as xr, CompositionLayerFlags}; -use vulkano::{command_buffer::CommandBufferUsage, image::view::ImageView}; +use vulkano::{ + command_buffer::CommandBufferUsage, image::view::ImageView, + pipeline::graphics::color_blend::AttachmentBlend, +}; use crate::{ backend::openxr::{helpers::translation_rotation_to_posef, swapchain::SwapchainOpts}, config_io, - graphics::{dds::WlxCommandBufferDds, format_is_srgb, WlxCommandBuffer}, + graphics::{dds::WlxCommandBufferDds, CommandBuffers, SWAPCHAIN_FORMAT}, state::AppState, }; use super::{ - swapchain::{create_swapchain_render_data, SwapchainRenderData}, + swapchain::{create_swapchain, WlxSwapchain}, CompositionLayer, XrState, }; pub(super) struct Skybox { view: Arc, - srd: Option<(SwapchainRenderData, SwapchainRenderData)>, + sky: Option, + grid: Option, } impl Skybox { @@ -66,53 +70,127 @@ impl Skybox { let view = ImageView::new_default(maybe_image.unwrap())?; // safe unwrap - Ok(Self { view, srd: None }) + Ok(Self { + view, + sky: None, + grid: None, + }) } - pub(super) fn present_xr<'a>( + fn prepare_sky<'a>( &'a mut self, xr: &'a XrState, - hmd: Affine3A, - command_buffer: &mut WlxCommandBuffer, - ) -> anyhow::Result>> { - let (sky_image, grid_image) = if let Some((ref mut srd_sky, ref mut srd_grid)) = self.srd { - (srd_sky.present_last()?, srd_grid.present_last()?) - } else { - log::debug!("Render skybox."); + app: &AppState, + buf: &mut CommandBuffers, + ) -> anyhow::Result<()> { + if self.sky.is_some() { + return Ok(()); + } + let opts = SwapchainOpts::new().immutable(); - let mut opts = SwapchainOpts::new().immutable(); - opts.srgb = format_is_srgb(self.view.image().format()); - - let srd_sky = create_swapchain_render_data( - xr, - command_buffer.graphics.clone(), - self.view.image().extent(), - opts, - )?; - - let srd_grid = create_swapchain_render_data( - xr, - command_buffer.graphics.clone(), - [1024, 1024, 1], - SwapchainOpts::new().immutable().grid(), - )?; - - self.srd = Some((srd_sky, srd_grid)); - - let (srd_sky, srd_grid) = self.srd.as_mut().unwrap(); // safe unwrap - - ( - srd_sky.acquire_present_release(command_buffer, self.view.clone(), 1.0)?, - srd_grid.acquire_compute_release(command_buffer)?, - ) + let Ok(shaders) = app.graphics.shared_shaders.read() else { + anyhow::bail!("Failed to lock shared shaders for reading"); }; + let extent = self.view.image().extent(); + let mut swapchain = create_swapchain(xr, app.graphics.clone(), extent, opts)?; + let tgt = swapchain.acquire_wait_image()?; + let pipeline = app.graphics.create_pipeline( + shaders.get("vert_common").unwrap().clone(), // want panic + shaders.get("frag_srgb").unwrap().clone(), // want panic + SWAPCHAIN_FORMAT, + None, + )?; + + let set0 = + pipeline.uniform_sampler(0, self.view.clone(), app.graphics.texture_filtering)?; + + let set1 = pipeline.uniform_buffer(1, vec![1f32])?; + + let pass = pipeline.create_pass_for_target(tgt.clone(), vec![set0, set1])?; + + let mut cmd_buffer = app + .graphics + .create_command_buffer(CommandBufferUsage::OneTimeSubmit)?; + cmd_buffer.begin_rendering(tgt)?; + cmd_buffer.run_ref(&pass)?; + cmd_buffer.end_rendering()?; + + buf.push(cmd_buffer.build()?); + + self.sky = Some(swapchain); + Ok(()) + } + + fn prepare_grid<'a>( + &'a mut self, + xr: &'a XrState, + app: &AppState, + buf: &mut CommandBuffers, + ) -> anyhow::Result<()> { + if self.grid.is_some() { + return Ok(()); + } + let Ok(shaders) = app.graphics.shared_shaders.read() else { + anyhow::bail!("Failed to lock shared shaders for reading"); + }; + + let extent = [1024, 1024, 1]; + let mut swapchain = create_swapchain( + xr, + app.graphics.clone(), + extent, + SwapchainOpts::new().immutable(), + )?; + let pipeline = app.graphics.create_pipeline( + shaders.get("vert_common").unwrap().clone(), // want panic + shaders.get("frag_grid").unwrap().clone(), // want panic + SWAPCHAIN_FORMAT, + Some(AttachmentBlend::alpha()), + )?; + + let tgt = swapchain.acquire_wait_image()?; + let pass = pipeline.create_pass_for_target(tgt.clone(), vec![])?; + + let mut cmd_buffer = app + .graphics + .create_command_buffer(CommandBufferUsage::OneTimeSubmit)?; + cmd_buffer.begin_rendering(tgt)?; + cmd_buffer.run_ref(&pass)?; + cmd_buffer.end_rendering()?; + + buf.push(cmd_buffer.build()?); + + self.grid = Some(swapchain); + Ok(()) + } + + pub(super) fn render( + &mut self, + xr: &XrState, + app: &AppState, + buf: &mut CommandBuffers, + ) -> anyhow::Result<()> { + self.prepare_sky(xr, app, buf)?; + self.prepare_grid(xr, app, buf)?; + Ok(()) + } + + pub(super) fn present<'a>( + &'a mut self, + xr: &'a XrState, + app: &AppState, + ) -> anyhow::Result>> { + static GRID_POSE: Lazy = Lazy::new(|| { + translation_rotation_to_posef(Vec3A::ZERO, Quat::from_rotation_x(PI * -0.5)) + }); + let pose = xr::Posef { orientation: xr::Quaternionf::IDENTITY, position: xr::Vector3f { - x: hmd.translation.x, - y: hmd.translation.y, - z: hmd.translation.z, + x: app.input_state.hmd.translation.x, + y: app.input_state.hmd.translation.y, + z: app.input_state.hmd.translation.z, }, }; @@ -121,25 +199,20 @@ impl Skybox { const HI_VERT_ANGLE: f32 = 0.5 * PI; const LO_VERT_ANGLE: f32 = -0.5 * PI; - let mut layers = vec![]; + self.sky.as_mut().unwrap().ensure_image_released()?; let sky = xr::CompositionLayerEquirect2KHR::new() .layer_flags(CompositionLayerFlags::BLEND_TEXTURE_SOURCE_ALPHA) .pose(pose) .radius(10.0) - .sub_image(sky_image) + .sub_image(self.sky.as_ref().unwrap().get_subimage()) .eye_visibility(xr::EyeVisibility::BOTH) .space(&xr.stage) .central_horizontal_angle(HORIZ_ANGLE) .upper_vertical_angle(HI_VERT_ANGLE) .lower_vertical_angle(LO_VERT_ANGLE); - layers.push(CompositionLayer::Equirect2(sky)); - - static GRID_POSE: Lazy = Lazy::new(|| { - translation_rotation_to_posef(Vec3A::ZERO, Quat::from_rotation_x(PI * -0.5)) - }); - + self.grid.as_mut().unwrap().ensure_image_released()?; let grid = xr::CompositionLayerQuad::new() .layer_flags(CompositionLayerFlags::BLEND_TEXTURE_SOURCE_ALPHA) .pose(*GRID_POSE) @@ -147,13 +220,14 @@ impl Skybox { width: 10.0, height: 10.0, }) - .sub_image(grid_image) + .sub_image(self.grid.as_ref().unwrap().get_subimage()) .eye_visibility(xr::EyeVisibility::BOTH) .space(&xr.stage); - layers.push(CompositionLayer::Quad(grid)); - - Ok(layers) + Ok(vec![ + CompositionLayer::Equirect2(sky), + CompositionLayer::Quad(grid), + ]) } } diff --git a/src/backend/openxr/swapchain.rs b/src/backend/openxr/swapchain.rs index 4075553..7cf6517 100644 --- a/src/backend/openxr/swapchain.rs +++ b/src/backend/openxr/swapchain.rs @@ -1,26 +1,21 @@ use std::sync::Arc; -use anyhow::bail; use ash::vk; use openxr as xr; use smallvec::SmallVec; use vulkano::{ - format::Format, image::{sys::RawImage, view::ImageView, ImageCreateInfo, ImageUsage}, - pipeline::graphics::color_blend::AttachmentBlend, Handle, }; -use crate::graphics::{WlxCommandBuffer, WlxGraphics, WlxPipeline, WlxPipelineDynamic}; +use crate::graphics::{WlxGraphics, SWAPCHAIN_FORMAT}; use super::XrState; #[derive(Default)] pub(super) struct SwapchainOpts { pub immutable: bool, - pub srgb: bool, - pub grid: bool, } impl SwapchainOpts { @@ -31,22 +26,14 @@ impl SwapchainOpts { self.immutable = true; self } - pub fn srgb(mut self) -> Self { - self.srgb = true; - self - } - pub fn grid(mut self) -> Self { - self.grid = true; - self - } } -pub(super) fn create_swapchain_render_data( +pub(super) fn create_swapchain( xr: &XrState, graphics: Arc, extent: [u32; 3], opts: SwapchainOpts, -) -> anyhow::Result { +) -> anyhow::Result { let create_flags = if opts.immutable { xr::SwapchainCreateFlags::STATIC_IMAGE } else { @@ -56,7 +43,7 @@ pub(super) fn create_swapchain_render_data( let swapchain = xr.session.create_swapchain(&xr::SwapchainCreateInfo { create_flags, usage_flags: xr::SwapchainUsageFlags::COLOR_ATTACHMENT | xr::SwapchainUsageFlags::SAMPLED, - format: Format::R8G8B8A8_SRGB as _, + format: SWAPCHAIN_FORMAT as _, sample_count: 1, width: extent[0], height: extent[1], @@ -65,28 +52,6 @@ pub(super) fn create_swapchain_render_data( mip_count: 1, })?; - let Ok(shaders) = graphics.shared_shaders.read() else { - bail!("Failed to lock shared shaders for reading"); - }; - - let image_fmt = if opts.srgb { - Format::R8G8B8A8_SRGB - } else { - Format::R8G8B8A8_UNORM - }; - - let frag_shader = if opts.grid { - "frag_grid" - } else { - "frag_swapchain" - }; - let pipeline = graphics.create_pipeline_dynamic( - shaders.get("vert_common").unwrap().clone(), // want panic - shaders.get(frag_shader).unwrap().clone(), // want panic - image_fmt, - Some(AttachmentBlend::alpha()), - )?; - let images = swapchain .enumerate_images()? .into_iter() @@ -98,7 +63,7 @@ pub(super) fn create_swapchain_render_data( graphics.device.clone(), vk_image, ImageCreateInfo { - format: image_fmt, // actually SRGB but we lie + format: SWAPCHAIN_FORMAT as _, extent, usage: ImageUsage::COLOR_ATTACHMENT, ..Default::default() @@ -111,118 +76,51 @@ pub(super) fn create_swapchain_render_data( }) .collect::; 4]>>>()?; - Ok(SwapchainRenderData { + Ok(WlxSwapchain { + acquired: false, + ever_acquired: false, swapchain, - pipeline, images, extent, - target_extent: [0, 0, 0], }) } -pub(super) struct SwapchainRenderData { +pub(super) struct WlxSwapchain { + acquired: bool, + pub(super) ever_acquired: bool, pub(super) swapchain: xr::Swapchain, - pub(super) pipeline: Arc>, pub(super) extent: [u32; 3], - pub(super) target_extent: [u32; 3], pub(super) images: SmallVec<[Arc; 4]>, } -impl SwapchainRenderData { - pub(super) fn acquire_present_release( - &mut self, - command_buffer: &mut WlxCommandBuffer, - view: Arc, - alpha: f32, - ) -> anyhow::Result> { +impl WlxSwapchain { + pub(super) fn acquire_wait_image(&mut self) -> anyhow::Result> { let idx = self.swapchain.acquire_image()? as usize; self.swapchain.wait_image(xr::Duration::INFINITE)?; - - let render_target = &mut self.images[idx]; - command_buffer.begin_rendering(render_target.clone())?; - - self.target_extent = render_target.image().extent(); - - let set0 = self.pipeline.uniform_sampler( - 0, - view.clone(), - command_buffer.graphics.texture_filtering, - )?; - - let set1 = self.pipeline.uniform_buffer(1, vec![alpha])?; - - let pass = self.pipeline.create_pass( - [self.target_extent[0] as _, self.target_extent[1] as _], - command_buffer.graphics.quad_verts.clone(), - command_buffer.graphics.quad_indices.clone(), - vec![set0, set1], - )?; - command_buffer.run_ref(&pass)?; - command_buffer.end_rendering()?; - - self.swapchain.release_image()?; - - Ok(xr::SwapchainSubImage::new() - .swapchain(&self.swapchain) - .image_rect(xr::Rect2Di { - offset: xr::Offset2Di { x: 0, y: 0 }, - extent: xr::Extent2Di { - width: self.target_extent[0] as _, - height: self.target_extent[1] as _, - }, - }) - .image_array_index(0)) + self.ever_acquired = true; + self.acquired = true; + Ok(self.images[idx].clone()) } - pub(super) fn acquire_compute_release( - &mut self, - command_buffer: &mut WlxCommandBuffer, - ) -> anyhow::Result> { - let idx = self.swapchain.acquire_image()? as usize; - self.swapchain.wait_image(xr::Duration::INFINITE)?; - - let render_target = &mut self.images[idx]; - command_buffer.begin_rendering(render_target.clone())?; - - self.target_extent = render_target.image().extent(); - - let pass = self.pipeline.create_pass( - [self.target_extent[0] as _, self.target_extent[1] as _], - command_buffer.graphics.quad_verts.clone(), - command_buffer.graphics.quad_indices.clone(), - vec![], - )?; - command_buffer.run_ref(&pass)?; - command_buffer.end_rendering()?; - - self.swapchain.release_image()?; - - Ok(xr::SwapchainSubImage::new() - .swapchain(&self.swapchain) - .image_rect(xr::Rect2Di { - offset: xr::Offset2Di { x: 0, y: 0 }, - extent: xr::Extent2Di { - width: self.target_extent[0] as _, - height: self.target_extent[1] as _, - }, - }) - .image_array_index(0)) + pub(super) fn ensure_image_released(&mut self) -> anyhow::Result<()> { + if self.acquired { + self.swapchain.release_image()?; + self.acquired = false; + } + Ok(()) } - pub(super) fn present_last(&self) -> anyhow::Result> { - debug_assert!( - self.target_extent[0] * self.target_extent[1] != 0, - "present_last: target_extent zero" - ); - Ok(xr::SwapchainSubImage::new() + pub(super) fn get_subimage(&self) -> xr::SwapchainSubImage { + debug_assert!(self.ever_acquired, "swapchain was never acquired!"); + xr::SwapchainSubImage::new() .swapchain(&self.swapchain) .image_rect(xr::Rect2Di { offset: xr::Offset2Di { x: 0, y: 0 }, extent: xr::Extent2Di { - width: self.target_extent[0] as _, - height: self.target_extent[1] as _, + width: self.extent[0] as _, + height: self.extent[1] as _, }, }) - .image_array_index(0)) + .image_array_index(0) } } diff --git a/src/backend/overlay.rs b/src/backend/overlay.rs index a9516e5..13d410e 100644 --- a/src/backend/overlay.rs +++ b/src/backend/overlay.rs @@ -9,10 +9,11 @@ use std::{ use anyhow::Ok; use glam::{Affine2, Affine3A, Mat3A, Quat, Vec2, Vec3, Vec3A}; use serde::Deserialize; -use vulkano::image::view::ImageView; +use vulkano::{format::Format, image::view::ImageView}; use crate::{ config::AStrMapExt, + graphics::CommandBuffers, state::{AppState, KeyboardFocus}, }; @@ -234,14 +235,20 @@ where } self.backend.init(app) } - pub fn render(&mut self, app: &mut AppState) -> anyhow::Result<()> { - self.backend.render(app) + pub fn should_render(&mut self, app: &mut AppState) -> anyhow::Result { + self.backend.should_render(app) } - pub fn view(&mut self) -> Option> { - self.backend.view() + pub fn render( + &mut self, + app: &mut AppState, + tgt: Arc, + buf: &mut CommandBuffers, + alpha: f32, + ) -> anyhow::Result { + self.backend.render(app, tgt, buf, alpha) } - pub fn frame_transform(&mut self) -> Option { - self.backend.frame_transform() + pub fn frame_meta(&mut self) -> Option { + self.backend.frame_meta() } pub fn set_visible(&mut self, app: &mut AppState, visible: bool) -> anyhow::Result<()> { let old_visible = self.state.want_visible; @@ -257,10 +264,17 @@ where } } -#[derive(Default)] -pub struct FrameTransform { +#[derive(Default, Clone, Copy)] +pub struct FrameMeta { pub extent: [u32; 3], pub transform: Affine3A, + pub format: Format, +} + +pub enum ShouldRender { + Should, + Can, + Unable, } pub trait OverlayRenderer { @@ -268,15 +282,24 @@ pub trait OverlayRenderer { fn init(&mut self, app: &mut AppState) -> anyhow::Result<()>; fn pause(&mut self, app: &mut AppState) -> anyhow::Result<()>; fn resume(&mut self, app: &mut AppState) -> anyhow::Result<()>; + /// Called when the presentation layer is ready to present a new frame - fn render(&mut self, app: &mut AppState) -> anyhow::Result<()>; - /// Called to retrieve the current image to be displayed - fn view(&mut self) -> Option>; + fn should_render(&mut self, app: &mut AppState) -> anyhow::Result; + + /// Called when the contents need to be rendered to the swapchain + fn render( + &mut self, + app: &mut AppState, + tgt: Arc, + buf: &mut CommandBuffers, + alpha: f32, + ) -> anyhow::Result; + /// Called to retrieve the effective extent of the image /// Used for creating swapchains. /// - /// Muse not be None if view() is also not None - fn frame_transform(&mut self) -> Option; + /// Must be true if should_render was also true on the same frame. + fn frame_meta(&mut self) -> Option; } pub struct FallbackRenderer; @@ -291,13 +314,19 @@ impl OverlayRenderer for FallbackRenderer { fn resume(&mut self, _app: &mut AppState) -> anyhow::Result<()> { Ok(()) } - fn render(&mut self, _app: &mut AppState) -> anyhow::Result<()> { - Ok(()) + fn should_render(&mut self, _app: &mut AppState) -> anyhow::Result { + Ok(ShouldRender::Unable) } - fn view(&mut self) -> Option> { - None + fn render( + &mut self, + _app: &mut AppState, + _tgt: Arc, + _buf: &mut CommandBuffers, + _alpha: f32, + ) -> anyhow::Result { + Ok(false) } - fn frame_transform(&mut self) -> Option { + fn frame_meta(&mut self) -> Option { None } } @@ -348,16 +377,23 @@ impl OverlayRenderer for SplitOverlayBackend { fn resume(&mut self, app: &mut AppState) -> anyhow::Result<()> { self.renderer.resume(app) } - fn render(&mut self, app: &mut AppState) -> anyhow::Result<()> { - self.renderer.render(app) + fn should_render(&mut self, app: &mut AppState) -> anyhow::Result { + self.renderer.should_render(app) } - fn view(&mut self) -> Option> { - self.renderer.view() + fn render( + &mut self, + app: &mut AppState, + tgt: Arc, + buf: &mut CommandBuffers, + alpha: f32, + ) -> anyhow::Result { + self.renderer.render(app, tgt, buf, alpha) } - fn frame_transform(&mut self) -> Option { - self.renderer.frame_transform() + fn frame_meta(&mut self) -> Option { + self.renderer.frame_meta() } } + impl InteractionHandler for SplitOverlayBackend { fn on_left(&mut self, app: &mut AppState, pointer: usize) { self.interaction.on_left(app, pointer); diff --git a/src/backend/uidev/mod.rs b/src/backend/uidev/mod.rs index d23150b..c3e355e 100644 --- a/src/backend/uidev/mod.rs +++ b/src/backend/uidev/mod.rs @@ -1,7 +1,6 @@ use std::sync::Arc; use vulkano::{ - command_buffer::CommandBufferUsage, image::{view::ImageView, ImageUsage}, swapchain::{ acquire_next_image, Surface, Swapchain, SwapchainCreateInfo, SwapchainPresentInfo, @@ -19,7 +18,7 @@ use winit::{ use crate::{ config::load_custom_ui, config_io, - graphics::{DynamicPass, DynamicPipeline, WlxGraphics, BLEND_ALPHA}, + graphics::{CommandBuffers, WlxGraphics}, gui::{ canvas::Canvas, modular::{modular_canvas, ModularData}, @@ -37,8 +36,6 @@ static LAST_SIZE: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::n struct PreviewState { canvas: Canvas<(), ModularData>, - pipeline: Arc, - pass: DynamicPass, swapchain: Arc, images: Vec>, } @@ -63,7 +60,7 @@ impl PreviewState { 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, + ((config.size[1] as u64) << 32) | config.size[0] as u64, std::sync::atomic::Ordering::Relaxed, ); } @@ -75,34 +72,9 @@ impl PreviewState { 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(), pipeline.graphics.texture_filtering) - .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, }) @@ -132,6 +104,7 @@ pub fn uidev_run(panel_name: &str) -> anyhow::Result<()> { let mut recreate = false; let mut last_draw = std::time::Instant::now(); + #[allow(deprecated)] event_loop.run(move |event, elwt| { elwt.set_control_flow(ControlFlow::Poll); @@ -182,26 +155,21 @@ pub fn uidev_run(panel_name: &str) -> anyhow::Result<()> { Err(e) => panic!("failed to acquire next image: {e}"), }; - if let Err(e) = preview.canvas.render(&mut state) { + let mut canvas_cmd_buf = CommandBuffers::default(); + let tgt = preview.images[image_index as usize].clone(); + + if let Err(e) = preview + .canvas + .render(&mut state, tgt, &mut canvas_cmd_buf, 1.0) + { 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) + canvas_cmd_buf + .execute_after(state.graphics.queue.clone(), Box::new(acquire_future)) .unwrap() .then_swapchain_present( graphics.queue.clone(), diff --git a/src/config_io.rs b/src/config_io.rs index 48cfc69..200b1a4 100644 --- a/src/config_io.rs +++ b/src/config_io.rs @@ -55,9 +55,5 @@ pub fn load(filename: &str) -> Option { let path = get_config_file_path(filename); log::info!("Loading config: {}", path.to_string_lossy()); - if let Ok(data) = std::fs::read_to_string(path) { - Some(data) - } else { - None - } + std::fs::read_to_string(path).ok() } diff --git a/src/graphics/mod.rs b/src/graphics/mod.rs index a03b280..87132df 100644 --- a/src/graphics/mod.rs +++ b/src/graphics/mod.rs @@ -20,12 +20,6 @@ use {ash::vk, std::os::raw::c_void}; pub type Vert2Buf = Subbuffer<[Vert2Uv]>; pub type IndexBuf = Subbuffer<[u16]>; -pub type LegacyPipeline = WlxPipeline; -pub type DynamicPipeline = WlxPipeline; - -pub type LegacyPass = WlxPass; -pub type DynamicPass = WlxPass; - use vulkano::{ buffer::{ allocator::{SubbufferAllocator, SubbufferAllocatorCreateInfo}, @@ -35,10 +29,9 @@ use vulkano::{ allocator::{StandardCommandBufferAllocator, StandardCommandBufferAllocatorCreateInfo}, sys::{CommandBufferBeginInfo, RawRecordingCommandBuffer}, CommandBuffer, CommandBufferExecFuture, CommandBufferInheritanceInfo, - CommandBufferInheritanceRenderPassInfo, CommandBufferInheritanceRenderPassType, - CommandBufferInheritanceRenderingInfo, CommandBufferLevel, CommandBufferUsage, - CopyBufferToImageInfo, RecordingCommandBuffer, RenderPassBeginInfo, - RenderingAttachmentInfo, RenderingInfo, SubpassBeginInfo, SubpassContents, SubpassEndInfo, + CommandBufferInheritanceRenderPassType, CommandBufferInheritanceRenderingInfo, + CommandBufferLevel, CommandBufferUsage, CopyBufferToImageInfo, RecordingCommandBuffer, + RenderingAttachmentInfo, RenderingInfo, SubpassContents, }, descriptor_set::{ allocator::StandardDescriptorSetAllocator, DescriptorSet, WriteDescriptorSet, @@ -52,8 +45,7 @@ use vulkano::{ sampler::{Filter, Sampler, SamplerAddressMode, SamplerCreateInfo}, sys::RawImage, view::ImageView, - Image, ImageCreateInfo, ImageLayout, ImageTiling, ImageType, ImageUsage, SampleCount, - SubresourceLayout, + Image, ImageCreateInfo, ImageLayout, ImageTiling, ImageType, ImageUsage, SubresourceLayout, }, instance::{Instance, InstanceCreateInfo, InstanceExtensions}, memory::{ @@ -80,11 +72,7 @@ use vulkano::{ layout::PipelineDescriptorSetLayoutCreateInfo, DynamicState, GraphicsPipeline, Pipeline, PipelineBindPoint, PipelineLayout, }, - render_pass::{ - AttachmentDescription, AttachmentLoadOp, AttachmentReference, AttachmentStoreOp, - Framebuffer, FramebufferCreateInfo, RenderPass, RenderPassCreateInfo, Subpass, - SubpassDescription, - }, + render_pass::{AttachmentLoadOp, AttachmentStoreOp}, shader::ShaderModule, sync::{ fence::Fence, future::NowFuture, AccessFlags, DependencyInfo, GpuFuture, @@ -109,6 +97,8 @@ pub struct Vert2Uv { pub in_uv: [f32; 2], } +pub const SWAPCHAIN_FORMAT: Format = Format::R8G8B8A8_SRGB; + pub const INDICES: [u16; 6] = [2, 1, 0, 1, 2, 3]; pub const BLEND_ALPHA: AttachmentBlend = AttachmentBlend { @@ -546,6 +536,7 @@ impl WlxGraphics { Ok(Arc::new(me)) } + #[allow(clippy::type_complexity)] #[cfg(feature = "uidev")] pub fn new_window() -> anyhow::Result<( Arc, @@ -572,6 +563,7 @@ impl WlxGraphics { }, )?; + #[allow(deprecated)] let window = Arc::new( event_loop .create_window(Window::default_attributes()) @@ -925,54 +917,12 @@ impl WlxGraphics { pub fn create_pipeline( self: &Arc, - render_target: Arc, vert: Arc, frag: Arc, format: Format, blend: Option, - ) -> anyhow::Result> { - Ok(Arc::new(LegacyPipeline::new( - render_target, - self.clone(), - vert, - frag, - format, - blend, - )?)) - } - - #[allow(clippy::too_many_arguments)] - pub fn create_pipeline_with_layouts( - self: &Arc, - render_target: Arc, - vert: Arc, - frag: Arc, - format: Format, - blend: Option, - initial_layout: ImageLayout, - final_layout: ImageLayout, - ) -> anyhow::Result> { - Ok(Arc::new(LegacyPipeline::new_with_layout( - render_target, - self.clone(), - vert, - frag, - format, - blend, - initial_layout, - final_layout, - )?)) - } - - #[allow(dead_code)] - pub fn create_pipeline_dynamic( - self: &Arc, - vert: Arc, - frag: Arc, - format: Format, - blend: Option, - ) -> anyhow::Result> { - Ok(Arc::new(DynamicPipeline::new( + ) -> anyhow::Result> { + Ok(Arc::new(WlxPipeline::new( self.clone(), vert, frag, @@ -1001,7 +951,6 @@ impl WlxGraphics { }) } - #[allow(dead_code)] pub fn transition_layout( &self, image: Arc, @@ -1066,22 +1015,7 @@ pub struct WlxCommandBuffer { pub command_buffer: RecordingCommandBuffer, } -#[allow(dead_code)] impl WlxCommandBuffer { - pub fn begin_render_pass(&mut self, pipeline: &LegacyPipeline) -> anyhow::Result<()> { - self.command_buffer.begin_render_pass( - RenderPassBeginInfo { - clear_values: vec![Some([0.0, 0.0, 0.0, 0.0].into())], - ..RenderPassBeginInfo::framebuffer(pipeline.data.framebuffer.clone()) - }, - SubpassBeginInfo { - contents: SubpassContents::SecondaryCommandBuffers, - ..Default::default() - }, - )?; - Ok(()) - } - pub fn begin_rendering(&mut self, render_target: Arc) -> anyhow::Result<()> { self.command_buffer.begin_rendering(RenderingInfo { contents: SubpassContents::SecondaryCommandBuffers, @@ -1096,7 +1030,7 @@ impl WlxCommandBuffer { Ok(()) } - pub fn run_ref(&mut self, pass: &WlxPass) -> anyhow::Result<()> { + pub fn run_ref(&mut self, pass: &WlxPass) -> anyhow::Result<()> { self.command_buffer .execute_commands(pass.command_buffer.clone())?; Ok(()) @@ -1149,12 +1083,6 @@ impl WlxCommandBuffer { Ok(image) } - pub fn end_render_pass(&mut self) -> anyhow::Result<()> { - self.command_buffer - .end_render_pass(SubpassEndInfo::default())?; - Ok(()) - } - pub fn end_rendering(&mut self) -> anyhow::Result<()> { self.command_buffer.end_rendering()?; Ok(()) @@ -1177,22 +1105,13 @@ impl WlxCommandBuffer { } } -pub struct WlxPipelineDynamic {} - -pub struct WlxPipelineLegacy { - pub render_pass: Arc, - pub framebuffer: Arc, -} - -pub struct WlxPipeline { +pub struct WlxPipeline { pub graphics: Arc, pub pipeline: Arc, pub format: Format, - pub data: D, } -#[allow(dead_code)] -impl WlxPipeline { +impl WlxPipeline { fn new( graphics: Arc, vert: Arc, @@ -1248,7 +1167,6 @@ impl WlxPipeline { graphics, pipeline, format, - data: WlxPipelineDynamic {}, }) } pub fn create_pass( @@ -1257,8 +1175,8 @@ impl WlxPipeline { vertex_buffer: Vert2Buf, index_buffer: IndexBuf, descriptor_sets: Vec>, - ) -> anyhow::Result { - DynamicPass::new( + ) -> anyhow::Result { + WlxPass::new( self.clone(), dimensions, vertex_buffer, @@ -1266,178 +1184,24 @@ impl WlxPipeline { descriptor_sets, ) } -} -impl WlxPipeline { - fn new( - render_target: Arc, - graphics: Arc, - vert: Arc, - frag: Arc, - format: Format, - blend: Option, - ) -> anyhow::Result { - let render_pass = vulkano::single_pass_renderpass!( - graphics.device.clone(), - attachments: { - color: { - format: format, - samples: 1, - load_op: Clear, - store_op: Store, - }, - }, - pass: { - color: [color], - depth_stencil: {}, - }, - )?; - - Self::new_from_pass( - render_target, - render_pass, - graphics, - vert, - frag, - format, - blend, - ) - } - - #[allow(clippy::too_many_arguments)] - fn new_with_layout( - render_target: Arc, - graphics: Arc, - vert: Arc, - frag: Arc, - format: Format, - blend: Option, - initial_layout: ImageLayout, - final_layout: ImageLayout, - ) -> anyhow::Result { - let render_pass_description = RenderPassCreateInfo { - attachments: vec![AttachmentDescription { - format, - samples: SampleCount::Sample1, - load_op: AttachmentLoadOp::Clear, - store_op: AttachmentStoreOp::Store, - initial_layout, - final_layout, - ..Default::default() - }], - subpasses: vec![SubpassDescription { - color_attachments: vec![Some(AttachmentReference { - attachment: 0, - layout: ImageLayout::ColorAttachmentOptimal, - ..Default::default() - })], - ..Default::default() - }], - ..Default::default() - }; - - let render_pass = RenderPass::new(graphics.device.clone(), render_pass_description)?; - - Self::new_from_pass( - render_target, - render_pass, - graphics, - vert, - frag, - format, - blend, - ) - } - - fn new_from_pass( - render_target: Arc, - render_pass: Arc, - graphics: Arc, - vert: Arc, - frag: Arc, - format: Format, - blend: Option, - ) -> anyhow::Result { - let vep = vert.entry_point("main").unwrap(); // want panic - let fep = frag.entry_point("main").unwrap(); // want panic - - let vertex_input_state = Vert2Uv::per_vertex().definition(&vep.info().input_interface)?; - - let stages = smallvec![ - vulkano::pipeline::PipelineShaderStageCreateInfo::new(vep), - vulkano::pipeline::PipelineShaderStageCreateInfo::new(fep), - ]; - - let layout = PipelineLayout::new( - graphics.device.clone(), - PipelineDescriptorSetLayoutCreateInfo::from_stages(&stages) - .into_pipeline_layout_create_info(graphics.device.clone())?, - )?; - - let framebuffer = Framebuffer::new( - render_pass.clone(), - FramebufferCreateInfo { - attachments: vec![render_target.clone()], - ..Default::default() - }, - )?; - - let pipeline = GraphicsPipeline::new( - graphics.device.clone(), - None, - GraphicsPipelineCreateInfo { - stages, - vertex_input_state: Some(vertex_input_state), - input_assembly_state: Some(InputAssemblyState::default()), - viewport_state: Some(ViewportState::default()), - color_blend_state: Some(ColorBlendState { - attachments: vec![ColorBlendAttachmentState { - blend, - ..Default::default() - }], - ..Default::default() - }), - rasterization_state: Some(RasterizationState::default()), - multisample_state: Some(MultisampleState::default()), - dynamic_state: [DynamicState::Viewport].into_iter().collect(), - subpass: Some( - Subpass::from(render_pass.clone(), 0) - .ok_or_else(|| anyhow!("Failed to create subpass"))? - .into(), - ), - ..GraphicsPipelineCreateInfo::layout(layout) - }, - )?; - - Ok(Self { - graphics, - pipeline, - format, - data: WlxPipelineLegacy { - render_pass, - framebuffer, - }, - }) - } - - pub fn create_pass( + pub fn create_pass_for_target( self: &Arc, - dimensions: [f32; 2], - vertex_buffer: Vert2Buf, - index_buffer: IndexBuf, + tgt: Arc, descriptor_sets: Vec>, - ) -> anyhow::Result { - LegacyPass::new( + ) -> anyhow::Result { + let extent = tgt.image().extent(); + WlxPass::new( self.clone(), - dimensions, - vertex_buffer, - index_buffer, + [extent[0] as _, extent[1] as _], + self.graphics.quad_verts.clone(), + self.graphics.quad_indices.clone(), descriptor_sets, ) } } -impl WlxPipeline { +impl WlxPipeline { pub fn inner(&self) -> Arc { self.pipeline.clone() } @@ -1498,78 +1262,13 @@ impl WlxPipeline { } } -#[allow(dead_code)] -pub struct WlxPass { - pipeline: Arc>, - vertex_buffer: Vert2Buf, - index_buffer: IndexBuf, - descriptor_sets: Vec>, +pub struct WlxPass { pub command_buffer: Arc, } -impl WlxPass { +impl WlxPass { fn new( - pipeline: Arc, - dimensions: [f32; 2], - vertex_buffer: Vert2Buf, - index_buffer: IndexBuf, - descriptor_sets: Vec>, - ) -> anyhow::Result { - let viewport = Viewport { - offset: [0.0, 0.0], - extent: dimensions, - depth_range: 0.0..=1.0, - }; - - let pipeline_inner = pipeline.inner().clone(); - let mut command_buffer = RecordingCommandBuffer::new( - pipeline.graphics.command_buffer_allocator.clone(), - pipeline.graphics.queue.queue_family_index(), - CommandBufferLevel::Secondary, - CommandBufferBeginInfo { - usage: CommandBufferUsage::MultipleSubmit, - inheritance_info: Some(CommandBufferInheritanceInfo { - render_pass: Some(CommandBufferInheritanceRenderPassType::BeginRenderPass( - CommandBufferInheritanceRenderPassInfo { - subpass: Subpass::from(pipeline.data.render_pass.clone(), 0) - .ok_or_else(|| anyhow!("Failed to get subpass"))?, - framebuffer: None, - }, - )), - ..Default::default() - }), - ..Default::default() - }, - )?; - - unsafe { - command_buffer - .set_viewport(0, smallvec![viewport])? - .bind_pipeline_graphics(pipeline_inner)? - .bind_descriptor_sets( - PipelineBindPoint::Graphics, - pipeline.inner().layout().clone(), - 0, - descriptor_sets.clone(), - )? - .bind_vertex_buffers(0, vertex_buffer.clone())? - .bind_index_buffer(index_buffer.clone())? - .draw_indexed(index_buffer.len() as u32, 1, 0, 0, 0)? - }; - - Ok(Self { - pipeline, - vertex_buffer, - index_buffer, - descriptor_sets, - command_buffer: command_buffer.end()?, - }) - } -} - -impl WlxPass { - fn new( - pipeline: Arc, + pipeline: Arc, dimensions: [f32; 2], vertex_buffer: Vert2Buf, index_buffer: IndexBuf, @@ -1616,15 +1315,57 @@ impl WlxPass { }; Ok(Self { - pipeline, - vertex_buffer, - index_buffer, - descriptor_sets, command_buffer: command_buffer.end()?, }) } } +#[derive(Default)] +pub struct CommandBuffers { + inner: Vec>, +} + +impl CommandBuffers { + pub fn push(&mut self, buffer: Arc) { + self.inner.push(buffer); + } + pub fn execute_now(self, queue: Arc) -> anyhow::Result>> { + let mut buffers = self.inner.into_iter(); + let Some(first) = buffers.next() else { + return Ok(None); + }; + + let future = first.execute(queue.clone())?; + let mut future: Box = Box::new(future); + + for buf in buffers { + future = Box::new(future.then_execute_same_queue(buf)?); + } + + Ok(Some(future)) + } + #[cfg(feature = "uidev")] + pub fn execute_after( + self, + queue: Arc, + future: Box, + ) -> anyhow::Result> { + let mut buffers = self.inner.into_iter(); + let Some(first) = buffers.next() else { + return Ok(future); + }; + + let future = future.then_execute(queue, first)?; + let mut future: Box = Box::new(future); + + for buf in buffers { + future = Box::new(future.then_execute_same_queue(buf)?); + } + + Ok(future) + } +} + pub fn fourcc_to_vk(fourcc: FourCC) -> anyhow::Result { match fourcc.value { DRM_FORMAT_ABGR8888 => Ok(Format::R8G8B8A8_UNORM), @@ -1675,15 +1416,3 @@ fn memory_allocator(device: Arc) -> Arc { Arc::new(StandardMemoryAllocator::new(device, create_info)) } - -pub fn format_is_srgb(format: Format) -> bool { - matches!( - format, - Format::R8G8B8A8_SRGB - | Format::B8G8R8A8_SRGB - | Format::BC1_RGBA_SRGB_BLOCK - | Format::BC2_SRGB_BLOCK - | Format::BC3_SRGB_BLOCK - | Format::BC7_SRGB_BLOCK - ) -} diff --git a/src/gui/canvas/control.rs b/src/gui/canvas/control.rs index 137e41b..4a37048 100644 --- a/src/gui/canvas/control.rs +++ b/src/gui/canvas/control.rs @@ -30,7 +30,8 @@ pub(crate) struct Control { pub size: isize, pub sprite: Option>, pub sprite_st: Vec4, - pub(super) dirty: bool, + pub(super) bg_dirty: bool, + pub(super) fg_dirty: bool, pub on_update: Option, pub on_press: Option, @@ -58,7 +59,8 @@ impl Control { text: Arc::from(""), sprite: None, sprite_st: Vec4::new(1., 1., 0., 0.), - dirty: true, + bg_dirty: true, + fg_dirty: true, size: 24, state: None, on_update: None, @@ -78,13 +80,13 @@ impl Control { return; } self.text = text.into(); - self.dirty = true; + self.fg_dirty = true; } #[inline(always)] pub fn set_sprite(&mut self, sprite: Arc) { self.sprite.replace(sprite); - self.dirty = true; + self.bg_dirty = true; } #[inline(always)] @@ -93,7 +95,7 @@ impl Control { return; } self.sprite_st = sprite_st; - self.dirty = true; + self.bg_dirty = true; } #[inline(always)] @@ -102,7 +104,7 @@ impl Control { return; } self.fg_color = color; - self.dirty = true; + self.fg_dirty = true; } pub fn render_rounded_rect( @@ -174,7 +176,7 @@ impl Control { let skew_radius = [clamped_radius / self.rect.w, clamped_radius / self.rect.h]; - let set0 = canvas.pipeline_bg_color.uniform_buffer( + let set0 = canvas.pipeline_hl_color.uniform_buffer( 0, vec![ color.x, @@ -186,7 +188,7 @@ impl Control { ], )?; - let pass = canvas.pipeline_bg_color.create_pass( + let pass = canvas.pipeline_hl_color.create_pass( [canvas.width as _, canvas.height as _], vertex_buffer.clone(), canvas.graphics.quad_indices.clone(), diff --git a/src/gui/canvas/mod.rs b/src/gui/canvas/mod.rs index e23f1d9..5e42b42 100644 --- a/src/gui/canvas/mod.rs +++ b/src/gui/canvas/mod.rs @@ -4,18 +4,14 @@ pub(crate) mod control; use std::sync::Arc; use glam::{Vec2, Vec4}; -use vulkano::{ - command_buffer::CommandBufferUsage, - format::Format, - image::{view::ImageView, ImageLayout}, -}; +use vulkano::{command_buffer::CommandBufferUsage, format::Format, image::view::ImageView}; use crate::{ backend::{ input::{Haptics, InteractionHandler, PointerHit}, - overlay::{FrameTransform, OverlayBackend, OverlayRenderer}, + overlay::{FrameMeta, OverlayBackend, OverlayRenderer, ShouldRender}, }, - graphics::{WlxGraphics, WlxPass, WlxPipeline, WlxPipelineLegacy, BLEND_ALPHA}, + graphics::{CommandBuffers, WlxGraphics, WlxPipeline, BLEND_ALPHA, SWAPCHAIN_FORMAT}, state::AppState, }; @@ -35,11 +31,11 @@ pub struct CanvasData { graphics: Arc, - pipeline_bg_color: Arc>, - pipeline_fg_glyph: Arc>, - pipeline_bg_sprite: Arc>, - pipeline_hl_sprite: Arc>, - pipeline_final: Arc>, + pipeline_bg_color: Arc, + pipeline_bg_sprite: Arc, + pipeline_fg_glyph: Arc, + pipeline_hl_color: Arc, + pipeline_hl_sprite: Arc, } pub struct Canvas { @@ -53,10 +49,16 @@ pub struct Canvas { interact_stride: usize, interact_rows: usize, - view_final: Arc, + pipeline_final: Arc, - pass_fg: WlxPass, - pass_bg: WlxPass, + view_fg: Arc, + view_bg: Arc, + + format: Format, + + bg_dirty: bool, + hl_dirty: bool, + fg_dirty: bool, } impl Canvas { @@ -69,76 +71,56 @@ impl Canvas { ) -> anyhow::Result { let tex_fg = graphics.render_texture(width as _, height as _, format)?; let tex_bg = graphics.render_texture(width as _, height as _, format)?; - let tex_final = graphics.render_texture(width as _, height as _, format)?; let view_fg = ImageView::new_default(tex_fg.clone())?; let view_bg = ImageView::new_default(tex_bg.clone())?; - let view_final = ImageView::new_default(tex_final.clone())?; let Ok(shaders) = graphics.shared_shaders.read() else { anyhow::bail!("Failed to lock shared shaders for reading"); }; + let vert = shaders.get("vert_common").unwrap().clone(); // want panic + let pipeline_bg_color = graphics.create_pipeline( - view_bg.clone(), - shaders.get("vert_common").unwrap().clone(), // want panic - shaders.get("frag_color").unwrap().clone(), // want panic + vert.clone(), + shaders.get("frag_color").unwrap().clone(), // want panic format, Some(BLEND_ALPHA), )?; let pipeline_fg_glyph = graphics.create_pipeline( - view_fg.clone(), - shaders.get("vert_common").unwrap().clone(), // want panic - shaders.get("frag_glyph").unwrap().clone(), // want panic + vert.clone(), + shaders.get("frag_glyph").unwrap().clone(), // want panic format, Some(BLEND_ALPHA), )?; let pipeline_bg_sprite = graphics.create_pipeline( - view_fg.clone(), - shaders.get("vert_common").unwrap().clone(), // want panic + vert.clone(), shaders.get("frag_sprite2").unwrap().clone(), // want panic format, Some(BLEND_ALPHA), )?; + let pipeline_hl_color = graphics.create_pipeline( + vert.clone(), + shaders.get("frag_color").unwrap().clone(), // want panic + SWAPCHAIN_FORMAT, + Some(BLEND_ALPHA), + )?; + let pipeline_hl_sprite = graphics.create_pipeline( - view_fg.clone(), - shaders.get("vert_common").unwrap().clone(), // want panic + vert.clone(), shaders.get("frag_sprite2_hl").unwrap().clone(), // want panic - format, + SWAPCHAIN_FORMAT, Some(BLEND_ALPHA), )?; - let vertex_buffer = - graphics.upload_verts(width as _, height as _, 0., 0., width as _, height as _)?; - - let pipeline_final = graphics.create_pipeline_with_layouts( - view_final.clone(), - shaders.get("vert_common").unwrap().clone(), // want panic - shaders.get("frag_sprite").unwrap().clone(), // want panic - format, + let pipeline_final = graphics.create_pipeline( + vert.clone(), + shaders.get("frag_srgb").unwrap().clone(), // want panic + SWAPCHAIN_FORMAT, Some(BLEND_ALPHA), - ImageLayout::TransferSrcOptimal, - ImageLayout::TransferSrcOptimal, - )?; - - let set_fg = - pipeline_final.uniform_sampler(0, view_fg.clone(), graphics.texture_filtering)?; - let set_bg = - pipeline_final.uniform_sampler(0, view_bg.clone(), graphics.texture_filtering)?; - let pass_fg = pipeline_final.create_pass( - [width as _, height as _], - vertex_buffer.clone(), - graphics.quad_indices.clone(), - vec![set_fg], - )?; - let pass_bg = pipeline_final.create_pass( - [width as _, height as _], - vertex_buffer.clone(), - graphics.quad_indices.clone(), - vec![set_bg], )?; let stride = width / RES_DIVIDER; @@ -151,10 +133,10 @@ impl Canvas { height, graphics: graphics.clone(), pipeline_bg_color, - pipeline_fg_glyph, pipeline_bg_sprite, + pipeline_fg_glyph, + pipeline_hl_color, pipeline_hl_sprite, - pipeline_final, }, controls: Vec::new(), hover_controls: [None, None], @@ -162,9 +144,13 @@ impl Canvas { interact_map: vec![None; stride * rows], interact_stride: stride, interact_rows: rows, - view_final, - pass_fg, - pass_bg, + pipeline_final, + view_fg, + view_bg, + format, + bg_dirty: false, + hl_dirty: false, + fg_dirty: false, }) } @@ -191,36 +177,6 @@ impl Canvas { self.interact_map[y * self.interact_stride + x].map(|x| x as usize) } - fn render_bg(&mut self, app: &mut AppState) -> anyhow::Result<()> { - let mut cmd_buffer = self - .canvas - .graphics - .create_command_buffer(CommandBufferUsage::OneTimeSubmit)?; - cmd_buffer.begin_render_pass(&self.canvas.pipeline_bg_color)?; - for c in self.controls.iter_mut() { - if let Some(fun) = c.on_render_bg { - fun(c, &self.canvas, app, &mut cmd_buffer)?; - } - } - cmd_buffer.end_render_pass()?; - cmd_buffer.build_and_execute_now() - } - - fn render_fg(&mut self, app: &mut AppState) -> anyhow::Result<()> { - let mut cmd_buffer = self - .canvas - .graphics - .create_command_buffer(CommandBufferUsage::OneTimeSubmit)?; - cmd_buffer.begin_render_pass(&self.canvas.pipeline_fg_glyph)?; - for c in self.controls.iter_mut() { - if let Some(fun) = c.on_render_fg { - fun(c, &self.canvas, app, &mut cmd_buffer)?; - } - } - cmd_buffer.end_render_pass()?; - cmd_buffer.build_and_execute_now() - } - pub fn data_mut(&mut self) -> &mut D { &mut self.canvas.data } @@ -228,9 +184,14 @@ impl Canvas { impl InteractionHandler for Canvas { fn on_left(&mut self, _app: &mut AppState, pointer: usize) { + self.hl_dirty = true; + self.hover_controls[pointer] = None; } fn on_hover(&mut self, _app: &mut AppState, hit: &PointerHit) -> Option { + // render on every frame if we are being hovered + self.hl_dirty = true; + let old = self.hover_controls[hit.pointer]; if let Some(i) = self.interactive_get_idx(hit.uv) { self.hover_controls[hit.pointer] = Some(i); @@ -280,9 +241,8 @@ impl InteractionHandler for Canvas { } impl OverlayRenderer for Canvas { - fn init(&mut self, app: &mut AppState) -> anyhow::Result<()> { - self.render_bg(app)?; - self.render_fg(app) + fn init(&mut self, _app: &mut AppState) -> anyhow::Result<()> { + Ok(()) } fn pause(&mut self, _app: &mut AppState) -> anyhow::Result<()> { Ok(()) @@ -290,49 +250,84 @@ impl OverlayRenderer for Canvas { fn resume(&mut self, _app: &mut AppState) -> anyhow::Result<()> { Ok(()) } - fn render(&mut self, app: &mut AppState) -> anyhow::Result<()> { - let mut dirty = false; - + fn should_render(&mut self, app: &mut AppState) -> anyhow::Result { for c in self.controls.iter_mut() { if let Some(fun) = c.on_update { fun(c, &mut self.canvas.data, app); } - if c.dirty { - dirty = true; - c.dirty = false; + if c.fg_dirty { + self.fg_dirty = true; + c.fg_dirty = false; + } + if c.bg_dirty { + self.bg_dirty = true; + c.bg_dirty = false; } } - if dirty { - self.render_bg(app)?; - self.render_fg(app)?; - } - - /* - let image = self.view_final.image().clone(); - if self.first_render { - self.first_render = false; + if self.bg_dirty || self.fg_dirty || self.hl_dirty { + Ok(ShouldRender::Should) } else { - self.canvas - .graphics - .transition_layout( - image.clone(), - ImageLayout::TransferSrcOptimal, - ImageLayout::ColorAttachmentOptimal, - ) - .wait(None) - .unwrap(); + Ok(ShouldRender::Can) } - */ + } + fn render( + &mut self, + app: &mut AppState, + tgt: Arc, + buf: &mut CommandBuffers, + alpha: f32, + ) -> anyhow::Result { + self.hl_dirty = false; let mut cmd_buffer = self .canvas .graphics .create_command_buffer(CommandBufferUsage::OneTimeSubmit)?; - cmd_buffer.begin_render_pass(&self.canvas.pipeline_final)?; - // static background - cmd_buffer.run_ref(&self.pass_bg)?; + if self.bg_dirty { + cmd_buffer.begin_rendering(self.view_bg.clone())?; + for c in self.controls.iter_mut() { + if let Some(fun) = c.on_render_bg { + fun(c, &self.canvas, app, &mut cmd_buffer)?; + } + } + cmd_buffer.end_rendering()?; + self.bg_dirty = false; + } + if self.fg_dirty { + cmd_buffer.begin_rendering(self.view_fg.clone())?; + for c in self.controls.iter_mut() { + if let Some(fun) = c.on_render_fg { + fun(c, &self.canvas, app, &mut cmd_buffer)?; + } + } + cmd_buffer.end_rendering()?; + self.fg_dirty = false; + } + + let set0_fg = self.pipeline_final.uniform_sampler( + 0, + self.view_fg.clone(), + app.graphics.texture_filtering, + )?; + let set0_bg = self.pipeline_final.uniform_sampler( + 0, + self.view_bg.clone(), + app.graphics.texture_filtering, + )?; + let set1 = self.pipeline_final.uniform_buffer(1, vec![alpha])?; + + let pass_fg = self + .pipeline_final + .create_pass_for_target(tgt.clone(), vec![set0_fg, set1.clone()])?; + + let pass_bg = self + .pipeline_final + .create_pass_for_target(tgt.clone(), vec![set0_bg, set1])?; + + cmd_buffer.begin_rendering(tgt.clone())?; + cmd_buffer.run_ref(&pass_bg)?; for (i, c) in self.controls.iter_mut().enumerate() { if let Some(render) = c.on_render_hl { @@ -354,30 +349,17 @@ impl OverlayRenderer for Canvas { } // mostly static text - cmd_buffer.run_ref(&self.pass_fg)?; + cmd_buffer.run_ref(&pass_fg)?; - cmd_buffer.end_render_pass()?; - cmd_buffer.build_and_execute_now() - - /* - self.canvas - .graphics - .transition_layout( - image, - ImageLayout::ColorAttachmentOptimal, - ImageLayout::TransferSrcOptimal, - ) - .wait(None) - .unwrap(); - */ - } - fn view(&mut self) -> Option> { - Some(self.view_final.clone()) + cmd_buffer.end_rendering()?; + buf.push(cmd_buffer.build()?); + Ok(true) } - fn frame_transform(&mut self) -> Option { - Some(FrameTransform { - extent: self.view_final.image().extent(), + fn frame_meta(&mut self) -> Option { + Some(FrameMeta { + extent: [self.canvas.width as _, self.canvas.height as _, 1], + format: self.format, ..Default::default() }) } diff --git a/src/gui/font.rs b/src/gui/font.rs index f8a4072..ab79742 100644 --- a/src/gui/font.rs +++ b/src/gui/font.rs @@ -5,7 +5,7 @@ use freetype::{bitmap::PixelMode, face::LoadFlag, Face, Library}; use idmap::IdMap; use vulkano::{command_buffer::CommandBufferUsage, format::Format, image::Image}; -use crate::graphics::WlxGraphics; +use crate::graphics::{WlxCommandBuffer, WlxGraphics}; pub struct FontCache { primary_font: Arc, @@ -56,13 +56,14 @@ impl FontCache { let sizef = size as f32; let height = sizef + ((text.lines().count() as f32) - 1f32) * (sizef * 1.5); + let mut cmd_buffer = None; let mut max_w = sizef * 0.33; for line in text.lines() { let w: f32 = line .chars() .filter_map(|c| { - self.get_glyph_for_cp(c as usize, size, graphics.clone()) + self.get_glyph_for_cp(c as usize, size, graphics.clone(), &mut cmd_buffer) .map(|glyph| glyph.advance) .ok() }) @@ -72,6 +73,11 @@ impl FontCache { max_w = w; } } + + if let Some(cmd_buffer) = cmd_buffer { + cmd_buffer.build_and_execute_now()?; + } + Ok((max_w, height)) } @@ -82,11 +88,23 @@ impl FontCache { graphics: Arc, ) -> anyhow::Result>> { let mut glyphs = Vec::new(); + let mut cmd_buffer = None; + for line in text.lines() { for c in line.chars() { - glyphs.push(self.get_glyph_for_cp(c as usize, size, graphics.clone())?); + glyphs.push(self.get_glyph_for_cp( + c as usize, + size, + graphics.clone(), + &mut cmd_buffer, + )?); } } + + if let Some(cmd_buffer) = cmd_buffer { + cmd_buffer.build_and_execute_now()?; + } + Ok(glyphs) } @@ -184,6 +202,7 @@ impl FontCache { cp: usize, size: isize, graphics: Arc, + cmd_buffer: &mut Option, ) -> anyhow::Result> { let key = self.get_font_for_cp(cp, size); @@ -220,9 +239,16 @@ impl FontCache { _ => return Ok(self.collections[size].zero_glyph.clone()), }; - let mut cmd_buffer = graphics.create_command_buffer(CommandBufferUsage::OneTimeSubmit)?; - let texture = cmd_buffer.texture2d_raw(bmp.width() as _, bmp.rows() as _, format, &buf)?; - cmd_buffer.build_and_execute_now()?; + if cmd_buffer.is_none() { + *cmd_buffer = Some(graphics.create_command_buffer(CommandBufferUsage::OneTimeSubmit)?); + } + + let texture = cmd_buffer.as_mut().unwrap().texture2d_raw( + bmp.width() as _, + bmp.rows() as _, + format, + &buf, + )?; let g = Glyph { tex: Some(texture), diff --git a/src/gui/modular/label.rs b/src/gui/modular/label.rs index fa06256..9d2aa4f 100644 --- a/src/gui/modular/label.rs +++ b/src/gui/modular/label.rs @@ -127,7 +127,7 @@ pub fn modular_label_init(label: &mut ModularControl, content: &LabelContent, ap } LabelContent::Timezone { timezone } => { if let Some(tz) = app.session.config.timezones.get(*timezone) { - let pretty_tz = tz.split('/').last().map(|x| x.replace("_", " ")); + let pretty_tz = tz.split('/').next_back().map(|x| x.replace("_", " ")); if let Some(pretty_tz) = pretty_tz { label.set_text(&pretty_tz); diff --git a/src/overlays/keyboard.rs b/src/overlays/keyboard.rs index 8b6a360..503fc38 100644 --- a/src/overlays/keyboard.rs +++ b/src/overlays/keyboard.rs @@ -2,14 +2,18 @@ use std::{ collections::HashMap, process::{Child, Command}, str::FromStr, + sync::Arc, }; use crate::{ backend::{ input::{InteractionHandler, PointerMode}, - overlay::{FrameTransform, OverlayBackend, OverlayData, OverlayRenderer, OverlayState}, + overlay::{ + FrameMeta, OverlayBackend, OverlayData, OverlayRenderer, OverlayState, ShouldRender, + }, }, config::{self, ConfigType}, + graphics::CommandBuffers, gui::{ canvas::{builder::CanvasBuilder, control::Control, Canvas}, color_parse, KeyCapType, @@ -24,6 +28,7 @@ use glam::{vec2, vec3a, Affine2, Vec4}; use once_cell::sync::Lazy; use regex::Regex; use serde::{Deserialize, Serialize}; +use vulkano::image::view::ImageView; const PIXELS_PER_UNIT: f32 = 80.; const BUTTON_PADDING: f32 = 4.; @@ -543,14 +548,20 @@ impl OverlayRenderer for KeyboardBackend { fn init(&mut self, app: &mut AppState) -> anyhow::Result<()> { self.canvas.init(app) } - fn render(&mut self, app: &mut AppState) -> anyhow::Result<()> { - self.canvas.render(app) + fn should_render(&mut self, app: &mut AppState) -> anyhow::Result { + self.canvas.should_render(app) } - fn frame_transform(&mut self) -> Option { - self.canvas.frame_transform() + fn render( + &mut self, + app: &mut AppState, + tgt: Arc, + buf: &mut CommandBuffers, + alpha: f32, + ) -> anyhow::Result { + self.canvas.render(app, tgt, buf, alpha) } - fn view(&mut self) -> Option> { - self.canvas.view() + fn frame_meta(&mut self) -> Option { + self.canvas.frame_meta() } fn pause(&mut self, app: &mut AppState) -> anyhow::Result<()> { self.canvas.data_mut().modifiers = 0; diff --git a/src/overlays/mirror.rs b/src/overlays/mirror.rs index 8f2117d..981d3a4 100644 --- a/src/overlays/mirror.rs +++ b/src/overlays/mirror.rs @@ -4,17 +4,19 @@ use std::{ }; use futures::{Future, FutureExt}; +use vulkano::image::view::ImageView; use wlx_capture::pipewire::{pipewire_select_screen, PipewireCapture, PipewireSelectScreenResult}; use crate::{ backend::{ common::OverlaySelector, overlay::{ - ui_transform, FrameTransform, OverlayBackend, OverlayRenderer, OverlayState, + ui_transform, FrameMeta, OverlayBackend, OverlayRenderer, OverlayState, ShouldRender, SplitOverlayBackend, }, task::TaskType, }, + graphics::CommandBuffers, state::{AppSession, AppState}, }; @@ -45,7 +47,19 @@ impl OverlayRenderer for MirrorRenderer { fn init(&mut self, _app: &mut AppState) -> anyhow::Result<()> { Ok(()) } - fn render(&mut self, app: &mut AppState) -> anyhow::Result<()> { + fn should_render(&mut self, app: &mut AppState) -> anyhow::Result { + self.renderer + .as_mut() + .map(|r| r.should_render(app)) + .unwrap_or(Ok(ShouldRender::Unable)) + } + fn render( + &mut self, + app: &mut AppState, + tgt: Arc, + buf: &mut CommandBuffers, + alpha: f32, + ) -> anyhow::Result { if let Some(mut selector) = self.selector.take() { let maybe_pw_result = match selector .poll_unpin(&mut Context::from_waker(futures::task::noop_waker_ref())) @@ -53,7 +67,7 @@ impl OverlayRenderer for MirrorRenderer { Poll::Ready(result) => result, Poll::Pending => { self.selector = Some(selector); - return Ok(()); + return Ok(false); } }; @@ -87,10 +101,11 @@ impl OverlayRenderer for MirrorRenderer { } } + let mut result = false; if let Some(renderer) = self.renderer.as_mut() { - renderer.render(app)?; - if let Some(view) = renderer.view() { - let extent = view.image().extent(); + result = renderer.render(app, tgt, buf, alpha)?; + if let Some(meta) = renderer.frame_meta() { + let extent = meta.extent; if self.last_extent != extent { self.last_extent = extent; // resized @@ -104,7 +119,7 @@ impl OverlayRenderer for MirrorRenderer { } } - Ok(()) + Ok(result) } fn pause(&mut self, app: &mut AppState) -> anyhow::Result<()> { if let Some(renderer) = self.renderer.as_mut() { @@ -118,12 +133,9 @@ impl OverlayRenderer for MirrorRenderer { } Ok(()) } - fn view(&mut self) -> Option> { - self.renderer.as_mut().and_then(|r| r.view()) - } - fn frame_transform(&mut self) -> Option { - Some(FrameTransform { + fn frame_meta(&mut self) -> Option { + Some(FrameMeta { extent: self.last_extent, ..Default::default() }) diff --git a/src/overlays/screen.rs b/src/overlays/screen.rs index 7282285..46c06d6 100644 --- a/src/overlays/screen.rs +++ b/src/overlays/screen.rs @@ -52,11 +52,12 @@ use glam::{vec2, vec3a, Affine2, Affine3A, Quat, Vec2, Vec3}; use crate::{ backend::{ input::{Haptics, InteractionHandler, PointerHit, PointerMode}, - overlay::{FrameTransform, OverlayRenderer, OverlayState, SplitOverlayBackend}, + overlay::{FrameMeta, OverlayRenderer, OverlayState, ShouldRender, SplitOverlayBackend}, }, config::{def_pw_tokens, GeneralConfig, PwTokenMap}, graphics::{ - fourcc_to_vk, WlxCommandBuffer, WlxPipeline, WlxPipelineLegacy, DRM_FORMAT_MOD_INVALID, + fourcc_to_vk, CommandBuffers, WlxCommandBuffer, WlxGraphics, WlxPipeline, + DRM_FORMAT_MOD_INVALID, SWAPCHAIN_FORMAT, }, hid::{MOUSE_LEFT, MOUSE_MIDDLE, MOUSE_RIGHT}, state::{AppSession, AppState, KeyboardFocus, ScreenMeta}, @@ -158,36 +159,27 @@ impl InteractionHandler for ScreenInteractionHandler { #[derive(Clone)] struct ScreenPipeline { - view: Arc, mouse: Option>, - pipeline: Arc>, + pipeline: Arc, extentf: [f32; 2], } impl ScreenPipeline { fn new(extent: &[u32; 3], app: &mut AppState) -> anyhow::Result { - let texture = - app.graphics - .render_texture(extent[0], extent[1], app.graphics.native_format)?; - - let view = ImageView::new_default(texture)?; - let Ok(shaders) = app.graphics.shared_shaders.read() else { return Err(anyhow::anyhow!("Could not lock shared shaders for reading")); }; let pipeline = app.graphics.create_pipeline( - view.clone(), shaders.get("vert_common").unwrap().clone(), // want panic shaders.get("frag_screen").unwrap().clone(), // want panic - app.graphics.native_format, + SWAPCHAIN_FORMAT, Some(AttachmentBlend::default()), )?; let extentf = [extent[0] as f32, extent[1] as f32]; Ok(ScreenPipeline { - view, mouse: None, pipeline, extentf, @@ -216,25 +208,25 @@ impl ScreenPipeline { fn render( &mut self, image: Arc, - mouse: Option<&MouseMeta>, + mouse: Option, app: &mut AppState, + tgt: Arc, + buf: &mut CommandBuffers, + alpha: f32, ) -> anyhow::Result<()> { - let mut cmd = app - .graphics - .create_command_buffer(CommandBufferUsage::OneTimeSubmit)?; let view = ImageView::new_default(image)?; let set0 = self .pipeline .uniform_sampler(0, view, app.graphics.texture_filtering)?; + let set1 = self.pipeline.uniform_buffer(1, vec![alpha])?; + let pass = self + .pipeline + .create_pass_for_target(tgt.clone(), vec![set0, set1])?; - let pass = self.pipeline.create_pass( - self.extentf, - app.graphics.quad_verts.clone(), - app.graphics.quad_indices.clone(), - vec![set0], - )?; - - cmd.begin_render_pass(&self.pipeline)?; + let mut cmd = app + .graphics + .create_command_buffer(CommandBufferUsage::OneTimeSubmit)?; + cmd.begin_rendering(tgt)?; cmd.run_ref(&pass)?; if let (Some(mouse), Some(mouse_view)) = (mouse, self.mouse.clone()) { @@ -264,30 +256,32 @@ impl ScreenPipeline { cmd.run_ref(&pass)?; } - cmd.end_render_pass()?; - cmd.build_and_execute_now() + cmd.end_rendering()?; + buf.push(cmd.build()?); + Ok(()) } } pub struct ScreenRenderer { name: Arc, - capture: Box, + capture: Box>, pipeline: Option, - last_view: Option>, - transform: Affine3A, - extent: Option<[u32; 3]>, + cur_frame: Option, + meta: Option, } impl ScreenRenderer { #[cfg(feature = "wayland")] - pub fn new_raw(name: Arc, capture: Box) -> ScreenRenderer { + pub fn new_raw( + name: Arc, + capture: Box>, + ) -> ScreenRenderer { ScreenRenderer { name, capture, pipeline: None, - last_view: None, - transform: Affine3A::IDENTITY, - extent: None, + cur_frame: None, + meta: None, } } @@ -300,9 +294,8 @@ impl ScreenRenderer { name: output.name.clone(), capture: Box::new(capture), pipeline: None, - last_view: None, - transform: Affine3A::IDENTITY, - extent: None, + cur_frame: None, + meta: None, }) } @@ -315,9 +308,8 @@ impl ScreenRenderer { name: output.name.clone(), capture: Box::new(capture), pipeline: None, - last_view: None, - transform: Affine3A::IDENTITY, - extent: None, + cur_frame: None, + meta: None, }) } @@ -358,9 +350,8 @@ impl ScreenRenderer { name: output.name.clone(), capture: Box::new(capture), pipeline: None, - last_view: None, - transform: Affine3A::IDENTITY, - extent: None, + cur_frame: None, + meta: None, }, select_screen_result.restore_token, )) @@ -374,9 +365,158 @@ impl ScreenRenderer { name: screen.name.clone(), capture: Box::new(capture), pipeline: None, - last_view: None, - transform: Affine3A::IDENTITY, - extent: None, + cur_frame: None, + meta: None, + } + } +} + +#[derive(Clone)] +pub struct WlxCaptureIn { + name: Arc, + graphics: Arc, +} + +#[derive(Clone)] +pub struct WlxCaptureOut { + image: Arc, + format: FrameFormat, + mouse: Option, +} + +fn receive_callback(me: &WlxCaptureIn, frame: wlx_frame::WlxFrame) -> Option { + match frame { + WlxFrame::Dmabuf(frame) => { + if !frame.is_valid() { + log::error!("{}: Invalid frame", me.name); + return None; + } + log::trace!("{}: New DMA-buf frame", me.name); + let format = frame.format; + match me.graphics.dmabuf_texture(frame) { + Ok(image) => Some(WlxCaptureOut { + image, + format, + mouse: None, + }), + Err(e) => { + log::error!( + "{}: Failed to create DMA-buf vkImage: {}", + me.name, + e.to_string() + ); + None + } + } + } + WlxFrame::MemFd(frame) => { + let Some(fd) = frame.plane.fd else { + log::error!("{}: No fd in MemFd frame", me.name); + return None; + }; + + let format = match fourcc_to_vk(frame.format.fourcc) { + Ok(x) => x, + Err(e) => { + log::error!("{}: {}", me.name, e); + return None; + } + }; + + let len = frame.plane.stride as usize * frame.format.height as usize; + let offset = frame.plane.offset as i64; + + let map = unsafe { + libc::mmap( + ptr::null_mut(), + len, + libc::PROT_READ, + libc::MAP_SHARED, + fd, + offset, + ) + } as *const u8; + + let pixels = unsafe { slice::from_raw_parts(map, len) }; + + let mut upload = match me + .graphics + .create_command_buffer(CommandBufferUsage::OneTimeSubmit) + { + Ok(x) => x, + Err(e) => { + log::error!("{}: Could not create vkCommandBuffer: {:?}", me.name, e); + return None; + } + }; + + let image = + match upload.texture2d_raw(frame.format.width, frame.format.height, format, pixels) + { + Ok(x) => x, + Err(e) => { + log::error!("{}: Could not create vkImage: {:?}", me.name, e); + return None; + } + }; + + if let Err(e) = upload.build_and_execute_now() { + log::error!("{}: Could not execute upload: {:?}", me.name, e); + return None; + } + + unsafe { libc::munmap(map as *mut _, len) }; + + Some(WlxCaptureOut { + image, + format: frame.format, + mouse: None, + }) + } + WlxFrame::MemPtr(frame) => { + log::trace!("{}: New MemPtr frame", me.name); + + let format = match fourcc_to_vk(frame.format.fourcc) { + Ok(x) => x, + Err(e) => { + log::error!("{}: {}", me.name, e); + return None; + } + }; + + let mut upload = match me + .graphics + .create_command_buffer(CommandBufferUsage::OneTimeSubmit) + { + Ok(x) => x, + Err(e) => { + log::error!("{}: Could not create vkCommandBuffer: {:?}", me.name, e); + return None; + } + }; + + let pixels = unsafe { slice::from_raw_parts(frame.ptr as *const u8, frame.size) }; + + let image = + match upload.texture2d_raw(frame.format.width, frame.format.height, format, pixels) + { + Ok(x) => x, + Err(e) => { + log::error!("{}: Could not create vkImage: {:?}", me.name, e); + return None; + } + }; + + if let Err(e) = upload.build_and_execute_now() { + log::error!("{}: Could not execute upload: {:?}", me.name, e); + return None; + } + + Some(WlxCaptureOut { + image, + format: frame.format, + mouse: frame.mouse, + }) } } } @@ -385,7 +525,7 @@ impl OverlayRenderer for ScreenRenderer { fn init(&mut self, _app: &mut AppState) -> anyhow::Result<()> { Ok(()) } - fn render(&mut self, app: &mut AppState) -> anyhow::Result<()> { + fn should_render(&mut self, app: &mut AppState) -> anyhow::Result { if !self.capture.is_ready() { let supports_dmabuf = app .graphics @@ -399,7 +539,7 @@ impl OverlayRenderer for ScreenRenderer { let capture_method = app.session.config.capture_method.clone(); - let drm_formats = DRM_FORMATS.get_or_init({ + let dmabuf_formats = DRM_FORMATS.get_or_init({ let graphics = app.graphics.clone(); move || { if !supports_dmabuf { @@ -453,144 +593,67 @@ impl OverlayRenderer for ScreenRenderer { } }); - self.capture.init(drm_formats); + let user_data = WlxCaptureIn { + name: self.name.clone(), + graphics: app.graphics.clone(), + }; + + self.capture + .init(dmabuf_formats, user_data, receive_callback); self.capture.request_new_frame(); - }; + return Ok(ShouldRender::Unable); + } for frame in self.capture.receive().into_iter() { - match frame { - WlxFrame::Dmabuf(frame) => { - if !frame.is_valid() { - log::error!("Invalid frame"); - continue; - } - self.extent.get_or_insert_with(|| { - extent_from_format(frame.format, &app.session.config) - }); - if let Some(new_transform) = affine_from_format(&frame.format) { - self.transform = new_transform; - } - match app.graphics.dmabuf_texture(frame) { - Ok(new) => { - let pipeline = match self.pipeline { - Some(ref mut p) => Some(p), - None if app.session.config.screen_render_down => { - log::info!("{}: Using render-down pass.", self.name); - let pipeline = ScreenPipeline::new(&self.extent.unwrap(), app)?; // safe - self.last_view = Some(pipeline.view.clone()); - self.pipeline = Some(pipeline); - self.pipeline.as_mut() - } - None => None, - }; - if let Some(pipeline) = pipeline { - pipeline.render(new.clone(), None, app)?; - } else { - let view = ImageView::new_default(new.clone())?; - self.last_view = Some(view); - } - } - Err(e) => { - log::error!( - "{}: Failed to create DMA-buf texture: {}", - self.name, - e.to_string() - ); - } - } - self.capture.request_new_frame(); - } - WlxFrame::MemFd(frame) => { - let mut upload = app - .graphics - .create_command_buffer(CommandBufferUsage::OneTimeSubmit)?; - - let Some(fd) = frame.plane.fd else { - log::error!("No fd"); - continue; - }; - self.extent.get_or_insert_with(|| { - extent_from_format(frame.format, &app.session.config) - }); - if let Some(new_transform) = affine_from_format(&frame.format) { - self.transform = new_transform; - } - log::debug!("{}: New MemFd frame", self.name); - let format = fourcc_to_vk(frame.format.fourcc)?; - - let len = frame.plane.stride as usize * frame.format.height as usize; - let offset = frame.plane.offset as i64; - - let map = unsafe { - libc::mmap( - ptr::null_mut(), - len, - libc::PROT_READ, - libc::MAP_SHARED, - fd, - offset, - ) - } as *const u8; - - let data = unsafe { slice::from_raw_parts(map, len) }; - - let image = upload.texture2d_raw( - frame.format.width, - frame.format.height, - format, - data, - )?; - upload.build_and_execute_now()?; - - unsafe { libc::munmap(map as *mut _, len) }; - - self.last_view = Some(ImageView::new_default(image)?); - self.capture.request_new_frame(); - } - WlxFrame::MemPtr(frame) => { - log::debug!("{}: New MemPtr frame", self.name); - let mut upload = app - .graphics - .create_command_buffer(CommandBufferUsage::OneTimeSubmit)?; - - let format = fourcc_to_vk(frame.format.fourcc)?; - - let data = unsafe { slice::from_raw_parts(frame.ptr as *const u8, frame.size) }; - - let image = upload.texture2d_raw( - frame.format.width, - frame.format.height, - format, - data, - )?; - - let pipeline = Some(match self.pipeline { - Some(ref mut p) => p, - _ => { - log::info!("{}: Using render-down pass.", self.name); - let extent = extent_from_format(frame.format, &app.session.config); - let mut pipeline = ScreenPipeline::new(&extent, app)?; - self.last_view = Some(pipeline.view.clone()); - pipeline.ensure_mouse_initialized(&mut upload)?; - self.pipeline = Some(pipeline); - self.extent = Some(extent); - self.pipeline.as_mut().unwrap() // safe - } - }); - - upload.build_and_execute_now()?; - - if let Some(pipeline) = pipeline { - pipeline.render(image, frame.mouse.as_ref(), app)?; - } else { - let view = ImageView::new_default(image)?; - self.last_view = Some(view); - } - self.capture.request_new_frame(); - } - }; + self.cur_frame = Some(frame); } - Ok(()) + + if let (Some(capture), None) = (self.cur_frame.as_ref(), self.meta.as_ref()) { + self.meta = Some(FrameMeta { + extent: extent_from_format(capture.format, &app.session.config), + transform: affine_from_format(&capture.format), + format: capture.image.format(), + }); + self.pipeline = Some({ + let mut pipeline = ScreenPipeline::new(&capture.image.extent(), app)?; + let mut upload = app + .graphics + .create_command_buffer(CommandBufferUsage::OneTimeSubmit)?; + pipeline.ensure_mouse_initialized(&mut upload)?; + upload.build_and_execute_now()?; + pipeline + }); + }; + + if self.cur_frame.is_some() { + Ok(ShouldRender::Should) + } else { + Ok(ShouldRender::Unable) + } + } + fn render( + &mut self, + app: &mut AppState, + tgt: Arc, + buf: &mut CommandBuffers, + alpha: f32, + ) -> anyhow::Result { + let Some(capture) = self.cur_frame.take() else { + return Ok(false); + }; + + // want panic; must be Some if cur_frame is also Some + self.pipeline.as_mut().unwrap().render( + capture.image, + capture.mouse, + app, + tgt, + buf, + alpha, + )?; + + self.capture.request_new_frame(); + Ok(true) } fn pause(&mut self, _app: &mut AppState) -> anyhow::Result<()> { self.capture.pause(); @@ -600,14 +663,8 @@ impl OverlayRenderer for ScreenRenderer { self.capture.resume(); Ok(()) } - fn view(&mut self) -> Option> { - self.last_view.clone() - } - fn frame_transform(&mut self) -> Option { - self.extent.map(|extent| FrameTransform { - extent, - transform: self.transform, - }) + fn frame_meta(&mut self) -> Option { + self.meta } } @@ -856,7 +913,7 @@ pub fn create_screens_x11pw(_app: &mut AppState) -> anyhow::Result anyhow::Result { - use anyhow::bail; + use wlx_capture::xshm::xshm_get_monitors; // Load existing Pipewire tokens from file let mut pw_tokens: PwTokenMap = load_pw_token_config().unwrap_or_default(); @@ -885,10 +942,10 @@ pub fn create_screens_x11pw(app: &mut AppState) -> anyhow::Result m, Err(e) => { - bail!(e.to_string()); + anyhow::bail!(e.to_string()); } }; log::info!("Got {} monitors", monitors.len()); @@ -924,13 +981,8 @@ pub fn create_screens_x11pw(app: &mut AppState) -> anyhow::Result anyhow::Result anyhow::Result { - use anyhow::bail; + use wlx_capture::xshm::xshm_get_monitors; let mut extent = vec2(0., 0.); - let monitors = match XshmCapture::get_monitors() { + let monitors = match xshm_get_monitors() { Ok(m) => m, Err(e) => { - bail!(e.to_string()); + anyhow::bail!(e.to_string()); } }; @@ -1040,19 +1092,25 @@ fn extent_from_format(fmt: FrameFormat, config: &GeneralConfig) -> [u32; 3] { fn extent_from_res(width: u32, height: u32, config: &GeneralConfig) -> [u32; 3] { // screens above a certain resolution will have severe aliasing - let h = height.min(config.screen_max_height as u32); + let height_limit = if config.screen_render_down { + config.screen_max_height.min(2560) as u32 + } else { + 2560 + }; + + let h = height.min(height_limit); let w = (width as f32 / height as f32 * h as f32) as u32; [w, h, 1] } -fn affine_from_format(format: &FrameFormat) -> Option { +fn affine_from_format(format: &FrameFormat) -> Affine3A { const FLIP_X: Vec3 = Vec3 { x: -1.0, y: 1.0, z: 1.0, }; - Some(match format.transform { + match format.transform { wlx_frame::Transform::Normal => Affine3A::IDENTITY, wlx_frame::Transform::Rotated90 => Affine3A::from_rotation_z(-PI / 2.0), wlx_frame::Transform::Rotated180 => Affine3A::from_rotation_z(PI), @@ -1067,8 +1125,8 @@ fn affine_from_format(format: &FrameFormat) -> Option { wlx_frame::Transform::Flipped270 => { Affine3A::from_scale(FLIP_X) * Affine3A::from_rotation_z(PI / 2.0) } - wlx_frame::Transform::Undefined => return None, - }) + wlx_frame::Transform::Undefined => Affine3A::IDENTITY, + } } #[cfg(all(feature = "pipewire", feature = "x11"))] diff --git a/src/overlays/toast.rs b/src/overlays/toast.rs index 474cb2b..2a30d9f 100644 --- a/src/overlays/toast.rs +++ b/src/overlays/toast.rs @@ -110,7 +110,6 @@ impl Toast { ), instant, ); - } } @@ -140,13 +139,13 @@ fn new_toast(toast: Toast, app: &mut AppState) -> Option<(OverlayState, Box 0 { + let title = if !toast.title.is_empty() { toast.title } else { "Notification".into() }; - let mut size = if toast.body.len() > 0 { + let mut size = if !toast.body.is_empty() { let (w0, _) = app .fc .get_text_size(&title, FONT_SIZE, app.graphics.clone()) @@ -181,7 +180,7 @@ fn new_toast(toast: Toast, app: &mut AppState) -> Option<(OverlayState, Box 0 { + if !toast.body.is_empty() { canvas.label(PADDING.0, 54., og_width, size.1 - 54., 3., toast.body); canvas.fg_color = color_parse("#b8c0e0").unwrap(); // want panic diff --git a/src/overlays/wayvr.rs b/src/overlays/wayvr.rs index 9cbefaa..4e675dc 100644 --- a/src/overlays/wayvr.rs +++ b/src/overlays/wayvr.rs @@ -12,8 +12,8 @@ use crate::{ common::{OverlayContainer, OverlaySelector}, input::{self, InteractionHandler}, overlay::{ - ui_transform, FrameTransform, OverlayData, OverlayID, OverlayRenderer, OverlayState, - SplitOverlayBackend, Z_ORDER_DASHBOARD, + ui_transform, FrameMeta, OverlayData, OverlayID, OverlayRenderer, OverlayState, + ShouldRender, SplitOverlayBackend, Z_ORDER_DASHBOARD, }, task::TaskType, wayvr::{ @@ -23,7 +23,7 @@ use crate::{ }, }, config_wayvr, - graphics::WlxGraphics, + graphics::{CommandBuffers, WlxGraphics, WlxPipeline, SWAPCHAIN_FORMAT}, gui::modular::button::{WayVRAction, WayVRDisplayClickAction}, state::{self, AppState, KeyboardFocus}, }; @@ -173,6 +173,7 @@ impl InteractionHandler for WayVRInteractionHandler { } pub struct WayVRRenderer { + pipeline: Arc, vk_image: Option>, vk_image_view: Option>, context: Rc>, @@ -185,7 +186,19 @@ impl WayVRRenderer { wvr: Rc>, display: wayvr::display::DisplayHandle, ) -> anyhow::Result { + let Ok(shaders) = app.graphics.shared_shaders.read() else { + anyhow::bail!("Failed to lock shared shaders for reading"); + }; + + let pipeline = app.graphics.create_pipeline( + shaders.get("vert_common").unwrap().clone(), // want panic + shaders.get("frag_srgb").unwrap().clone(), // want panic + SWAPCHAIN_FORMAT, + None, + )?; + Ok(Self { + pipeline, context: Rc::new(RefCell::new(WayVRContext::new(wvr, display)?)), vk_image: None, vk_image_view: None, @@ -569,8 +582,11 @@ impl WayVRRenderer { &data.data, )?; + // FIXME: can we use _buffers_ here? upload.build_and_execute_now()?; + //buffers.push(upload.build()?); + self.vk_image = Some(tex.clone()); self.vk_image_view = Some(ImageView::new_default(tex).unwrap()); @@ -652,22 +668,32 @@ impl OverlayRenderer for WayVRRenderer { wayvr.state.set_display_visible(ctx.display, true); Ok(()) } - - fn render(&mut self, _app: &mut state::AppState) -> anyhow::Result<()> { + fn should_render(&mut self, _app: &mut AppState) -> anyhow::Result { let ctx = self.context.borrow(); let mut wayvr = ctx.wayvr.borrow_mut(); - let redrawn = match wayvr.data.tick_display(ctx.display) { Ok(r) => r, Err(e) => { log::error!("tick_display failed: {}", e); - return Ok(()); // do not proceed further + return Ok(ShouldRender::Unable); } }; - if !redrawn { - return Ok(()); + if redrawn { + Ok(ShouldRender::Should) + } else { + Ok(ShouldRender::Can) } + } + fn render( + &mut self, + app: &mut state::AppState, + tgt: Arc, + buf: &mut CommandBuffers, + alpha: f32, + ) -> anyhow::Result { + let ctx = self.context.borrow(); + let wayvr = ctx.wayvr.borrow_mut(); let data = wayvr .data @@ -680,25 +706,44 @@ impl OverlayRenderer for WayVRRenderer { drop(ctx); match data { + //TODO: render to _tgt_ 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)?; + self.ensure_software_data(new_frame)? } } } - Ok(()) + let Some(view) = self.vk_image_view.as_ref() else { + return Ok(false); + }; + + let set0 = + self.pipeline + .uniform_sampler(0, view.clone(), app.graphics.texture_filtering)?; + + let set1 = self.pipeline.uniform_buffer(1, vec![alpha])?; + + let pass = self + .pipeline + .create_pass_for_target(tgt.clone(), vec![set0, set1])?; + + let mut cmd_buffer = app + .graphics + .create_command_buffer(CommandBufferUsage::OneTimeSubmit)?; + cmd_buffer.begin_rendering(tgt)?; + cmd_buffer.run_ref(&pass)?; + cmd_buffer.end_rendering()?; + buf.push(cmd_buffer.build()?); + + Ok(true) } - fn view(&mut self) -> Option> { - self.vk_image_view.clone() - } - - fn frame_transform(&mut self) -> Option { - self.vk_image_view.as_ref().map(|view| FrameTransform { + fn frame_meta(&mut self) -> Option { + self.vk_image_view.as_ref().map(|view| FrameMeta { extent: view.image().extent(), ..Default::default() }) diff --git a/src/shaders/mod.rs b/src/shaders/mod.rs index 15d835e..4de46d1 100644 --- a/src/shaders/mod.rs +++ b/src/shaders/mod.rs @@ -46,6 +46,7 @@ pub mod frag_color { ", } } + //layout (location = 1) in float corner_radius; //out_color = in_color; // Some equation that determines whether to keep the pixel @@ -176,17 +177,14 @@ pub mod frag_screen { layout (location = 0) out vec4 out_color; layout (set = 0, binding = 0) uniform sampler2D in_texture; + layout (set = 1, binding = 0) uniform AlphaBlock { + uniform float alpha; + }; void main() { out_color = texture(in_texture, in_uv); - - // linear to srgb - bvec4 cutoff = lessThan(out_color, vec4(0.0031308)); - vec4 higher = (pow(out_color, vec4(0.4166667)) * vec4(1.055)) - vec4(0.055); - vec4 lower = out_color*vec4(12.92); - out_color = mix(higher, lower, cutoff); - out_color.a = 1.0; + out_color.a = alpha; } ", } @@ -216,7 +214,7 @@ pub mod frag_srgb { vec4 lower = out_color/vec4(12.92); out_color = mix(higher, lower, cutoff); - out_color.a = alpha; + out_color.a *= alpha; } ", } @@ -244,3 +242,25 @@ pub mod frag_swapchain { ", } } + +pub mod frag_line { + vulkano_shaders::shader! { + ty: "fragment", + src: r"#version 310 es + precision highp float; + + layout (location = 0) in vec2 in_uv; + layout (location = 0) out vec4 out_color; + + layout (set = 0, binding = 0) uniform ColorBlock { + uniform vec4 in_color; + uniform vec2 unused; + }; + + void main() + { + out_color = in_color; + } + ", + } +} diff --git a/src/state.rs b/src/state.rs index 314a2df..6a3395f 100644 --- a/src/state.rs +++ b/src/state.rs @@ -26,8 +26,8 @@ use crate::{ hid::HidProvider, overlays::toast::{DisplayMethod, ToastTopic}, shaders::{ - frag_color, frag_glyph, frag_grid, frag_screen, frag_sprite, frag_sprite2, frag_sprite2_hl, - frag_swapchain, vert_common, + frag_color, frag_glyph, frag_grid, frag_line, frag_screen, frag_sprite, frag_sprite2, + frag_sprite2_hl, frag_srgb, frag_swapchain, vert_common, }, }; @@ -74,6 +74,12 @@ impl AppState { let shader = frag_color::load(graphics.device.clone())?; shaders.insert("frag_color", shader); + let shader = frag_line::load(graphics.device.clone())?; + shaders.insert("frag_line", shader); + + let shader = frag_srgb::load(graphics.device.clone())?; + shaders.insert("frag_srgb", shader); + let shader = frag_glyph::load(graphics.device.clone())?; shaders.insert("frag_glyph", shader); @@ -114,7 +120,7 @@ impl AppState { let toast_sound_wav = AppState::try_load_bytes( &session.config.notification_sound, - include_bytes!("res/557297.wav") + include_bytes!("res/557297.wav"), ); Ok(AppState { @@ -153,39 +159,28 @@ impl AppState { } } - pub fn try_load_bytes(path: &str, fallback_data: &'static [u8]) -> &'static [u8] - { + pub fn try_load_bytes(path: &str, fallback_data: &'static [u8]) -> &'static [u8] { if path.is_empty() { return fallback_data; } - let real_path = config_io::get_config_root().join(&*path); + let real_path = config_io::get_config_root().join(path); if std::fs::File::open(real_path.clone()).is_err() { - log::warn!( - "Could not open file at: {}", - path - ); + log::warn!("Could not open file at: {}", path); return fallback_data; }; - return match std::fs::read(real_path.clone()){ + match std::fs::read(real_path.clone()) { // Box is used here to work around `f`'s limited lifetime - Ok(f) => { - Box::leak(Box::new(f)).as_slice() - }, + Ok(f) => Box::leak(Box::new(f)).as_slice(), Err(e) => { - log::warn!( - "Failed to read file at: {}", - path - ); + log::warn!("Failed to read file at: {}", path); log::warn!("{:?}", e); fallback_data } - }; - + } } - } pub struct AppSession {