Properly throttle rendering

This commit is contained in:
Ottatop 2024-08-10 23:01:10 -05:00
parent 8776281493
commit f1bcd8357b
13 changed files with 753 additions and 400 deletions

18
Cargo.lock generated
View file

@ -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",

View file

@ -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 }

View file

@ -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::<Vec<_>>()
{
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<WindowElement>,
dmabuf_feedback: Option<SurfaceDmabufFeedback<'_>>,
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 {

View file

@ -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<State>, 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::<Vec<_>>() {
// 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::<UdevOutputData>().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<GbmGlesBackend<GlesRenderer, DrmDeviceFd>>,
composition: &GbmDrmCompositor,
) -> Option<DrmSurfaceDmabufFeedback> {
) -> Option<SurfaceDmabufFeedback> {
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<DrmSurfaceDmabufFeedback>,
dmabuf_feedback: Option<SurfaceDmabufFeedback>,
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<DrmEventMetadata>,
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;

View file

@ -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<f64>) -> 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)
}

78
src/backend/udev/frame.rs Normal file
View file

@ -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<Duration>,
refresh_interval_ns: Option<NonZeroU64>,
// TODO: vrr
}
impl FrameClock {
// TODO: vrr
pub fn new(refresh_interval: Option<Duration>) -> 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<Duration> {
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<Monotonic>) -> 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
}
}

View file

@ -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
}
}

View file

@ -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::<State>(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

View file

@ -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![]
}
}
};

View file

@ -266,44 +266,6 @@ fn window_render_elements<R: PRenderer>(
)
}
/// Render elements for any pending layout transaction.
///
/// Returns fullscreen_and_up elements then under_fullscreen elements.
fn layout_transaction_render_elements<R: PRenderer + AsGlesRenderer>(
transaction: &LayoutTransaction,
space: &Space<WindowElement>,
renderer: &mut R,
scale: Scale<f64>,
output_loc: Point<i32, Logical>,
) -> (Vec<SnapshotRenderElement<R>>, Vec<SnapshotRenderElement<R>>) {
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::<Vec<_>>()
}
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::<Vec<_>>(),
transaction
.under_fullscreen_snapshots
.iter()
.flat_map(&mut flat_map)
.collect::<Vec<_>>(),
)
}
/// 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<R: PRenderer + AsGlesRenderer>(
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<R: PRenderer + AsGlesRenderer>(
.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::<R>(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();

View file

@ -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))
}

View file

@ -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<Duration> = Some(Duration::from_millis(995));
// const FRAME_CALLBACK_THROTTLE: Option<Duration> = 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<FrameCallbackSequence>) {
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::<PrimaryScanoutOutput>::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<Option<(Output, FrameCallbackSequence)>>,
}
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 {

View file

@ -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<DecorationMode>,
pub floating_loc: Option<Point<f64, Logical>>,
pub floating_size: Option<Size<i32, Logical>>,
pub offscreen_elem_id: Option<Id>,
}
impl WindowElement {
@ -425,6 +427,7 @@ impl WindowElementState {
snapshot: None,
snapshot_hook_id: None,
decoration_mode: None,
offscreen_elem_id: None,
}
}
}