diff --git a/src/backend.rs b/src/backend.rs index 130b17f0..a62516af 100644 --- a/src/backend.rs +++ b/src/backend.rs @@ -33,6 +33,10 @@ pub trait Backend { fn is_freestanding(&self) -> bool { false } + + fn supports_presentation_feedback(&self) -> bool { + false + } } #[derive(Copy, Clone, Debug, Default, Eq, PartialEq)] diff --git a/src/backends/metal.rs b/src/backends/metal.rs index 7d3a5b5b..89583cfd 100644 --- a/src/backends/metal.rs +++ b/src/backends/metal.rs @@ -187,6 +187,10 @@ impl Backend for MetalBackend { fn is_freestanding(&self) -> bool { true } + + fn supports_presentation_feedback(&self) -> bool { + true + } } fn dup_async_fd(state: &Rc, fd: c::c_int) -> Result { diff --git a/src/backends/metal/video.rs b/src/backends/metal/video.rs index 5517d408..88a2d03b 100644 --- a/src/backends/metal/video.rs +++ b/src/backends/metal/video.rs @@ -1,17 +1,19 @@ use { crate::{ - async_engine::{AsyncFd, SpawnedFuture}, + async_engine::{AsyncFd, Phase, SpawnedFuture}, backend::{ BackendEvent, Connector, ConnectorEvent, ConnectorId, ConnectorKernelId, MonitorInfo, }, backends::metal::{DrmId, MetalBackend, MetalError}, edid::Descriptor, format::{Format, XRGB8888}, - render::{Framebuffer, RenderContext}, + ifs::wp_presentation_feedback::ExecutedPresentation, + render::{Framebuffer, RenderContext, RenderResult}, state::State, utils::{ - bitflags::BitflagsExt, clonecell::CloneCell, debug_fn::debug_fn, errorfmt::ErrorFmt, - numcell::NumCell, oserror::OsError, syncqueue::SyncQueue, + asyncevent::AsyncEvent, bitflags::BitflagsExt, clonecell::CloneCell, + debug_fn::debug_fn, errorfmt::ErrorFmt, numcell::NumCell, oserror::OsError, + syncqueue::SyncQueue, }, video::{ drm::{ @@ -28,15 +30,13 @@ use { ahash::{AHashMap, AHashSet}, bstr::{BString, ByteSlice}, std::{ - cell::Cell, + cell::{Cell, RefCell}, ffi::CString, fmt::{Debug, Formatter}, rc::Rc, }, uapi::c, }; -use crate::async_engine::Phase; -use crate::utils::asyncevent::AsyncEvent; pub struct PendingDrmDevice { pub id: DrmId, @@ -91,6 +91,7 @@ pub struct MetalConnector { pub crtcs: AHashMap>, pub modes: Vec, pub mode: CloneCell>>, + pub refresh: Cell, pub monitor_manufacturer: String, pub monitor_name: String, @@ -120,6 +121,8 @@ pub struct MetalConnector { pub on_change: OnChange, pub present_trigger: AsyncEvent, + + pub render_result: RefCell, } pub struct ConnectorFutures { @@ -179,9 +182,17 @@ impl MetalConnector { }; let buffer = &buffers[self.next_buffer.fetch_add(1) % buffers.len()]; if let Some(node) = self.state.root.outputs.get(&self.connector_id) { - buffer - .egl - .render(&*node, &self.state, Some(node.global.pos.get())); + let mut rr = self.render_result.borrow_mut(); + buffer.egl.render( + &*node, + &self.state, + Some(node.global.pos.get()), + true, + &mut rr, + ); + for fr in rr.frame_requests.drain(..) { + fr.client.dispatch_frame_requests.push(fr.clone()); + } } let mut changes = self.master.change(); changes.change_object(plane.id, |c| { @@ -285,7 +296,13 @@ fn get_connectors( state: &Rc, dev: &Rc, ids: &[DrmConnector], -) -> Result<(AHashMap>, Vec), DrmError> { +) -> Result< + ( + AHashMap>, + Vec, + ), + DrmError, +> { let mut connectors = AHashMap::new(); let mut futures = vec![]; for connector in ids { @@ -385,13 +402,19 @@ fn create_connector( serial_number = edid.base_block.id_serial_number.to_string(); } } + let mode = info.modes.first().cloned().map(Rc::new); + let refresh = mode + .as_ref() + .map(|m| 1_000_000_000_000u64 / (m.refresh_rate_millihz() as u64)) + .unwrap_or(0) as u32; let slf = Rc::new(MetalConnector { id: connector, master: dev.master.clone(), state: state.clone(), connector_id: state.connector_ids.next(), crtcs, - mode: CloneCell::new(info.modes.first().cloned().map(Rc::new)), + mode: CloneCell::new(mode), + refresh: Cell::new(refresh), monitor_manufacturer: manufacturer, monitor_name: name, monitor_serial_number: serial_number, @@ -411,7 +434,8 @@ fn create_connector( crtc_id: props.get("CRTC_ID")?.map(|v| DrmCrtc(v as _)), crtc: Default::default(), on_change: Default::default(), - present_trigger: Default::default() + present_trigger: Default::default(), + render_result: RefCell::new(Default::default()), }); let futures = ConnectorFutures { present: state.eng.spawn2(Phase::Present, slf.clone().present_loop()), @@ -655,7 +679,11 @@ impl MetalBackend { let (connectors, futures) = get_connectors(&self.state, &dev, &resources.connectors)?; - let slf = Rc::new(MetalDrmDevice { dev, connectors, futures }); + let slf = Rc::new(MetalDrmDevice { + dev, + connectors, + futures, + }); self.init_drm_device(&slf)?; @@ -782,9 +810,9 @@ impl MetalBackend { self: &Rc, dev: &Rc, crtc_id: DrmCrtc, - _tv_sec: u32, - _tv_usec: u32, - _sequence: u32, + tv_sec: u32, + tv_usec: u32, + sequence: u32, ) { let crtc = match dev.dev.crtcs.get(&crtc_id) { Some(c) => c, @@ -798,6 +826,27 @@ impl MetalBackend { if connector.has_damage.get() { connector.schedule_present(); } + { + let global = self.state.outputs.get(&connector.connector_id); + let mut rr = connector.render_result.borrow_mut(); + for fb in rr.presentation_feedbacks.drain(..) { + match &global { + Some(g) => { + fb.client + .dispatch_presentation_feedback + .push(ExecutedPresentation { + feedback: fb.clone(), + output: g.node.global.clone(), + tv_sec: tv_sec as _, + tv_nsec: tv_usec * 1000, + seq: sequence as _, + refresh: connector.refresh.get(), + }) + } + _ => fb.client.discard_presentation_feedback.push(fb.clone()), + } + } + } } fn reset_planes(&self, dev: &MetalDrmDevice, changes: &mut Change) { diff --git a/src/backends/x.rs b/src/backends/x.rs index 196caab6..598579ba 100644 --- a/src/backends/x.rs +++ b/src/backends/x.rs @@ -9,7 +9,7 @@ use { fixed::Fixed, format::XRGB8888, ifs::wl_seat::PX_PER_SCROLL, - render::{Framebuffer, RenderContext, RenderError}, + render::{Framebuffer, RenderContext, RenderError, RenderResult}, state::State, utils::{ clonecell::CloneCell, copyhashmap::CopyHashMap, errorfmt::ErrorFmt, numcell::NumCell, @@ -53,6 +53,7 @@ use { collections::VecDeque, error::Error, future::pending, + ops::DerefMut, rc::Rc, }, thiserror::Error, @@ -222,6 +223,7 @@ pub async fn create(state: &Rc) -> Result, XBackendError> { root, scheduled_present: Default::default(), grab_requests: Default::default(), + render_result: Default::default(), }); data.add_output().await?; @@ -250,6 +252,7 @@ pub struct XBackend { root: u32, scheduled_present: AsyncQueue>, grab_requests: AsyncQueue<(Rc, bool)>, + render_result: RefCell, } impl XBackend { @@ -673,8 +676,18 @@ impl XBackend { image.last_serial.set(serial); if let Some(node) = self.state.root.outputs.get(&output.id) { + let mut rr = self.render_result.borrow_mut(); let fb = image.fb.get(); - fb.render(&*node, &self.state, Some(node.global.pos.get())); + fb.render( + &*node, + &self.state, + Some(node.global.pos.get()), + true, + rr.deref_mut(), + ); + for fr in rr.frame_requests.drain(..) { + fr.client.dispatch_frame_requests.push(fr.clone()); + } } let pp = PresentPixmap { diff --git a/src/client.rs b/src/client.rs index 2d51a685..e4e1893f 100644 --- a/src/client.rs +++ b/src/client.rs @@ -3,7 +3,12 @@ use { crate::{ async_engine::{AsyncFd, SpawnedFuture}, client::{error::LookupError, objects::Objects}, - ifs::{wl_callback::WlCallback, wl_display::WlDisplay, wl_registry::WlRegistry}, + ifs::{ + wl_callback::WlCallback, + wl_display::WlDisplay, + wl_registry::WlRegistry, + wp_presentation_feedback::{ExecutedPresentation, WpPresentationFeedback}, + }, leaks::Tracker, object::{Interface, Object, ObjectId, WL_DISPLAY_ID}, state::State, @@ -132,6 +137,8 @@ impl Clients { flush_request: Default::default(), shutdown: Default::default(), dispatch_frame_requests: AsyncQueue::new(), + discard_presentation_feedback: Default::default(), + dispatch_presentation_feedback: Default::default(), tracker: Default::default(), is_xwayland, secure, @@ -239,6 +246,8 @@ pub struct Client { flush_request: AsyncEvent, shutdown: AsyncEvent, pub dispatch_frame_requests: AsyncQueue>, + pub discard_presentation_feedback: AsyncQueue>, + pub dispatch_presentation_feedback: AsyncQueue, pub tracker: Tracker, pub is_xwayland: bool, pub secure: bool, diff --git a/src/client/tasks.rs b/src/client/tasks.rs index fc0da5c8..844cfa39 100644 --- a/src/client/tasks.rs +++ b/src/client/tasks.rs @@ -2,6 +2,7 @@ use { crate::{ async_engine::Phase, client::{Client, ClientError}, + ifs::wp_presentation_feedback::{KIND_HW_COMPLETION, KIND_VSYNC}, object::ObjectId, utils::{ buffd::{BufFdIn, BufFdOut, MsgParser}, @@ -16,6 +17,8 @@ use { pub async fn client(data: Rc) { let mut recv = data.state.eng.spawn(receive(data.clone())).fuse(); let mut dispatch_fr = data.state.eng.spawn(dispatch_fr(data.clone())).fuse(); + let discard_fb = data.state.eng.spawn(discard_fb(data.clone())).fuse(); + let dispatch_fb = data.state.eng.spawn(dispatch_fb(data.clone())).fuse(); let mut shutdown = data.shutdown.triggered().fuse(); let _send = data.state.eng.spawn2(Phase::PostLayout, send(data.clone())); select! { @@ -25,6 +28,8 @@ pub async fn client(data: Rc) { } drop(recv); drop(dispatch_fr); + drop(discard_fb); + drop(dispatch_fb); data.flush_request.trigger(); match data.state.eng.timeout(5000) { Ok(timeout) => { @@ -56,6 +61,38 @@ async fn dispatch_fr(data: Rc) { } } +async fn discard_fb(data: Rc) { + loop { + data.discard_presentation_feedback.non_empty().await; + while let Some(fr) = data.discard_presentation_feedback.try_pop() { + fr.send_discarded(); + let _ = data.remove_obj(&*fr); + } + } +} + +async fn dispatch_fb(data: Rc) { + loop { + data.dispatch_presentation_feedback.non_empty().await; + while let Some(fr) = data.dispatch_presentation_feedback.try_pop() { + let bindings = fr.output.bindings.borrow_mut(); + if let Some(bindings) = bindings.get(&data.id) { + for binding in bindings.values() { + fr.feedback.send_sync_output(binding); + } + } + fr.feedback.send_presented( + fr.tv_sec, + fr.tv_nsec, + fr.refresh, + fr.seq, + KIND_VSYNC | KIND_HW_COMPLETION, + ); + let _ = data.remove_obj(&*fr.feedback); + } + } +} + async fn receive(data: Rc) { let display = data.display().unwrap(); let recv = async { diff --git a/src/globals.rs b/src/globals.rs index 638c8425..cdb28abe 100644 --- a/src/globals.rs +++ b/src/globals.rs @@ -16,6 +16,7 @@ use { wl_seat::WlSeatGlobal, wl_shm::WlShmGlobal, wl_subcompositor::WlSubcompositorGlobal, + wp_presentation::WpPresentationGlobal, xdg_wm_base::XdgWmBaseGlobal, zwlr_layer_shell_v1::ZwlrLayerShellV1Global, zwp_idle_inhibit_manager_v1::ZwpIdleInhibitManagerV1Global, @@ -135,6 +136,10 @@ impl Globals { if backend.supports_idle() { add_singleton!(ZwpIdleInhibitManagerV1Global); } + + if backend.supports_presentation_feedback() { + add_singleton!(WpPresentationGlobal); + } } pub fn name(&self) -> GlobalName { diff --git a/src/ifs.rs b/src/ifs.rs index 96064b88..fbb2c738 100644 --- a/src/ifs.rs +++ b/src/ifs.rs @@ -18,6 +18,8 @@ pub mod wl_shm; pub mod wl_shm_pool; pub mod wl_subcompositor; pub mod wl_surface; +pub mod wp_presentation; +pub mod wp_presentation_feedback; pub mod xdg_positioner; pub mod xdg_wm_base; pub mod zwlr_layer_shell_v1; diff --git a/src/ifs/wl_callback.rs b/src/ifs/wl_callback.rs index c3f3038c..df6dd5ec 100644 --- a/src/ifs/wl_callback.rs +++ b/src/ifs/wl_callback.rs @@ -10,8 +10,8 @@ use { }; pub struct WlCallback { - client: Rc, - id: WlCallbackId, + pub client: Rc, + pub id: WlCallbackId, pub tracker: Tracker, } diff --git a/src/ifs/wl_surface.rs b/src/ifs/wl_surface.rs index 44bea82f..866ae730 100644 --- a/src/ifs/wl_surface.rs +++ b/src/ifs/wl_surface.rs @@ -18,6 +18,7 @@ use { cursor::CursorSurface, wl_subsurface::WlSubsurface, xdg_surface::XdgSurfaceError, zwlr_layer_surface_v1::ZwlrLayerSurfaceV1Error, }, + wp_presentation_feedback::WpPresentationFeedback, }, leaks::Tracker, object::Object, @@ -97,6 +98,7 @@ pub struct WlSurface { pub children: RefCell>>, ext: CloneCell>, pub frame_requests: RefCell>>, + pub presentation_feedback: RefCell>>, seat_state: NodeSeatState, toplevel: CloneCell>>, cursors: SmallMap, 1>, @@ -181,6 +183,7 @@ struct PendingState { input_region: Cell>>>, frame_request: RefCell>>, damage: Cell, + presentation_feedback: RefCell>>, } #[derive(Default)] @@ -215,6 +218,7 @@ impl WlSurface { children: Default::default(), ext: CloneCell::new(client.state.none_surface_ext.clone()), frame_requests: Default::default(), + presentation_feedback: Default::default(), seat_state: Default::default(), toplevel: Default::default(), cursors: Default::default(), @@ -236,6 +240,13 @@ impl WlSurface { } } + pub fn add_presentation_feedback(&self, fb: &Rc) { + self.pending + .presentation_feedback + .borrow_mut() + .push(fb.clone()); + } + pub fn is_cursor(&self) -> bool { self.role.get() == SurfaceRole::Cursor } @@ -497,6 +508,15 @@ impl WlSurface { let mut pfr = self.pending.frame_request.borrow_mut(); self.frame_requests.borrow_mut().extend(pfr.drain(..)); } + { + let mut fbs = self.presentation_feedback.borrow_mut(); + for fb in fbs.drain(..) { + fb.send_discarded(); + let _ = self.client.remove_obj(&*fb); + } + let mut pfbs = self.pending.presentation_feedback.borrow_mut(); + mem::swap(fbs.deref_mut(), pfbs.deref_mut()); + } { if let Some(region) = self.pending.input_region.take() { self.input_region.set(region); diff --git a/src/ifs/wl_surface/xdg_surface/xdg_toplevel.rs b/src/ifs/wl_surface/xdg_surface/xdg_toplevel.rs index 92fefe92..98c919eb 100644 --- a/src/ifs/wl_surface/xdg_surface/xdg_toplevel.rs +++ b/src/ifs/wl_surface/xdg_surface/xdg_toplevel.rs @@ -328,21 +328,6 @@ impl XdgToplevel { Ok(()) } - fn notify_parent(&self) { - let parent = match self.toplevel_data.parent.get() { - Some(p) => p, - _ => return, - }; - let extents = self.xdg.extents.get(); - parent.clone().node_child_active_changed( - self, - self.toplevel_data.active_children.get() > 0, - 1, - ); - parent.node_child_size_changed(self, extents.width(), extents.height()); - parent.node_child_title_changed(self, self.toplevel_data.title.borrow_mut().deref()); - } - fn map_floating(self: &Rc, workspace: &Rc) { let (width, height) = self.toplevel_data.float_size(workspace); self.state @@ -460,9 +445,6 @@ impl ToplevelNode for XdgToplevel { } fn tl_set_active(&self, active: bool) { - if let Some(parent) = self.toplevel_data.parent.get() { - parent.node_child_active_changed(self, active, 1); - } let changed = { let mut states = self.states.borrow_mut(); match active { @@ -587,10 +569,8 @@ impl XdgSurfaceExt for XdgToplevel { } fn extents_changed(&self) { - self.notify_parent(); - if self.toplevel_data.parent.get().is_some() { - self.state.tree_changed(); - } + self.toplevel_data.pos.set(self.xdg.extents.get()); + self.tl_extents_changed(); } } diff --git a/src/ifs/wl_surface/xwindow.rs b/src/ifs/wl_surface/xwindow.rs index c0117101..e7c22607 100644 --- a/src/ifs/wl_surface/xwindow.rs +++ b/src/ifs/wl_surface/xwindow.rs @@ -307,7 +307,8 @@ impl SurfaceExt for Xwindow { } fn extents_changed(&self) { - self.tl_notify_parent(); + self.toplevel_data.pos.set(self.surface.extents.get()); + self.tl_extents_changed(); } } @@ -391,12 +392,6 @@ impl ToplevelNode for Xwindow { && self.data.info.input_model.get() != XInputModel::None } - fn tl_set_active(&self, active: bool) { - if let Some(pn) = self.toplevel_data.parent.get() { - pn.node_child_active_changed(self, active, 1); - } - } - fn tl_on_activate(&self) { self.data .state diff --git a/src/ifs/wl_surface/zwlr_layer_surface_v1.rs b/src/ifs/wl_surface/zwlr_layer_surface_v1.rs index bfe78352..268280c9 100644 --- a/src/ifs/wl_surface/zwlr_layer_surface_v1.rs +++ b/src/ifs/wl_surface/zwlr_layer_surface_v1.rs @@ -307,6 +307,7 @@ impl ZwlrLayerSurfaceV1 { self.mapped.set(false); self.surface.destroy_node(); self.seat_state.destroy_node(self); + self.client.state.tree_changed(); } } diff --git a/src/ifs/wp_presentation.rs b/src/ifs/wp_presentation.rs new file mode 100644 index 00000000..4a3e73f4 --- /dev/null +++ b/src/ifs/wp_presentation.rs @@ -0,0 +1,116 @@ +pub use crate::wire::{wp_presentation::*, WpPresentationId}; +use { + crate::{ + client::{Client, ClientError}, + globals::{Global, GlobalName}, + ifs::wp_presentation_feedback::WpPresentationFeedback, + leaks::Tracker, + object::Object, + utils::buffd::{MsgParser, MsgParserError}, + }, + std::rc::Rc, + thiserror::Error, + uapi::c, +}; + +pub struct WpPresentationGlobal { + pub name: GlobalName, +} + +impl WpPresentationGlobal { + pub fn new(name: GlobalName) -> Self { + Self { name } + } + + fn bind_( + self: Rc, + id: WpPresentationId, + client: &Rc, + _version: u32, + ) -> Result<(), WpPresentationError> { + let obj = Rc::new(WpPresentation { + id, + client: client.clone(), + tracker: Default::default(), + }); + track!(client, obj); + client.add_client_obj(&obj)?; + obj.send_clock_id(); + Ok(()) + } +} + +global_base!(WpPresentationGlobal, WpPresentation, WpPresentationError); + +impl Global for WpPresentationGlobal { + fn singleton(&self) -> bool { + true + } + + fn version(&self) -> u32 { + 1 + } +} + +simple_add_global!(WpPresentationGlobal); + +pub struct WpPresentation { + pub id: WpPresentationId, + pub client: Rc, + pub tracker: Tracker, +} + +impl WpPresentation { + fn send_clock_id(&self) { + self.client.event(ClockId { + self_id: self.id, + clk_id: c::CLOCK_MONOTONIC as _, + }); + } + + pub fn destroy(&self, parser: MsgParser<'_, '_>) -> Result<(), WpPresentationError> { + let _req: Destroy = self.client.parse(self, parser)?; + self.client.remove_obj(self)?; + Ok(()) + } + + pub fn feedback(&self, parser: MsgParser<'_, '_>) -> Result<(), WpPresentationError> { + let req: Feedback = self.client.parse(self, parser)?; + let surface = self.client.lookup(req.surface)?; + let fb = Rc::new(WpPresentationFeedback { + id: req.callback, + client: self.client.clone(), + surface: surface.clone(), + tracker: Default::default(), + }); + track!(self.client, fb); + self.client.add_client_obj(&fb)?; + surface.add_presentation_feedback(&fb); + Ok(()) + } +} + +object_base2! { + WpPresentation; + + DESTROY => destroy, + FEEDBACK => feedback, +} + +impl Object for WpPresentation { + fn num_requests(&self) -> u32 { + FEEDBACK + 1 + } +} + +simple_add_obj!(WpPresentation); + +#[derive(Debug, Error)] +pub enum WpPresentationError { + #[error("Parsing failed")] + MsgParserError(#[source] Box), + #[error(transparent)] + ClientError(Box), +} +efrom!(WpPresentationError, MsgParserError); +efrom!(WpPresentationError, ClientError); diff --git a/src/ifs/wp_presentation_feedback.rs b/src/ifs/wp_presentation_feedback.rs new file mode 100644 index 00000000..99cd1c8c --- /dev/null +++ b/src/ifs/wp_presentation_feedback.rs @@ -0,0 +1,78 @@ +use { + crate::{ + client::Client, + ifs::{ + wl_output::{WlOutput, WlOutputGlobal}, + wl_surface::WlSurface, + }, + leaks::Tracker, + object::Object, + wire::{wp_presentation_feedback::*, WpPresentationFeedbackId}, + }, + std::rc::Rc, + thiserror::Error, +}; + +pub struct WpPresentationFeedback { + pub id: WpPresentationFeedbackId, + pub client: Rc, + pub surface: Rc, + pub tracker: Tracker, +} + +pub struct ExecutedPresentation { + pub feedback: Rc, + pub output: Rc, + pub tv_sec: u64, + pub tv_nsec: u32, + pub seq: u64, + pub refresh: u32, +} + +pub const KIND_VSYNC: u32 = 0x1; +#[allow(dead_code)] +pub const KIND_HW_CLOCK: u32 = 0x2; +pub const KIND_HW_COMPLETION: u32 = 0x4; +#[allow(dead_code)] +pub const KIND_ZERO_COPY: u32 = 0x8; + +impl WpPresentationFeedback { + pub fn send_sync_output(&self, output: &WlOutput) { + self.client.event(SyncOutput { + self_id: self.id, + output: output.id, + }); + } + + pub fn send_presented(&self, tv_sec: u64, tv_nsec: u32, refresh: u32, seq: u64, flags: u32) { + self.client.event(Presented { + self_id: self.id, + tv_sec_hi: (tv_sec >> 32) as u32, + tv_sec_lo: tv_sec as u32, + tv_nsec, + refresh, + seq_hi: (seq >> 32) as u32, + seq_lo: seq as u32, + flags, + }); + } + + pub fn send_discarded(&self) { + self.client.event(Discarded { self_id: self.id }); + } +} + +object_base2! { + WpPresentationFeedback; +} + +impl Object for WpPresentationFeedback { + fn num_requests(&self) -> u32 { + 0 + } +} + +simple_add_obj!(WpPresentationFeedback); + +#[derive(Debug, Error)] +pub enum WpPresentationFeedbackError {} diff --git a/src/render/renderer/framebuffer.rs b/src/render/renderer/framebuffer.rs index 16d10143..378cace0 100644 --- a/src/render/renderer/framebuffer.rs +++ b/src/render/renderer/framebuffer.rs @@ -11,6 +11,7 @@ use { }, renderer::{context::RenderContext, renderer::Renderer}, sys::{glBlendFunc, glFlush, GL_ONE, GL_ONE_MINUS_SRC_ALPHA}, + RenderResult, }, state::State, tree::Node, @@ -45,7 +46,14 @@ impl Framebuffer { }); } - pub fn render(&self, node: &dyn Node, state: &State, cursor_rect: Option) { + pub fn render( + &self, + node: &dyn Node, + state: &State, + cursor_rect: Option, + on_output: bool, + result: &mut RenderResult, + ) { let _ = self.ctx.ctx.with_current(|| { let c = state.theme.background_color.get(); unsafe { @@ -59,6 +67,8 @@ impl Framebuffer { ctx: &self.ctx, fb: &self.gl, state, + on_output, + result, }; node.node_render(&mut renderer, 0, 0); if let Some(rect) = cursor_rect { diff --git a/src/render/renderer/renderer.rs b/src/render/renderer/renderer.rs index 822b9d94..6e6e01ad 100644 --- a/src/render/renderer/renderer.rs +++ b/src/render/renderer/renderer.rs @@ -3,9 +3,11 @@ use { format::{Format, ARGB8888}, ifs::{ wl_buffer::WlBuffer, + wl_callback::WlCallback, wl_surface::{ xdg_surface::XdgSurface, zwlr_layer_surface_v1::ZwlrLayerSurfaceV1, WlSurface, }, + wp_presentation_feedback::WpPresentationFeedback, }, rect::Rect, render::{ @@ -31,13 +33,32 @@ use { }, utils::rc_eq::rc_eq, }, - std::{ops::Deref, rc::Rc, slice}, + std::{ + fmt::{Debug, Formatter}, + ops::Deref, + rc::Rc, + slice, + }, }; +#[derive(Default)] +pub struct RenderResult { + pub frame_requests: Vec>, + pub presentation_feedbacks: Vec>, +} + +impl Debug for RenderResult { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + f.debug_struct("RenderResult").finish_non_exhaustive() + } +} + pub struct Renderer<'a> { pub(super) ctx: &'a Rc, pub(super) fb: &'a GlFrameBuffer, pub(super) state: &'a State, + pub(super) on_output: bool, + pub(super) result: &'a mut RenderResult, } impl Renderer<'_> { @@ -271,9 +292,15 @@ impl Renderer<'_> { } else { self.render_buffer(&buffer, x, y); } - let mut fr = surface.frame_requests.borrow_mut(); - for cb in fr.drain(..) { - surface.client.dispatch_frame_requests.push(cb); + if self.on_output { + { + let mut fr = surface.frame_requests.borrow_mut(); + self.result.frame_requests.extend(fr.drain(..)); + } + { + let mut fbs = surface.presentation_feedback.borrow_mut(); + self.result.presentation_feedbacks.extend(fbs.drain(..)); + } } } diff --git a/src/screenshoter.rs b/src/screenshoter.rs index 6c7ca277..b6b73fe0 100644 --- a/src/screenshoter.rs +++ b/src/screenshoter.rs @@ -53,7 +53,13 @@ pub fn take_screenshot(state: &State) -> Result GBM_BO_USE_RENDERING | GBM_BO_USE_LINEAR, )?; let fb = ctx.dmabuf_fb(bo.dmabuf())?; - fb.render(state.root.deref(), state, Some(state.root.extents.get())); + fb.render( + state.root.deref(), + state, + Some(state.root.extents.get()), + false, + &mut Default::default(), + ); let drm = ctx.gbm.drm.dup_render()?.fd().clone(); Ok(Screenshot { drm, bo }) } diff --git a/src/state.rs b/src/state.rs index 37459ea3..96c9d4bd 100644 --- a/src/state.rs +++ b/src/state.rs @@ -1,4 +1,3 @@ -use std::fmt::{Debug, Formatter}; use { crate::{ async_engine::{AsyncEngine, SpawnedFuture}, @@ -42,6 +41,7 @@ use { jay_config::Direction, std::{ cell::{Cell, RefCell}, + fmt::{Debug, Formatter}, num::Wrapping, rc::Rc, sync::Arc, diff --git a/src/tree/container.rs b/src/tree/container.rs index 6e7a44d5..896d3a8b 100644 --- a/src/tree/container.rs +++ b/src/tree/container.rs @@ -351,6 +351,7 @@ impl ContainerNode { } else { self.perform_split_layout(); } + self.state.tree_changed(); self.schedule_compute_render_data(); } diff --git a/src/tree/toplevel.rs b/src/tree/toplevel.rs index e022af1d..75dee8a3 100644 --- a/src/tree/toplevel.rs +++ b/src/tree/toplevel.rs @@ -38,13 +38,20 @@ pub trait ToplevelNode: Node { } fn tl_surface_active_changed(&self, active: bool) { + let data = self.tl_data(); if active { - if self.tl_data().active_children.fetch_add(1) == 0 { + if data.active_children.fetch_add(1) == 0 { self.tl_set_active(true); + if let Some(parent) = data.parent.get() { + parent.node_child_active_changed(self.tl_as_node(), true, 1); + } } } else { - if self.tl_data().active_children.fetch_sub(1) == 1 { + if data.active_children.fetch_sub(1) == 1 { self.tl_set_active(false); + if let Some(parent) = data.parent.get() { + parent.node_child_active_changed(self.tl_as_node(), false, 1); + } } } } @@ -83,7 +90,9 @@ pub trait ToplevelNode: Node { let data = self.tl_data(); data.parent.set(Some(parent.clone())); data.is_floating.set(parent.node_is_float()); - self.tl_notify_parent(); + self.tl_extents_changed(); + self.tl_title_changed(); + self.tl_active_changed(); self.tl_after_parent_set(parent); } @@ -91,14 +100,13 @@ pub trait ToplevelNode: Node { let _ = parent; } - fn tl_notify_parent(&self) { + fn tl_active_changed(&self) { let data = self.tl_data(); let parent = match data.parent.get() { Some(p) => p, _ => return, }; let node = self.tl_as_node(); - let pos = data.pos.get(); let depth = if data.active.get() { 1 } else if data.active_children.get() > 0 { @@ -109,10 +117,18 @@ pub trait ToplevelNode: Node { if depth > 0 { parent.clone().node_child_active_changed(node, true, depth); } + } + + fn tl_extents_changed(&self) { + let data = self.tl_data(); + let parent = match data.parent.get() { + Some(p) => p, + _ => return, + }; + let node = self.tl_as_node(); + let pos = data.pos.get(); parent.node_child_size_changed(node, pos.width(), pos.height()); - parent - .clone() - .node_child_title_changed(node, data.title.borrow_mut().deref()); + data.state.tree_changed(); } fn tl_set_workspace(self: Rc, ws: &Rc) { @@ -253,6 +269,7 @@ impl ToplevelData { placeholder, workspace: ws.clone(), }); + drop(data); self.is_fullscreen.set(true); ws.fullscreen.set(Some(node.clone())); node.tl_set_parent(ws.clone()); diff --git a/src/utils/asyncevent.rs b/src/utils/asyncevent.rs index 2917121e..1242ecd9 100644 --- a/src/utils/asyncevent.rs +++ b/src/utils/asyncevent.rs @@ -1,8 +1,8 @@ -use std::fmt::{Debug, Formatter}; use { crate::utils::numcell::NumCell, std::{ cell::Cell, + fmt::{Debug, Formatter}, future::Future, pin::Pin, task::{Context, Poll, Waker}, @@ -17,7 +17,9 @@ pub struct AsyncEvent { impl Debug for AsyncEvent { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - f.debug_struct("AsyncEvent").field("triggers", &self.triggers.get()).finish_non_exhaustive() + f.debug_struct("AsyncEvent") + .field("triggers", &self.triggers.get()) + .finish_non_exhaustive() } } diff --git a/wire/wp_presentation.txt b/wire/wp_presentation.txt new file mode 100644 index 00000000..5685684c --- /dev/null +++ b/wire/wp_presentation.txt @@ -0,0 +1,15 @@ +# requests + +msg destroy = 0 { +} + +msg feedback = 1 { + surface: id(wl_surface), + callback: id(wp_presentation_feedback), +} + +# events + +msg clock_id = 0 { + clk_id: u32, +} diff --git a/wire/wp_presentation_feedback.txt b/wire/wp_presentation_feedback.txt new file mode 100644 index 00000000..0a8e34d9 --- /dev/null +++ b/wire/wp_presentation_feedback.txt @@ -0,0 +1,19 @@ +# events + +msg sync_output = 0 { + output: id(wl_output), +} + +msg presented = 1 { + tv_sec_hi : u32, + tv_sec_lo : u32, + tv_nsec : u32, + refresh : u32, + seq_hi : u32, + seq_lo : u32, + flags : u32, +} + +msg discarded = 2 { + +}