rework rendering backend

This commit is contained in:
galister
2025-04-06 22:12:56 +09:00
parent 55867e803f
commit cf29682e66
27 changed files with 1135 additions and 1181 deletions

10
Cargo.lock generated
View File

@@ -263,16 +263,16 @@ dependencies = [
[[package]] [[package]]
name = "ashpd" name = "ashpd"
version = "0.10.2" version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e9c39d707614dbcc6bed00015539f488d8e3fe3e66ed60961efc0c90f4b380b3" checksum = "6cbdf310d77fd3aaee6ea2093db7011dc2d35d2eb3481e5607f1f8d942ed99df"
dependencies = [ dependencies = [
"async-fs", "async-fs",
"async-net", "async-net",
"enumflags2", "enumflags2",
"futures-channel", "futures-channel",
"futures-util", "futures-util",
"rand 0.8.5", "rand 0.9.0",
"serde", "serde",
"serde_repr", "serde_repr",
"url", "url",
@@ -5165,8 +5165,8 @@ dependencies = [
[[package]] [[package]]
name = "wlx-capture" name = "wlx-capture"
version = "0.4.2" version = "0.5.0"
source = "git+https://github.com/galister/wlx-capture?tag=v0.4.2#514a81c0cfd900edb89ae4ec33f927848a49eae3" source = "git+https://github.com/galister/wlx-capture?tag=v0.5.0#fc77da75dcf8c9d59cdeea384da21bdab685d18b"
dependencies = [ dependencies = [
"ashpd", "ashpd",
"drm-fourcc", "drm-fourcc",

View File

@@ -54,7 +54,7 @@ sysinfo = { version = "0.32.0" }
thiserror = "2.0.3" thiserror = "2.0.3"
vulkano = { git = "https://github.com/vulkano-rs/vulkano", rev = "94f50f1" } vulkano = { git = "https://github.com/vulkano-rs/vulkano", rev = "94f50f1" }
vulkano-shaders = { 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 } libmonado = { git = "https://github.com/technobaboo/libmonado-rs", rev = "256895b18c8f9368174fad8a6232ff07764eeacb", optional = true }
winit = { version = "0.30.0", optional = true } winit = { version = "0.30.0", optional = true }
xdg = "2.5.2" xdg = "2.5.2"

View File

@@ -429,7 +429,7 @@ where
.is_some_and(|x| x.grabbed_id == hit.overlay) .is_some_and(|x| x.grabbed_id == hit.overlay)
{ {
let can_curve = hovered let can_curve = hovered
.frame_transform() .frame_meta()
.is_some_and(|e| e.extent[0] >= e.extent[1]); .is_some_and(|e| e.extent[0] >= e.extent[1]);
if can_curve { if can_curve {

View File

@@ -12,9 +12,10 @@ use vulkano::image::view::ImageView;
use vulkano::image::ImageLayout; use vulkano::image::ImageLayout;
use crate::backend::overlay::{ 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 crate::state::AppState;
use super::overlay::OpenVrOverlayData; use super::overlay::OpenVrOverlayData;
@@ -185,14 +186,20 @@ impl OverlayRenderer for StaticRenderer {
fn resume(&mut self, _app: &mut AppState) -> anyhow::Result<()> { fn resume(&mut self, _app: &mut AppState) -> anyhow::Result<()> {
Ok(()) Ok(())
} }
fn render(&mut self, _app: &mut AppState) -> anyhow::Result<()> { fn should_render(&mut self, _app: &mut AppState) -> anyhow::Result<ShouldRender> {
Ok(()) Ok(ShouldRender::Unable)
} }
fn view(&mut self) -> Option<Arc<ImageView>> { fn render(
Some(self.view.clone()) &mut self,
_app: &mut AppState,
_tgt: Arc<ImageView>,
_buf: &mut CommandBuffers,
_alpha: f32,
) -> anyhow::Result<bool> {
Ok(false)
} }
fn frame_transform(&mut self) -> Option<FrameTransform> { fn frame_meta(&mut self) -> Option<FrameMeta> {
Some(FrameTransform { Some(FrameMeta {
extent: self.view.image().extent(), extent: self.view.image().extent(),
..Default::default() ..Default::default()
}) })

View File

@@ -31,10 +31,10 @@ use crate::{
manifest::{install_manifest, uninstall_manifest}, manifest::{install_manifest, uninstall_manifest},
overlay::OpenVrOverlayData, overlay::OpenVrOverlayData,
}, },
overlay::OverlayData, overlay::{OverlayData, ShouldRender},
task::{SystemTask, TaskType}, task::{SystemTask, TaskType},
}, },
graphics::WlxGraphics, graphics::{CommandBuffers, WlxGraphics},
overlays::{ overlays::{
toast::{Toast, ToastTopic}, toast::{Toast, ToastTopic},
watch::{watch_fade, WATCH_NAME}, watch::{watch_fade, WATCH_NAME},
@@ -321,6 +321,7 @@ pub fn openvr_run(running: Arc<AtomicBool>, show_by_default: bool) -> Result<(),
} }
state.hid_provider.commit(); state.hid_provider.commit();
let mut buffers = CommandBuffers::default();
lines.update(universe.clone(), &mut overlay_mgr, &mut state)?; lines.update(universe.clone(), &mut overlay_mgr, &mut state)?;
@@ -344,12 +345,30 @@ pub fn openvr_run(running: Arc<AtomicBool>, show_by_default: bool) -> Result<(),
for o in overlays.iter_mut() { for o in overlays.iter_mut() {
if o.state.want_visible { 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"); 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 overlays
.iter_mut() .iter_mut()
.for_each(|o| o.after_render(universe.clone(), &mut overlay_mgr, &state.graphics)); .for_each(|o| o.after_render(universe.clone(), &mut overlay_mgr, &state.graphics));

View File

@@ -1,4 +1,5 @@
use core::f32; use core::f32;
use std::sync::Arc;
use glam::Vec4; use glam::Vec4;
use ovr_overlay::{ use ovr_overlay::{
@@ -6,7 +7,7 @@ use ovr_overlay::{
pose::Matrix3x4, pose::Matrix3x4,
sys::{ETrackingUniverseOrigin, VRVulkanTextureData_t}, sys::{ETrackingUniverseOrigin, VRVulkanTextureData_t},
}; };
use vulkano::{Handle, VulkanObject}; use vulkano::{image::view::ImageView, Handle, VulkanObject};
use crate::{ use crate::{
backend::overlay::{OverlayData, RelativeTo}, backend::overlay::{OverlayData, RelativeTo},
@@ -19,12 +20,13 @@ use super::helpers::Affine3AConvert;
#[derive(Default)] #[derive(Default)]
pub(super) struct OpenVrOverlayData { pub(super) struct OpenVrOverlayData {
pub(super) handle: Option<OverlayHandle>, pub(super) handle: Option<OverlayHandle>,
pub(super) last_image: Option<u64>,
pub(super) visible: bool, pub(super) visible: bool,
pub(super) color: Vec4, pub(super) color: Vec4,
pub(crate) width: f32, pub(crate) width: f32,
pub(super) override_width: bool, pub(super) override_width: bool,
pub(super) relative_to: RelativeTo, pub(super) relative_to: RelativeTo,
pub(super) image_view: Option<Arc<ImageView>>,
pub(super) image_dirty: bool,
} }
impl OverlayData<OpenVrOverlayData> { impl OverlayData<OpenVrOverlayData> {
@@ -61,6 +63,20 @@ impl OverlayData<OpenVrOverlayData> {
Ok(handle) Ok(handle)
} }
pub(super) fn ensure_image_allocated(&mut self, app: &mut AppState) -> anyhow::Result<bool> {
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( pub(super) fn after_input(
&mut self, &mut self,
overlay: &mut OverlayManager, overlay: &mut OverlayManager,
@@ -200,7 +216,7 @@ impl OverlayData<OpenVrOverlayData> {
let effective = self.state.transform let effective = self.state.transform
* self * self
.backend .backend
.frame_transform() .frame_meta()
.map(|f| f.transform) .map(|f| f.transform)
.unwrap_or_default(); .unwrap_or_default();
@@ -221,21 +237,17 @@ impl OverlayData<OpenVrOverlayData> {
return; 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); log::debug!("{}: Not rendered", self.state.name);
return; return;
}; };
let image = view.image().clone(); if !self.data.image_dirty {
return;
let raw_image = image.handle().as_raw();
if let Some(last_image) = self.data.last_image {
if last_image == raw_image {
return;
}
} }
self.data.image_dirty = false;
let image = view.image().clone();
let dimensions = image.extent(); let dimensions = image.extent();
if !self.data.override_width { if !self.data.override_width {
let new_width = ((dimensions[0] as f32) / (dimensions[1] as f32)).min(1.0); let new_width = ((dimensions[0] as f32) / (dimensions[1] as f32)).min(1.0);
@@ -246,6 +258,7 @@ impl OverlayData<OpenVrOverlayData> {
} }
} }
let raw_image = image.handle().as_raw();
let format = image.format(); let format = image.format();
let mut texture = VRVulkanTextureData_t { let mut texture = VRVulkanTextureData_t {

View File

@@ -475,7 +475,7 @@ fn is_bool(maybe_type_str: &Option<String>) -> bool {
.as_ref() .as_ref()
.unwrap() // want panic .unwrap() // want panic
.split('/') .split('/')
.last() .next_back()
.map(|last| matches!(last, "click" | "touch")) .map(|last| matches!(last, "click" | "touch"))
.unwrap_or(false) .unwrap_or(false)
} }

View File

@@ -9,55 +9,53 @@ use std::{
}, },
}; };
use vulkano::{command_buffer::CommandBufferUsage, format::Format, image::view::ImageView}; use vulkano::command_buffer::CommandBufferUsage;
use crate::{ use crate::{
backend::openxr::helpers, backend::openxr::helpers,
graphics::{WlxCommandBuffer, WlxGraphics}, graphics::{CommandBuffers, WlxGraphics, WlxPipeline, SWAPCHAIN_FORMAT},
}; };
use super::{ use super::{
swapchain::{create_swapchain_render_data, SwapchainOpts, SwapchainRenderData}, swapchain::{create_swapchain, SwapchainOpts, WlxSwapchain},
CompositionLayer, XrState, CompositionLayer, XrState,
}; };
static LINE_AUTO_INCREMENT: AtomicUsize = AtomicUsize::new(1); static LINE_AUTO_INCREMENT: AtomicUsize = AtomicUsize::new(1);
pub(super) const LINE_WIDTH: f32 = 0.002; 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 { pub(super) struct LinePool {
lines: IdMap<usize, LineContainer>, lines: IdMap<usize, LineContainer>,
colors: Vec<Arc<ImageView>>, pipeline: Arc<WlxPipeline>,
} }
impl LinePool { impl LinePool {
pub(super) fn new(graphics: Arc<WlxGraphics>) -> anyhow::Result<Self> { pub(super) fn new(graphics: Arc<WlxGraphics>) -> anyhow::Result<Self> {
let mut command_buffer = let Ok(shaders) = graphics.shared_shaders.read() else {
graphics.create_command_buffer(CommandBufferUsage::OneTimeSubmit)?; anyhow::bail!("Failed to lock shared shaders for reading");
};
// TODO customizable colors let pipeline = graphics.create_pipeline(
let colors = [ shaders.get("vert_common").unwrap().clone(), // want panic
[0xff, 0xff, 0xff, 0xff], shaders.get("frag_color").unwrap().clone(), // want panic
[0x00, 0x60, 0x80, 0xff], SWAPCHAIN_FORMAT,
[0xB0, 0x30, 0x00, 0xff], None,
[0x60, 0x00, 0x80, 0xff], )?;
[0xff, 0x00, 0x00, 0xff],
];
let views: anyhow::Result<Vec<Arc<ImageView>>> = 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()?;
Ok(LinePool { Ok(LinePool {
lines: IdMap::new(), lines: IdMap::new(),
colors: views?, pipeline,
}) })
} }
@@ -68,8 +66,7 @@ impl LinePool {
) -> anyhow::Result<usize> { ) -> anyhow::Result<usize> {
let id = LINE_AUTO_INCREMENT.fetch_add(1, Ordering::Relaxed); let id = LINE_AUTO_INCREMENT.fetch_add(1, Ordering::Relaxed);
let srd = let srd = create_swapchain(xr, graphics, [1, 1, 1], SwapchainOpts::new())?;
create_swapchain_render_data(xr, graphics, [1, 1, 1], SwapchainOpts::new().srgb())?;
self.lines.insert( self.lines.insert(
id, id,
LineContainer { LineContainer {
@@ -92,7 +89,7 @@ impl LinePool {
return; return;
} }
debug_assert!(color < self.colors.len()); debug_assert!(color < COLORS.len());
let Some(line) = self.lines.get_mut(id) else { let Some(line) = self.lines.get_mut(id) else {
log::warn!("Line {} not found", id); log::warn!("Line {} not found", id);
@@ -125,28 +122,55 @@ impl LinePool {
let posef = helpers::transform_to_posef(&transform); let posef = helpers::transform_to_posef(&transform);
line.maybe_line = Some(Line { line.maybe_line = Some(Line {
view: self.colors[color].clone(), color,
pose: posef, pose: posef,
length: len, length: len,
}); });
} }
pub(super) fn present_xr<'a>( pub(super) fn render(
&mut self,
graphics: Arc<WlxGraphics>,
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, &'a mut self,
xr: &'a XrState, xr: &'a XrState,
command_buffer: &mut WlxCommandBuffer,
) -> anyhow::Result<Vec<CompositionLayer<'a>>> { ) -> anyhow::Result<Vec<CompositionLayer<'a>>> {
let mut quads = Vec::new(); let mut quads = Vec::new();
for line in self.lines.values_mut() { for line in self.lines.values_mut() {
line.swapchain.ensure_image_released()?;
if let Some(inner) = line.maybe_line.take() { if let Some(inner) = line.maybe_line.take() {
let quad = xr::CompositionLayerQuad::new() let quad = xr::CompositionLayerQuad::new()
.pose(inner.pose) .pose(inner.pose)
.sub_image(line.swapchain.acquire_present_release( .sub_image(line.swapchain.get_subimage())
command_buffer,
inner.view,
1.0,
)?)
.eye_visibility(xr::EyeVisibility::BOTH) .eye_visibility(xr::EyeVisibility::BOTH)
.space(&xr.stage) .space(&xr.stage)
.size(xr::Extent2Df { .size(xr::Extent2Df {
@@ -171,12 +195,12 @@ impl LinePool {
} }
pub(super) struct Line { pub(super) struct Line {
pub(super) view: Arc<ImageView>, pub(super) color: usize,
pub(super) pose: xr::Posef, pub(super) pose: xr::Posef,
pub(super) length: f32, pub(super) length: f32,
} }
struct LineContainer { struct LineContainer {
swapchain: SwapchainRenderData, swapchain: WlxSwapchain,
maybe_line: Option<Line>, maybe_line: Option<Line>,
} }

View File

@@ -12,7 +12,7 @@ use glam::{Affine3A, Vec3};
use libmonado::Monado; use libmonado::Monado;
use openxr as xr; use openxr as xr;
use skybox::create_skybox; use skybox::create_skybox;
use vulkano::{command_buffer::CommandBufferUsage, Handle, VulkanObject}; use vulkano::{Handle, VulkanObject};
use crate::{ use crate::{
backend::{ backend::{
@@ -20,10 +20,10 @@ use crate::{
input::interact, input::interact,
notifications::NotificationManager, notifications::NotificationManager,
openxr::{lines::LinePool, overlay::OpenXrOverlayData}, openxr::{lines::LinePool, overlay::OpenXrOverlayData},
overlay::OverlayData, overlay::{OverlayData, ShouldRender},
task::{SystemTask, TaskType}, task::{SystemTask, TaskType},
}, },
graphics::WlxGraphics, graphics::{CommandBuffers, WlxGraphics},
overlays::{ overlays::{
toast::{Toast, ToastTopic}, toast::{Toast, ToastTopic},
watch::{watch_fade, WATCH_NAME}, watch::{watch_fade, WATCH_NAME},
@@ -364,23 +364,6 @@ pub fn openxr_run(running: Arc<AtomicBool>, 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")] #[cfg(feature = "wayvr")]
if let Err(e) = if let Err(e) =
crate::overlays::wayvr::tick_events::<OpenXrOverlayData>(&mut app_state, &mut overlays) crate::overlays::wayvr::tick_events::<OpenXrOverlayData>(&mut app_state, &mut overlays)
@@ -388,7 +371,17 @@ pub fn openxr_run(running: Arc<AtomicBool>, show_by_default: bool) -> Result<(),
log::error!("WayVR tick_events failed: {:?}", e); 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() { for o in overlays.iter_mut() {
o.data.cur_visible = false;
if !o.state.want_visible { if !o.state.want_visible {
continue; continue;
} }
@@ -398,37 +391,85 @@ pub fn openxr_run(running: Arc<AtomicBool>, show_by_default: bool) -> Result<(),
o.data.init = true; 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) let dist_sq = (app_state.input_state.hmd.translation - o.state.transform.translation)
.length_squared() .length_squared()
+ (100f32 - o.state.z_order as f32); + (100f32 - o.state.z_order as f32);
if !dist_sq.is_normal() { if !dist_sq.is_normal() {
o.data.swapchain.as_mut().unwrap().ensure_image_released()?;
continue; continue;
} }
let maybe_layer = o.present(&xr_state)?;
let maybe_layer = o.present_xr(&xr_state, &mut command_buffer)?;
if let CompositionLayer::None = maybe_layer { if let CompositionLayer::None = maybe_layer {
continue; continue;
} }
layers.push((dist_sq, maybe_layer)); 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 { if let CompositionLayer::None = maybe_layer {
continue; continue;
} }
layers.push((0.0, maybe_layer)); layers.push((0.0, maybe_layer));
} }
// End layer composition
#[cfg(feature = "wayvr")] #[cfg(feature = "wayvr")]
if let Some(wayvr) = &app_state.wayvr { if let Some(wayvr) = &app_state.wayvr {
wayvr.borrow_mut().data.tick_finish()?; 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)); layers.sort_by(|a, b| b.0.total_cmp(&a.0));
let frame_ref = layers let frame_ref = layers
@@ -446,6 +487,7 @@ pub fn openxr_run(running: Arc<AtomicBool>, show_by_default: bool) -> Result<(),
environment_blend_mode, environment_blend_mode,
&frame_ref, &frame_ref,
)?; )?;
// End layer submit
let removed_overlays = overlays.update(&mut app_state)?; let removed_overlays = overlays.update(&mut app_state)?;
for o in removed_overlays { for o in removed_overlays {

View File

@@ -1,74 +1,72 @@
use glam::Vec3A; use glam::Vec3A;
use openxr::{self as xr, CompositionLayerFlags}; use openxr::{self as xr, CompositionLayerFlags};
use std::{f32::consts::PI, sync::Arc}; use std::f32::consts::PI;
use xr::EyeVisibility; use xr::EyeVisibility;
use super::{helpers, swapchain::SwapchainRenderData, CompositionLayer, XrState}; use super::{helpers, swapchain::WlxSwapchain, CompositionLayer, XrState};
use crate::{ use crate::{
backend::{ backend::{
openxr::swapchain::{create_swapchain_render_data, SwapchainOpts}, openxr::swapchain::{create_swapchain, SwapchainOpts},
overlay::OverlayData, overlay::OverlayData,
}, },
graphics::WlxCommandBuffer,
state::AppState, state::AppState,
}; };
use vulkano::image::view::ImageView;
#[derive(Default)] #[derive(Default)]
pub struct OpenXrOverlayData { pub struct OpenXrOverlayData {
last_view: Option<Arc<ImageView>>,
last_visible: bool, last_visible: bool,
pub(super) swapchain: Option<SwapchainRenderData>, pub(super) swapchain: Option<WlxSwapchain>,
pub(super) init: bool, pub(super) init: bool,
pub(super) cur_visible: bool,
pub(super) last_alpha: f32,
} }
impl OverlayData<OpenXrOverlayData> { impl OverlayData<OpenXrOverlayData> {
pub(super) fn present_xr<'a>( pub(super) fn ensure_swapchain<'a>(
&'a mut self, &'a mut self,
app: &AppState,
xr: &'a XrState, xr: &'a XrState,
command_buffer: &mut WlxCommandBuffer, ) -> anyhow::Result<bool> {
) -> anyhow::Result<CompositionLayer<'a>> { if self.data.swapchain.is_some() {
if let Some(new_view) = self.view() { return Ok(true);
self.data.last_view = Some(new_view);
} }
let my_view = if let Some(view) = self.data.last_view.as_ref() { let Some(meta) = self.frame_meta() else {
view.clone() log::warn!(
} else { "{}: swapchain cannot be created due to missing metadata",
log::warn!("{}: Will not show - image not ready", self.state.name); 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<CompositionLayer<'a>> {
let Some(swapchain) = self.data.swapchain.as_mut() else {
log::warn!("{}: swapchain not ready", self.state.name);
return Ok(CompositionLayer::None); 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 sub_image = swapchain.get_subimage();
let extent = frame_transform.extent; let transform = self.state.transform * self.backend.frame_meta().unwrap().transform; // contract
let data = match self.data.swapchain { let aspect_ratio = swapchain.extent[1] as f32 / swapchain.extent[0] as f32;
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 (scale_x, scale_y) = if aspect_ratio < 1.0 { let (scale_x, scale_y) = if aspect_ratio < 1.0 {
let major = transform.matrix3.col(0).length(); let major = transform.matrix3.col(0).length();
(major, major * aspect_ratio) (major, major * aspect_ratio)

View File

@@ -1,25 +1,29 @@
use std::{f32::consts::PI, fs::File, sync::Arc}; use std::{f32::consts::PI, fs::File, sync::Arc};
use glam::{Affine3A, Quat, Vec3A}; use glam::{Quat, Vec3A};
use once_cell::sync::Lazy; use once_cell::sync::Lazy;
use openxr::{self as xr, CompositionLayerFlags}; 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::{ use crate::{
backend::openxr::{helpers::translation_rotation_to_posef, swapchain::SwapchainOpts}, backend::openxr::{helpers::translation_rotation_to_posef, swapchain::SwapchainOpts},
config_io, config_io,
graphics::{dds::WlxCommandBufferDds, format_is_srgb, WlxCommandBuffer}, graphics::{dds::WlxCommandBufferDds, CommandBuffers, SWAPCHAIN_FORMAT},
state::AppState, state::AppState,
}; };
use super::{ use super::{
swapchain::{create_swapchain_render_data, SwapchainRenderData}, swapchain::{create_swapchain, WlxSwapchain},
CompositionLayer, XrState, CompositionLayer, XrState,
}; };
pub(super) struct Skybox { pub(super) struct Skybox {
view: Arc<ImageView>, view: Arc<ImageView>,
srd: Option<(SwapchainRenderData, SwapchainRenderData)>, sky: Option<WlxSwapchain>,
grid: Option<WlxSwapchain>,
} }
impl Skybox { impl Skybox {
@@ -66,53 +70,127 @@ impl Skybox {
let view = ImageView::new_default(maybe_image.unwrap())?; // safe unwrap 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, &'a mut self,
xr: &'a XrState, xr: &'a XrState,
hmd: Affine3A, app: &AppState,
command_buffer: &mut WlxCommandBuffer, buf: &mut CommandBuffers,
) -> anyhow::Result<Vec<CompositionLayer<'a>>> { ) -> anyhow::Result<()> {
let (sky_image, grid_image) = if let Some((ref mut srd_sky, ref mut srd_grid)) = self.srd { if self.sky.is_some() {
(srd_sky.present_last()?, srd_grid.present_last()?) return Ok(());
} else { }
log::debug!("Render skybox."); let opts = SwapchainOpts::new().immutable();
let mut opts = SwapchainOpts::new().immutable(); let Ok(shaders) = app.graphics.shared_shaders.read() else {
opts.srgb = format_is_srgb(self.view.image().format()); anyhow::bail!("Failed to lock shared shaders for reading");
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 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<Vec<CompositionLayer<'a>>> {
static GRID_POSE: Lazy<xr::Posef> = Lazy::new(|| {
translation_rotation_to_posef(Vec3A::ZERO, Quat::from_rotation_x(PI * -0.5))
});
let pose = xr::Posef { let pose = xr::Posef {
orientation: xr::Quaternionf::IDENTITY, orientation: xr::Quaternionf::IDENTITY,
position: xr::Vector3f { position: xr::Vector3f {
x: hmd.translation.x, x: app.input_state.hmd.translation.x,
y: hmd.translation.y, y: app.input_state.hmd.translation.y,
z: hmd.translation.z, z: app.input_state.hmd.translation.z,
}, },
}; };
@@ -121,25 +199,20 @@ impl Skybox {
const HI_VERT_ANGLE: f32 = 0.5 * PI; const HI_VERT_ANGLE: f32 = 0.5 * PI;
const LO_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() let sky = xr::CompositionLayerEquirect2KHR::new()
.layer_flags(CompositionLayerFlags::BLEND_TEXTURE_SOURCE_ALPHA) .layer_flags(CompositionLayerFlags::BLEND_TEXTURE_SOURCE_ALPHA)
.pose(pose) .pose(pose)
.radius(10.0) .radius(10.0)
.sub_image(sky_image) .sub_image(self.sky.as_ref().unwrap().get_subimage())
.eye_visibility(xr::EyeVisibility::BOTH) .eye_visibility(xr::EyeVisibility::BOTH)
.space(&xr.stage) .space(&xr.stage)
.central_horizontal_angle(HORIZ_ANGLE) .central_horizontal_angle(HORIZ_ANGLE)
.upper_vertical_angle(HI_VERT_ANGLE) .upper_vertical_angle(HI_VERT_ANGLE)
.lower_vertical_angle(LO_VERT_ANGLE); .lower_vertical_angle(LO_VERT_ANGLE);
layers.push(CompositionLayer::Equirect2(sky)); self.grid.as_mut().unwrap().ensure_image_released()?;
static GRID_POSE: Lazy<xr::Posef> = Lazy::new(|| {
translation_rotation_to_posef(Vec3A::ZERO, Quat::from_rotation_x(PI * -0.5))
});
let grid = xr::CompositionLayerQuad::new() let grid = xr::CompositionLayerQuad::new()
.layer_flags(CompositionLayerFlags::BLEND_TEXTURE_SOURCE_ALPHA) .layer_flags(CompositionLayerFlags::BLEND_TEXTURE_SOURCE_ALPHA)
.pose(*GRID_POSE) .pose(*GRID_POSE)
@@ -147,13 +220,14 @@ impl Skybox {
width: 10.0, width: 10.0,
height: 10.0, height: 10.0,
}) })
.sub_image(grid_image) .sub_image(self.grid.as_ref().unwrap().get_subimage())
.eye_visibility(xr::EyeVisibility::BOTH) .eye_visibility(xr::EyeVisibility::BOTH)
.space(&xr.stage); .space(&xr.stage);
layers.push(CompositionLayer::Quad(grid)); Ok(vec![
CompositionLayer::Equirect2(sky),
Ok(layers) CompositionLayer::Quad(grid),
])
} }
} }

View File

@@ -1,26 +1,21 @@
use std::sync::Arc; use std::sync::Arc;
use anyhow::bail;
use ash::vk; use ash::vk;
use openxr as xr; use openxr as xr;
use smallvec::SmallVec; use smallvec::SmallVec;
use vulkano::{ use vulkano::{
format::Format,
image::{sys::RawImage, view::ImageView, ImageCreateInfo, ImageUsage}, image::{sys::RawImage, view::ImageView, ImageCreateInfo, ImageUsage},
pipeline::graphics::color_blend::AttachmentBlend,
Handle, Handle,
}; };
use crate::graphics::{WlxCommandBuffer, WlxGraphics, WlxPipeline, WlxPipelineDynamic}; use crate::graphics::{WlxGraphics, SWAPCHAIN_FORMAT};
use super::XrState; use super::XrState;
#[derive(Default)] #[derive(Default)]
pub(super) struct SwapchainOpts { pub(super) struct SwapchainOpts {
pub immutable: bool, pub immutable: bool,
pub srgb: bool,
pub grid: bool,
} }
impl SwapchainOpts { impl SwapchainOpts {
@@ -31,22 +26,14 @@ impl SwapchainOpts {
self.immutable = true; self.immutable = true;
self 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, xr: &XrState,
graphics: Arc<WlxGraphics>, graphics: Arc<WlxGraphics>,
extent: [u32; 3], extent: [u32; 3],
opts: SwapchainOpts, opts: SwapchainOpts,
) -> anyhow::Result<SwapchainRenderData> { ) -> anyhow::Result<WlxSwapchain> {
let create_flags = if opts.immutable { let create_flags = if opts.immutable {
xr::SwapchainCreateFlags::STATIC_IMAGE xr::SwapchainCreateFlags::STATIC_IMAGE
} else { } else {
@@ -56,7 +43,7 @@ pub(super) fn create_swapchain_render_data(
let swapchain = xr.session.create_swapchain(&xr::SwapchainCreateInfo { let swapchain = xr.session.create_swapchain(&xr::SwapchainCreateInfo {
create_flags, create_flags,
usage_flags: xr::SwapchainUsageFlags::COLOR_ATTACHMENT | xr::SwapchainUsageFlags::SAMPLED, usage_flags: xr::SwapchainUsageFlags::COLOR_ATTACHMENT | xr::SwapchainUsageFlags::SAMPLED,
format: Format::R8G8B8A8_SRGB as _, format: SWAPCHAIN_FORMAT as _,
sample_count: 1, sample_count: 1,
width: extent[0], width: extent[0],
height: extent[1], height: extent[1],
@@ -65,28 +52,6 @@ pub(super) fn create_swapchain_render_data(
mip_count: 1, 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 let images = swapchain
.enumerate_images()? .enumerate_images()?
.into_iter() .into_iter()
@@ -98,7 +63,7 @@ pub(super) fn create_swapchain_render_data(
graphics.device.clone(), graphics.device.clone(),
vk_image, vk_image,
ImageCreateInfo { ImageCreateInfo {
format: image_fmt, // actually SRGB but we lie format: SWAPCHAIN_FORMAT as _,
extent, extent,
usage: ImageUsage::COLOR_ATTACHMENT, usage: ImageUsage::COLOR_ATTACHMENT,
..Default::default() ..Default::default()
@@ -111,118 +76,51 @@ pub(super) fn create_swapchain_render_data(
}) })
.collect::<anyhow::Result<SmallVec<[Arc<ImageView>; 4]>>>()?; .collect::<anyhow::Result<SmallVec<[Arc<ImageView>; 4]>>>()?;
Ok(SwapchainRenderData { Ok(WlxSwapchain {
acquired: false,
ever_acquired: false,
swapchain, swapchain,
pipeline,
images, images,
extent, 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<xr::Vulkan>, pub(super) swapchain: xr::Swapchain<xr::Vulkan>,
pub(super) pipeline: Arc<WlxPipeline<WlxPipelineDynamic>>,
pub(super) extent: [u32; 3], pub(super) extent: [u32; 3],
pub(super) target_extent: [u32; 3],
pub(super) images: SmallVec<[Arc<ImageView>; 4]>, pub(super) images: SmallVec<[Arc<ImageView>; 4]>,
} }
impl SwapchainRenderData { impl WlxSwapchain {
pub(super) fn acquire_present_release( pub(super) fn acquire_wait_image(&mut self) -> anyhow::Result<Arc<ImageView>> {
&mut self,
command_buffer: &mut WlxCommandBuffer,
view: Arc<ImageView>,
alpha: f32,
) -> anyhow::Result<xr::SwapchainSubImage<xr::Vulkan>> {
let idx = self.swapchain.acquire_image()? as usize; let idx = self.swapchain.acquire_image()? as usize;
self.swapchain.wait_image(xr::Duration::INFINITE)?; self.swapchain.wait_image(xr::Duration::INFINITE)?;
self.ever_acquired = true;
let render_target = &mut self.images[idx]; self.acquired = true;
command_buffer.begin_rendering(render_target.clone())?; Ok(self.images[idx].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))
} }
pub(super) fn acquire_compute_release( pub(super) fn ensure_image_released(&mut self) -> anyhow::Result<()> {
&mut self, if self.acquired {
command_buffer: &mut WlxCommandBuffer, self.swapchain.release_image()?;
) -> anyhow::Result<xr::SwapchainSubImage<xr::Vulkan>> { self.acquired = false;
let idx = self.swapchain.acquire_image()? as usize; }
self.swapchain.wait_image(xr::Duration::INFINITE)?; Ok(())
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 present_last(&self) -> anyhow::Result<xr::SwapchainSubImage<xr::Vulkan>> { pub(super) fn get_subimage(&self) -> xr::SwapchainSubImage<xr::Vulkan> {
debug_assert!( debug_assert!(self.ever_acquired, "swapchain was never acquired!");
self.target_extent[0] * self.target_extent[1] != 0, xr::SwapchainSubImage::new()
"present_last: target_extent zero"
);
Ok(xr::SwapchainSubImage::new()
.swapchain(&self.swapchain) .swapchain(&self.swapchain)
.image_rect(xr::Rect2Di { .image_rect(xr::Rect2Di {
offset: xr::Offset2Di { x: 0, y: 0 }, offset: xr::Offset2Di { x: 0, y: 0 },
extent: xr::Extent2Di { extent: xr::Extent2Di {
width: self.target_extent[0] as _, width: self.extent[0] as _,
height: self.target_extent[1] as _, height: self.extent[1] as _,
}, },
}) })
.image_array_index(0)) .image_array_index(0)
} }
} }

View File

@@ -9,10 +9,11 @@ use std::{
use anyhow::Ok; use anyhow::Ok;
use glam::{Affine2, Affine3A, Mat3A, Quat, Vec2, Vec3, Vec3A}; use glam::{Affine2, Affine3A, Mat3A, Quat, Vec2, Vec3, Vec3A};
use serde::Deserialize; use serde::Deserialize;
use vulkano::image::view::ImageView; use vulkano::{format::Format, image::view::ImageView};
use crate::{ use crate::{
config::AStrMapExt, config::AStrMapExt,
graphics::CommandBuffers,
state::{AppState, KeyboardFocus}, state::{AppState, KeyboardFocus},
}; };
@@ -234,14 +235,20 @@ where
} }
self.backend.init(app) self.backend.init(app)
} }
pub fn render(&mut self, app: &mut AppState) -> anyhow::Result<()> { pub fn should_render(&mut self, app: &mut AppState) -> anyhow::Result<ShouldRender> {
self.backend.render(app) self.backend.should_render(app)
} }
pub fn view(&mut self) -> Option<Arc<ImageView>> { pub fn render(
self.backend.view() &mut self,
app: &mut AppState,
tgt: Arc<ImageView>,
buf: &mut CommandBuffers,
alpha: f32,
) -> anyhow::Result<bool> {
self.backend.render(app, tgt, buf, alpha)
} }
pub fn frame_transform(&mut self) -> Option<FrameTransform> { pub fn frame_meta(&mut self) -> Option<FrameMeta> {
self.backend.frame_transform() self.backend.frame_meta()
} }
pub fn set_visible(&mut self, app: &mut AppState, visible: bool) -> anyhow::Result<()> { pub fn set_visible(&mut self, app: &mut AppState, visible: bool) -> anyhow::Result<()> {
let old_visible = self.state.want_visible; let old_visible = self.state.want_visible;
@@ -257,10 +264,17 @@ where
} }
} }
#[derive(Default)] #[derive(Default, Clone, Copy)]
pub struct FrameTransform { pub struct FrameMeta {
pub extent: [u32; 3], pub extent: [u32; 3],
pub transform: Affine3A, pub transform: Affine3A,
pub format: Format,
}
pub enum ShouldRender {
Should,
Can,
Unable,
} }
pub trait OverlayRenderer { pub trait OverlayRenderer {
@@ -268,15 +282,24 @@ pub trait OverlayRenderer {
fn init(&mut self, app: &mut AppState) -> anyhow::Result<()>; fn init(&mut self, app: &mut AppState) -> anyhow::Result<()>;
fn pause(&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<()>; fn resume(&mut self, app: &mut AppState) -> anyhow::Result<()>;
/// Called when the presentation layer is ready to present a new frame /// Called when the presentation layer is ready to present a new frame
fn render(&mut self, app: &mut AppState) -> anyhow::Result<()>; fn should_render(&mut self, app: &mut AppState) -> anyhow::Result<ShouldRender>;
/// Called to retrieve the current image to be displayed
fn view(&mut self) -> Option<Arc<ImageView>>; /// Called when the contents need to be rendered to the swapchain
fn render(
&mut self,
app: &mut AppState,
tgt: Arc<ImageView>,
buf: &mut CommandBuffers,
alpha: f32,
) -> anyhow::Result<bool>;
/// Called to retrieve the effective extent of the image /// Called to retrieve the effective extent of the image
/// Used for creating swapchains. /// Used for creating swapchains.
/// ///
/// Muse not be None if view() is also not None /// Must be true if should_render was also true on the same frame.
fn frame_transform(&mut self) -> Option<FrameTransform>; fn frame_meta(&mut self) -> Option<FrameMeta>;
} }
pub struct FallbackRenderer; pub struct FallbackRenderer;
@@ -291,13 +314,19 @@ impl OverlayRenderer for FallbackRenderer {
fn resume(&mut self, _app: &mut AppState) -> anyhow::Result<()> { fn resume(&mut self, _app: &mut AppState) -> anyhow::Result<()> {
Ok(()) Ok(())
} }
fn render(&mut self, _app: &mut AppState) -> anyhow::Result<()> { fn should_render(&mut self, _app: &mut AppState) -> anyhow::Result<ShouldRender> {
Ok(()) Ok(ShouldRender::Unable)
} }
fn view(&mut self) -> Option<Arc<ImageView>> { fn render(
None &mut self,
_app: &mut AppState,
_tgt: Arc<ImageView>,
_buf: &mut CommandBuffers,
_alpha: f32,
) -> anyhow::Result<bool> {
Ok(false)
} }
fn frame_transform(&mut self) -> Option<FrameTransform> { fn frame_meta(&mut self) -> Option<FrameMeta> {
None None
} }
} }
@@ -348,16 +377,23 @@ impl OverlayRenderer for SplitOverlayBackend {
fn resume(&mut self, app: &mut AppState) -> anyhow::Result<()> { fn resume(&mut self, app: &mut AppState) -> anyhow::Result<()> {
self.renderer.resume(app) self.renderer.resume(app)
} }
fn render(&mut self, app: &mut AppState) -> anyhow::Result<()> { fn should_render(&mut self, app: &mut AppState) -> anyhow::Result<ShouldRender> {
self.renderer.render(app) self.renderer.should_render(app)
} }
fn view(&mut self) -> Option<Arc<ImageView>> { fn render(
self.renderer.view() &mut self,
app: &mut AppState,
tgt: Arc<ImageView>,
buf: &mut CommandBuffers,
alpha: f32,
) -> anyhow::Result<bool> {
self.renderer.render(app, tgt, buf, alpha)
} }
fn frame_transform(&mut self) -> Option<FrameTransform> { fn frame_meta(&mut self) -> Option<FrameMeta> {
self.renderer.frame_transform() self.renderer.frame_meta()
} }
} }
impl InteractionHandler for SplitOverlayBackend { impl InteractionHandler for SplitOverlayBackend {
fn on_left(&mut self, app: &mut AppState, pointer: usize) { fn on_left(&mut self, app: &mut AppState, pointer: usize) {
self.interaction.on_left(app, pointer); self.interaction.on_left(app, pointer);

View File

@@ -1,7 +1,6 @@
use std::sync::Arc; use std::sync::Arc;
use vulkano::{ use vulkano::{
command_buffer::CommandBufferUsage,
image::{view::ImageView, ImageUsage}, image::{view::ImageView, ImageUsage},
swapchain::{ swapchain::{
acquire_next_image, Surface, Swapchain, SwapchainCreateInfo, SwapchainPresentInfo, acquire_next_image, Surface, Swapchain, SwapchainCreateInfo, SwapchainPresentInfo,
@@ -19,7 +18,7 @@ use winit::{
use crate::{ use crate::{
config::load_custom_ui, config::load_custom_ui,
config_io, config_io,
graphics::{DynamicPass, DynamicPipeline, WlxGraphics, BLEND_ALPHA}, graphics::{CommandBuffers, WlxGraphics},
gui::{ gui::{
canvas::Canvas, canvas::Canvas,
modular::{modular_canvas, ModularData}, modular::{modular_canvas, ModularData},
@@ -37,8 +36,6 @@ static LAST_SIZE: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::n
struct PreviewState { struct PreviewState {
canvas: Canvas<(), ModularData>, canvas: Canvas<(), ModularData>,
pipeline: Arc<DynamicPipeline>,
pass: DynamicPass,
swapchain: Arc<Swapchain>, swapchain: Arc<Swapchain>,
images: Vec<Arc<ImageView>>, images: Vec<Arc<ImageView>>,
} }
@@ -63,7 +60,7 @@ impl PreviewState {
window.set_min_inner_size(Some(logical_size)); window.set_min_inner_size(Some(logical_size));
window.set_max_inner_size(Some(logical_size)); window.set_max_inner_size(Some(logical_size));
LAST_SIZE.store( 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, std::sync::atomic::Ordering::Relaxed,
); );
} }
@@ -75,34 +72,9 @@ impl PreviewState {
let mut canvas = modular_canvas(&config.size, &config.elements, state)?; let mut canvas = modular_canvas(&config.size, &config.elements, state)?;
canvas.init(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 { Ok(PreviewState {
canvas, canvas,
pipeline,
pass,
swapchain, swapchain,
images, images,
}) })
@@ -132,6 +104,7 @@ pub fn uidev_run(panel_name: &str) -> anyhow::Result<()> {
let mut recreate = false; let mut recreate = false;
let mut last_draw = std::time::Instant::now(); let mut last_draw = std::time::Instant::now();
#[allow(deprecated)]
event_loop.run(move |event, elwt| { event_loop.run(move |event, elwt| {
elwt.set_control_flow(ControlFlow::Poll); 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}"), 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}"); log::error!("failed to render canvas: {e}");
window.request_redraw(); 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(); last_draw = std::time::Instant::now();
let command_buffer = cmd_buf.build().unwrap(); canvas_cmd_buf
vulkano::sync::now(graphics.device.clone()) .execute_after(state.graphics.queue.clone(), Box::new(acquire_future))
.join(acquire_future)
.then_execute(graphics.queue.clone(), command_buffer)
.unwrap() .unwrap()
.then_swapchain_present( .then_swapchain_present(
graphics.queue.clone(), graphics.queue.clone(),

View File

@@ -55,9 +55,5 @@ pub fn load(filename: &str) -> Option<String> {
let path = get_config_file_path(filename); let path = get_config_file_path(filename);
log::info!("Loading config: {}", path.to_string_lossy()); log::info!("Loading config: {}", path.to_string_lossy());
if let Ok(data) = std::fs::read_to_string(path) { std::fs::read_to_string(path).ok()
Some(data)
} else {
None
}
} }

View File

@@ -20,12 +20,6 @@ use {ash::vk, std::os::raw::c_void};
pub type Vert2Buf = Subbuffer<[Vert2Uv]>; pub type Vert2Buf = Subbuffer<[Vert2Uv]>;
pub type IndexBuf = Subbuffer<[u16]>; pub type IndexBuf = Subbuffer<[u16]>;
pub type LegacyPipeline = WlxPipeline<WlxPipelineLegacy>;
pub type DynamicPipeline = WlxPipeline<WlxPipelineDynamic>;
pub type LegacyPass = WlxPass<WlxPipelineLegacy>;
pub type DynamicPass = WlxPass<WlxPipelineDynamic>;
use vulkano::{ use vulkano::{
buffer::{ buffer::{
allocator::{SubbufferAllocator, SubbufferAllocatorCreateInfo}, allocator::{SubbufferAllocator, SubbufferAllocatorCreateInfo},
@@ -35,10 +29,9 @@ use vulkano::{
allocator::{StandardCommandBufferAllocator, StandardCommandBufferAllocatorCreateInfo}, allocator::{StandardCommandBufferAllocator, StandardCommandBufferAllocatorCreateInfo},
sys::{CommandBufferBeginInfo, RawRecordingCommandBuffer}, sys::{CommandBufferBeginInfo, RawRecordingCommandBuffer},
CommandBuffer, CommandBufferExecFuture, CommandBufferInheritanceInfo, CommandBuffer, CommandBufferExecFuture, CommandBufferInheritanceInfo,
CommandBufferInheritanceRenderPassInfo, CommandBufferInheritanceRenderPassType, CommandBufferInheritanceRenderPassType, CommandBufferInheritanceRenderingInfo,
CommandBufferInheritanceRenderingInfo, CommandBufferLevel, CommandBufferUsage, CommandBufferLevel, CommandBufferUsage, CopyBufferToImageInfo, RecordingCommandBuffer,
CopyBufferToImageInfo, RecordingCommandBuffer, RenderPassBeginInfo, RenderingAttachmentInfo, RenderingInfo, SubpassContents,
RenderingAttachmentInfo, RenderingInfo, SubpassBeginInfo, SubpassContents, SubpassEndInfo,
}, },
descriptor_set::{ descriptor_set::{
allocator::StandardDescriptorSetAllocator, DescriptorSet, WriteDescriptorSet, allocator::StandardDescriptorSetAllocator, DescriptorSet, WriteDescriptorSet,
@@ -52,8 +45,7 @@ use vulkano::{
sampler::{Filter, Sampler, SamplerAddressMode, SamplerCreateInfo}, sampler::{Filter, Sampler, SamplerAddressMode, SamplerCreateInfo},
sys::RawImage, sys::RawImage,
view::ImageView, view::ImageView,
Image, ImageCreateInfo, ImageLayout, ImageTiling, ImageType, ImageUsage, SampleCount, Image, ImageCreateInfo, ImageLayout, ImageTiling, ImageType, ImageUsage, SubresourceLayout,
SubresourceLayout,
}, },
instance::{Instance, InstanceCreateInfo, InstanceExtensions}, instance::{Instance, InstanceCreateInfo, InstanceExtensions},
memory::{ memory::{
@@ -80,11 +72,7 @@ use vulkano::{
layout::PipelineDescriptorSetLayoutCreateInfo, layout::PipelineDescriptorSetLayoutCreateInfo,
DynamicState, GraphicsPipeline, Pipeline, PipelineBindPoint, PipelineLayout, DynamicState, GraphicsPipeline, Pipeline, PipelineBindPoint, PipelineLayout,
}, },
render_pass::{ render_pass::{AttachmentLoadOp, AttachmentStoreOp},
AttachmentDescription, AttachmentLoadOp, AttachmentReference, AttachmentStoreOp,
Framebuffer, FramebufferCreateInfo, RenderPass, RenderPassCreateInfo, Subpass,
SubpassDescription,
},
shader::ShaderModule, shader::ShaderModule,
sync::{ sync::{
fence::Fence, future::NowFuture, AccessFlags, DependencyInfo, GpuFuture, fence::Fence, future::NowFuture, AccessFlags, DependencyInfo, GpuFuture,
@@ -109,6 +97,8 @@ pub struct Vert2Uv {
pub in_uv: [f32; 2], 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 INDICES: [u16; 6] = [2, 1, 0, 1, 2, 3];
pub const BLEND_ALPHA: AttachmentBlend = AttachmentBlend { pub const BLEND_ALPHA: AttachmentBlend = AttachmentBlend {
@@ -546,6 +536,7 @@ impl WlxGraphics {
Ok(Arc::new(me)) Ok(Arc::new(me))
} }
#[allow(clippy::type_complexity)]
#[cfg(feature = "uidev")] #[cfg(feature = "uidev")]
pub fn new_window() -> anyhow::Result<( pub fn new_window() -> anyhow::Result<(
Arc<Self>, Arc<Self>,
@@ -572,6 +563,7 @@ impl WlxGraphics {
}, },
)?; )?;
#[allow(deprecated)]
let window = Arc::new( let window = Arc::new(
event_loop event_loop
.create_window(Window::default_attributes()) .create_window(Window::default_attributes())
@@ -925,54 +917,12 @@ impl WlxGraphics {
pub fn create_pipeline( pub fn create_pipeline(
self: &Arc<Self>, self: &Arc<Self>,
render_target: Arc<ImageView>,
vert: Arc<ShaderModule>, vert: Arc<ShaderModule>,
frag: Arc<ShaderModule>, frag: Arc<ShaderModule>,
format: Format, format: Format,
blend: Option<AttachmentBlend>, blend: Option<AttachmentBlend>,
) -> anyhow::Result<Arc<LegacyPipeline>> { ) -> anyhow::Result<Arc<WlxPipeline>> {
Ok(Arc::new(LegacyPipeline::new( Ok(Arc::new(WlxPipeline::new(
render_target,
self.clone(),
vert,
frag,
format,
blend,
)?))
}
#[allow(clippy::too_many_arguments)]
pub fn create_pipeline_with_layouts(
self: &Arc<Self>,
render_target: Arc<ImageView>,
vert: Arc<ShaderModule>,
frag: Arc<ShaderModule>,
format: Format,
blend: Option<AttachmentBlend>,
initial_layout: ImageLayout,
final_layout: ImageLayout,
) -> anyhow::Result<Arc<LegacyPipeline>> {
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<Self>,
vert: Arc<ShaderModule>,
frag: Arc<ShaderModule>,
format: Format,
blend: Option<AttachmentBlend>,
) -> anyhow::Result<Arc<DynamicPipeline>> {
Ok(Arc::new(DynamicPipeline::new(
self.clone(), self.clone(),
vert, vert,
frag, frag,
@@ -1001,7 +951,6 @@ impl WlxGraphics {
}) })
} }
#[allow(dead_code)]
pub fn transition_layout( pub fn transition_layout(
&self, &self,
image: Arc<Image>, image: Arc<Image>,
@@ -1066,22 +1015,7 @@ pub struct WlxCommandBuffer {
pub command_buffer: RecordingCommandBuffer, pub command_buffer: RecordingCommandBuffer,
} }
#[allow(dead_code)]
impl WlxCommandBuffer { 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<ImageView>) -> anyhow::Result<()> { pub fn begin_rendering(&mut self, render_target: Arc<ImageView>) -> anyhow::Result<()> {
self.command_buffer.begin_rendering(RenderingInfo { self.command_buffer.begin_rendering(RenderingInfo {
contents: SubpassContents::SecondaryCommandBuffers, contents: SubpassContents::SecondaryCommandBuffers,
@@ -1096,7 +1030,7 @@ impl WlxCommandBuffer {
Ok(()) Ok(())
} }
pub fn run_ref<D>(&mut self, pass: &WlxPass<D>) -> anyhow::Result<()> { pub fn run_ref(&mut self, pass: &WlxPass) -> anyhow::Result<()> {
self.command_buffer self.command_buffer
.execute_commands(pass.command_buffer.clone())?; .execute_commands(pass.command_buffer.clone())?;
Ok(()) Ok(())
@@ -1149,12 +1083,6 @@ impl WlxCommandBuffer {
Ok(image) 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<()> { pub fn end_rendering(&mut self) -> anyhow::Result<()> {
self.command_buffer.end_rendering()?; self.command_buffer.end_rendering()?;
Ok(()) Ok(())
@@ -1177,22 +1105,13 @@ impl WlxCommandBuffer {
} }
} }
pub struct WlxPipelineDynamic {} pub struct WlxPipeline {
pub struct WlxPipelineLegacy {
pub render_pass: Arc<RenderPass>,
pub framebuffer: Arc<Framebuffer>,
}
pub struct WlxPipeline<D> {
pub graphics: Arc<WlxGraphics>, pub graphics: Arc<WlxGraphics>,
pub pipeline: Arc<GraphicsPipeline>, pub pipeline: Arc<GraphicsPipeline>,
pub format: Format, pub format: Format,
pub data: D,
} }
#[allow(dead_code)] impl WlxPipeline {
impl WlxPipeline<WlxPipelineDynamic> {
fn new( fn new(
graphics: Arc<WlxGraphics>, graphics: Arc<WlxGraphics>,
vert: Arc<ShaderModule>, vert: Arc<ShaderModule>,
@@ -1248,7 +1167,6 @@ impl WlxPipeline<WlxPipelineDynamic> {
graphics, graphics,
pipeline, pipeline,
format, format,
data: WlxPipelineDynamic {},
}) })
} }
pub fn create_pass( pub fn create_pass(
@@ -1257,8 +1175,8 @@ impl WlxPipeline<WlxPipelineDynamic> {
vertex_buffer: Vert2Buf, vertex_buffer: Vert2Buf,
index_buffer: IndexBuf, index_buffer: IndexBuf,
descriptor_sets: Vec<Arc<DescriptorSet>>, descriptor_sets: Vec<Arc<DescriptorSet>>,
) -> anyhow::Result<DynamicPass> { ) -> anyhow::Result<WlxPass> {
DynamicPass::new( WlxPass::new(
self.clone(), self.clone(),
dimensions, dimensions,
vertex_buffer, vertex_buffer,
@@ -1266,178 +1184,24 @@ impl WlxPipeline<WlxPipelineDynamic> {
descriptor_sets, descriptor_sets,
) )
} }
}
impl WlxPipeline<WlxPipelineLegacy> { pub fn create_pass_for_target(
fn new(
render_target: Arc<ImageView>,
graphics: Arc<WlxGraphics>,
vert: Arc<ShaderModule>,
frag: Arc<ShaderModule>,
format: Format,
blend: Option<AttachmentBlend>,
) -> anyhow::Result<Self> {
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<ImageView>,
graphics: Arc<WlxGraphics>,
vert: Arc<ShaderModule>,
frag: Arc<ShaderModule>,
format: Format,
blend: Option<AttachmentBlend>,
initial_layout: ImageLayout,
final_layout: ImageLayout,
) -> anyhow::Result<Self> {
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<ImageView>,
render_pass: Arc<RenderPass>,
graphics: Arc<WlxGraphics>,
vert: Arc<ShaderModule>,
frag: Arc<ShaderModule>,
format: Format,
blend: Option<AttachmentBlend>,
) -> anyhow::Result<Self> {
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(
self: &Arc<Self>, self: &Arc<Self>,
dimensions: [f32; 2], tgt: Arc<ImageView>,
vertex_buffer: Vert2Buf,
index_buffer: IndexBuf,
descriptor_sets: Vec<Arc<DescriptorSet>>, descriptor_sets: Vec<Arc<DescriptorSet>>,
) -> anyhow::Result<LegacyPass> { ) -> anyhow::Result<WlxPass> {
LegacyPass::new( let extent = tgt.image().extent();
WlxPass::new(
self.clone(), self.clone(),
dimensions, [extent[0] as _, extent[1] as _],
vertex_buffer, self.graphics.quad_verts.clone(),
index_buffer, self.graphics.quad_indices.clone(),
descriptor_sets, descriptor_sets,
) )
} }
} }
impl<D> WlxPipeline<D> { impl WlxPipeline {
pub fn inner(&self) -> Arc<GraphicsPipeline> { pub fn inner(&self) -> Arc<GraphicsPipeline> {
self.pipeline.clone() self.pipeline.clone()
} }
@@ -1498,78 +1262,13 @@ impl<D> WlxPipeline<D> {
} }
} }
#[allow(dead_code)] pub struct WlxPass {
pub struct WlxPass<D> {
pipeline: Arc<WlxPipeline<D>>,
vertex_buffer: Vert2Buf,
index_buffer: IndexBuf,
descriptor_sets: Vec<Arc<DescriptorSet>>,
pub command_buffer: Arc<CommandBuffer>, pub command_buffer: Arc<CommandBuffer>,
} }
impl WlxPass<WlxPipelineLegacy> { impl WlxPass {
fn new( fn new(
pipeline: Arc<LegacyPipeline>, pipeline: Arc<WlxPipeline>,
dimensions: [f32; 2],
vertex_buffer: Vert2Buf,
index_buffer: IndexBuf,
descriptor_sets: Vec<Arc<DescriptorSet>>,
) -> anyhow::Result<Self> {
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<WlxPipelineDynamic> {
fn new(
pipeline: Arc<DynamicPipeline>,
dimensions: [f32; 2], dimensions: [f32; 2],
vertex_buffer: Vert2Buf, vertex_buffer: Vert2Buf,
index_buffer: IndexBuf, index_buffer: IndexBuf,
@@ -1616,15 +1315,57 @@ impl WlxPass<WlxPipelineDynamic> {
}; };
Ok(Self { Ok(Self {
pipeline,
vertex_buffer,
index_buffer,
descriptor_sets,
command_buffer: command_buffer.end()?, command_buffer: command_buffer.end()?,
}) })
} }
} }
#[derive(Default)]
pub struct CommandBuffers {
inner: Vec<Arc<CommandBuffer>>,
}
impl CommandBuffers {
pub fn push(&mut self, buffer: Arc<CommandBuffer>) {
self.inner.push(buffer);
}
pub fn execute_now(self, queue: Arc<Queue>) -> anyhow::Result<Option<Box<dyn GpuFuture>>> {
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<dyn GpuFuture> = 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<Queue>,
future: Box<dyn GpuFuture>,
) -> anyhow::Result<Box<dyn GpuFuture>> {
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<dyn GpuFuture> = 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<Format> { pub fn fourcc_to_vk(fourcc: FourCC) -> anyhow::Result<Format> {
match fourcc.value { match fourcc.value {
DRM_FORMAT_ABGR8888 => Ok(Format::R8G8B8A8_UNORM), DRM_FORMAT_ABGR8888 => Ok(Format::R8G8B8A8_UNORM),
@@ -1675,15 +1416,3 @@ fn memory_allocator(device: Arc<Device>) -> Arc<StandardMemoryAllocator> {
Arc::new(StandardMemoryAllocator::new(device, create_info)) 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
)
}

View File

@@ -30,7 +30,8 @@ pub(crate) struct Control<D, S> {
pub size: isize, pub size: isize,
pub sprite: Option<Arc<ImageView>>, pub sprite: Option<Arc<ImageView>>,
pub sprite_st: Vec4, pub sprite_st: Vec4,
pub(super) dirty: bool, pub(super) bg_dirty: bool,
pub(super) fg_dirty: bool,
pub on_update: Option<fn(&mut Self, &mut D, &mut AppState)>, pub on_update: Option<fn(&mut Self, &mut D, &mut AppState)>,
pub on_press: Option<fn(&mut Self, &mut D, &mut AppState, PointerMode)>, pub on_press: Option<fn(&mut Self, &mut D, &mut AppState, PointerMode)>,
@@ -58,7 +59,8 @@ impl<D, S> Control<D, S> {
text: Arc::from(""), text: Arc::from(""),
sprite: None, sprite: None,
sprite_st: Vec4::new(1., 1., 0., 0.), sprite_st: Vec4::new(1., 1., 0., 0.),
dirty: true, bg_dirty: true,
fg_dirty: true,
size: 24, size: 24,
state: None, state: None,
on_update: None, on_update: None,
@@ -78,13 +80,13 @@ impl<D, S> Control<D, S> {
return; return;
} }
self.text = text.into(); self.text = text.into();
self.dirty = true; self.fg_dirty = true;
} }
#[inline(always)] #[inline(always)]
pub fn set_sprite(&mut self, sprite: Arc<ImageView>) { pub fn set_sprite(&mut self, sprite: Arc<ImageView>) {
self.sprite.replace(sprite); self.sprite.replace(sprite);
self.dirty = true; self.bg_dirty = true;
} }
#[inline(always)] #[inline(always)]
@@ -93,7 +95,7 @@ impl<D, S> Control<D, S> {
return; return;
} }
self.sprite_st = sprite_st; self.sprite_st = sprite_st;
self.dirty = true; self.bg_dirty = true;
} }
#[inline(always)] #[inline(always)]
@@ -102,7 +104,7 @@ impl<D, S> Control<D, S> {
return; return;
} }
self.fg_color = color; self.fg_color = color;
self.dirty = true; self.fg_dirty = true;
} }
pub fn render_rounded_rect( pub fn render_rounded_rect(
@@ -174,7 +176,7 @@ impl<D, S> Control<D, S> {
let skew_radius = [clamped_radius / self.rect.w, clamped_radius / self.rect.h]; 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, 0,
vec![ vec![
color.x, color.x,
@@ -186,7 +188,7 @@ impl<D, S> Control<D, S> {
], ],
)?; )?;
let pass = canvas.pipeline_bg_color.create_pass( let pass = canvas.pipeline_hl_color.create_pass(
[canvas.width as _, canvas.height as _], [canvas.width as _, canvas.height as _],
vertex_buffer.clone(), vertex_buffer.clone(),
canvas.graphics.quad_indices.clone(), canvas.graphics.quad_indices.clone(),

View File

@@ -4,18 +4,14 @@ pub(crate) mod control;
use std::sync::Arc; use std::sync::Arc;
use glam::{Vec2, Vec4}; use glam::{Vec2, Vec4};
use vulkano::{ use vulkano::{command_buffer::CommandBufferUsage, format::Format, image::view::ImageView};
command_buffer::CommandBufferUsage,
format::Format,
image::{view::ImageView, ImageLayout},
};
use crate::{ use crate::{
backend::{ backend::{
input::{Haptics, InteractionHandler, PointerHit}, 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, state::AppState,
}; };
@@ -35,11 +31,11 @@ pub struct CanvasData<D> {
graphics: Arc<WlxGraphics>, graphics: Arc<WlxGraphics>,
pipeline_bg_color: Arc<WlxPipeline<WlxPipelineLegacy>>, pipeline_bg_color: Arc<WlxPipeline>,
pipeline_fg_glyph: Arc<WlxPipeline<WlxPipelineLegacy>>, pipeline_bg_sprite: Arc<WlxPipeline>,
pipeline_bg_sprite: Arc<WlxPipeline<WlxPipelineLegacy>>, pipeline_fg_glyph: Arc<WlxPipeline>,
pipeline_hl_sprite: Arc<WlxPipeline<WlxPipelineLegacy>>, pipeline_hl_color: Arc<WlxPipeline>,
pipeline_final: Arc<WlxPipeline<WlxPipelineLegacy>>, pipeline_hl_sprite: Arc<WlxPipeline>,
} }
pub struct Canvas<D, S> { pub struct Canvas<D, S> {
@@ -53,10 +49,16 @@ pub struct Canvas<D, S> {
interact_stride: usize, interact_stride: usize,
interact_rows: usize, interact_rows: usize,
view_final: Arc<ImageView>, pipeline_final: Arc<WlxPipeline>,
pass_fg: WlxPass<WlxPipelineLegacy>, view_fg: Arc<ImageView>,
pass_bg: WlxPass<WlxPipelineLegacy>, view_bg: Arc<ImageView>,
format: Format,
bg_dirty: bool,
hl_dirty: bool,
fg_dirty: bool,
} }
impl<D, S> Canvas<D, S> { impl<D, S> Canvas<D, S> {
@@ -69,76 +71,56 @@ impl<D, S> Canvas<D, S> {
) -> anyhow::Result<Self> { ) -> anyhow::Result<Self> {
let tex_fg = graphics.render_texture(width as _, height as _, format)?; let tex_fg = graphics.render_texture(width as _, height as _, format)?;
let tex_bg = 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_fg = ImageView::new_default(tex_fg.clone())?;
let view_bg = ImageView::new_default(tex_bg.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 { let Ok(shaders) = graphics.shared_shaders.read() else {
anyhow::bail!("Failed to lock shared shaders for reading"); 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( let pipeline_bg_color = graphics.create_pipeline(
view_bg.clone(), vert.clone(),
shaders.get("vert_common").unwrap().clone(), // want panic shaders.get("frag_color").unwrap().clone(), // want panic
shaders.get("frag_color").unwrap().clone(), // want panic
format, format,
Some(BLEND_ALPHA), Some(BLEND_ALPHA),
)?; )?;
let pipeline_fg_glyph = graphics.create_pipeline( let pipeline_fg_glyph = graphics.create_pipeline(
view_fg.clone(), vert.clone(),
shaders.get("vert_common").unwrap().clone(), // want panic shaders.get("frag_glyph").unwrap().clone(), // want panic
shaders.get("frag_glyph").unwrap().clone(), // want panic
format, format,
Some(BLEND_ALPHA), Some(BLEND_ALPHA),
)?; )?;
let pipeline_bg_sprite = graphics.create_pipeline( let pipeline_bg_sprite = graphics.create_pipeline(
view_fg.clone(), vert.clone(),
shaders.get("vert_common").unwrap().clone(), // want panic
shaders.get("frag_sprite2").unwrap().clone(), // want panic shaders.get("frag_sprite2").unwrap().clone(), // want panic
format, format,
Some(BLEND_ALPHA), 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( let pipeline_hl_sprite = graphics.create_pipeline(
view_fg.clone(), vert.clone(),
shaders.get("vert_common").unwrap().clone(), // want panic
shaders.get("frag_sprite2_hl").unwrap().clone(), // want panic shaders.get("frag_sprite2_hl").unwrap().clone(), // want panic
format, SWAPCHAIN_FORMAT,
Some(BLEND_ALPHA), Some(BLEND_ALPHA),
)?; )?;
let vertex_buffer = let pipeline_final = graphics.create_pipeline(
graphics.upload_verts(width as _, height as _, 0., 0., width as _, height as _)?; vert.clone(),
shaders.get("frag_srgb").unwrap().clone(), // want panic
let pipeline_final = graphics.create_pipeline_with_layouts( SWAPCHAIN_FORMAT,
view_final.clone(),
shaders.get("vert_common").unwrap().clone(), // want panic
shaders.get("frag_sprite").unwrap().clone(), // want panic
format,
Some(BLEND_ALPHA), 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; let stride = width / RES_DIVIDER;
@@ -151,10 +133,10 @@ impl<D, S> Canvas<D, S> {
height, height,
graphics: graphics.clone(), graphics: graphics.clone(),
pipeline_bg_color, pipeline_bg_color,
pipeline_fg_glyph,
pipeline_bg_sprite, pipeline_bg_sprite,
pipeline_fg_glyph,
pipeline_hl_color,
pipeline_hl_sprite, pipeline_hl_sprite,
pipeline_final,
}, },
controls: Vec::new(), controls: Vec::new(),
hover_controls: [None, None], hover_controls: [None, None],
@@ -162,9 +144,13 @@ impl<D, S> Canvas<D, S> {
interact_map: vec![None; stride * rows], interact_map: vec![None; stride * rows],
interact_stride: stride, interact_stride: stride,
interact_rows: rows, interact_rows: rows,
view_final, pipeline_final,
pass_fg, view_fg,
pass_bg, view_bg,
format,
bg_dirty: false,
hl_dirty: false,
fg_dirty: false,
}) })
} }
@@ -191,36 +177,6 @@ impl<D, S> Canvas<D, S> {
self.interact_map[y * self.interact_stride + x].map(|x| x as usize) 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 { pub fn data_mut(&mut self) -> &mut D {
&mut self.canvas.data &mut self.canvas.data
} }
@@ -228,9 +184,14 @@ impl<D, S> Canvas<D, S> {
impl<D, S> InteractionHandler for Canvas<D, S> { impl<D, S> InteractionHandler for Canvas<D, S> {
fn on_left(&mut self, _app: &mut AppState, pointer: usize) { fn on_left(&mut self, _app: &mut AppState, pointer: usize) {
self.hl_dirty = true;
self.hover_controls[pointer] = None; self.hover_controls[pointer] = None;
} }
fn on_hover(&mut self, _app: &mut AppState, hit: &PointerHit) -> Option<Haptics> { fn on_hover(&mut self, _app: &mut AppState, hit: &PointerHit) -> Option<Haptics> {
// render on every frame if we are being hovered
self.hl_dirty = true;
let old = self.hover_controls[hit.pointer]; let old = self.hover_controls[hit.pointer];
if let Some(i) = self.interactive_get_idx(hit.uv) { if let Some(i) = self.interactive_get_idx(hit.uv) {
self.hover_controls[hit.pointer] = Some(i); self.hover_controls[hit.pointer] = Some(i);
@@ -280,9 +241,8 @@ impl<D, S> InteractionHandler for Canvas<D, S> {
} }
impl<D, S> OverlayRenderer for Canvas<D, S> { impl<D, S> OverlayRenderer for Canvas<D, S> {
fn init(&mut self, app: &mut AppState) -> anyhow::Result<()> { fn init(&mut self, _app: &mut AppState) -> anyhow::Result<()> {
self.render_bg(app)?; Ok(())
self.render_fg(app)
} }
fn pause(&mut self, _app: &mut AppState) -> anyhow::Result<()> { fn pause(&mut self, _app: &mut AppState) -> anyhow::Result<()> {
Ok(()) Ok(())
@@ -290,49 +250,84 @@ impl<D, S> OverlayRenderer for Canvas<D, S> {
fn resume(&mut self, _app: &mut AppState) -> anyhow::Result<()> { fn resume(&mut self, _app: &mut AppState) -> anyhow::Result<()> {
Ok(()) Ok(())
} }
fn render(&mut self, app: &mut AppState) -> anyhow::Result<()> { fn should_render(&mut self, app: &mut AppState) -> anyhow::Result<ShouldRender> {
let mut dirty = false;
for c in self.controls.iter_mut() { for c in self.controls.iter_mut() {
if let Some(fun) = c.on_update { if let Some(fun) = c.on_update {
fun(c, &mut self.canvas.data, app); fun(c, &mut self.canvas.data, app);
} }
if c.dirty { if c.fg_dirty {
dirty = true; self.fg_dirty = true;
c.dirty = false; c.fg_dirty = false;
}
if c.bg_dirty {
self.bg_dirty = true;
c.bg_dirty = false;
} }
} }
if dirty { if self.bg_dirty || self.fg_dirty || self.hl_dirty {
self.render_bg(app)?; Ok(ShouldRender::Should)
self.render_fg(app)?;
}
/*
let image = self.view_final.image().clone();
if self.first_render {
self.first_render = false;
} else { } else {
self.canvas Ok(ShouldRender::Can)
.graphics
.transition_layout(
image.clone(),
ImageLayout::TransferSrcOptimal,
ImageLayout::ColorAttachmentOptimal,
)
.wait(None)
.unwrap();
} }
*/ }
fn render(
&mut self,
app: &mut AppState,
tgt: Arc<ImageView>,
buf: &mut CommandBuffers,
alpha: f32,
) -> anyhow::Result<bool> {
self.hl_dirty = false;
let mut cmd_buffer = self let mut cmd_buffer = self
.canvas .canvas
.graphics .graphics
.create_command_buffer(CommandBufferUsage::OneTimeSubmit)?; .create_command_buffer(CommandBufferUsage::OneTimeSubmit)?;
cmd_buffer.begin_render_pass(&self.canvas.pipeline_final)?;
// static background if self.bg_dirty {
cmd_buffer.run_ref(&self.pass_bg)?; 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() { for (i, c) in self.controls.iter_mut().enumerate() {
if let Some(render) = c.on_render_hl { if let Some(render) = c.on_render_hl {
@@ -354,30 +349,17 @@ impl<D, S> OverlayRenderer for Canvas<D, S> {
} }
// mostly static text // mostly static text
cmd_buffer.run_ref(&self.pass_fg)?; cmd_buffer.run_ref(&pass_fg)?;
cmd_buffer.end_render_pass()?; cmd_buffer.end_rendering()?;
cmd_buffer.build_and_execute_now() buf.push(cmd_buffer.build()?);
Ok(true)
/*
self.canvas
.graphics
.transition_layout(
image,
ImageLayout::ColorAttachmentOptimal,
ImageLayout::TransferSrcOptimal,
)
.wait(None)
.unwrap();
*/
}
fn view(&mut self) -> Option<Arc<ImageView>> {
Some(self.view_final.clone())
} }
fn frame_transform(&mut self) -> Option<FrameTransform> { fn frame_meta(&mut self) -> Option<FrameMeta> {
Some(FrameTransform { Some(FrameMeta {
extent: self.view_final.image().extent(), extent: [self.canvas.width as _, self.canvas.height as _, 1],
format: self.format,
..Default::default() ..Default::default()
}) })
} }

View File

@@ -5,7 +5,7 @@ use freetype::{bitmap::PixelMode, face::LoadFlag, Face, Library};
use idmap::IdMap; use idmap::IdMap;
use vulkano::{command_buffer::CommandBufferUsage, format::Format, image::Image}; use vulkano::{command_buffer::CommandBufferUsage, format::Format, image::Image};
use crate::graphics::WlxGraphics; use crate::graphics::{WlxCommandBuffer, WlxGraphics};
pub struct FontCache { pub struct FontCache {
primary_font: Arc<str>, primary_font: Arc<str>,
@@ -56,13 +56,14 @@ impl FontCache {
let sizef = size as f32; let sizef = size as f32;
let height = sizef + ((text.lines().count() as f32) - 1f32) * (sizef * 1.5); let height = sizef + ((text.lines().count() as f32) - 1f32) * (sizef * 1.5);
let mut cmd_buffer = None;
let mut max_w = sizef * 0.33; let mut max_w = sizef * 0.33;
for line in text.lines() { for line in text.lines() {
let w: f32 = line let w: f32 = line
.chars() .chars()
.filter_map(|c| { .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) .map(|glyph| glyph.advance)
.ok() .ok()
}) })
@@ -72,6 +73,11 @@ impl FontCache {
max_w = w; max_w = w;
} }
} }
if let Some(cmd_buffer) = cmd_buffer {
cmd_buffer.build_and_execute_now()?;
}
Ok((max_w, height)) Ok((max_w, height))
} }
@@ -82,11 +88,23 @@ impl FontCache {
graphics: Arc<WlxGraphics>, graphics: Arc<WlxGraphics>,
) -> anyhow::Result<Vec<Rc<Glyph>>> { ) -> anyhow::Result<Vec<Rc<Glyph>>> {
let mut glyphs = Vec::new(); let mut glyphs = Vec::new();
let mut cmd_buffer = None;
for line in text.lines() { for line in text.lines() {
for c in line.chars() { 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) Ok(glyphs)
} }
@@ -184,6 +202,7 @@ impl FontCache {
cp: usize, cp: usize,
size: isize, size: isize,
graphics: Arc<WlxGraphics>, graphics: Arc<WlxGraphics>,
cmd_buffer: &mut Option<WlxCommandBuffer>,
) -> anyhow::Result<Rc<Glyph>> { ) -> anyhow::Result<Rc<Glyph>> {
let key = self.get_font_for_cp(cp, size); let key = self.get_font_for_cp(cp, size);
@@ -220,9 +239,16 @@ impl FontCache {
_ => return Ok(self.collections[size].zero_glyph.clone()), _ => return Ok(self.collections[size].zero_glyph.clone()),
}; };
let mut cmd_buffer = graphics.create_command_buffer(CommandBufferUsage::OneTimeSubmit)?; if cmd_buffer.is_none() {
let texture = cmd_buffer.texture2d_raw(bmp.width() as _, bmp.rows() as _, format, &buf)?; *cmd_buffer = Some(graphics.create_command_buffer(CommandBufferUsage::OneTimeSubmit)?);
cmd_buffer.build_and_execute_now()?; }
let texture = cmd_buffer.as_mut().unwrap().texture2d_raw(
bmp.width() as _,
bmp.rows() as _,
format,
&buf,
)?;
let g = Glyph { let g = Glyph {
tex: Some(texture), tex: Some(texture),

View File

@@ -127,7 +127,7 @@ pub fn modular_label_init(label: &mut ModularControl, content: &LabelContent, ap
} }
LabelContent::Timezone { timezone } => { LabelContent::Timezone { timezone } => {
if let Some(tz) = app.session.config.timezones.get(*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 { if let Some(pretty_tz) = pretty_tz {
label.set_text(&pretty_tz); label.set_text(&pretty_tz);

View File

@@ -2,14 +2,18 @@ use std::{
collections::HashMap, collections::HashMap,
process::{Child, Command}, process::{Child, Command},
str::FromStr, str::FromStr,
sync::Arc,
}; };
use crate::{ use crate::{
backend::{ backend::{
input::{InteractionHandler, PointerMode}, input::{InteractionHandler, PointerMode},
overlay::{FrameTransform, OverlayBackend, OverlayData, OverlayRenderer, OverlayState}, overlay::{
FrameMeta, OverlayBackend, OverlayData, OverlayRenderer, OverlayState, ShouldRender,
},
}, },
config::{self, ConfigType}, config::{self, ConfigType},
graphics::CommandBuffers,
gui::{ gui::{
canvas::{builder::CanvasBuilder, control::Control, Canvas}, canvas::{builder::CanvasBuilder, control::Control, Canvas},
color_parse, KeyCapType, color_parse, KeyCapType,
@@ -24,6 +28,7 @@ use glam::{vec2, vec3a, Affine2, Vec4};
use once_cell::sync::Lazy; use once_cell::sync::Lazy;
use regex::Regex; use regex::Regex;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use vulkano::image::view::ImageView;
const PIXELS_PER_UNIT: f32 = 80.; const PIXELS_PER_UNIT: f32 = 80.;
const BUTTON_PADDING: f32 = 4.; const BUTTON_PADDING: f32 = 4.;
@@ -543,14 +548,20 @@ impl OverlayRenderer for KeyboardBackend {
fn init(&mut self, app: &mut AppState) -> anyhow::Result<()> { fn init(&mut self, app: &mut AppState) -> anyhow::Result<()> {
self.canvas.init(app) self.canvas.init(app)
} }
fn render(&mut self, app: &mut AppState) -> anyhow::Result<()> { fn should_render(&mut self, app: &mut AppState) -> anyhow::Result<ShouldRender> {
self.canvas.render(app) self.canvas.should_render(app)
} }
fn frame_transform(&mut self) -> Option<FrameTransform> { fn render(
self.canvas.frame_transform() &mut self,
app: &mut AppState,
tgt: Arc<ImageView>,
buf: &mut CommandBuffers,
alpha: f32,
) -> anyhow::Result<bool> {
self.canvas.render(app, tgt, buf, alpha)
} }
fn view(&mut self) -> Option<std::sync::Arc<vulkano::image::view::ImageView>> { fn frame_meta(&mut self) -> Option<FrameMeta> {
self.canvas.view() self.canvas.frame_meta()
} }
fn pause(&mut self, app: &mut AppState) -> anyhow::Result<()> { fn pause(&mut self, app: &mut AppState) -> anyhow::Result<()> {
self.canvas.data_mut().modifiers = 0; self.canvas.data_mut().modifiers = 0;

View File

@@ -4,17 +4,19 @@ use std::{
}; };
use futures::{Future, FutureExt}; use futures::{Future, FutureExt};
use vulkano::image::view::ImageView;
use wlx_capture::pipewire::{pipewire_select_screen, PipewireCapture, PipewireSelectScreenResult}; use wlx_capture::pipewire::{pipewire_select_screen, PipewireCapture, PipewireSelectScreenResult};
use crate::{ use crate::{
backend::{ backend::{
common::OverlaySelector, common::OverlaySelector,
overlay::{ overlay::{
ui_transform, FrameTransform, OverlayBackend, OverlayRenderer, OverlayState, ui_transform, FrameMeta, OverlayBackend, OverlayRenderer, OverlayState, ShouldRender,
SplitOverlayBackend, SplitOverlayBackend,
}, },
task::TaskType, task::TaskType,
}, },
graphics::CommandBuffers,
state::{AppSession, AppState}, state::{AppSession, AppState},
}; };
@@ -45,7 +47,19 @@ impl OverlayRenderer for MirrorRenderer {
fn init(&mut self, _app: &mut AppState) -> anyhow::Result<()> { fn init(&mut self, _app: &mut AppState) -> anyhow::Result<()> {
Ok(()) Ok(())
} }
fn render(&mut self, app: &mut AppState) -> anyhow::Result<()> { fn should_render(&mut self, app: &mut AppState) -> anyhow::Result<ShouldRender> {
self.renderer
.as_mut()
.map(|r| r.should_render(app))
.unwrap_or(Ok(ShouldRender::Unable))
}
fn render(
&mut self,
app: &mut AppState,
tgt: Arc<ImageView>,
buf: &mut CommandBuffers,
alpha: f32,
) -> anyhow::Result<bool> {
if let Some(mut selector) = self.selector.take() { if let Some(mut selector) = self.selector.take() {
let maybe_pw_result = match selector let maybe_pw_result = match selector
.poll_unpin(&mut Context::from_waker(futures::task::noop_waker_ref())) .poll_unpin(&mut Context::from_waker(futures::task::noop_waker_ref()))
@@ -53,7 +67,7 @@ impl OverlayRenderer for MirrorRenderer {
Poll::Ready(result) => result, Poll::Ready(result) => result,
Poll::Pending => { Poll::Pending => {
self.selector = Some(selector); 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() { if let Some(renderer) = self.renderer.as_mut() {
renderer.render(app)?; result = renderer.render(app, tgt, buf, alpha)?;
if let Some(view) = renderer.view() { if let Some(meta) = renderer.frame_meta() {
let extent = view.image().extent(); let extent = meta.extent;
if self.last_extent != extent { if self.last_extent != extent {
self.last_extent = extent; self.last_extent = extent;
// resized // resized
@@ -104,7 +119,7 @@ impl OverlayRenderer for MirrorRenderer {
} }
} }
Ok(()) Ok(result)
} }
fn pause(&mut self, app: &mut AppState) -> anyhow::Result<()> { fn pause(&mut self, app: &mut AppState) -> anyhow::Result<()> {
if let Some(renderer) = self.renderer.as_mut() { if let Some(renderer) = self.renderer.as_mut() {
@@ -118,12 +133,9 @@ impl OverlayRenderer for MirrorRenderer {
} }
Ok(()) Ok(())
} }
fn view(&mut self) -> Option<std::sync::Arc<vulkano::image::view::ImageView>> {
self.renderer.as_mut().and_then(|r| r.view())
}
fn frame_transform(&mut self) -> Option<FrameTransform> { fn frame_meta(&mut self) -> Option<FrameMeta> {
Some(FrameTransform { Some(FrameMeta {
extent: self.last_extent, extent: self.last_extent,
..Default::default() ..Default::default()
}) })

View File

@@ -52,11 +52,12 @@ use glam::{vec2, vec3a, Affine2, Affine3A, Quat, Vec2, Vec3};
use crate::{ use crate::{
backend::{ backend::{
input::{Haptics, InteractionHandler, PointerHit, PointerMode}, input::{Haptics, InteractionHandler, PointerHit, PointerMode},
overlay::{FrameTransform, OverlayRenderer, OverlayState, SplitOverlayBackend}, overlay::{FrameMeta, OverlayRenderer, OverlayState, ShouldRender, SplitOverlayBackend},
}, },
config::{def_pw_tokens, GeneralConfig, PwTokenMap}, config::{def_pw_tokens, GeneralConfig, PwTokenMap},
graphics::{ 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}, hid::{MOUSE_LEFT, MOUSE_MIDDLE, MOUSE_RIGHT},
state::{AppSession, AppState, KeyboardFocus, ScreenMeta}, state::{AppSession, AppState, KeyboardFocus, ScreenMeta},
@@ -158,36 +159,27 @@ impl InteractionHandler for ScreenInteractionHandler {
#[derive(Clone)] #[derive(Clone)]
struct ScreenPipeline { struct ScreenPipeline {
view: Arc<ImageView>,
mouse: Option<Arc<ImageView>>, mouse: Option<Arc<ImageView>>,
pipeline: Arc<WlxPipeline<WlxPipelineLegacy>>, pipeline: Arc<WlxPipeline>,
extentf: [f32; 2], extentf: [f32; 2],
} }
impl ScreenPipeline { impl ScreenPipeline {
fn new(extent: &[u32; 3], app: &mut AppState) -> anyhow::Result<ScreenPipeline> { fn new(extent: &[u32; 3], app: &mut AppState) -> anyhow::Result<ScreenPipeline> {
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 { let Ok(shaders) = app.graphics.shared_shaders.read() else {
return Err(anyhow::anyhow!("Could not lock shared shaders for reading")); return Err(anyhow::anyhow!("Could not lock shared shaders for reading"));
}; };
let pipeline = app.graphics.create_pipeline( let pipeline = app.graphics.create_pipeline(
view.clone(),
shaders.get("vert_common").unwrap().clone(), // want panic shaders.get("vert_common").unwrap().clone(), // want panic
shaders.get("frag_screen").unwrap().clone(), // want panic shaders.get("frag_screen").unwrap().clone(), // want panic
app.graphics.native_format, SWAPCHAIN_FORMAT,
Some(AttachmentBlend::default()), Some(AttachmentBlend::default()),
)?; )?;
let extentf = [extent[0] as f32, extent[1] as f32]; let extentf = [extent[0] as f32, extent[1] as f32];
Ok(ScreenPipeline { Ok(ScreenPipeline {
view,
mouse: None, mouse: None,
pipeline, pipeline,
extentf, extentf,
@@ -216,25 +208,25 @@ impl ScreenPipeline {
fn render( fn render(
&mut self, &mut self,
image: Arc<Image>, image: Arc<Image>,
mouse: Option<&MouseMeta>, mouse: Option<MouseMeta>,
app: &mut AppState, app: &mut AppState,
tgt: Arc<ImageView>,
buf: &mut CommandBuffers,
alpha: f32,
) -> anyhow::Result<()> { ) -> anyhow::Result<()> {
let mut cmd = app
.graphics
.create_command_buffer(CommandBufferUsage::OneTimeSubmit)?;
let view = ImageView::new_default(image)?; let view = ImageView::new_default(image)?;
let set0 = self let set0 = self
.pipeline .pipeline
.uniform_sampler(0, view, app.graphics.texture_filtering)?; .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( let mut cmd = app
self.extentf, .graphics
app.graphics.quad_verts.clone(), .create_command_buffer(CommandBufferUsage::OneTimeSubmit)?;
app.graphics.quad_indices.clone(), cmd.begin_rendering(tgt)?;
vec![set0],
)?;
cmd.begin_render_pass(&self.pipeline)?;
cmd.run_ref(&pass)?; cmd.run_ref(&pass)?;
if let (Some(mouse), Some(mouse_view)) = (mouse, self.mouse.clone()) { if let (Some(mouse), Some(mouse_view)) = (mouse, self.mouse.clone()) {
@@ -264,30 +256,32 @@ impl ScreenPipeline {
cmd.run_ref(&pass)?; cmd.run_ref(&pass)?;
} }
cmd.end_render_pass()?; cmd.end_rendering()?;
cmd.build_and_execute_now() buf.push(cmd.build()?);
Ok(())
} }
} }
pub struct ScreenRenderer { pub struct ScreenRenderer {
name: Arc<str>, name: Arc<str>,
capture: Box<dyn WlxCapture>, capture: Box<dyn WlxCapture<WlxCaptureIn, WlxCaptureOut>>,
pipeline: Option<ScreenPipeline>, pipeline: Option<ScreenPipeline>,
last_view: Option<Arc<ImageView>>, cur_frame: Option<WlxCaptureOut>,
transform: Affine3A, meta: Option<FrameMeta>,
extent: Option<[u32; 3]>,
} }
impl ScreenRenderer { impl ScreenRenderer {
#[cfg(feature = "wayland")] #[cfg(feature = "wayland")]
pub fn new_raw(name: Arc<str>, capture: Box<dyn WlxCapture>) -> ScreenRenderer { pub fn new_raw(
name: Arc<str>,
capture: Box<dyn WlxCapture<WlxCaptureIn, WlxCaptureOut>>,
) -> ScreenRenderer {
ScreenRenderer { ScreenRenderer {
name, name,
capture, capture,
pipeline: None, pipeline: None,
last_view: None, cur_frame: None,
transform: Affine3A::IDENTITY, meta: None,
extent: None,
} }
} }
@@ -300,9 +294,8 @@ impl ScreenRenderer {
name: output.name.clone(), name: output.name.clone(),
capture: Box::new(capture), capture: Box::new(capture),
pipeline: None, pipeline: None,
last_view: None, cur_frame: None,
transform: Affine3A::IDENTITY, meta: None,
extent: None,
}) })
} }
@@ -315,9 +308,8 @@ impl ScreenRenderer {
name: output.name.clone(), name: output.name.clone(),
capture: Box::new(capture), capture: Box::new(capture),
pipeline: None, pipeline: None,
last_view: None, cur_frame: None,
transform: Affine3A::IDENTITY, meta: None,
extent: None,
}) })
} }
@@ -358,9 +350,8 @@ impl ScreenRenderer {
name: output.name.clone(), name: output.name.clone(),
capture: Box::new(capture), capture: Box::new(capture),
pipeline: None, pipeline: None,
last_view: None, cur_frame: None,
transform: Affine3A::IDENTITY, meta: None,
extent: None,
}, },
select_screen_result.restore_token, select_screen_result.restore_token,
)) ))
@@ -374,9 +365,158 @@ impl ScreenRenderer {
name: screen.name.clone(), name: screen.name.clone(),
capture: Box::new(capture), capture: Box::new(capture),
pipeline: None, pipeline: None,
last_view: None, cur_frame: None,
transform: Affine3A::IDENTITY, meta: None,
extent: None, }
}
}
#[derive(Clone)]
pub struct WlxCaptureIn {
name: Arc<str>,
graphics: Arc<WlxGraphics>,
}
#[derive(Clone)]
pub struct WlxCaptureOut {
image: Arc<Image>,
format: FrameFormat,
mouse: Option<MouseMeta>,
}
fn receive_callback(me: &WlxCaptureIn, frame: wlx_frame::WlxFrame) -> Option<WlxCaptureOut> {
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<()> { fn init(&mut self, _app: &mut AppState) -> anyhow::Result<()> {
Ok(()) Ok(())
} }
fn render(&mut self, app: &mut AppState) -> anyhow::Result<()> { fn should_render(&mut self, app: &mut AppState) -> anyhow::Result<ShouldRender> {
if !self.capture.is_ready() { if !self.capture.is_ready() {
let supports_dmabuf = app let supports_dmabuf = app
.graphics .graphics
@@ -399,7 +539,7 @@ impl OverlayRenderer for ScreenRenderer {
let capture_method = app.session.config.capture_method.clone(); 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(); let graphics = app.graphics.clone();
move || { move || {
if !supports_dmabuf { 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(); self.capture.request_new_frame();
}; return Ok(ShouldRender::Unable);
}
for frame in self.capture.receive().into_iter() { for frame in self.capture.receive().into_iter() {
match frame { self.cur_frame = Some(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();
}
};
} }
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<ImageView>,
buf: &mut CommandBuffers,
alpha: f32,
) -> anyhow::Result<bool> {
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<()> { fn pause(&mut self, _app: &mut AppState) -> anyhow::Result<()> {
self.capture.pause(); self.capture.pause();
@@ -600,14 +663,8 @@ impl OverlayRenderer for ScreenRenderer {
self.capture.resume(); self.capture.resume();
Ok(()) Ok(())
} }
fn view(&mut self) -> Option<Arc<ImageView>> { fn frame_meta(&mut self) -> Option<FrameMeta> {
self.last_view.clone() self.meta
}
fn frame_transform(&mut self) -> Option<FrameTransform> {
self.extent.map(|extent| FrameTransform {
extent,
transform: self.transform,
})
} }
} }
@@ -856,7 +913,7 @@ pub fn create_screens_x11pw(_app: &mut AppState) -> anyhow::Result<ScreenCreateD
#[cfg(all(feature = "x11", feature = "pipewire"))] #[cfg(all(feature = "x11", feature = "pipewire"))]
pub fn create_screens_x11pw(app: &mut AppState) -> anyhow::Result<ScreenCreateData> { pub fn create_screens_x11pw(app: &mut AppState) -> anyhow::Result<ScreenCreateData> {
use anyhow::bail; use wlx_capture::xshm::xshm_get_monitors;
// Load existing Pipewire tokens from file // Load existing Pipewire tokens from file
let mut pw_tokens: PwTokenMap = load_pw_token_config().unwrap_or_default(); 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<ScreenCreateDa
} }
} }
let monitors = match XshmCapture::get_monitors() { let monitors = match xshm_get_monitors() {
Ok(m) => m, Ok(m) => m,
Err(e) => { Err(e) => {
bail!(e.to_string()); anyhow::bail!(e.to_string());
} }
}; };
log::info!("Got {} monitors", monitors.len()); log::info!("Got {} monitors", monitors.len());
@@ -924,13 +981,8 @@ pub fn create_screens_x11pw(app: &mut AppState) -> anyhow::Result<ScreenCreateDa
name: m.name.clone(), name: m.name.clone(),
capture: Box::new(PipewireCapture::new(m.name.clone(), s.node_id)), capture: Box::new(PipewireCapture::new(m.name.clone(), s.node_id)),
pipeline: None, pipeline: None,
last_view: None, cur_frame: None,
transform: Affine3A::IDENTITY, meta: None,
extent: Some(extent_from_res(
size.0 as _,
size.1 as _,
&app.session.config,
)),
}; };
let backend = Box::new(SplitOverlayBackend { let backend = Box::new(SplitOverlayBackend {
@@ -949,14 +1001,14 @@ pub fn create_screens_x11pw(app: &mut AppState) -> anyhow::Result<ScreenCreateDa
#[cfg(feature = "x11")] #[cfg(feature = "x11")]
pub fn create_screens_xshm(app: &mut AppState) -> anyhow::Result<ScreenCreateData> { pub fn create_screens_xshm(app: &mut AppState) -> anyhow::Result<ScreenCreateData> {
use anyhow::bail; use wlx_capture::xshm::xshm_get_monitors;
let mut extent = vec2(0., 0.); let mut extent = vec2(0., 0.);
let monitors = match XshmCapture::get_monitors() { let monitors = match xshm_get_monitors() {
Ok(m) => m, Ok(m) => m,
Err(e) => { 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] { fn extent_from_res(width: u32, height: u32, config: &GeneralConfig) -> [u32; 3] {
// screens above a certain resolution will have severe aliasing // 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; let w = (width as f32 / height as f32 * h as f32) as u32;
[w, h, 1] [w, h, 1]
} }
fn affine_from_format(format: &FrameFormat) -> Option<Affine3A> { fn affine_from_format(format: &FrameFormat) -> Affine3A {
const FLIP_X: Vec3 = Vec3 { const FLIP_X: Vec3 = Vec3 {
x: -1.0, x: -1.0,
y: 1.0, y: 1.0,
z: 1.0, z: 1.0,
}; };
Some(match format.transform { match format.transform {
wlx_frame::Transform::Normal => Affine3A::IDENTITY, wlx_frame::Transform::Normal => Affine3A::IDENTITY,
wlx_frame::Transform::Rotated90 => Affine3A::from_rotation_z(-PI / 2.0), wlx_frame::Transform::Rotated90 => Affine3A::from_rotation_z(-PI / 2.0),
wlx_frame::Transform::Rotated180 => Affine3A::from_rotation_z(PI), wlx_frame::Transform::Rotated180 => Affine3A::from_rotation_z(PI),
@@ -1067,8 +1125,8 @@ fn affine_from_format(format: &FrameFormat) -> Option<Affine3A> {
wlx_frame::Transform::Flipped270 => { wlx_frame::Transform::Flipped270 => {
Affine3A::from_scale(FLIP_X) * Affine3A::from_rotation_z(PI / 2.0) 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"))] #[cfg(all(feature = "pipewire", feature = "x11"))]

View File

@@ -110,7 +110,6 @@ impl Toast {
), ),
instant, instant,
); );
} }
} }
@@ -140,13 +139,13 @@ fn new_toast(toast: Toast, app: &mut AppState) -> Option<(OverlayState, Box<dyn
} }
}; };
let title = if toast.title.len() > 0 { let title = if !toast.title.is_empty() {
toast.title toast.title
} else { } else {
"Notification".into() "Notification".into()
}; };
let mut size = if toast.body.len() > 0 { let mut size = if !toast.body.is_empty() {
let (w0, _) = app let (w0, _) = app
.fc .fc
.get_text_size(&title, FONT_SIZE, app.graphics.clone()) .get_text_size(&title, FONT_SIZE, app.graphics.clone())
@@ -181,7 +180,7 @@ fn new_toast(toast: Toast, app: &mut AppState) -> Option<(OverlayState, Box<dyn
canvas.bg_color = color_parse("#1e2030").unwrap(); // want panic canvas.bg_color = color_parse("#1e2030").unwrap(); // want panic
canvas.panel(0., 0., size.0, size.1, 16.); canvas.panel(0., 0., size.0, size.1, 16.);
if toast.body.len() > 0 { if !toast.body.is_empty() {
canvas.label(PADDING.0, 54., og_width, size.1 - 54., 3., toast.body); canvas.label(PADDING.0, 54., og_width, size.1 - 54., 3., toast.body);
canvas.fg_color = color_parse("#b8c0e0").unwrap(); // want panic canvas.fg_color = color_parse("#b8c0e0").unwrap(); // want panic

View File

@@ -12,8 +12,8 @@ use crate::{
common::{OverlayContainer, OverlaySelector}, common::{OverlayContainer, OverlaySelector},
input::{self, InteractionHandler}, input::{self, InteractionHandler},
overlay::{ overlay::{
ui_transform, FrameTransform, OverlayData, OverlayID, OverlayRenderer, OverlayState, ui_transform, FrameMeta, OverlayData, OverlayID, OverlayRenderer, OverlayState,
SplitOverlayBackend, Z_ORDER_DASHBOARD, ShouldRender, SplitOverlayBackend, Z_ORDER_DASHBOARD,
}, },
task::TaskType, task::TaskType,
wayvr::{ wayvr::{
@@ -23,7 +23,7 @@ use crate::{
}, },
}, },
config_wayvr, config_wayvr,
graphics::WlxGraphics, graphics::{CommandBuffers, WlxGraphics, WlxPipeline, SWAPCHAIN_FORMAT},
gui::modular::button::{WayVRAction, WayVRDisplayClickAction}, gui::modular::button::{WayVRAction, WayVRDisplayClickAction},
state::{self, AppState, KeyboardFocus}, state::{self, AppState, KeyboardFocus},
}; };
@@ -173,6 +173,7 @@ impl InteractionHandler for WayVRInteractionHandler {
} }
pub struct WayVRRenderer { pub struct WayVRRenderer {
pipeline: Arc<WlxPipeline>,
vk_image: Option<Arc<vulkano::image::Image>>, vk_image: Option<Arc<vulkano::image::Image>>,
vk_image_view: Option<Arc<vulkano::image::view::ImageView>>, vk_image_view: Option<Arc<vulkano::image::view::ImageView>>,
context: Rc<RefCell<WayVRContext>>, context: Rc<RefCell<WayVRContext>>,
@@ -185,7 +186,19 @@ impl WayVRRenderer {
wvr: Rc<RefCell<WayVRData>>, wvr: Rc<RefCell<WayVRData>>,
display: wayvr::display::DisplayHandle, display: wayvr::display::DisplayHandle,
) -> anyhow::Result<Self> { ) -> anyhow::Result<Self> {
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 { Ok(Self {
pipeline,
context: Rc::new(RefCell::new(WayVRContext::new(wvr, display)?)), context: Rc::new(RefCell::new(WayVRContext::new(wvr, display)?)),
vk_image: None, vk_image: None,
vk_image_view: None, vk_image_view: None,
@@ -569,8 +582,11 @@ impl WayVRRenderer {
&data.data, &data.data,
)?; )?;
// FIXME: can we use _buffers_ here?
upload.build_and_execute_now()?; upload.build_and_execute_now()?;
//buffers.push(upload.build()?);
self.vk_image = Some(tex.clone()); self.vk_image = Some(tex.clone());
self.vk_image_view = Some(ImageView::new_default(tex).unwrap()); 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); wayvr.state.set_display_visible(ctx.display, true);
Ok(()) Ok(())
} }
fn should_render(&mut self, _app: &mut AppState) -> anyhow::Result<ShouldRender> {
fn render(&mut self, _app: &mut state::AppState) -> anyhow::Result<()> {
let ctx = self.context.borrow(); let ctx = self.context.borrow();
let mut wayvr = ctx.wayvr.borrow_mut(); let mut wayvr = ctx.wayvr.borrow_mut();
let redrawn = match wayvr.data.tick_display(ctx.display) { let redrawn = match wayvr.data.tick_display(ctx.display) {
Ok(r) => r, Ok(r) => r,
Err(e) => { Err(e) => {
log::error!("tick_display failed: {}", e); log::error!("tick_display failed: {}", e);
return Ok(()); // do not proceed further return Ok(ShouldRender::Unable);
} }
}; };
if !redrawn { if redrawn {
return Ok(()); Ok(ShouldRender::Should)
} else {
Ok(ShouldRender::Can)
} }
}
fn render(
&mut self,
app: &mut state::AppState,
tgt: Arc<ImageView>,
buf: &mut CommandBuffers,
alpha: f32,
) -> anyhow::Result<bool> {
let ctx = self.context.borrow();
let wayvr = ctx.wayvr.borrow_mut();
let data = wayvr let data = wayvr
.data .data
@@ -680,25 +706,44 @@ impl OverlayRenderer for WayVRRenderer {
drop(ctx); drop(ctx);
match data { match data {
//TODO: render to _tgt_
wayvr::egl_data::RenderData::Dmabuf(data) => { wayvr::egl_data::RenderData::Dmabuf(data) => {
self.ensure_dmabuf_data(&data)?; self.ensure_dmabuf_data(&data)?;
} }
wayvr::egl_data::RenderData::Software(data) => { wayvr::egl_data::RenderData::Software(data) => {
if let Some(new_frame) = &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<Arc<vulkano::image::view::ImageView>> { fn frame_meta(&mut self) -> Option<FrameMeta> {
self.vk_image_view.clone() self.vk_image_view.as_ref().map(|view| FrameMeta {
}
fn frame_transform(&mut self) -> Option<FrameTransform> {
self.vk_image_view.as_ref().map(|view| FrameTransform {
extent: view.image().extent(), extent: view.image().extent(),
..Default::default() ..Default::default()
}) })

View File

@@ -46,6 +46,7 @@ pub mod frag_color {
", ",
} }
} }
//layout (location = 1) in float corner_radius; //layout (location = 1) in float corner_radius;
//out_color = in_color; //out_color = in_color;
// Some equation that determines whether to keep the pixel // 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 (location = 0) out vec4 out_color;
layout (set = 0, binding = 0) uniform sampler2D in_texture; layout (set = 0, binding = 0) uniform sampler2D in_texture;
layout (set = 1, binding = 0) uniform AlphaBlock {
uniform float alpha;
};
void main() void main()
{ {
out_color = texture(in_texture, in_uv); out_color = texture(in_texture, in_uv);
out_color.a = alpha;
// 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;
} }
", ",
} }
@@ -216,7 +214,7 @@ pub mod frag_srgb {
vec4 lower = out_color/vec4(12.92); vec4 lower = out_color/vec4(12.92);
out_color = mix(higher, lower, cutoff); 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;
}
",
}
}

View File

@@ -26,8 +26,8 @@ use crate::{
hid::HidProvider, hid::HidProvider,
overlays::toast::{DisplayMethod, ToastTopic}, overlays::toast::{DisplayMethod, ToastTopic},
shaders::{ shaders::{
frag_color, frag_glyph, frag_grid, frag_screen, frag_sprite, frag_sprite2, frag_sprite2_hl, frag_color, frag_glyph, frag_grid, frag_line, frag_screen, frag_sprite, frag_sprite2,
frag_swapchain, vert_common, frag_sprite2_hl, frag_srgb, frag_swapchain, vert_common,
}, },
}; };
@@ -74,6 +74,12 @@ impl AppState {
let shader = frag_color::load(graphics.device.clone())?; let shader = frag_color::load(graphics.device.clone())?;
shaders.insert("frag_color", shader); 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())?; let shader = frag_glyph::load(graphics.device.clone())?;
shaders.insert("frag_glyph", shader); shaders.insert("frag_glyph", shader);
@@ -114,7 +120,7 @@ impl AppState {
let toast_sound_wav = AppState::try_load_bytes( let toast_sound_wav = AppState::try_load_bytes(
&session.config.notification_sound, &session.config.notification_sound,
include_bytes!("res/557297.wav") include_bytes!("res/557297.wav"),
); );
Ok(AppState { 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() { if path.is_empty() {
return fallback_data; 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() { if std::fs::File::open(real_path.clone()).is_err() {
log::warn!( log::warn!("Could not open file at: {}", path);
"Could not open file at: {}",
path
);
return fallback_data; 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 // Box is used here to work around `f`'s limited lifetime
Ok(f) => { Ok(f) => Box::leak(Box::new(f)).as_slice(),
Box::leak(Box::new(f)).as_slice()
},
Err(e) => { Err(e) => {
log::warn!( log::warn!("Failed to read file at: {}", path);
"Failed to read file at: {}",
path
);
log::warn!("{:?}", e); log::warn!("{:?}", e);
fallback_data fallback_data
} }
}; }
} }
} }
pub struct AppSession { pub struct AppSession {