From f1bcd8357b1e7746cc56a6bea323a727c425f72c Mon Sep 17 00:00:00 2001 From: Ottatop Date: Sat, 10 Aug 2024 23:01:10 -0500 Subject: [PATCH] Properly throttle rendering --- Cargo.lock | 18 -- Cargo.toml | 1 - src/backend.rs | 154 +++--------- src/backend/udev.rs | 451 +++++++++++++++++++++++------------ src/backend/udev/drm/util.rs | 28 ++- src/backend/udev/frame.rs | 78 ++++++ src/backend/winit.rs | 40 ++-- src/handlers.rs | 9 +- src/layout/transaction.rs | 38 ++- src/render.rs | 45 +--- src/render/util/snapshot.rs | 5 +- src/state.rs | 283 ++++++++++++++++++++-- src/window/window_state.rs | 3 + 13 files changed, 753 insertions(+), 400 deletions(-) create mode 100644 src/backend/udev/frame.rs diff --git a/Cargo.lock b/Cargo.lock index 920ce46..5695e90 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -507,12 +507,6 @@ dependencies = [ "syn 2.0.71", ] -[[package]] -name = "byteorder" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" - [[package]] name = "bytes" version = "1.6.1" @@ -2186,17 +2180,6 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" -[[package]] -name = "image" -version = "0.25.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd54d660e773627692c524beaad361aca785a4f9f5730ce91f42aabe5bce3d11" -dependencies = [ - "bytemuck", - "byteorder", - "num-traits", -] - [[package]] name = "indexmap" version = "1.9.3" @@ -3288,7 +3271,6 @@ dependencies = [ "dircpy", "drm-sys", "gag", - "image", "indexmap 2.2.6", "libdisplay-info-sys", "pinnacle", diff --git a/Cargo.toml b/Cargo.toml index a21c177..5906433 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -100,7 +100,6 @@ anyhow = { version = "1.0.86", features = ["backtrace"] } thiserror = "1.0.61" # xcursor stuff xcursor = { version = "0.3.5" } -image = { version = "0.25.1", default-features = false } # gRPC prost = { workspace = true } tonic = { workspace = true } diff --git a/src/backend.rs b/src/backend.rs index 8dfc97b..21becbc 100644 --- a/src/backend.rs +++ b/src/backend.rs @@ -1,42 +1,20 @@ // SPDX-License-Identifier: GPL-3.0-or-later -use std::time::Duration; - use smithay::{ backend::{ allocator::dmabuf::Dmabuf, - renderer::{ - element::{ - default_primary_scanout_output_compare, utils::select_dmabuf_feedback, - RenderElementStates, - }, - gles::GlesRenderer, - ImportDma, Renderer, TextureFilter, - }, + renderer::{gles::GlesRenderer, ImportDma, Renderer, TextureFilter}, }, delegate_dmabuf, - desktop::{ - layer_map_for_output, - utils::{ - send_frames_surface_tree, surface_primary_scanout_output, - update_surface_primary_scanout_output, - }, - Space, - }, - input::pointer::CursorImageStatus, output::Output, reexports::wayland_server::protocol::wl_surface::WlSurface, - wayland::{ - dmabuf::{DmabufGlobal, DmabufHandler, DmabufState, ImportNotifier}, - fractional_scale::with_fractional_scale, - }, + wayland::dmabuf::{DmabufGlobal, DmabufHandler, DmabufState, ImportNotifier}, }; use tracing::error; use crate::{ output::OutputMode, - state::{Pinnacle, State, SurfaceDmabufFeedback, WithState}, - window::WindowElement, + state::{Pinnacle, State}, }; #[cfg(feature = "testing")] @@ -129,6 +107,25 @@ impl Backend { } } + pub fn render_scheduled_outputs(&mut self, pinnacle: &mut Pinnacle) { + match self { + Backend::Winit(winit) => winit.render_if_scheduled(pinnacle), + Backend::Udev(udev) => { + for output in pinnacle + .outputs + .iter() + .filter(|(_, global)| global.is_some()) + .map(|(op, _)| op.clone()) + .collect::>() + { + udev.render_if_scheduled(pinnacle, &output); + } + } + #[cfg(feature = "testing")] + Backend::Dummy(_) => todo!(), + } + } + /// Returns `true` if the backend is [`Winit`]. /// /// [`Winit`]: Backend::Winit @@ -146,6 +143,13 @@ impl Backend { } } +#[derive(Debug, Clone, Copy)] +pub enum RenderResult { + Submitted, + NoDamage, + Skipped, +} + pub trait BackendData: 'static { fn seat_name(&self) -> String; fn reset_buffers(&mut self, output: &Output); @@ -194,106 +198,6 @@ impl BackendData for Backend { } } -/// Update surface primary scanout outputs and send frames and dmabuf feedback to visible windows -/// and layers. -pub fn post_repaint( - output: &Output, - render_element_states: &RenderElementStates, - space: &Space, - dmabuf_feedback: Option>, - time: Duration, - cursor_status: &CursorImageStatus, -) { - // let throttle = Some(Duration::from_secs(1)); - let throttle = Some(Duration::ZERO); - - space.elements().for_each(|window| { - window.with_surfaces(|surface, states_inner| { - let primary_scanout_output = update_surface_primary_scanout_output( - surface, - output, - states_inner, - render_element_states, - default_primary_scanout_output_compare, - ); - - if let Some(output) = primary_scanout_output { - with_fractional_scale(states_inner, |fraction_scale| { - fraction_scale.set_preferred_scale(output.current_scale().fractional_scale()); - }); - } - }); - - if space.outputs_for_element(window).contains(output) { - window.send_frame(output, time, throttle, surface_primary_scanout_output); - if let Some(dmabuf_feedback) = dmabuf_feedback { - window.send_dmabuf_feedback( - output, - surface_primary_scanout_output, - |surface, _| { - select_dmabuf_feedback( - surface, - render_element_states, - dmabuf_feedback.render_feedback, - dmabuf_feedback.scanout_feedback, - ) - }, - ); - } - } - }); - - let map = layer_map_for_output(output); - for layer_surface in map.layers() { - layer_surface.with_surfaces(|surface, states| { - let primary_scanout_output = update_surface_primary_scanout_output( - surface, - output, - states, - render_element_states, - default_primary_scanout_output_compare, - ); - - if let Some(output) = primary_scanout_output { - with_fractional_scale(states, |fraction_scale| { - fraction_scale.set_preferred_scale(output.current_scale().fractional_scale()); - }); - } - }); - - layer_surface.send_frame(output, time, throttle, surface_primary_scanout_output); - if let Some(dmabuf_feedback) = dmabuf_feedback { - layer_surface.send_dmabuf_feedback( - output, - surface_primary_scanout_output, - |surface, _| { - select_dmabuf_feedback( - surface, - render_element_states, - dmabuf_feedback.render_feedback, - dmabuf_feedback.scanout_feedback, - ) - }, - ); - } - } - - // Send frames to the cursor surface so it updates correctly - if let CursorImageStatus::Surface(surf) = cursor_status { - send_frames_surface_tree(surf, output, time, Some(Duration::ZERO), |_, _| None); - } - - if let Some(lock_surface) = output.with_state(|state| state.lock_surface.clone()) { - send_frames_surface_tree( - lock_surface.wl_surface(), - output, - time, - Some(Duration::ZERO), - |_, _| None, - ); - } -} - impl DmabufHandler for State { fn dmabuf_state(&mut self) -> &mut DmabufState { match &mut self.backend { diff --git a/src/backend/udev.rs b/src/backend/udev.rs index 381d56c..8df4bcf 100644 --- a/src/backend/udev.rs +++ b/src/backend/udev.rs @@ -1,16 +1,21 @@ // SPDX-License-Identifier: GPL-3.0-or-later mod drm; +mod frame; mod gamma; use assert_matches::assert_matches; pub use drm::util::drm_mode_from_api_modeline; +use frame::FrameClock; use indexmap::IndexSet; -use std::{collections::HashMap, path::Path, time::Duration}; +use std::{collections::HashMap, mem, path::Path, time::Duration}; use anyhow::{anyhow, ensure, Context}; -use drm::{set_crtc_active, util::create_drm_mode}; +use drm::{ + set_crtc_active, + util::{create_drm_mode, refresh_interval}, +}; use pinnacle_api_defs::pinnacle::signal::v0alpha1::OutputConnectResponse; use smithay::{ backend::{ @@ -49,8 +54,10 @@ use smithay::{ output::{Output, PhysicalProperties, Subpixel}, reexports::{ calloop::{ - self, generic::Generic, timer::Timer, Dispatcher, Idle, Interest, LoopHandle, - PostAction, RegistrationToken, + self, + generic::Generic, + timer::{TimeoutAction, Timer}, + Dispatcher, Interest, LoopHandle, PostAction, RegistrationToken, }, drm::control::{connector, crtc, ModeTypeFlags}, input::Libinput, @@ -81,12 +88,12 @@ use crate::{ pointer::pointer_render_elements, take_presentation_feedback, OutputRenderElement, CLEAR_COLOR, CLEAR_COLOR_LOCKED, }, - state::{Pinnacle, State, SurfaceDmabufFeedback, WithState}, + state::{FrameCallbackSequence, Pinnacle, State, WithState}, }; use self::drm::util::EdidInfo; -use super::{BackendData, UninitBackend}; +use super::{BackendData, RenderResult, UninitBackend}; const SUPPORTED_FORMATS: &[Fourcc] = &[ Fourcc::Abgr2101010, @@ -333,7 +340,7 @@ impl Udev { // Also welcome to some really doodoo code for (crtc, surface) in backend.surfaces.iter_mut() { - match std::mem::take(&mut surface.pending_gamma_change) { + match mem::take(&mut surface.pending_gamma_change) { PendingGammaChange::Idle => { debug!("Restoring from previous gamma"); if let Err(err) = Udev::set_gamma_internal( @@ -450,7 +457,7 @@ impl Udev { } /// Schedule a new render that will cause the compositor to redraw everything. - pub fn schedule_render(&mut self, loop_handle: &LoopHandle, output: &Output) { + pub fn schedule_render(&mut self, output: &Output) { let Some(surface) = render_surface_for_output(output, &mut self.backends) else { debug!("no render surface on output {}", output.name()); return; @@ -458,25 +465,46 @@ impl Udev { // tracing::info!(state = ?surface.render_state, name = output.name()); - match &surface.render_state { - RenderState::Idle => { - let output = output.clone(); - let token = loop_handle.insert_idle(move |state| { - state - .backend - .udev_mut() - .render_surface(&mut state.pinnacle, &output); - }); + let old_state = mem::take(&mut surface.render_state); - surface.render_state = RenderState::Scheduled(token); + surface.render_state = match old_state { + RenderState::Idle => { + // let output = output.clone(); + // let token = loop_handle.insert_idle(move |state| { + // state + // .backend + // .udev_mut() + // .render_surface(&mut state.pinnacle, &output); + // }); + + RenderState::Scheduled } - RenderState::Scheduled(_) => (), - RenderState::WaitingForVblank { dirty: _ } => { - surface.render_state = RenderState::WaitingForVblank { dirty: true } + value @ (RenderState::Scheduled + | RenderState::WaitingForEstimatedVblankAndScheduled(_)) => value, + RenderState::WaitingForVblank { render_needed: _ } => RenderState::WaitingForVblank { + render_needed: true, + }, + RenderState::WaitingForEstimatedVblank(token) => { + RenderState::WaitingForEstimatedVblankAndScheduled(token) } - } + }; } + // pub fn render_scheduled_outputs(&mut self, pinnacle: &mut Pinnacle) { + // for output in pinnacle.outputs.keys().cloned().collect::>() { + // let Some(surface) = render_surface_for_output(&output, &mut self.backends) else { + // continue; + // }; + // + // if matches!( + // surface.render_state, + // RenderState::Scheduled | RenderState::WaitingForEstimatedVblankAndScheduled(_) + // ) { + // self.render_surface(pinnacle, &output); + // } + // } + // } + pub(super) fn set_output_powered(&mut self, output: &Output, powered: bool) { let UdevOutputData { device_id, crtc } = output.user_data().get::().unwrap(); @@ -507,9 +535,11 @@ impl Udev { } if let Some(surface) = render_surface_for_output(output, &mut self.backends) { - if let RenderState::Scheduled(idle) = std::mem::take(&mut surface.render_state) { - idle.cancel(); - } + // TODO: test + surface.render_state = RenderState::Idle; + // if let RenderState::Scheduled(idle) = std::mem::take(&mut surface.render_state) { + // idle.cancel(); + // } } } } @@ -634,7 +664,7 @@ fn get_surface_dmabuf_feedback( render_node: DrmNode, gpu_manager: &mut GpuManager>, composition: &GbmDrmCompositor, -) -> Option { +) -> Option { let primary_formats = gpu_manager .single_renderer(&primary_gpu) .ok()? @@ -683,15 +713,16 @@ fn get_surface_dmabuf_feedback( .build() .ok()?; // INFO: this is an unwrap in Anvil, does it matter? - Some(DrmSurfaceDmabufFeedback { + Some(SurfaceDmabufFeedback { render_feedback, scanout_feedback, }) } -struct DrmSurfaceDmabufFeedback { - render_feedback: DmabufFeedback, - scanout_feedback: DmabufFeedback, +#[derive(Debug)] +pub struct SurfaceDmabufFeedback { + pub render_feedback: DmabufFeedback, + pub scanout_feedback: DmabufFeedback, } /// The state of a [`RenderSurface`]. @@ -700,20 +731,18 @@ enum RenderState { /// No render is scheduled. #[default] Idle, - // TODO: remove the token on tty switch or output unplug - /// A render has been queued. - Scheduled( - /// The idle token from a render being scheduled. - /// This is used to cancel renders if, for example, - /// the output being rendered is removed. - Idle<'static>, - ), - /// A frame was rendered and scheduled and we are waiting for vblank. + /// A render is scheduled to happen at the end of the current event loop cycle. + Scheduled, + /// A frame was rendered and we are waiting for vblank. WaitingForVblank { /// A render was scheduled while waiting for vblank. /// In this case, another render will be scheduled once vblank happens. - dirty: bool, + render_needed: bool, }, + /// A frame caused no damage, but we'll still wait as if it did to prevent busy loops. + WaitingForEstimatedVblank(RegistrationToken), + /// A render was scheduled during a wait for estimated vblank. + WaitingForEstimatedVblankAndScheduled(RegistrationToken), } /// Render surface for an output. @@ -727,12 +756,15 @@ struct RenderSurface { render_node: DrmNode, /// The thing rendering elements and queueing frames. compositor: GbmDrmCompositor, - dmabuf_feedback: Option, + dmabuf_feedback: Option, render_state: RenderState, screencopy_commit_state: ScreencopyCommitState, previous_gamma: Option<[Box<[u16]>; 3]>, pending_gamma_change: PendingGammaChange, + + frame_clock: FrameClock, + frame_callback_sequence: FrameCallbackSequence, } #[derive(Debug, Clone, Default)] @@ -809,15 +841,20 @@ impl Udev { let registration_token = pinnacle .loop_handle - .insert_source(notifier, move |event, metadata, state| match event { - DrmEvent::VBlank(crtc) => { - state - .backend - .udev_mut() - .on_vblank(&mut state.pinnacle, node, crtc, metadata); - } - DrmEvent::Error(error) => { - error!("{:?}", error); + .insert_source(notifier, move |event, metadata, state| { + let metadata = metadata.expect("vblank events must have metadata"); + match event { + DrmEvent::VBlank(crtc) => { + state.backend.udev_mut().on_vblank( + &mut state.pinnacle, + node, + crtc, + metadata, + ); + } + DrmEvent::Error(error) => { + error!("{:?}", error); + } } }) .expect("failed to insert drm notifier into event loop"); @@ -890,7 +927,7 @@ impl Udev { .unwrap_or(0); let drm_mode = connector.modes()[mode_id]; - let wl_mode = smithay::output::Mode::from(drm_mode); + let smithay_mode = smithay::output::Mode::from(drm_mode); let surface = match device .drm @@ -943,7 +980,7 @@ impl Udev { state.serial = serial; }); - output.set_preferred(wl_mode); + output.set_preferred(smithay_mode); let modes = connector .modes() @@ -975,8 +1012,7 @@ impl Udev { GbmBufferFlags::RENDERING | GbmBufferFlags::SCANOUT, ); - // I like how this is still in here - let color_formats = if std::env::var("ANVIL_DISABLE_10BIT").is_ok() { + let color_formats = if std::env::var("PINNACLE_DISABLE_10BIT").is_ok() { SUPPORTED_FORMATS_8BIT_ONLY } else { SUPPORTED_FORMATS @@ -1026,6 +1062,8 @@ impl Udev { screencopy_commit_state: ScreencopyCommitState::default(), previous_gamma: None, pending_gamma_change: PendingGammaChange::Idle, + frame_clock: FrameClock::new(Some(refresh_interval(drm_mode))), + frame_callback_sequence: FrameCallbackSequence::default(), }; device.surfaces.insert(crtc, surface); @@ -1033,7 +1071,7 @@ impl Udev { pinnacle.change_output_state( self, &output, - Some(OutputMode::Smithay(wl_mode)), + Some(OutputMode::Smithay(smithay_mode)), None, None, Some(position), @@ -1158,13 +1196,12 @@ impl Udev { } } - /// Mark [`OutputPresentationFeedback`]s as presented and schedule a new render on idle. fn on_vblank( &mut self, pinnacle: &mut Pinnacle, dev_id: DrmNode, crtc: crtc::Handle, - metadata: &mut Option, + metadata: DrmEventMetadata, ) { let Some(surface) = self .backends @@ -1185,6 +1222,11 @@ impl Udev { return; }; + if matches!(surface.render_state, RenderState::Idle) { + warn!(output = output.name(), "Got vblank for an idle output"); + return; + } + match surface .compositor .frame_submitted() @@ -1192,35 +1234,35 @@ impl Udev { { Ok(user_data) => { if let Some(mut feedback) = user_data.flatten() { - let tp = metadata.as_ref().and_then(|metadata| match metadata.time { - smithay::backend::drm::DrmEventTime::Monotonic(tp) => Some(tp), - smithay::backend::drm::DrmEventTime::Realtime(_) => None, - }); - let seq = metadata - .as_ref() - .map(|metadata| metadata.sequence) - .unwrap_or(0); + let presentation_time = match metadata.time { + smithay::backend::drm::DrmEventTime::Monotonic(tp) => tp, + smithay::backend::drm::DrmEventTime::Realtime(_) => { + // Not supported - let (clock, flags) = if let Some(tp) = tp { - ( - tp.into(), - wp_presentation_feedback::Kind::Vsync - | wp_presentation_feedback::Kind::HwClock - | wp_presentation_feedback::Kind::HwCompletion, - ) + // This value will be ignored in the frame clock code + Duration::ZERO + } + }; + let seq = metadata.sequence as u64; + + let mut flags = wp_presentation_feedback::Kind::Vsync + | wp_presentation_feedback::Kind::HwCompletion; + + let time: Duration = if presentation_time.is_zero() { + pinnacle.clock.now().into() } else { - (pinnacle.clock.now(), wp_presentation_feedback::Kind::Vsync) + flags.insert(wp_presentation_feedback::Kind::HwClock); + presentation_time }; - feedback.presented( - clock, - output - .current_mode() - .map(|mode| Duration::from_secs_f64(1000f64 / mode.refresh as f64)) - .unwrap_or_default(), - seq as u64, + feedback.presented::<_, smithay::utils::Monotonic>( + time, + surface.frame_clock.refresh_interval().unwrap_or_default(), + seq, flags, ); + + surface.frame_clock.presented(presentation_time); } output.with_state_mut(|state| { @@ -1238,79 +1280,68 @@ impl Udev { } }; - let dirty = match std::mem::take(&mut surface.render_state) { - RenderState::WaitingForVblank { dirty } => dirty, + let render_needed = match mem::take(&mut surface.render_state) { + RenderState::WaitingForVblank { render_needed } => render_needed, state => { debug!("vblank happened but render state was {state:?}",); - self.schedule_render(&pinnacle.loop_handle, &output); + // TODO: unreachable return; } }; - if dirty { - self.schedule_render(&pinnacle.loop_handle, &output); - } else { - for window in pinnacle.windows.iter() { - window.send_frame( - &output, - pinnacle.clock.now(), - Some(Duration::ZERO), - |_, _| Some(output.clone()), - ); - } - } - - // Schedule a render when the next frame of an animated cursor should be drawn. - // - // TODO: Remove this and improve the render pipeline. - // Because of how the event loop works and the current implementation of rendering, - // immediately queuing a render here has the possibility of not submitting a new frame to - // DRM, meaning no vblank. The event loop will wait as it has no events, so things like - // animated cursors may hitch and only update when, for example, the cursor is actively - // moving as this generates events. - // - // What we should do is what Niri does: if `render_surface` doesn't cause any damage, - // instead of setting the `RenderState` to Idle, set it to some "waiting for estimated - // vblank" state and have `render_surface` always schedule a timer to fire at the estimated - // vblank time that will attempt another render schedule. - // - // This has the advantage of scheduling a render in a source and not in an idle callback, - // meaning we are guarenteed to have a render happen immediately and we won't have to wait - // for another event or call `loop_signal.wakeup()`. - if let Some(until) = pinnacle - .cursor_state - .time_until_next_animated_cursor_frame() + // TODO: is_animated or unfinished_animations_remain + if render_needed + || pinnacle + .cursor_state + .time_until_next_animated_cursor_frame() + .is_some() { - let _ = pinnacle.loop_handle.insert_source( - Timer::from_duration(until), - move |_, _, state| { - state.schedule_render(&output); - calloop::timer::TimeoutAction::Drop - }, - ); + self.schedule_render(&output); + } else { + pinnacle.send_frame_callbacks(&output, Some(surface.frame_callback_sequence)); + } + } + + pub(super) fn render_if_scheduled(&mut self, pinnacle: &mut Pinnacle, output: &Output) { + let Some(surface) = render_surface_for_output(output, &mut self.backends) else { + return; + }; + + if matches!( + surface.render_state, + RenderState::Scheduled | RenderState::WaitingForEstimatedVblankAndScheduled(_) + ) { + self.render_surface(pinnacle, output); } } /// Render to the [`RenderSurface`] associated with the given `output`. #[tracing::instrument(level = "debug", skip(self, pinnacle), fields(output = output.name()))] - fn render_surface(&mut self, pinnacle: &mut Pinnacle, output: &Output) { + fn render_surface(&mut self, pinnacle: &mut Pinnacle, output: &Output) -> RenderResult { let Some(surface) = render_surface_for_output(output, &mut self.backends) else { - return; + return RenderResult::Skipped; }; if !pinnacle.outputs.contains_key(output) { surface.render_state = RenderState::Idle; - return; + return RenderResult::Skipped; } // TODO: possibly lift this out and make it so that scheduling a render // does nothing on powered off outputs if output.with_state(|state| !state.powered) { surface.render_state = RenderState::Idle; - return; + return RenderResult::Skipped; } - assert_matches!(surface.render_state, RenderState::Scheduled(_)); + assert_matches!( + surface.render_state, + RenderState::Scheduled | RenderState::WaitingForEstimatedVblankAndScheduled(_) + ); + + let time_to_next_presentation = surface + .frame_clock + .time_to_next_presentation(&pinnacle.clock); let render_node = surface.render_node; let primary_gpu = self.primary_gpu; @@ -1428,9 +1459,13 @@ impl Udev { clear_color, )?; - if let PrimaryPlaneElement::Swapchain(element) = &render_frame_result.primary_element { - if let Err(err) = element.sync.wait() { - warn!("Failed to wait for sync point: {err}"); + if render_frame_result.needs_sync() { + if let PrimaryPlaneElement::Swapchain(element) = + &render_frame_result.primary_element + { + if let Err(err) = element.sync.wait() { + warn!("Failed to wait for sync point: {err}"); + } } } @@ -1445,20 +1480,11 @@ impl Udev { ); } - super::post_repaint( - output, - &render_frame_result.states, - &pinnacle.space, - surface - .dmabuf_feedback - .as_ref() - .map(|feedback| SurfaceDmabufFeedback { - render_feedback: &feedback.render_feedback, - scanout_feedback: &feedback.scanout_feedback, - }), - Duration::from(pinnacle.clock.now()), - pinnacle.cursor_state.cursor_image(), - ); + pinnacle.update_primary_scanout_output(output, &render_frame_result.states); + + if let Some(dmabuf_feedback) = surface.dmabuf_feedback.as_ref() { + pinnacle.send_dmabuf_feedback(output, dmabuf_feedback, &render_frame_result.states); + } let rendered = !render_frame_result.is_empty; @@ -1471,24 +1497,137 @@ impl Udev { surface .compositor - .queue_frame(Some(output_presentation_feedback)) - .map_err(SwapBuffersError::from)?; + .queue_frame(Some(output_presentation_feedback))?; } Ok(rendered) })(); - match result { - Ok(true) => surface.render_state = RenderState::WaitingForVblank { dirty: false }, - // TODO: Don't immediately set this to Idle; this allows hot loops of `render_surface`. - // Instead, pull a Niri and schedule a timer for the next estimated vblank to allow - // another scheduled render. - Ok(false) | Err(_) => surface.render_state = RenderState::Idle, + let render_result = match result { + Ok(true) => { + let new_state = RenderState::WaitingForVblank { + render_needed: false, + }; + + match mem::replace(&mut surface.render_state, new_state) { + RenderState::Idle => unreachable!(), + RenderState::Scheduled => (), + RenderState::WaitingForVblank { .. } => unreachable!(), + RenderState::WaitingForEstimatedVblank(_) => unreachable!(), + RenderState::WaitingForEstimatedVblankAndScheduled(token) => { + pinnacle.loop_handle.remove(token); + } + }; + + // From niri: We queued this frame successfully, so the current client buffers were + // latched. We can send frame callbacks now, since a new client commit + // will no longer overwrite this frame and will wait for a VBlank. + surface.frame_callback_sequence.increment(); + + pinnacle.send_frame_callbacks(output, Some(surface.frame_callback_sequence)); + + // Return here to not queue the estimated vblank timer on a submitted frame + return RenderResult::Submitted; + } + Ok(false) => RenderResult::NoDamage, + Err(err) => { + warn!(output = output.name(), "Render failed for surface: {err}"); + + surface.render_state = if let RenderState::WaitingForEstimatedVblank(token) + | RenderState::WaitingForEstimatedVblankAndScheduled( + token, + ) = surface.render_state + { + RenderState::WaitingForEstimatedVblank(token) + } else { + RenderState::Idle + }; + + RenderResult::Skipped + } + }; + + self.queue_estimated_vblank_timer(pinnacle, output, time_to_next_presentation); + + // if render_after_transaction_finish { + // self.schedule_render(output); + // } + + render_result + } + + fn queue_estimated_vblank_timer( + &mut self, + pinnacle: &mut Pinnacle, + output: &Output, + mut time_to_next_presentation: Duration, + ) { + let Some(surface) = render_surface_for_output(output, &mut self.backends) else { + return; + }; + + match mem::take(&mut surface.render_state) { + RenderState::Idle => { + // TODO: supposed to be unreachable, fix above + // unreachable!() + } + RenderState::Scheduled => (), + RenderState::WaitingForVblank { .. } => unreachable!(), + RenderState::WaitingForEstimatedVblank(token) + | RenderState::WaitingForEstimatedVblankAndScheduled(token) => { + surface.render_state = RenderState::WaitingForEstimatedVblank(token); + return; + } } - if render_after_transaction_finish { - self.schedule_render(&pinnacle.loop_handle, output); + // No use setting a zero timer, since we'll send frame callbacks anyway right after the call to + // render(). This can happen for example with unknown presentation time from DRM. + if time_to_next_presentation.is_zero() { + time_to_next_presentation += surface + .frame_clock + .refresh_interval() + // Unknown refresh interval, i.e. winit backend. Would be good to estimate it somehow + // but it's not that important for this code path. + .unwrap_or(Duration::from_micros(16_667)); } + + let timer = Timer::from_duration(time_to_next_presentation); + + let output = output.clone(); + let token = pinnacle + .loop_handle + .insert_source(timer, move |_, _, state| { + state + .backend + .udev_mut() + .on_estimated_vblank_timer(&mut state.pinnacle, &output); + TimeoutAction::Drop + }) + .unwrap(); + + surface.render_state = RenderState::WaitingForEstimatedVblank(token); + } + + fn on_estimated_vblank_timer(&mut self, pinnacle: &mut Pinnacle, output: &Output) { + let Some(surface) = render_surface_for_output(output, &mut self.backends) else { + return; + }; + + surface.frame_callback_sequence.increment(); + + match mem::take(&mut surface.render_state) { + RenderState::Idle => unreachable!(), + RenderState::Scheduled => unreachable!(), + RenderState::WaitingForVblank { .. } => unreachable!(), + RenderState::WaitingForEstimatedVblank(_) => (), + RenderState::WaitingForEstimatedVblankAndScheduled(_) => { + surface.render_state = RenderState::Scheduled; + return; + } + } + + // If animations, queue redraw, else + pinnacle.send_frame_callbacks(output, Some(surface.frame_callback_sequence)) } } @@ -1514,7 +1653,7 @@ fn handle_pending_screencopy<'a>( let Some(mut screencopy) = output.with_state_mut(|state| state.screencopy.take()) else { return; }; - assert!(screencopy.output() == output); + assert_eq!(screencopy.output(), output); let untransformed_output_size = output.current_mode().expect("output no mode").size; diff --git a/src/backend/udev/drm/util.rs b/src/backend/udev/drm/util.rs index 56c43d6..8900831 100644 --- a/src/backend/udev/drm/util.rs +++ b/src/backend/udev/drm/util.rs @@ -1,4 +1,4 @@ -use std::{ffi::CString, io::Write, mem::MaybeUninit, num::NonZeroU32}; +use std::{ffi::CString, io::Write, mem::MaybeUninit, num::NonZeroU32, time::Duration}; use anyhow::Context; use drm_sys::{ @@ -12,7 +12,7 @@ use libdisplay_info_sys::cvt::{ use pinnacle_api_defs::pinnacle::output::v0alpha1::SetModelineRequest; use smithay::reexports::drm::{ self, - control::{connector, property, Device, ResourceHandle}, + control::{connector, property, Device, ModeFlags, ResourceHandle}, }; use super::edid_manus::get_manufacturer; @@ -269,3 +269,27 @@ fn generate_cvt_mode(hdisplay: i32, vdisplay: i32, vrefresh: Option) -> drm name, } } + +pub fn refresh_interval(mode: drm::control::Mode) -> Duration { + let clock = mode.clock() as u64; + let htotal = mode.hsync().2 as u64; + let vtotal = mode.vsync().2 as u64; + + let mut numerator = htotal * vtotal * 1_000_000; + let mut denominator = clock; + + if mode.flags().contains(ModeFlags::INTERLACE) { + denominator *= 2; + } + + if mode.flags().contains(ModeFlags::DBLSCAN) { + numerator *= 2; + } + + if mode.vscan() > 1 { + numerator *= mode.vscan() as u64; + } + + let refresh_interval_ns = (numerator + denominator / 2) / denominator; + Duration::from_nanos(refresh_interval_ns) +} diff --git a/src/backend/udev/frame.rs b/src/backend/udev/frame.rs new file mode 100644 index 0000000..e4c8f38 --- /dev/null +++ b/src/backend/udev/frame.rs @@ -0,0 +1,78 @@ +use std::{num::NonZeroU64, time::Duration}; + +use smithay::utils::{Clock, Monotonic}; +use tracing::error; + +pub struct FrameClock { + last_presentation_time: Option, + refresh_interval_ns: Option, + // TODO: vrr +} + +impl FrameClock { + // TODO: vrr + pub fn new(refresh_interval: Option) -> Self { + let refresh_interval_ns = refresh_interval.map(|interval| { + assert_eq!(interval.as_secs(), 0); + NonZeroU64::new(interval.subsec_nanos().into()).unwrap() + }); + + Self { + last_presentation_time: None, + refresh_interval_ns, + } + } + + pub fn refresh_interval(&self) -> Option { + self.refresh_interval_ns + .map(|ns| Duration::from_nanos(ns.get())) + } + + pub fn presented(&mut self, presentation_time: Duration) { + if presentation_time.is_zero() { + // Not interested in these + return; + } + + self.last_presentation_time = Some(presentation_time); + } + + /// Returns the amount of time from now to the time of the next estimated presentation. + pub fn time_to_next_presentation(&self, clock: &Clock) -> Duration { + let mut now: Duration = clock.now().into(); + + let Some(refresh_interval_ns) = self.refresh_interval_ns else { + return Duration::ZERO; + }; + + let Some(last_presentation_time) = self.last_presentation_time else { + return Duration::ZERO; + }; + + let refresh_interval_ns = refresh_interval_ns.get(); + + if now <= last_presentation_time { + // Got an early vblank + let orig_now = now; + now += Duration::from_nanos(refresh_interval_ns); + + if now < last_presentation_time { + // Not sure when this can happen + error!( + now = ?orig_now, + ?last_presentation_time, + "Got a 2+ early vblank, {:?} until presentation", + last_presentation_time - now, + ); + now = last_presentation_time + Duration::from_nanos(refresh_interval_ns); + } + } + + let duration_since_last = now - last_presentation_time; + let ns_since_last = duration_since_last.as_nanos() as u64; + let ns_to_next = (ns_since_last / refresh_interval_ns + 1) * refresh_interval_ns; + + // TODO: vrr + last_presentation_time + Duration::from_nanos(ns_to_next) - now + } +} diff --git a/src/backend/winit.rs b/src/backend/winit.rs index 19c25c8..0160310 100644 --- a/src/backend/winit.rs +++ b/src/backend/winit.rs @@ -44,7 +44,7 @@ use crate::{ state::{Pinnacle, State, WithState}, }; -use super::{Backend, BackendData, UninitBackend}; +use super::{Backend, BackendData, RenderResult, UninitBackend}; const LOGO_BYTES: &[u8] = include_bytes!("../../resources/pinnacle_logo_icon.rgba"); @@ -222,9 +222,10 @@ impl Winit { state.process_input_event(input_evt); } WinitEvent::Redraw => { - let winit = state.backend.winit_mut(); - winit.render_winit_window(&mut state.pinnacle); - winit.output_render_scheduled = false; + // let winit = state.backend.winit_mut(); + // winit.output_render_scheduled = true; + // winit.render_if_scheduled(&mut state.pinnacle); + state.backend.winit_mut().schedule_render(); } WinitEvent::CloseRequested => { state.pinnacle.shutdown(); @@ -250,12 +251,11 @@ impl Winit { /// Render the winit window if a render has been scheduled. pub fn render_if_scheduled(&mut self, pinnacle: &mut Pinnacle) { if self.output_render_scheduled { - self.output_render_scheduled = false; self.render_winit_window(pinnacle); } } - fn render_winit_window(&mut self, pinnacle: &mut Pinnacle) { + pub(super) fn render_winit_window(&mut self, pinnacle: &mut Pinnacle) -> RenderResult { let full_redraw = &mut self.full_redraw; *full_redraw = full_redraw.saturating_sub(1); @@ -377,7 +377,7 @@ impl Winit { }) }); - match render_res { + let render_result = match render_res { Ok(render_output_result) => { if pinnacle.lock_state.is_unlocked() { Winit::handle_pending_screencopy( @@ -406,16 +406,9 @@ impl Winit { } } - let time = pinnacle.clock.now(); + let now = pinnacle.clock.now(); - super::post_repaint( - &self.output, - &render_output_result.states, - &pinnacle.space, - None, - time.into(), - pinnacle.cursor_state.cursor_image(), - ); + pinnacle.update_primary_scanout_output(&self.output, &render_output_result.states); if has_rendered { let mut output_presentation_feedback = take_presentation_feedback( @@ -424,7 +417,7 @@ impl Winit { &render_output_result.states, ); output_presentation_feedback.presented( - time, + now, self.output .current_mode() .map(|mode| Duration::from_secs_f64(1000f64 / mode.refresh as f64)) @@ -432,17 +425,28 @@ impl Winit { 0, wp_presentation_feedback::Kind::Vsync, ); + RenderResult::Submitted + } else { + RenderResult::NoDamage } } Err(err) => { warn!("{}", err); + RenderResult::Skipped } - } + }; + + pinnacle.send_frame_callbacks(&self.output, None); + + assert!(self.output_render_scheduled); + self.output_render_scheduled = false; // At the end cuz borrow checker if render_after_transaction_finish { self.schedule_render(); } + + render_result } } diff --git a/src/handlers.rs b/src/handlers.rs index 9ce0410..408f94e 100644 --- a/src/handlers.rs +++ b/src/handlers.rs @@ -154,7 +154,7 @@ impl CompositorHandler for State { } fn commit(&mut self, surface: &WlSurface) { - trace!("commit on surface {surface:?}"); + // tracing::info!("commit on surface {surface:?}"); utils::on_commit_buffer_handler::(surface); @@ -273,6 +273,13 @@ impl CompositorHandler for State { } else { self.pinnacle.begin_layout_transaction(&focused_output); self.pinnacle.request_layout(&focused_output); + + // FIXME: Map immediately to get the primary scanout output to be correct + // self.pinnacle.space.map_element( + // unmapped_window.clone(), + // focused_output.current_location(), + // true, + // ); } // It seems wlcs needs immediate frame sends for client tests to work diff --git a/src/layout/transaction.rs b/src/layout/transaction.rs index 75631ce..082f84d 100644 --- a/src/layout/transaction.rs +++ b/src/layout/transaction.rs @@ -11,6 +11,7 @@ use smithay::{ surface::WaylandSurfaceRenderElement, texture::{TextureBuffer, TextureRenderElement}, utils::RescaleRenderElement, + Element, }, desktop::Space, reexports::calloop::{ @@ -26,7 +27,7 @@ use crate::{ texture::CommonTextureRenderElement, util::snapshot::RenderSnapshot, AsGlesRenderer, PRenderer, }, - state::State, + state::{State, WithState}, window::WindowElement, }; @@ -53,7 +54,10 @@ pub enum SnapshotTarget { /// Render a window. Window(WindowElement), /// Render a snapshot. - Snapshot(LayoutSnapshot), + Snapshot { + snapshot: LayoutSnapshot, + window: WindowElement, + }, } /// A layout transaction. @@ -174,28 +178,18 @@ impl LayoutTransaction { .map(SnapshotRenderElement::Window) .collect() } - SnapshotTarget::Snapshot(snapshot) => { - let Some((texture, loc)) = snapshot.texture(renderer.as_gles_renderer()) else { - return Vec::new(); - }; - let buffer = - TextureBuffer::from_texture(renderer, texture, 1, Transform::Normal, None); - let elem = TextureRenderElement::from_texture_buffer( - loc.to_f64(), - &buffer, - Some(alpha), - None, - None, - element::Kind::Unspecified, - ); + SnapshotTarget::Snapshot { snapshot, window } => { + let elem = snapshot.render_elements(renderer, scale, alpha); - let common = CommonTextureRenderElement::new(elem); + if let Some(elem) = elem { + window.with_state_mut(|state| { + state.offscreen_elem_id.replace(elem.id().clone()); + }); - let scale = Scale::from((1.0 / scale.x, 1.0 / scale.y)); - - vec![SnapshotRenderElement::Snapshot( - RescaleRenderElement::from_element(common, loc, scale), - )] + vec![elem] + } else { + vec![] + } } }; diff --git a/src/render.rs b/src/render.rs index 6c9eaff..a5dbcc1 100644 --- a/src/render.rs +++ b/src/render.rs @@ -266,44 +266,6 @@ fn window_render_elements( ) } -/// Render elements for any pending layout transaction. -/// -/// Returns fullscreen_and_up elements then under_fullscreen elements. -fn layout_transaction_render_elements( - transaction: &LayoutTransaction, - space: &Space, - renderer: &mut R, - scale: Scale, - output_loc: Point, -) -> (Vec>, Vec>) { - let mut flat_map = |target: &SnapshotTarget| match target { - SnapshotTarget::Window(win) => { - let loc = space.element_location(win).unwrap_or_default() - output_loc; - win.render_elements(renderer, loc, scale, 1.0) - .into_iter() - .map(SnapshotRenderElement::from) - .collect::>() - } - SnapshotTarget::Snapshot(snapshot) => snapshot - .render_elements(renderer, scale, 1.0) - .into_iter() - .collect(), - }; - - ( - transaction - .fullscreen_and_up_snapshots - .iter() - .flat_map(&mut flat_map) - .collect::>(), - transaction - .under_fullscreen_snapshots - .iter() - .flat_map(&mut flat_map) - .collect::>(), - ) -} - /// Generate render elements for the given output. /// /// Render elements will be pulled from the provided windows, @@ -377,7 +339,7 @@ pub fn output_render_elements( state .layout_transaction .as_ref() - .map(|ts| layout_transaction_render_elements(ts, space, renderer, scale, output_loc)) + .map(|ts| ts.render_elements(renderer, space, output_loc, scale, 1.0)) }) { fullscreen_and_up_elements = fs_and_up_elements .into_iter() @@ -388,6 +350,9 @@ pub fn output_render_elements( .map(OutputRenderElement::from) .collect(); } else { + for window in windows.iter() { + window.with_state_mut(|state| state.offscreen_elem_id.take()); + } (fullscreen_and_up_elements, rest_of_window_elements) = window_render_elements::(output, &windows, space, renderer, scale); } @@ -443,7 +408,7 @@ impl State { pub fn schedule_render(&mut self, output: &Output) { match &mut self.backend { Backend::Udev(udev) => { - udev.schedule_render(&self.pinnacle.loop_handle, output); + udev.schedule_render(output); } Backend::Winit(winit) => { winit.schedule_render(); diff --git a/src/render/util/snapshot.rs b/src/render/util/snapshot.rs index df1bba5..850267a 100644 --- a/src/render/util/snapshot.rs +++ b/src/render/util/snapshot.rs @@ -210,7 +210,10 @@ pub fn capture_snapshots_on_output( 1.0, ); - snapshot.map(SnapshotTarget::Snapshot) + snapshot.map(|ss| SnapshotTarget::Snapshot { + snapshot: ss, + window: win.clone(), + }) } else { Some(SnapshotTarget::Window(win)) } diff --git a/src/state.rs b/src/state.rs index 5f2f3ab..d9b9412 100644 --- a/src/state.rs +++ b/src/state.rs @@ -2,7 +2,12 @@ use crate::{ api::signal::SignalState, - backend::{self, udev::Udev, winit::Winit, Backend}, + backend::{ + self, + udev::{SurfaceDmabufFeedback, Udev}, + winit::Winit, + Backend, + }, cli::{self, Cli}, config::Config, cursor::CursorState, @@ -23,8 +28,19 @@ use anyhow::Context; use indexmap::IndexMap; use pinnacle_api_defs::pinnacle::v0alpha1::ShutdownWatchResponse; use smithay::{ - desktop::{PopupManager, Space}, - input::{keyboard::XkbConfig, Seat, SeatState}, + backend::renderer::element::{ + default_primary_scanout_output_compare, utils::select_dmabuf_feedback, Id, + PrimaryScanoutOutput, RenderElementStates, + }, + desktop::{ + layer_map_for_output, + utils::{ + send_dmabuf_feedback_surface_tree, send_frames_surface_tree, + surface_primary_scanout_output, update_surface_primary_scanout_output, + }, + PopupManager, Space, + }, + input::{keyboard::XkbConfig, pointer::CursorImageStatus, Seat, SeatState}, output::Output, reexports::{ calloop::{generic::Generic, Interest, LoopHandle, LoopSignal, Mode, PostAction}, @@ -37,10 +53,9 @@ use smithay::{ }, utils::{Clock, Monotonic}, wayland::{ - compositor::{self, CompositorClientState, CompositorState}, + compositor::{self, CompositorClientState, CompositorState, SurfaceData}, cursor_shape::CursorShapeManagerState, - dmabuf::DmabufFeedback, - fractional_scale::FractionalScaleManagerState, + fractional_scale::{with_fractional_scale, FractionalScaleManagerState}, idle_notify::IdleNotifierState, keyboard_shortcuts_inhibit::KeyboardShortcutsInhibitState, output::OutputManagerState, @@ -72,7 +87,8 @@ use std::{ cell::RefCell, collections::{HashMap, HashSet}, path::PathBuf, - sync::Arc, + sync::{Arc, Mutex}, + time::Duration, }; use sysinfo::{ProcessRefreshKind, RefreshKind}; use tracing::{info, warn}; @@ -83,6 +99,12 @@ use crate::input::InputState; #[cfg(feature = "testing")] use crate::backend::dummy::Dummy; +// We'll try to send frame callbacks at least once a second. We'll make a timer that fires once a +// second, so with the worst timing the maximum interval between two frame callbacks for a surface +// should be ~1.995 seconds. +const FRAME_CALLBACK_THROTTLE: Option = Some(Duration::from_millis(995)); +// const FRAME_CALLBACK_THROTTLE: Option = Some(Duration::ZERO); + /// The main state of the application. pub struct State { /// Which backend is currently running @@ -202,9 +224,7 @@ impl State { foreign_toplevel::refresh(self); self.pinnacle.refresh_idle_inhibit(); - if let Backend::Winit(winit) = &mut self.backend { - winit.render_if_scheduled(&mut self.pinnacle); - } + self.backend.render_scheduled_outputs(&mut self.pinnacle); #[cfg(feature = "snowcap")] if self @@ -463,6 +483,243 @@ impl Pinnacle { stop_signal.stop(); } } + + pub fn send_frame_callbacks(&self, output: &Output, sequence: Option) { + let should_send = |surface: &WlSurface, states: &SurfaceData| { + // Do the standard primary scanout output check. For pointer surfaces it deduplicates + // the frame callbacks across potentially multiple outputs, and for regular windows and + // layer-shell surfaces it avoids sending frame callbacks to invisible surfaces. + let current_primary_output = surface_primary_scanout_output(surface, states); + // tracing::info!( + // "current primary output is {:?}", + // current_primary_output.as_ref().map(|o| o.name()) + // ); + if current_primary_output.as_ref() != Some(output) { + // return self + // .window_for_surface(surface) + // .and_then(|win| win.output(self)); + return None; + } + + let Some(sequence) = sequence else { + return Some(output.clone()); + }; + + // Next, check the throttling status. + let frame_throttling_state = states + .data_map + .get_or_insert(SurfaceFrameThrottlingState::default); + let mut last_sent_at = frame_throttling_state.last_sent_at.borrow_mut(); + + let mut send = true; + + // If we already sent a frame callback to this surface this output refresh + // cycle, don't send one again to prevent empty-damage commit busy loops. + if let Some((last_output, last_sequence)) = &*last_sent_at { + if last_output == output && *last_sequence == sequence { + send = false; + } + } + + if send { + *last_sent_at = Some((output.clone(), sequence)); + Some(output.clone()) + } else { + None + } + }; + + let now = self.clock.now(); + + for window in self.space.elements_for_output(output) { + window.send_frame(output, now, FRAME_CALLBACK_THROTTLE, should_send); + } + + for layer in layer_map_for_output(output).layers() { + layer.send_frame(output, now, FRAME_CALLBACK_THROTTLE, should_send); + } + + if let Some(lock_surface) = output.with_state(|state| state.lock_surface.clone()) { + send_frames_surface_tree( + lock_surface.wl_surface(), + output, + now, + FRAME_CALLBACK_THROTTLE, + should_send, + ); + } + + if let Some(dnd) = self.dnd_icon.as_ref() { + send_frames_surface_tree(dnd, output, now, FRAME_CALLBACK_THROTTLE, should_send); + } + + if let CursorImageStatus::Surface(surface) = self.cursor_state.cursor_image() { + send_frames_surface_tree(surface, output, now, FRAME_CALLBACK_THROTTLE, should_send); + } + } + + pub fn update_primary_scanout_output( + &self, + output: &Output, + render_element_states: &RenderElementStates, + ) { + for window in self.space.elements() { + let offscreen_id = window.with_state(|state| state.offscreen_elem_id.clone()); + + window.with_surfaces(|surface, states| { + // let primary_scanout_output = update_surface_primary_scanout_output( + // surface, + // output, + // states, + // render_element_states, + // default_primary_scanout_output_compare, + // ); + + let surface_primary_scanout_output = states + .data_map + .get_or_insert_threadsafe(Mutex::::default); + + let primary_scanout_output = surface_primary_scanout_output + .lock() + .unwrap() + .update_from_render_element_states( + offscreen_id.clone().unwrap_or_else(|| Id::from(surface)), + output, + render_element_states, + default_primary_scanout_output_compare, + ); + + if let Some(output) = primary_scanout_output { + with_fractional_scale(states, |fraction_scale| { + fraction_scale + .set_preferred_scale(output.current_scale().fractional_scale()); + }); + } + }); + } + + let map = layer_map_for_output(output); + for layer_surface in map.layers() { + layer_surface.with_surfaces(|surface, states| { + let primary_scanout_output = update_surface_primary_scanout_output( + surface, + output, + states, + render_element_states, + default_primary_scanout_output_compare, + ); + + if let Some(output) = primary_scanout_output { + with_fractional_scale(states, |fraction_scale| { + fraction_scale + .set_preferred_scale(output.current_scale().fractional_scale()); + }); + } + }); + } + } + + pub fn send_dmabuf_feedback( + &self, + output: &Output, + feedback: &SurfaceDmabufFeedback, + render_element_states: &RenderElementStates, + ) { + for window in self.space.elements() { + if self.space.outputs_for_element(window).contains(output) { + window.send_dmabuf_feedback( + output, + surface_primary_scanout_output, + |surface, _| { + select_dmabuf_feedback( + surface, + render_element_states, + &feedback.render_feedback, + &feedback.scanout_feedback, + ) + }, + ); + } + } + + let map = layer_map_for_output(output); + for layer_surface in map.layers() { + layer_surface.send_dmabuf_feedback( + output, + surface_primary_scanout_output, + |surface, _| { + select_dmabuf_feedback( + surface, + render_element_states, + &feedback.render_feedback, + &feedback.scanout_feedback, + ) + }, + ); + } + + if let Some(lock_surface) = output.with_state(|state| state.lock_surface.clone()) { + send_dmabuf_feedback_surface_tree( + lock_surface.wl_surface(), + output, + surface_primary_scanout_output, + |surface, _| { + select_dmabuf_feedback( + surface, + render_element_states, + &feedback.render_feedback, + &feedback.scanout_feedback, + ) + }, + ); + } + + if let Some(dnd) = self.dnd_icon.as_ref() { + send_dmabuf_feedback_surface_tree( + dnd, + output, + surface_primary_scanout_output, + |surface, _| { + select_dmabuf_feedback( + surface, + render_element_states, + &feedback.render_feedback, + &feedback.scanout_feedback, + ) + }, + ); + } + + if let CursorImageStatus::Surface(surface) = self.cursor_state.cursor_image() { + send_dmabuf_feedback_surface_tree( + surface, + output, + surface_primary_scanout_output, + |surface, _| { + select_dmabuf_feedback( + surface, + render_element_states, + &feedback.render_feedback, + &feedback.scanout_feedback, + ) + }, + ); + } + } +} + +#[derive(Debug, Default, Copy, Clone, PartialEq, Eq)] +pub struct FrameCallbackSequence(u32); + +impl FrameCallbackSequence { + pub fn increment(&mut self) { + self.0 = self.0.wrapping_add(1); + } +} + +#[derive(Default)] +struct SurfaceFrameThrottlingState { + last_sent_at: RefCell>, } impl State { @@ -537,12 +794,6 @@ impl ClientData for ClientState { fn disconnected(&self, _client_id: ClientId, _reason: DisconnectReason) {} } -#[derive(Debug, Copy, Clone)] -pub struct SurfaceDmabufFeedback<'a> { - pub render_feedback: &'a DmabufFeedback, - pub scanout_feedback: &'a DmabufFeedback, -} - /// A trait meant to be used in types with a [`UserDataMap`][smithay::utils::user_data::UserDataMap] /// to get user-defined state. pub trait WithState { diff --git a/src/window/window_state.rs b/src/window/window_state.rs index 19d7cf3..4d95f4c 100644 --- a/src/window/window_state.rs +++ b/src/window/window_state.rs @@ -4,6 +4,7 @@ use std::sync::atomic::{AtomicU32, Ordering}; use indexmap::IndexSet; use smithay::{ + backend::renderer::element::Id, desktop::{space::SpaceElement, WindowSurface}, reexports::wayland_protocols::xdg::shell::server::xdg_toplevel, utils::{Logical, Point, Serial, Size}, @@ -206,6 +207,7 @@ pub struct WindowElementState { pub decoration_mode: Option, pub floating_loc: Option>, pub floating_size: Option>, + pub offscreen_elem_id: Option, } impl WindowElement { @@ -425,6 +427,7 @@ impl WindowElementState { snapshot: None, snapshot_hook_id: None, decoration_mode: None, + offscreen_elem_id: None, } } }