Commit 595b66571820
tarball6 files changed+239-34
@@ -37,7 +37,10 @@ use smithay::reexports::input::Libinput;
3737
use smithay::reexports::input::event::switch::{Switch, SwitchState};3838
use smithay::reexports::rustix::fs::OFlags;3939
use smithay::reexports::wayland_server::backend::GlobalId;40
-use smithay::utils::{DeviceFd, Logical, Point, Transform};40
+use smithay::desktop::utils::OutputPresentationFeedback;41
+use smithay::reexports::wayland_protocols::wp::presentation_time::server::wp_presentation_feedback::Kind;42
+use smithay::utils::{DeviceFd, Logical, Monotonic, Point, Time, Transform};43
+use smithay::wayland::presentation::Refresh;4144
use smithay::wayland::drm_syncobj::{DrmSyncobjState, supports_syncobj_eventfd};4245
use smithay_drm_extras::drm_scanner::{DrmScanEvent, DrmScanner};4346
use std::collections::{HashMap, HashSet};@@ -55,8 +58,8 @@ const GLOBAL_REMOVAL_DELAY: Duration = Duration::from_secs(5);
5558
5659
type Allocator = GbmAllocator<DrmDeviceFd>;5760
type Exporter = GbmFramebufferExporter<DrmDeviceFd>;58
-type Manager = DrmOutputManager<Allocator, Exporter, (), DrmDeviceFd>;59
-type Out = DrmOutput<Allocator, Exporter, (), DrmDeviceFd>;61
+type Manager = DrmOutputManager<Allocator, Exporter, OutputPresentationFeedback, DrmDeviceFd>;62
+type Out = DrmOutput<Allocator, Exporter, OutputPresentationFeedback, DrmDeviceFd>;6063
6164
pub struct Drm {6265
pub session: LibSeatSession,@@ -684,9 +687,10 @@ fn on_vblank(state: &mut State, crtc: crtc::Handle, meta: &mut Option<DrmEventMe
684687
else {685688
return;686689
};687
- if let Err(e) = surface.drm.frame_submitted() {690
+ let feedback = surface.drm.frame_submitted().unwrap_or_else(|e| {688691
tracing::warn!("frame was not submitted: {e}");689
- }692
+ None693
+ });690694
surface.queued = false;691695
if let Err(e) = state.renderer.absorb_submit_stall() {692696
tracing::warn!("absorbing a submit stall failed: {e}");@@ -698,6 +702,25 @@ fn on_vblank(state: &mut State, crtc: crtc::Handle, meta: &mut Option<DrmEventMe
698702
else {699703
return;700704
};705
+ if let Some(mut feedback) = feedback {706
+ let (time, clock) = match meta.as_ref().map(|m| m.time) {707
+ Some(DrmEventTime::Monotonic(t)) => (t, Kind::HwClock | Kind::HwCompletion),708
+ _ => (Duration::from_nanos(now_ns()), Kind::empty()),709
+ };710
+ let refresh = Duration::from_nanos(surface.refresh_ns);711
+ let refresh = if surface.vrr_on {712
+ Refresh::variable(refresh)713
+ } else {714
+ Refresh::fixed(refresh)715
+ };716
+ let sequence = meta.as_ref().map_or(0, |m| u64::from(m.sequence));717
+ feedback.presented(718
+ Time::<Monotonic>::from(time),719
+ refresh,720
+ sequence,721
+ Kind::Vsync | clock,722
+ );723
+ }701724
if let Some(DrmEventTime::Monotonic(t)) = meta.as_ref().map(|m| m.time) {702725
let t = t.as_nanos() as u64;703726
if let Some(target) = surface.target_ns.take() {@@ -1017,6 +1040,15 @@ fn render_surface(state: &mut State, crtc: crtc::Handle, target: u64) -> bool {
10171040
{10181041
Ok(result) if !result.is_empty => {10191042
let direct = matches!(result.primary_element, PrimaryPlaneElement::Element(_));1043
+ let feedback = crate::frame::take_presentation_feedback(1044
+ wm,1045
+ lock,1046
+ xwayland.as_ref().map_or(&[], |x| &x.unmanaged),1047
+ surface.id,1048
+ &surface.output,1049
+ now,1050
+ &result.states,1051
+ );10201052
if direct {10211053
surface.scanned_out += 1;10221054
}@@ -1032,7 +1064,7 @@ fn render_surface(state: &mut State, crtc: crtc::Handle, target: u64) -> bool {
10321064
);10331065
}10341066
let queue_began = std::time::Instant::now();1035
- match surface.drm.queue_frame(()) {1067
+ match surface.drm.queue_frame(feedback) {10361068
Ok(()) => {10371069
surface.queued = true;10381070
surface.queued_at = now_ns();@@ -1047,7 +1079,18 @@ fn render_surface(state: &mut State, crtc: crtc::Handle, target: u64) -> bool {
10471079
}10481080
queue_ns = queue_began.elapsed().as_nanos() as u64;10491081
}1050
- Ok(_) => lock.shown(&surface.output),1082
+ Ok(result) => {1083
+ drop(crate::frame::take_presentation_feedback(1084
+ wm,1085
+ lock,1086
+ xwayland.as_ref().map_or(&[], |x| &x.unmanaged),1087
+ surface.id,1088
+ &surface.output,1089
+ now,1090
+ &result.states,1091
+ ));1092
+ lock.shown(&surface.output);1093
+ }10511094
Err(e) => {10521095
failed = true;10531096
tracing::warn!("rendering failed: {e}");@@ -9,10 +9,17 @@ use crate::wm::Wm;
99
use monolith_config::Decoration;1010
use monolith_layout::OutputId;1111
use monolith_render_vk::VulkanRenderer;12
-use smithay::desktop::utils::send_frames_surface_tree;12
+use smithay::backend::renderer::element::RenderElementStates;13
+use smithay::desktop::Window;14
+use smithay::desktop::utils::{15
+ OutputPresentationFeedback, send_frames_surface_tree,16
+ surface_presentation_feedback_flags_from_states, take_presentation_feedback_surface_tree,17
+};1318
use smithay::input::pointer::CursorImageStatus;1419
use smithay::output::Output;20
+use smithay::reexports::wayland_server::protocol::wl_surface::WlSurface;1521
use smithay::utils::Point;22
+use smithay::wayland::compositor::SurfaceData;1623
use smithay::xwayland::X11Surface;1724
use std::time::Duration;1825
@@ -100,21 +107,13 @@ pub fn send_frame_callbacks(
100107
crate::lock::send_frames(lock, output, elapsed);101108
return;102109
}103
- let arr = wm.layout.arrange(o);104
- let covering = arr105
- .fullscreen106
- .filter(|&w| wm.anim.gliding(w, now).is_none())107
- .and_then(|w| wm.window(w));108
- let over: Vec<_> = arr.over.iter().filter_map(|&w| wm.window(w)).collect();109
- for window in wm.space.elements_for_output(output) {110
- if covering.is_some_and(|c| c != window && !over.contains(&window)) {111
- continue;112
- }110
+ let (windows, covered) = shown_windows(wm, o, output, now);111
+ for window in windows {113112
window.send_frame(output, elapsed, Some(Duration::ZERO), |_, _| {114113
Some(output.clone())115114
});116115
}117
- crate::layers::send_frames(output, elapsed, covering.is_some());116
+ crate::layers::send_frames(output, elapsed, covered);118117
if let Some(geo) = wm.space.output_geometry(output) {119118
for surface in unmanaged120119
.iter()@@ -128,6 +127,66 @@ pub fn send_frame_callbacks(
128127
}129128
}130129
130
+/// The windows `output` shows at `now`, and whether a fullscreen window at131
+/// rest hides everything below the overlay layer.132
+fn shown_windows<'a>(133
+ wm: &'a Wm,134
+ o: OutputId,135
+ output: &'a Output,136
+ now: u64,137
+) -> (Vec<&'a Window>, bool) {138
+ let arr = wm.layout.arrange(o);139
+ let covering = arr140
+ .fullscreen141
+ .filter(|&w| wm.anim.gliding(w, now).is_none())142
+ .and_then(|w| wm.window(w));143
+ let over: Vec<_> = arr.over.iter().filter_map(|&w| wm.window(w)).collect();144
+ let windows = wm145
+ .space146
+ .elements_for_output(output)147
+ .filter(|&w| covering.is_none_or(|c| c == w || over.contains(&w)))148
+ .collect();149
+ (windows, covering.is_some())150
+}151
+152
+/// Takes the presentation feedback of everything `output` shows at `now`,153
+/// to be marked presented once the frame that was just rendered reaches the154
+/// screen. `states` tells which surfaces went to a plane untouched.155
+pub fn take_presentation_feedback(156
+ wm: &Wm,157
+ lock: &Lock,158
+ unmanaged: &[X11Surface],159
+ o: OutputId,160
+ output: &Output,161
+ now: u64,162
+ states: &RenderElementStates,163
+) -> OutputPresentationFeedback {164
+ let mut feedback = OutputPresentationFeedback::new(output);165
+ let primary = |_: &WlSurface, _: &SurfaceData| Some(output.clone());166
+ let flags = |s: &WlSurface, _: &SurfaceData| {167
+ surface_presentation_feedback_flags_from_states(s, None, states)168
+ };169
+ if lock.active() {170
+ crate::lock::take_presentation_feedback(lock, output, &mut feedback, primary, flags);171
+ return feedback;172
+ }173
+ let (windows, covered) = shown_windows(wm, o, output, now);174
+ for window in windows {175
+ window.take_presentation_feedback(&mut feedback, primary, flags);176
+ }177
+ crate::layers::take_presentation_feedback(output, covered, &mut feedback, primary, flags);178
+ if let Some(geo) = wm.space.output_geometry(output) {179
+ for surface in unmanaged180
+ .iter()181
+ .filter(|x| geo.overlaps(x.geometry()))182
+ .filter_map(|x| x.wl_surface())183
+ {184
+ take_presentation_feedback_surface_tree(&surface, &mut feedback, primary, flags);185
+ }186
+ }187
+ feedback188
+}189
+131190
/// Lets a client that set its own cursor image draw the next frame of it132191
/// while the pointer is on `output`, so animated cursors keep moving.133192
pub fn send_cursor_frames(wm: &Wm, input: &Input, o: OutputId, output: &Output, elapsed: Duration) {@@ -9,6 +9,7 @@ use monolith_render_vk::VulkanRenderer;
99
use smithay::backend::renderer::ImportDma;1010
use smithay::reexports::wayland_protocols::xdg::shell::server::xdg_toplevel::WmCapabilities;1111
use smithay::reexports::wayland_server::DisplayHandle;12
+use smithay::utils::{ClockSource, Monotonic};1213
use smithay::wayland::compositor::CompositorState;1314
use smithay::wayland::cursor_shape::CursorShapeManagerState;1415
use smithay::wayland::dmabuf::{DmabufFeedbackBuilder, DmabufGlobal, DmabufState};@@ -20,6 +21,7 @@ use smithay::wayland::image_capture_source::{
2021
};2122
use smithay::wayland::image_copy_capture::ImageCopyCaptureState;2223
use smithay::wayland::output::OutputManagerState;24
+use smithay::wayland::presentation::PresentationState;2325
use smithay::wayland::selection::data_device::DataDeviceState;2426
use smithay::wayland::selection::ext_data_control::DataControlState as ExtDataControlState;2527
use smithay::wayland::selection::primary_selection::PrimarySelectionState;@@ -67,6 +69,7 @@ pub struct Protocols {
6769
_viewporter: ViewporterState,6870
_single_pixel_buffer: SinglePixelBufferState,6971
_fractional_scale: FractionalScaleManagerState,72
+ _presentation: PresentationState,7073
}7174
7275
impl Protocols {@@ -130,6 +133,7 @@ impl Protocols {
130133
_viewporter: ViewporterState::new::<State>(dh),131134
_single_pixel_buffer: SinglePixelBufferState::new::<State>(dh),132135
_fractional_scale: FractionalScaleManagerState::new::<State>(dh),136
+ _presentation: PresentationState::new::<State>(dh, Monotonic::ID as u32),133137
}134138
}135139
}@@ -11,12 +11,14 @@ use smithay::backend::renderer::element::surface::{
1111
WaylandSurfaceRenderElement, render_elements_from_surface_tree,1212
};1313
use smithay::desktop::{LayerSurface, PopupKind, WindowSurfaceType, layer_map_for_output};14
+use smithay::desktop::utils::OutputPresentationFeedback;1415
use smithay::output::Output;16
+use smithay::reexports::wayland_protocols::wp::presentation_time::server::wp_presentation_feedback::Kind as PresentationKind;1517
use smithay::reexports::wayland_server::Resource;1618
use smithay::reexports::wayland_server::protocol::wl_output::WlOutput;1719
use smithay::reexports::wayland_server::protocol::wl_surface::WlSurface;1820
use smithay::utils::{Logical, Point, Scale};19
-use smithay::wayland::compositor::with_states;21
+use smithay::wayland::compositor::{SurfaceData, with_states};2022
use smithay::wayland::seat::WaylandFocus;2123
use smithay::wayland::shell::wlr_layer::{2224
KeyboardInteractivity, Layer, LayerSurface as WlrLayerSurface, LayerSurfaceData,@@ -306,3 +308,18 @@ pub fn send_frames(output: &Output, elapsed: Duration, covered: bool) {
306308
});307309
}308310
}311
+312
+pub fn take_presentation_feedback(313
+ output: &Output,314
+ covered: bool,315
+ feedback: &mut OutputPresentationFeedback,316
+ primary: impl FnMut(&WlSurface, &SurfaceData) -> Option<Output> + Copy,317
+ flags: impl FnMut(&WlSurface, &SurfaceData) -> PresentationKind + Copy,318
+) {319
+ for l in layer_map_for_output(output).layers() {320
+ if covered && l.layer() != Layer::Overlay {321
+ continue;322
+ }323
+ l.take_presentation_feedback(feedback, primary, flags);324
+ }325
+}@@ -7,11 +7,16 @@ use monolith_render_vk::VulkanRenderer;
77
use smithay::backend::renderer::element::Kind;88
use smithay::backend::renderer::element::surface::render_elements_from_surface_tree;99
use smithay::desktop::WindowSurfaceType;10
-use smithay::desktop::utils::{send_frames_surface_tree, under_from_surface_tree};10
+use smithay::desktop::utils::{11
+ OutputPresentationFeedback, send_frames_surface_tree, take_presentation_feedback_surface_tree,12
+ under_from_surface_tree,13
+};1114
use smithay::output::Output;1215
use smithay::reexports::wayland_server::protocol::wl_output::WlOutput;1316
use smithay::reexports::wayland_server::protocol::wl_surface::WlSurface;17
+use smithay::reexports::wayland_protocols::wp::presentation_time::server::wp_presentation_feedback::Kind as PresentationKind;1418
use smithay::utils::{Logical, Point};19
+use smithay::wayland::compositor::SurfaceData;1520
use smithay::wayland::session_lock::{1621
LockSurface, SessionLockHandler, SessionLockManagerState, SessionLocker,1722
};@@ -212,3 +217,15 @@ pub fn send_frames(lock: &Lock, output: &Output, elapsed: Duration) {
212217
);213218
}214219
}220
+221
+pub fn take_presentation_feedback(222
+ lock: &Lock,223
+ output: &Output,224
+ feedback: &mut OutputPresentationFeedback,225
+ primary: impl FnMut(&WlSurface, &SurfaceData) -> Option<Output>,226
+ flags: impl FnMut(&WlSurface, &SurfaceData) -> PresentationKind,227
+) {228
+ if let Some(surface) = lock.surface_on(output) {229
+ take_presentation_feedback_surface_tree(surface.wl_surface(), feedback, primary, flags);230
+ }231
+}@@ -7,7 +7,7 @@ mod input;
77
88
use crate::backend::{Backend, logical_area};99
use crate::state::Services;10
-use crate::state::State;10
+use crate::state::{State, now_ns};1111
use calloop_wayland_source::WaylandSource;1212
use monolith_layout::OutputId;1313
use monolith_render_vk::VulkanAllocator;@@ -17,10 +17,14 @@ use smithay::backend::renderer::Bind;
1717
use smithay::backend::renderer::damage::OutputDamageTracker;1818
use smithay::output::{Mode, Output, PhysicalProperties, Scale, Subpixel};1919
use smithay::reexports::calloop::EventLoop;20
-use smithay::utils::{Logical, Size, Transform};20
+use smithay::desktop::utils::OutputPresentationFeedback;21
+use smithay::reexports::wayland_protocols::wp::presentation_time::server::wp_presentation_feedback::Kind;22
+use smithay::utils::{Logical, Monotonic, Size, Time, Transform};23
+use smithay::wayland::presentation::Refresh;24
+use std::time::Duration;2125
use wayland_client::globals::{GlobalListContents, registry_queue_init};2226
use wayland_client::protocol::{wl_buffer, wl_callback, wl_compositor, wl_registry, wl_surface};23
-use wayland_client::{Connection, Dispatch, QueueHandle, delegate_noop};27
+use wayland_client::{Connection, Dispatch, QueueHandle, WEnum, delegate_noop};2428
use wayland_protocols::wp::linux_dmabuf::zv1::client::{2529
zwp_linux_buffer_params_v1, zwp_linux_dmabuf_v1,2630
};@@ -56,6 +60,11 @@ pub struct Host {
5660
monotonic: bool,5761
last_present_ns: Option<u64>,5862
refresh_ns: u64,63
+ feedback: Vec<(u64, OutputPresentationFeedback)>,64
+ next_feedback: u64,65
+ /// Without host presentation-time the frame callback is the nearest66
+ /// thing to a presented event.67
+ awaiting_frame: Vec<OutputPresentationFeedback>,5968
}6069
6170
impl Host {@@ -155,6 +164,9 @@ pub fn init(
155164
monotonic: false,156165
last_present_ns: None,157166
refresh_ns: 16_666_667,167
+ feedback: Vec::new(),168
+ next_feedback: 0,169
+ awaiting_frame: Vec::new(),158170
}));159171
queue.roundtrip(state)?;160172
queue.roundtrip(state)?;@@ -252,7 +264,7 @@ pub fn render(state: &mut State) {
252264
}253265
};254266
let mut dmabuf = (*slot).clone();255
- let damage = {267
+ let (damage, feedback) = {256268
let mut fb = match renderer.bind(&mut dmabuf) {257269
Ok(fb) => fb,258270
Err(e) => {@@ -278,7 +290,16 @@ pub fn render(state: &mut State) {
278290
if let Err(e) = result.sync.wait() {279291
tracing::warn!("waiting for the frame failed: {e:?}");280292
}281
- result.damage.cloned()293
+ let feedback = crate::frame::take_presentation_feedback(294
+ wm,295
+ lock,296
+ xwayland.as_ref().map_or(&[], |x| &x.unmanaged),297
+ o,298
+ &host.output,299
+ now,300
+ &result.states,301
+ );302
+ (result.damage.cloned(), feedback)282303
}283304
Err(e) => {284305
tracing::warn!("rendering failed: {e:?}");@@ -326,7 +347,12 @@ pub fn render(state: &mut State) {
326347
}327348
host.surface.frame(&host.qh, ());328349
if let Some(p) = &host.presentation {329
- p.feedback(&host.surface, &host.qh, ());350
+ let id = host.next_feedback;351
+ host.next_feedback += 1;352
+ p.feedback(&host.surface, &host.qh, id);353
+ host.feedback.push((id, feedback));354
+ } else {355
+ host.awaiting_frame.push(feedback);330356
}331357
host.surface.commit();332358
swapchain.submitted(&slot);@@ -520,6 +546,14 @@ impl Dispatch<wl_callback::WlCallback, ()> for State {
520546
&& let Some(host) = state.backend.nested_mut()521547
{522548
host.frame_pending = false;549
+ for mut feedback in host.awaiting_frame.drain(..) {550
+ feedback.presented(551
+ Time::<Monotonic>::from(Duration::from_nanos(now_ns())),552
+ Refresh::fixed(Duration::from_nanos(host.refresh_ns)),553
+ 0,554
+ Kind::empty(),555
+ );556
+ }523557
}524558
}525559
}@@ -547,30 +581,61 @@ impl Dispatch<wp_presentation::WpPresentation, ()> for State {
547581
}548582
}549583
550
-impl Dispatch<wp_presentation_feedback::WpPresentationFeedback, ()> for State {584
+impl Dispatch<wp_presentation_feedback::WpPresentationFeedback, u64> for State {551585
fn event(552586
state: &mut Self,553587
_: &wp_presentation_feedback::WpPresentationFeedback,554588
event: wp_presentation_feedback::Event,555
- _: &(),589
+ id: &u64,556590
_: &Connection,557591
_: &QueueHandle<Self>,558592
) {593
+ let Some(host) = state.backend.nested_mut() else {594
+ return;595
+ };596
+ let pending = host.feedback.iter().position(|(i, _)| i == id);559597
let wp_presentation_feedback::Event::Presented {560598
tv_sec_hi,561599
tv_sec_lo,562600
tv_nsec,563601
refresh,564
- ..602
+ seq_hi,603
+ seq_lo,604
+ flags,565605
} = event566606
else {567
- return;568
- };569
- let Some(host) = state.backend.nested_mut() else {607
+ if let wp_presentation_feedback::Event::Discarded = event608
+ && let Some(i) = pending609
+ {610
+ host.feedback.remove(i);611
+ }570612
return;571613
};572614
let secs = (u64::from(tv_sec_hi) << 32) | u64::from(tv_sec_lo);573
- host.last_present_ns = Some(secs * 1_000_000_000 + u64::from(tv_nsec));615
+ let presented_ns = secs * 1_000_000_000 + u64::from(tv_nsec);616
+ host.last_present_ns = Some(presented_ns);617
+ if let Some(i) = pending {618
+ let (_, mut feedback) = host.feedback.remove(i);619
+ let (time, flags) = if host.monotonic {620
+ let bits = match flags {621
+ WEnum::Value(k) => k.bits(),622
+ WEnum::Unknown(bits) => bits,623
+ };624
+ (presented_ns, Kind::from_bits_truncate(bits))625
+ } else {626
+ (now_ns(), Kind::empty())627
+ };628
+ let refresh = match refresh {629
+ 0 => Refresh::Unknown,630
+ ns => Refresh::fixed(Duration::from_nanos(u64::from(ns))),631
+ };632
+ feedback.presented(633
+ Time::<Monotonic>::from(Duration::from_nanos(time)),634
+ refresh,635
+ (u64::from(seq_hi) << 32) | u64::from(seq_lo),636
+ flags,637
+ );638
+ }574639
if refresh > 0 && u64::from(refresh) != host.refresh_ns {575640
host.refresh_ns = u64::from(refresh);576641
let mode = Mode {