mirror of
https://github.com/pinnacle-comp/pinnacle.git
synced 2025-02-12 08:48:32 +01:00
Merge pull request #268 from pinnacle-comp/render_improvements
Rendering improvements
This commit is contained in:
commit
65f2dce246
17 changed files with 914 additions and 532 deletions
18
Cargo.lock
generated
18
Cargo.lock
generated
|
@ -507,12 +507,6 @@ dependencies = [
|
||||||
"syn 2.0.71",
|
"syn 2.0.71",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "byteorder"
|
|
||||||
version = "1.5.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bytes"
|
name = "bytes"
|
||||||
version = "1.6.1"
|
version = "1.6.1"
|
||||||
|
@ -2186,17 +2180,6 @@ version = "1.0.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39"
|
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]]
|
[[package]]
|
||||||
name = "indexmap"
|
name = "indexmap"
|
||||||
version = "1.9.3"
|
version = "1.9.3"
|
||||||
|
@ -3288,7 +3271,6 @@ dependencies = [
|
||||||
"dircpy",
|
"dircpy",
|
||||||
"drm-sys",
|
"drm-sys",
|
||||||
"gag",
|
"gag",
|
||||||
"image",
|
|
||||||
"indexmap 2.2.6",
|
"indexmap 2.2.6",
|
||||||
"libdisplay-info-sys",
|
"libdisplay-info-sys",
|
||||||
"pinnacle",
|
"pinnacle",
|
||||||
|
|
|
@ -100,7 +100,6 @@ anyhow = { version = "1.0.86", features = ["backtrace"] }
|
||||||
thiserror = "1.0.61"
|
thiserror = "1.0.61"
|
||||||
# xcursor stuff
|
# xcursor stuff
|
||||||
xcursor = { version = "0.3.5" }
|
xcursor = { version = "0.3.5" }
|
||||||
image = { version = "0.25.1", default-features = false }
|
|
||||||
# gRPC
|
# gRPC
|
||||||
prost = { workspace = true }
|
prost = { workspace = true }
|
||||||
tonic = { workspace = true }
|
tonic = { workspace = true }
|
||||||
|
|
|
@ -1343,7 +1343,9 @@ impl output_service_server::OutputService for OutputService {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
state.backend.set_output_powered(&output, powered);
|
state
|
||||||
|
.backend
|
||||||
|
.set_output_powered(&output, &state.pinnacle.loop_handle, powered);
|
||||||
|
|
||||||
if powered {
|
if powered {
|
||||||
state.schedule_render(&output);
|
state.schedule_render(&output);
|
||||||
|
|
160
src/backend.rs
160
src/backend.rs
|
@ -1,42 +1,20 @@
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
use std::time::Duration;
|
|
||||||
|
|
||||||
use smithay::{
|
use smithay::{
|
||||||
backend::{
|
backend::{
|
||||||
allocator::dmabuf::Dmabuf,
|
allocator::dmabuf::Dmabuf,
|
||||||
renderer::{
|
renderer::{gles::GlesRenderer, ImportDma, Renderer, TextureFilter},
|
||||||
element::{
|
|
||||||
default_primary_scanout_output_compare, utils::select_dmabuf_feedback,
|
|
||||||
RenderElementStates,
|
|
||||||
},
|
|
||||||
gles::GlesRenderer,
|
|
||||||
ImportDma, Renderer, TextureFilter,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
delegate_dmabuf,
|
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,
|
output::Output,
|
||||||
reexports::wayland_server::protocol::wl_surface::WlSurface,
|
reexports::{calloop::LoopHandle, wayland_server::protocol::wl_surface::WlSurface},
|
||||||
wayland::{
|
wayland::dmabuf::{DmabufGlobal, DmabufHandler, DmabufState, ImportNotifier},
|
||||||
dmabuf::{DmabufGlobal, DmabufHandler, DmabufState, ImportNotifier},
|
|
||||||
fractional_scale::with_fractional_scale,
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
use tracing::error;
|
use tracing::error;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
output::OutputMode,
|
output::OutputMode,
|
||||||
state::{Pinnacle, State, SurfaceDmabufFeedback, WithState},
|
state::{Pinnacle, State},
|
||||||
window::WindowElement,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#[cfg(feature = "testing")]
|
#[cfg(feature = "testing")]
|
||||||
|
@ -120,15 +98,34 @@ impl Backend {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_output_powered(&mut self, output: &Output, powered: bool) {
|
pub fn set_output_powered(
|
||||||
|
&mut self,
|
||||||
|
output: &Output,
|
||||||
|
loop_handle: &LoopHandle<'static, State>,
|
||||||
|
powered: bool,
|
||||||
|
) {
|
||||||
match self {
|
match self {
|
||||||
Backend::Winit(_) => (),
|
Backend::Winit(_) => (),
|
||||||
Backend::Udev(udev) => udev.set_output_powered(output, powered),
|
Backend::Udev(udev) => udev.set_output_powered(output, loop_handle, powered),
|
||||||
#[cfg(feature = "testing")]
|
#[cfg(feature = "testing")]
|
||||||
Backend::Dummy(dummy) => dummy.set_output_powered(output, powered),
|
Backend::Dummy(dummy) => dummy.set_output_powered(output, powered),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn render_scheduled_outputs(&mut self, pinnacle: &mut Pinnacle) {
|
||||||
|
if let Backend::Udev(udev) = self {
|
||||||
|
for output in pinnacle
|
||||||
|
.outputs
|
||||||
|
.iter()
|
||||||
|
.filter(|(_, global)| global.is_some())
|
||||||
|
.map(|(op, _)| op.clone())
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
{
|
||||||
|
udev.render_if_scheduled(pinnacle, &output);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Returns `true` if the backend is [`Winit`].
|
/// Returns `true` if the backend is [`Winit`].
|
||||||
///
|
///
|
||||||
/// [`Winit`]: Backend::Winit
|
/// [`Winit`]: Backend::Winit
|
||||||
|
@ -146,6 +143,13 @@ impl Backend {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
pub enum RenderResult {
|
||||||
|
Submitted,
|
||||||
|
NoDamage,
|
||||||
|
Skipped,
|
||||||
|
}
|
||||||
|
|
||||||
pub trait BackendData: 'static {
|
pub trait BackendData: 'static {
|
||||||
fn seat_name(&self) -> String;
|
fn seat_name(&self) -> String;
|
||||||
fn reset_buffers(&mut self, output: &Output);
|
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 {
|
impl DmabufHandler for State {
|
||||||
fn dmabuf_state(&mut self) -> &mut DmabufState {
|
fn dmabuf_state(&mut self) -> &mut DmabufState {
|
||||||
match &mut self.backend {
|
match &mut self.backend {
|
||||||
|
|
|
@ -1,16 +1,21 @@
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
mod drm;
|
mod drm;
|
||||||
|
mod frame;
|
||||||
mod gamma;
|
mod gamma;
|
||||||
|
|
||||||
use assert_matches::assert_matches;
|
use assert_matches::assert_matches;
|
||||||
pub use drm::util::drm_mode_from_api_modeline;
|
pub use drm::util::drm_mode_from_api_modeline;
|
||||||
|
use frame::FrameClock;
|
||||||
use indexmap::IndexSet;
|
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 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 pinnacle_api_defs::pinnacle::signal::v0alpha1::OutputConnectResponse;
|
||||||
use smithay::{
|
use smithay::{
|
||||||
backend::{
|
backend::{
|
||||||
|
@ -27,7 +32,7 @@ use smithay::{
|
||||||
egl::{self, context::ContextPriority, EGLDevice, EGLDisplay},
|
egl::{self, context::ContextPriority, EGLDevice, EGLDisplay},
|
||||||
libinput::{LibinputInputBackend, LibinputSessionInterface},
|
libinput::{LibinputInputBackend, LibinputSessionInterface},
|
||||||
renderer::{
|
renderer::{
|
||||||
self, damage,
|
self,
|
||||||
element::{self, surface::render_elements_from_surface_tree, Element, Id},
|
element::{self, surface::render_elements_from_surface_tree, Element, Id},
|
||||||
gles::{GlesRenderbuffer, GlesRenderer},
|
gles::{GlesRenderbuffer, GlesRenderer},
|
||||||
multigpu::{gbm::GbmGlesBackend, GpuManager, MultiRenderer},
|
multigpu::{gbm::GbmGlesBackend, GpuManager, MultiRenderer},
|
||||||
|
@ -45,12 +50,13 @@ use smithay::{
|
||||||
SwapBuffersError,
|
SwapBuffersError,
|
||||||
},
|
},
|
||||||
desktop::utils::OutputPresentationFeedback,
|
desktop::utils::OutputPresentationFeedback,
|
||||||
input::pointer::CursorImageStatus,
|
|
||||||
output::{Output, PhysicalProperties, Subpixel},
|
output::{Output, PhysicalProperties, Subpixel},
|
||||||
reexports::{
|
reexports::{
|
||||||
calloop::{
|
calloop::{
|
||||||
self, generic::Generic, timer::Timer, Dispatcher, Idle, Interest, LoopHandle,
|
self,
|
||||||
PostAction, RegistrationToken,
|
generic::Generic,
|
||||||
|
timer::{TimeoutAction, Timer},
|
||||||
|
Dispatcher, Interest, LoopHandle, PostAction, RegistrationToken,
|
||||||
},
|
},
|
||||||
drm::control::{connector, crtc, ModeTypeFlags},
|
drm::control::{connector, crtc, ModeTypeFlags},
|
||||||
input::Libinput,
|
input::Libinput,
|
||||||
|
@ -64,7 +70,7 @@ use smithay::{
|
||||||
DisplayHandle,
|
DisplayHandle,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
utils::{DeviceFd, IsAlive, Point, Rectangle, Transform},
|
utils::{DeviceFd, Point, Rectangle, Transform},
|
||||||
wayland::{
|
wayland::{
|
||||||
dmabuf::{self, DmabufFeedback, DmabufFeedbackBuilder, DmabufGlobal, DmabufState},
|
dmabuf::{self, DmabufFeedback, DmabufFeedbackBuilder, DmabufGlobal, DmabufState},
|
||||||
shm::shm_format_to_fourcc,
|
shm::shm_format_to_fourcc,
|
||||||
|
@ -81,7 +87,7 @@ use crate::{
|
||||||
pointer::pointer_render_elements, take_presentation_feedback, OutputRenderElement,
|
pointer::pointer_render_elements, take_presentation_feedback, OutputRenderElement,
|
||||||
CLEAR_COLOR, CLEAR_COLOR_LOCKED,
|
CLEAR_COLOR, CLEAR_COLOR_LOCKED,
|
||||||
},
|
},
|
||||||
state::{Pinnacle, State, SurfaceDmabufFeedback, WithState},
|
state::{FrameCallbackSequence, Pinnacle, State, WithState},
|
||||||
};
|
};
|
||||||
|
|
||||||
use self::drm::util::EdidInfo;
|
use self::drm::util::EdidInfo;
|
||||||
|
@ -333,7 +339,7 @@ impl Udev {
|
||||||
// Also welcome to some really doodoo code
|
// Also welcome to some really doodoo code
|
||||||
|
|
||||||
for (crtc, surface) in backend.surfaces.iter_mut() {
|
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 => {
|
PendingGammaChange::Idle => {
|
||||||
debug!("Restoring from previous gamma");
|
debug!("Restoring from previous gamma");
|
||||||
if let Err(err) = Udev::set_gamma_internal(
|
if let Err(err) = Udev::set_gamma_internal(
|
||||||
|
@ -450,34 +456,43 @@ impl Udev {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Schedule a new render that will cause the compositor to redraw everything.
|
/// 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 {
|
let Some(surface) = render_surface_for_output(output, &mut self.backends) else {
|
||||||
debug!("no render surface on output {}", output.name());
|
debug!("no render surface on output {}", output.name());
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
// tracing::info!(state = ?surface.render_state, name = output.name());
|
let old_state = mem::take(&mut surface.render_state);
|
||||||
|
|
||||||
match &surface.render_state {
|
// tracing::info!(
|
||||||
RenderState::Idle => {
|
// ?old_state,
|
||||||
let output = output.clone();
|
// op = output.name(),
|
||||||
let token = loop_handle.insert_idle(move |state| {
|
// powered = output.with_state(|state| state.powered),
|
||||||
state
|
// "scheduled render"
|
||||||
.backend
|
// );
|
||||||
.udev_mut()
|
|
||||||
.render_surface(&mut state.pinnacle, &output);
|
|
||||||
});
|
|
||||||
|
|
||||||
surface.render_state = RenderState::Scheduled(token);
|
surface.render_state = match old_state {
|
||||||
}
|
RenderState::Idle => RenderState::Scheduled,
|
||||||
RenderState::Scheduled(_) => (),
|
|
||||||
RenderState::WaitingForVblank { dirty: _ } => {
|
value @ (RenderState::Scheduled
|
||||||
surface.render_state = RenderState::WaitingForVblank { dirty: true }
|
| RenderState::WaitingForEstimatedVblankAndScheduled(_)) => value,
|
||||||
}
|
|
||||||
|
RenderState::WaitingForVblank { .. } => RenderState::WaitingForVblank {
|
||||||
|
render_needed: true,
|
||||||
|
},
|
||||||
|
|
||||||
|
RenderState::WaitingForEstimatedVblank(token) => {
|
||||||
|
RenderState::WaitingForEstimatedVblankAndScheduled(token)
|
||||||
}
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) fn set_output_powered(&mut self, output: &Output, powered: bool) {
|
pub(super) fn set_output_powered(
|
||||||
|
&mut self,
|
||||||
|
output: &Output,
|
||||||
|
loop_handle: &LoopHandle<'static, State>,
|
||||||
|
powered: bool,
|
||||||
|
) {
|
||||||
let UdevOutputData { device_id, crtc } =
|
let UdevOutputData { device_id, crtc } =
|
||||||
output.user_data().get::<UdevOutputData>().unwrap();
|
output.user_data().get::<UdevOutputData>().unwrap();
|
||||||
|
|
||||||
|
@ -507,8 +522,11 @@ impl Udev {
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(surface) = render_surface_for_output(output, &mut self.backends) {
|
if let Some(surface) = render_surface_for_output(output, &mut self.backends) {
|
||||||
if let RenderState::Scheduled(idle) = std::mem::take(&mut surface.render_state) {
|
if let RenderState::WaitingForEstimatedVblankAndScheduled(token)
|
||||||
idle.cancel();
|
| RenderState::WaitingForEstimatedVblank(token) =
|
||||||
|
mem::take(&mut surface.render_state)
|
||||||
|
{
|
||||||
|
loop_handle.remove(token);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -634,7 +652,7 @@ fn get_surface_dmabuf_feedback(
|
||||||
render_node: DrmNode,
|
render_node: DrmNode,
|
||||||
gpu_manager: &mut GpuManager<GbmGlesBackend<GlesRenderer, DrmDeviceFd>>,
|
gpu_manager: &mut GpuManager<GbmGlesBackend<GlesRenderer, DrmDeviceFd>>,
|
||||||
composition: &GbmDrmCompositor,
|
composition: &GbmDrmCompositor,
|
||||||
) -> Option<DrmSurfaceDmabufFeedback> {
|
) -> Option<SurfaceDmabufFeedback> {
|
||||||
let primary_formats = gpu_manager
|
let primary_formats = gpu_manager
|
||||||
.single_renderer(&primary_gpu)
|
.single_renderer(&primary_gpu)
|
||||||
.ok()?
|
.ok()?
|
||||||
|
@ -683,15 +701,16 @@ fn get_surface_dmabuf_feedback(
|
||||||
.build()
|
.build()
|
||||||
.ok()?; // INFO: this is an unwrap in Anvil, does it matter?
|
.ok()?; // INFO: this is an unwrap in Anvil, does it matter?
|
||||||
|
|
||||||
Some(DrmSurfaceDmabufFeedback {
|
Some(SurfaceDmabufFeedback {
|
||||||
render_feedback,
|
render_feedback,
|
||||||
scanout_feedback,
|
scanout_feedback,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
struct DrmSurfaceDmabufFeedback {
|
#[derive(Debug)]
|
||||||
render_feedback: DmabufFeedback,
|
pub struct SurfaceDmabufFeedback {
|
||||||
scanout_feedback: DmabufFeedback,
|
pub render_feedback: DmabufFeedback,
|
||||||
|
pub scanout_feedback: DmabufFeedback,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The state of a [`RenderSurface`].
|
/// The state of a [`RenderSurface`].
|
||||||
|
@ -700,20 +719,18 @@ enum RenderState {
|
||||||
/// No render is scheduled.
|
/// No render is scheduled.
|
||||||
#[default]
|
#[default]
|
||||||
Idle,
|
Idle,
|
||||||
// TODO: remove the token on tty switch or output unplug
|
/// A render is scheduled to happen at the end of the current event loop cycle.
|
||||||
/// A render has been queued.
|
Scheduled,
|
||||||
Scheduled(
|
/// A frame was rendered and we are waiting for vblank.
|
||||||
/// 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.
|
|
||||||
WaitingForVblank {
|
WaitingForVblank {
|
||||||
/// A render was scheduled while waiting for vblank.
|
/// A render was scheduled while waiting for vblank.
|
||||||
/// In this case, another render will be scheduled once vblank happens.
|
/// 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.
|
/// Render surface for an output.
|
||||||
|
@ -727,12 +744,15 @@ struct RenderSurface {
|
||||||
render_node: DrmNode,
|
render_node: DrmNode,
|
||||||
/// The thing rendering elements and queueing frames.
|
/// The thing rendering elements and queueing frames.
|
||||||
compositor: GbmDrmCompositor,
|
compositor: GbmDrmCompositor,
|
||||||
dmabuf_feedback: Option<DrmSurfaceDmabufFeedback>,
|
dmabuf_feedback: Option<SurfaceDmabufFeedback>,
|
||||||
render_state: RenderState,
|
render_state: RenderState,
|
||||||
screencopy_commit_state: ScreencopyCommitState,
|
screencopy_commit_state: ScreencopyCommitState,
|
||||||
|
|
||||||
previous_gamma: Option<[Box<[u16]>; 3]>,
|
previous_gamma: Option<[Box<[u16]>; 3]>,
|
||||||
pending_gamma_change: PendingGammaChange,
|
pending_gamma_change: PendingGammaChange,
|
||||||
|
|
||||||
|
frame_clock: FrameClock,
|
||||||
|
frame_callback_sequence: FrameCallbackSequence,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Default)]
|
#[derive(Debug, Clone, Default)]
|
||||||
|
@ -760,26 +780,6 @@ type GbmDrmCompositor = DrmCompositor<
|
||||||
DrmDeviceFd,
|
DrmDeviceFd,
|
||||||
>;
|
>;
|
||||||
|
|
||||||
/// Render a frame with the given elements.
|
|
||||||
///
|
|
||||||
/// This frame needs to be queued for scanout afterwards.
|
|
||||||
fn render_frame<'a>(
|
|
||||||
compositor: &mut GbmDrmCompositor,
|
|
||||||
renderer: &mut UdevRenderer<'a>,
|
|
||||||
elements: &'a [OutputRenderElement<UdevRenderer<'a>>],
|
|
||||||
clear_color: [f32; 4],
|
|
||||||
) -> Result<UdevRenderFrameResult<'a>, SwapBuffersError> {
|
|
||||||
use smithay::backend::drm::compositor::RenderFrameError;
|
|
||||||
|
|
||||||
compositor
|
|
||||||
.render_frame(renderer, elements, clear_color)
|
|
||||||
.map_err(|err| match err {
|
|
||||||
RenderFrameError::PrepareFrame(err) => err.into(),
|
|
||||||
RenderFrameError::RenderFrame(damage::Error::Rendering(err)) => err.into(),
|
|
||||||
_ => unreachable!(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Udev {
|
impl Udev {
|
||||||
pub fn renderer(&mut self) -> anyhow::Result<UdevRenderer<'_>> {
|
pub fn renderer(&mut self) -> anyhow::Result<UdevRenderer<'_>> {
|
||||||
Ok(self.gpu_manager.single_renderer(&self.primary_gpu)?)
|
Ok(self.gpu_manager.single_renderer(&self.primary_gpu)?)
|
||||||
|
@ -809,16 +809,21 @@ impl Udev {
|
||||||
|
|
||||||
let registration_token = pinnacle
|
let registration_token = pinnacle
|
||||||
.loop_handle
|
.loop_handle
|
||||||
.insert_source(notifier, move |event, metadata, state| match event {
|
.insert_source(notifier, move |event, metadata, state| {
|
||||||
|
let metadata = metadata.expect("vblank events must have metadata");
|
||||||
|
match event {
|
||||||
DrmEvent::VBlank(crtc) => {
|
DrmEvent::VBlank(crtc) => {
|
||||||
state
|
state.backend.udev_mut().on_vblank(
|
||||||
.backend
|
&mut state.pinnacle,
|
||||||
.udev_mut()
|
node,
|
||||||
.on_vblank(&mut state.pinnacle, node, crtc, metadata);
|
crtc,
|
||||||
|
metadata,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
DrmEvent::Error(error) => {
|
DrmEvent::Error(error) => {
|
||||||
error!("{:?}", error);
|
error!("{:?}", error);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
})
|
})
|
||||||
.expect("failed to insert drm notifier into event loop");
|
.expect("failed to insert drm notifier into event loop");
|
||||||
|
|
||||||
|
@ -890,7 +895,7 @@ impl Udev {
|
||||||
.unwrap_or(0);
|
.unwrap_or(0);
|
||||||
|
|
||||||
let drm_mode = connector.modes()[mode_id];
|
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
|
let surface = match device
|
||||||
.drm
|
.drm
|
||||||
|
@ -943,7 +948,7 @@ impl Udev {
|
||||||
state.serial = serial;
|
state.serial = serial;
|
||||||
});
|
});
|
||||||
|
|
||||||
output.set_preferred(wl_mode);
|
output.set_preferred(smithay_mode);
|
||||||
|
|
||||||
let modes = connector
|
let modes = connector
|
||||||
.modes()
|
.modes()
|
||||||
|
@ -975,8 +980,7 @@ impl Udev {
|
||||||
GbmBufferFlags::RENDERING | GbmBufferFlags::SCANOUT,
|
GbmBufferFlags::RENDERING | GbmBufferFlags::SCANOUT,
|
||||||
);
|
);
|
||||||
|
|
||||||
// I like how this is still in here
|
let color_formats = if std::env::var("PINNACLE_DISABLE_10BIT").is_ok() {
|
||||||
let color_formats = if std::env::var("ANVIL_DISABLE_10BIT").is_ok() {
|
|
||||||
SUPPORTED_FORMATS_8BIT_ONLY
|
SUPPORTED_FORMATS_8BIT_ONLY
|
||||||
} else {
|
} else {
|
||||||
SUPPORTED_FORMATS
|
SUPPORTED_FORMATS
|
||||||
|
@ -1026,6 +1030,8 @@ impl Udev {
|
||||||
screencopy_commit_state: ScreencopyCommitState::default(),
|
screencopy_commit_state: ScreencopyCommitState::default(),
|
||||||
previous_gamma: None,
|
previous_gamma: None,
|
||||||
pending_gamma_change: PendingGammaChange::Idle,
|
pending_gamma_change: PendingGammaChange::Idle,
|
||||||
|
frame_clock: FrameClock::new(Some(refresh_interval(drm_mode))),
|
||||||
|
frame_callback_sequence: FrameCallbackSequence::default(),
|
||||||
};
|
};
|
||||||
|
|
||||||
device.surfaces.insert(crtc, surface);
|
device.surfaces.insert(crtc, surface);
|
||||||
|
@ -1033,7 +1039,7 @@ impl Udev {
|
||||||
pinnacle.change_output_state(
|
pinnacle.change_output_state(
|
||||||
self,
|
self,
|
||||||
&output,
|
&output,
|
||||||
Some(OutputMode::Smithay(wl_mode)),
|
Some(OutputMode::Smithay(smithay_mode)),
|
||||||
None,
|
None,
|
||||||
None,
|
None,
|
||||||
Some(position),
|
Some(position),
|
||||||
|
@ -1158,13 +1164,12 @@ impl Udev {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Mark [`OutputPresentationFeedback`]s as presented and schedule a new render on idle.
|
|
||||||
fn on_vblank(
|
fn on_vblank(
|
||||||
&mut self,
|
&mut self,
|
||||||
pinnacle: &mut Pinnacle,
|
pinnacle: &mut Pinnacle,
|
||||||
dev_id: DrmNode,
|
dev_id: DrmNode,
|
||||||
crtc: crtc::Handle,
|
crtc: crtc::Handle,
|
||||||
metadata: &mut Option<DrmEventMetadata>,
|
metadata: DrmEventMetadata,
|
||||||
) {
|
) {
|
||||||
let Some(surface) = self
|
let Some(surface) = self
|
||||||
.backends
|
.backends
|
||||||
|
@ -1192,35 +1197,35 @@ impl Udev {
|
||||||
{
|
{
|
||||||
Ok(user_data) => {
|
Ok(user_data) => {
|
||||||
if let Some(mut feedback) = user_data.flatten() {
|
if let Some(mut feedback) = user_data.flatten() {
|
||||||
let tp = metadata.as_ref().and_then(|metadata| match metadata.time {
|
let presentation_time = match metadata.time {
|
||||||
smithay::backend::drm::DrmEventTime::Monotonic(tp) => Some(tp),
|
smithay::backend::drm::DrmEventTime::Monotonic(tp) => tp,
|
||||||
smithay::backend::drm::DrmEventTime::Realtime(_) => None,
|
smithay::backend::drm::DrmEventTime::Realtime(_) => {
|
||||||
});
|
// Not supported
|
||||||
let seq = metadata
|
|
||||||
.as_ref()
|
|
||||||
.map(|metadata| metadata.sequence)
|
|
||||||
.unwrap_or(0);
|
|
||||||
|
|
||||||
let (clock, flags) = if let Some(tp) = tp {
|
// This value will be ignored in the frame clock code
|
||||||
(
|
Duration::ZERO
|
||||||
tp.into(),
|
}
|
||||||
wp_presentation_feedback::Kind::Vsync
|
};
|
||||||
| wp_presentation_feedback::Kind::HwClock
|
let seq = metadata.sequence as u64;
|
||||||
| wp_presentation_feedback::Kind::HwCompletion,
|
|
||||||
)
|
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 {
|
} else {
|
||||||
(pinnacle.clock.now(), wp_presentation_feedback::Kind::Vsync)
|
flags.insert(wp_presentation_feedback::Kind::HwClock);
|
||||||
|
presentation_time
|
||||||
};
|
};
|
||||||
|
|
||||||
feedback.presented(
|
feedback.presented::<_, smithay::utils::Monotonic>(
|
||||||
clock,
|
time,
|
||||||
output
|
surface.frame_clock.refresh_interval().unwrap_or_default(),
|
||||||
.current_mode()
|
seq,
|
||||||
.map(|mode| Duration::from_secs_f64(1000f64 / mode.refresh as f64))
|
|
||||||
.unwrap_or_default(),
|
|
||||||
seq as u64,
|
|
||||||
flags,
|
flags,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
surface.frame_clock.presented(presentation_time);
|
||||||
}
|
}
|
||||||
|
|
||||||
output.with_state_mut(|state| {
|
output.with_state_mut(|state| {
|
||||||
|
@ -1238,56 +1243,31 @@ impl Udev {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let dirty = match std::mem::take(&mut surface.render_state) {
|
let render_needed = match mem::take(&mut surface.render_state) {
|
||||||
RenderState::WaitingForVblank { dirty } => dirty,
|
RenderState::WaitingForVblank { render_needed } => render_needed,
|
||||||
state => {
|
state => {
|
||||||
debug!("vblank happened but render state was {state:?}",);
|
debug!("vblank happened but render state was {state:?}",);
|
||||||
self.schedule_render(&pinnacle.loop_handle, &output);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
if dirty {
|
if render_needed || pinnacle.cursor_state.is_current_cursor_animated() {
|
||||||
self.schedule_render(&pinnacle.loop_handle, &output);
|
self.schedule_render(&output);
|
||||||
} else {
|
} else {
|
||||||
for window in pinnacle.windows.iter() {
|
pinnacle.send_frame_callbacks(&output, Some(surface.frame_callback_sequence));
|
||||||
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.
|
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 {
|
||||||
// TODO: Remove this and improve the render pipeline.
|
return;
|
||||||
// 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
|
if matches!(
|
||||||
// animated cursors may hitch and only update when, for example, the cursor is actively
|
surface.render_state,
|
||||||
// moving as this generates events.
|
RenderState::Scheduled | RenderState::WaitingForEstimatedVblankAndScheduled(_)
|
||||||
//
|
) {
|
||||||
// What we should do is what Niri does: if `render_surface` doesn't cause any damage,
|
self.render_surface(pinnacle, output);
|
||||||
// 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()
|
|
||||||
{
|
|
||||||
let _ = pinnacle.loop_handle.insert_source(
|
|
||||||
Timer::from_duration(until),
|
|
||||||
move |_, _, state| {
|
|
||||||
state.schedule_render(&output);
|
|
||||||
calloop::timer::TimeoutAction::Drop
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1310,7 +1290,14 @@ impl Udev {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
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 render_node = surface.render_node;
|
||||||
let primary_gpu = self.primary_gpu;
|
let primary_gpu = self.primary_gpu;
|
||||||
|
@ -1326,20 +1313,6 @@ impl Udev {
|
||||||
let _ = renderer.upscale_filter(self.upscale_filter);
|
let _ = renderer.upscale_filter(self.upscale_filter);
|
||||||
let _ = renderer.downscale_filter(self.downscale_filter);
|
let _ = renderer.downscale_filter(self.downscale_filter);
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
|
||||||
|
|
||||||
// draw the cursor as relevant and
|
|
||||||
// reset the cursor if the surface is no longer alive
|
|
||||||
if let CursorImageStatus::Surface(surface) = &pinnacle.cursor_state.cursor_image() {
|
|
||||||
if !surface.alive() {
|
|
||||||
pinnacle
|
|
||||||
.cursor_state
|
|
||||||
.set_cursor_image(CursorImageStatus::default_named());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
|
||||||
|
|
||||||
let pointer_location = pinnacle
|
let pointer_location = pinnacle
|
||||||
.seat
|
.seat
|
||||||
.get_pointer()
|
.get_pointer()
|
||||||
|
@ -1420,74 +1393,182 @@ impl Udev {
|
||||||
CLEAR_COLOR_LOCKED
|
CLEAR_COLOR_LOCKED
|
||||||
};
|
};
|
||||||
|
|
||||||
let result = (|| -> Result<bool, SwapBuffersError> {
|
let render_frame_result =
|
||||||
let render_frame_result = render_frame(
|
surface
|
||||||
&mut surface.compositor,
|
.compositor
|
||||||
&mut renderer,
|
.render_frame(&mut renderer, &output_render_elements, clear_color);
|
||||||
&output_render_elements,
|
|
||||||
clear_color,
|
|
||||||
)?;
|
|
||||||
|
|
||||||
if let PrimaryPlaneElement::Swapchain(element) = &render_frame_result.primary_element {
|
let failed = match render_frame_result {
|
||||||
|
Ok(res) => {
|
||||||
|
if res.needs_sync() {
|
||||||
|
if let PrimaryPlaneElement::Swapchain(element) = &res.primary_element {
|
||||||
if let Err(err) = element.sync.wait() {
|
if let Err(err) = element.sync.wait() {
|
||||||
warn!("Failed to wait for sync point: {err}");
|
warn!("Failed to wait for sync point: {err}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if pinnacle.lock_state.is_unlocked() {
|
if pinnacle.lock_state.is_unlocked() {
|
||||||
handle_pending_screencopy(
|
handle_pending_screencopy(
|
||||||
&mut renderer,
|
&mut renderer,
|
||||||
output,
|
output,
|
||||||
surface,
|
surface,
|
||||||
&render_frame_result,
|
&res,
|
||||||
&pinnacle.loop_handle,
|
&pinnacle.loop_handle,
|
||||||
cursor_ids,
|
cursor_ids,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
super::post_repaint(
|
pinnacle.update_primary_scanout_output(output, &res.states);
|
||||||
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(),
|
|
||||||
);
|
|
||||||
|
|
||||||
let rendered = !render_frame_result.is_empty;
|
if let Some(dmabuf_feedback) = surface.dmabuf_feedback.as_ref() {
|
||||||
|
pinnacle.send_dmabuf_feedback(output, dmabuf_feedback, &res.states);
|
||||||
if rendered {
|
|
||||||
let output_presentation_feedback = take_presentation_feedback(
|
|
||||||
output,
|
|
||||||
&pinnacle.space,
|
|
||||||
&render_frame_result.states,
|
|
||||||
);
|
|
||||||
|
|
||||||
surface
|
|
||||||
.compositor
|
|
||||||
.queue_frame(Some(output_presentation_feedback))
|
|
||||||
.map_err(SwapBuffersError::from)?;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(rendered)
|
let rendered = !res.is_empty;
|
||||||
})();
|
|
||||||
|
|
||||||
match result {
|
if rendered {
|
||||||
Ok(true) => surface.render_state = RenderState::WaitingForVblank { dirty: false },
|
let output_presentation_feedback =
|
||||||
// TODO: Don't immediately set this to Idle; this allows hot loops of `render_surface`.
|
take_presentation_feedback(output, &pinnacle.space, &res.states);
|
||||||
// Instead, pull a Niri and schedule a timer for the next estimated vblank to allow
|
|
||||||
// another scheduled render.
|
match surface
|
||||||
Ok(false) | Err(_) => surface.render_state = RenderState::Idle,
|
.compositor
|
||||||
|
.queue_frame(Some(output_presentation_feedback))
|
||||||
|
{
|
||||||
|
Ok(()) => {
|
||||||
|
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),
|
||||||
|
);
|
||||||
|
|
||||||
|
if render_after_transaction_finish {
|
||||||
|
self.schedule_render(output);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return here to not queue the estimated vblank timer on a submitted frame
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Err(err) => {
|
||||||
|
warn!("Error queueing frame: {err}");
|
||||||
|
true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(err) => {
|
||||||
|
// Can fail if we switched to a different TTY
|
||||||
|
warn!("Render failed for surface: {err}");
|
||||||
|
true
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Self::queue_estimated_vblank_timer(surface, pinnacle, output, time_to_next_presentation);
|
||||||
|
|
||||||
|
if failed {
|
||||||
|
surface.render_state = if let RenderState::WaitingForEstimatedVblank(token)
|
||||||
|
| RenderState::WaitingForEstimatedVblankAndScheduled(
|
||||||
|
token,
|
||||||
|
) = surface.render_state
|
||||||
|
{
|
||||||
|
RenderState::WaitingForEstimatedVblank(token)
|
||||||
|
} else {
|
||||||
|
RenderState::Idle
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
if render_after_transaction_finish {
|
if render_after_transaction_finish {
|
||||||
self.schedule_render(&pinnacle.loop_handle, output);
|
self.schedule_render(output);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn queue_estimated_vblank_timer(
|
||||||
|
surface: &mut RenderSurface,
|
||||||
|
pinnacle: &mut Pinnacle,
|
||||||
|
output: &Output,
|
||||||
|
mut time_to_next_presentation: Duration,
|
||||||
|
) {
|
||||||
|
match mem::take(&mut surface.render_state) {
|
||||||
|
RenderState::Idle => unreachable!(),
|
||||||
|
RenderState::Scheduled => (),
|
||||||
|
RenderState::WaitingForVblank { .. } => unreachable!(),
|
||||||
|
RenderState::WaitingForEstimatedVblank(token)
|
||||||
|
| RenderState::WaitingForEstimatedVblankAndScheduled(token) => {
|
||||||
|
surface.render_state = RenderState::WaitingForEstimatedVblank(token);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 pinnacle.cursor_state.is_current_cursor_animated() {
|
||||||
|
self.schedule_render(output);
|
||||||
|
} else {
|
||||||
|
pinnacle.send_frame_callbacks(output, Some(surface.frame_callback_sequence));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1514,7 +1595,7 @@ fn handle_pending_screencopy<'a>(
|
||||||
let Some(mut screencopy) = output.with_state_mut(|state| state.screencopy.take()) else {
|
let Some(mut screencopy) = output.with_state_mut(|state| state.screencopy.take()) else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
assert!(screencopy.output() == output);
|
assert_eq!(screencopy.output(), output);
|
||||||
|
|
||||||
let untransformed_output_size = output.current_mode().expect("output no mode").size;
|
let untransformed_output_size = output.current_mode().expect("output no mode").size;
|
||||||
|
|
||||||
|
|
|
@ -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 anyhow::Context;
|
||||||
use drm_sys::{
|
use drm_sys::{
|
||||||
|
@ -12,7 +12,7 @@ use libdisplay_info_sys::cvt::{
|
||||||
use pinnacle_api_defs::pinnacle::output::v0alpha1::SetModelineRequest;
|
use pinnacle_api_defs::pinnacle::output::v0alpha1::SetModelineRequest;
|
||||||
use smithay::reexports::drm::{
|
use smithay::reexports::drm::{
|
||||||
self,
|
self,
|
||||||
control::{connector, property, Device, ResourceHandle},
|
control::{connector, property, Device, ModeFlags, ResourceHandle},
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::edid_manus::get_manufacturer;
|
use super::edid_manus::get_manufacturer;
|
||||||
|
@ -269,3 +269,27 @@ fn generate_cvt_mode(hdisplay: i32, vdisplay: i32, vrefresh: Option<f64>) -> drm
|
||||||
name,
|
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
78
src/backend/udev/frame.rs
Normal 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
|
||||||
|
}
|
||||||
|
}
|
|
@ -16,7 +16,6 @@ use smithay::{
|
||||||
},
|
},
|
||||||
winit::{self, WinitEvent, WinitGraphicsBackend},
|
winit::{self, WinitEvent, WinitGraphicsBackend},
|
||||||
},
|
},
|
||||||
input::pointer::CursorImageStatus,
|
|
||||||
output::{Output, Scale, Subpixel},
|
output::{Output, Scale, Subpixel},
|
||||||
reexports::{
|
reexports::{
|
||||||
calloop::{self, generic::Generic, Interest, LoopHandle, PostAction},
|
calloop::{self, generic::Generic, Interest, LoopHandle, PostAction},
|
||||||
|
@ -30,7 +29,7 @@ use smithay::{
|
||||||
window::{Icon, WindowAttributes},
|
window::{Icon, WindowAttributes},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
utils::{IsAlive, Point, Rectangle, Transform},
|
utils::{Point, Rectangle, Transform},
|
||||||
wayland::dmabuf::{self, DmabufFeedback, DmabufFeedbackBuilder, DmabufGlobal, DmabufState},
|
wayland::dmabuf::{self, DmabufFeedback, DmabufFeedbackBuilder, DmabufGlobal, DmabufState},
|
||||||
};
|
};
|
||||||
use tracing::{debug, error, trace, warn};
|
use tracing::{debug, error, trace, warn};
|
||||||
|
@ -53,7 +52,6 @@ pub struct Winit {
|
||||||
pub damage_tracker: OutputDamageTracker,
|
pub damage_tracker: OutputDamageTracker,
|
||||||
pub dmabuf_state: (DmabufState, DmabufGlobal, Option<DmabufFeedback>),
|
pub dmabuf_state: (DmabufState, DmabufGlobal, Option<DmabufFeedback>),
|
||||||
pub full_redraw: u8,
|
pub full_redraw: u8,
|
||||||
output_render_scheduled: bool,
|
|
||||||
output: Output,
|
output: Output,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -173,7 +171,6 @@ impl Winit {
|
||||||
damage_tracker: OutputDamageTracker::from_output(&output),
|
damage_tracker: OutputDamageTracker::from_output(&output),
|
||||||
dmabuf_state,
|
dmabuf_state,
|
||||||
full_redraw: 0,
|
full_redraw: 0,
|
||||||
output_render_scheduled: false,
|
|
||||||
output,
|
output,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -222,9 +219,10 @@ impl Winit {
|
||||||
state.process_input_event(input_evt);
|
state.process_input_event(input_evt);
|
||||||
}
|
}
|
||||||
WinitEvent::Redraw => {
|
WinitEvent::Redraw => {
|
||||||
let winit = state.backend.winit_mut();
|
state
|
||||||
winit.render_winit_window(&mut state.pinnacle);
|
.backend
|
||||||
winit.output_render_scheduled = false;
|
.winit_mut()
|
||||||
|
.render_winit_window(&mut state.pinnacle);
|
||||||
}
|
}
|
||||||
WinitEvent::CloseRequested => {
|
WinitEvent::CloseRequested => {
|
||||||
state.pinnacle.shutdown();
|
state.pinnacle.shutdown();
|
||||||
|
@ -243,30 +241,13 @@ impl Winit {
|
||||||
|
|
||||||
/// Schedule a render on the winit window.
|
/// Schedule a render on the winit window.
|
||||||
pub fn schedule_render(&mut self) {
|
pub fn schedule_render(&mut self) {
|
||||||
trace!("Scheduling winit render");
|
self.backend.window().request_redraw();
|
||||||
self.output_render_scheduled = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 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) {
|
fn render_winit_window(&mut self, pinnacle: &mut Pinnacle) {
|
||||||
let full_redraw = &mut self.full_redraw;
|
let full_redraw = &mut self.full_redraw;
|
||||||
*full_redraw = full_redraw.saturating_sub(1);
|
*full_redraw = full_redraw.saturating_sub(1);
|
||||||
|
|
||||||
if let CursorImageStatus::Surface(surface) = pinnacle.cursor_state.cursor_image() {
|
|
||||||
if !surface.alive() {
|
|
||||||
pinnacle
|
|
||||||
.cursor_state
|
|
||||||
.set_cursor_image(CursorImageStatus::default_named());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// The z-index of these is determined by `state.fixup_z_layering()`, which is called at the end
|
// The z-index of these is determined by `state.fixup_z_layering()`, which is called at the end
|
||||||
// of every event loop cycle
|
// of every event loop cycle
|
||||||
let windows = pinnacle.space.elements().cloned().collect::<Vec<_>>();
|
let windows = pinnacle.space.elements().cloned().collect::<Vec<_>>();
|
||||||
|
@ -406,16 +387,9 @@ impl Winit {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let time = pinnacle.clock.now();
|
let now = pinnacle.clock.now();
|
||||||
|
|
||||||
super::post_repaint(
|
pinnacle.update_primary_scanout_output(&self.output, &render_output_result.states);
|
||||||
&self.output,
|
|
||||||
&render_output_result.states,
|
|
||||||
&pinnacle.space,
|
|
||||||
None,
|
|
||||||
time.into(),
|
|
||||||
pinnacle.cursor_state.cursor_image(),
|
|
||||||
);
|
|
||||||
|
|
||||||
if has_rendered {
|
if has_rendered {
|
||||||
let mut output_presentation_feedback = take_presentation_feedback(
|
let mut output_presentation_feedback = take_presentation_feedback(
|
||||||
|
@ -424,7 +398,7 @@ impl Winit {
|
||||||
&render_output_result.states,
|
&render_output_result.states,
|
||||||
);
|
);
|
||||||
output_presentation_feedback.presented(
|
output_presentation_feedback.presented(
|
||||||
time,
|
now,
|
||||||
self.output
|
self.output
|
||||||
.current_mode()
|
.current_mode()
|
||||||
.map(|mode| Duration::from_secs_f64(1000f64 / mode.refresh as f64))
|
.map(|mode| Duration::from_secs_f64(1000f64 / mode.refresh as f64))
|
||||||
|
@ -437,10 +411,12 @@ impl Winit {
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
warn!("{}", err);
|
warn!("{}", err);
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
|
pinnacle.send_frame_callbacks(&self.output, None);
|
||||||
|
|
||||||
// At the end cuz borrow checker
|
// At the end cuz borrow checker
|
||||||
if render_after_transaction_finish {
|
if render_after_transaction_finish || pinnacle.cursor_state.is_current_cursor_animated() {
|
||||||
self.schedule_render();
|
self.schedule_render();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -457,7 +433,7 @@ impl Winit {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
assert!(screencopy.output() == output);
|
assert_eq!(screencopy.output(), output);
|
||||||
|
|
||||||
if screencopy.with_damage() {
|
if screencopy.with_damage() {
|
||||||
match render_output_result.damage.as_ref() {
|
match render_output_result.damage.as_ref() {
|
||||||
|
|
|
@ -1,10 +1,11 @@
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
use std::time::{Duration, Instant};
|
use std::time::Duration;
|
||||||
use std::{collections::HashMap, rc::Rc};
|
use std::{collections::HashMap, rc::Rc};
|
||||||
|
|
||||||
use anyhow::Context;
|
use anyhow::Context;
|
||||||
use smithay::backend::allocator::Fourcc;
|
use smithay::backend::allocator::Fourcc;
|
||||||
|
use smithay::utils::IsAlive;
|
||||||
use smithay::{
|
use smithay::{
|
||||||
backend::renderer::element::memory::MemoryRenderBuffer,
|
backend::renderer::element::memory::MemoryRenderBuffer,
|
||||||
input::pointer::{CursorIcon, CursorImageStatus},
|
input::pointer::{CursorIcon, CursorImageStatus},
|
||||||
|
@ -20,13 +21,11 @@ use crate::render::pointer::PointerElement;
|
||||||
static FALLBACK_CURSOR_DATA: &[u8] = include_bytes!("../resources/cursor.rgba");
|
static FALLBACK_CURSOR_DATA: &[u8] = include_bytes!("../resources/cursor.rgba");
|
||||||
|
|
||||||
pub struct CursorState {
|
pub struct CursorState {
|
||||||
start_time: Instant,
|
|
||||||
current_cursor_image: CursorImageStatus,
|
current_cursor_image: CursorImageStatus,
|
||||||
theme: CursorTheme,
|
theme: CursorTheme,
|
||||||
size: u32,
|
size: u32,
|
||||||
// memory buffer cache
|
|
||||||
mem_buffer_cache: Vec<(Image, MemoryRenderBuffer)>,
|
mem_buffer_cache: Vec<(Image, MemoryRenderBuffer)>,
|
||||||
// map of cursor icons to loaded images
|
/// A map of cursor icons to loaded images
|
||||||
loaded_images: HashMap<CursorIcon, Option<Rc<XCursor>>>,
|
loaded_images: HashMap<CursorIcon, Option<Rc<XCursor>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -38,7 +37,6 @@ impl CursorState {
|
||||||
std::env::set_var("XCURSOR_SIZE", size.to_string());
|
std::env::set_var("XCURSOR_SIZE", size.to_string());
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
start_time: Instant::now(),
|
|
||||||
current_cursor_image: CursorImageStatus::default_named(),
|
current_cursor_image: CursorImageStatus::default_named(),
|
||||||
theme: CursorTheme::load(&theme),
|
theme: CursorTheme::load(&theme),
|
||||||
size,
|
size,
|
||||||
|
@ -130,36 +128,28 @@ impl CursorState {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: update render to wait for est vblank, then you can remove this
|
pub fn is_current_cursor_animated(&mut self) -> bool {
|
||||||
/// If the current cursor is named and animated, get the time to the next frame, in milliseconds.
|
|
||||||
pub fn time_until_next_animated_cursor_frame(&mut self) -> Option<Duration> {
|
|
||||||
match &self.current_cursor_image {
|
match &self.current_cursor_image {
|
||||||
CursorImageStatus::Hidden => None,
|
CursorImageStatus::Hidden => false,
|
||||||
CursorImageStatus::Named(icon) => {
|
CursorImageStatus::Named(icon) => {
|
||||||
let cursor = self
|
let cursor = self
|
||||||
.get_xcursor_images(*icon)
|
.get_xcursor_images(*icon)
|
||||||
.or_else(|| self.get_xcursor_images(CursorIcon::Default))
|
.or_else(|| self.get_xcursor_images(CursorIcon::Default))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
if cursor.images.len() <= 1 {
|
let is_animated = cursor.images.len() > 1;
|
||||||
return None;
|
is_animated
|
||||||
|
}
|
||||||
|
CursorImageStatus::Surface(_) => false,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut millis = self.start_time.elapsed().as_millis() as u32;
|
/// Cleans up the current cursor if it is a dead WlSurface.
|
||||||
let animation_length_ms = nearest_size_images(self.size, &cursor.images)
|
pub fn cleanup(&mut self) {
|
||||||
.fold(0, |acc, image| acc + image.delay);
|
if let CursorImageStatus::Surface(surface) = &self.current_cursor_image {
|
||||||
millis %= animation_length_ms;
|
if !surface.alive() {
|
||||||
|
self.current_cursor_image = CursorImageStatus::default_named();
|
||||||
for img in nearest_size_images(self.size, &cursor.images) {
|
|
||||||
if millis < img.delay {
|
|
||||||
return Some(Duration::from_millis((img.delay - millis).into()));
|
|
||||||
}
|
}
|
||||||
millis -= img.delay;
|
|
||||||
}
|
|
||||||
|
|
||||||
None
|
|
||||||
}
|
|
||||||
CursorImageStatus::Surface(_) => None,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -171,12 +161,14 @@ pub struct XCursor {
|
||||||
impl XCursor {
|
impl XCursor {
|
||||||
pub fn image(&self, time: Duration, size: u32) -> Image {
|
pub fn image(&self, time: Duration, size: u32) -> Image {
|
||||||
let mut millis = time.as_millis() as u32;
|
let mut millis = time.as_millis() as u32;
|
||||||
|
|
||||||
let animation_length_ms =
|
let animation_length_ms =
|
||||||
nearest_size_images(size, &self.images).fold(0, |acc, image| acc + image.delay);
|
nearest_size_images(size, &self.images).fold(0, |acc, image| acc + image.delay);
|
||||||
millis %= animation_length_ms;
|
|
||||||
|
millis = millis.checked_rem(animation_length_ms).unwrap_or_default();
|
||||||
|
|
||||||
for img in nearest_size_images(size, &self.images) {
|
for img in nearest_size_images(size, &self.images) {
|
||||||
if millis < img.delay {
|
if millis <= img.delay {
|
||||||
return img.clone();
|
return img.clone();
|
||||||
}
|
}
|
||||||
millis -= img.delay;
|
millis -= img.delay;
|
||||||
|
|
|
@ -109,6 +109,10 @@ impl OutputFocusStack {
|
||||||
self.stack.retain(|op| op != &output);
|
self.stack.retain(|op| op != &output);
|
||||||
self.stack.push(output);
|
self.stack.push(output);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn remove(&mut self, output: &Output) {
|
||||||
|
self.stack.retain(|op| op != output);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A stack of windows, with the top one being the one in focus.
|
/// A stack of windows, with the top one being the one in focus.
|
||||||
|
|
|
@ -154,7 +154,7 @@ impl CompositorHandler for State {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn commit(&mut self, surface: &WlSurface) {
|
fn commit(&mut self, surface: &WlSurface) {
|
||||||
trace!("commit on surface {surface:?}");
|
// tracing::info!("commit on surface {surface:?}");
|
||||||
|
|
||||||
utils::on_commit_buffer_handler::<State>(surface);
|
utils::on_commit_buffer_handler::<State>(surface);
|
||||||
|
|
||||||
|
@ -273,6 +273,13 @@ impl CompositorHandler for State {
|
||||||
} else {
|
} else {
|
||||||
self.pinnacle.begin_layout_transaction(&focused_output);
|
self.pinnacle.begin_layout_transaction(&focused_output);
|
||||||
self.pinnacle.request_layout(&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
|
// It seems wlcs needs immediate frame sends for client tests to work
|
||||||
|
@ -416,6 +423,15 @@ impl CompositorHandler for State {
|
||||||
.cloned()
|
.cloned()
|
||||||
{
|
{
|
||||||
vec![output] // surface is a layer surface
|
vec![output] // surface is a layer surface
|
||||||
|
} else if matches!(self.pinnacle.cursor_state.cursor_image(), CursorImageStatus::Surface(s) if s == surface)
|
||||||
|
{
|
||||||
|
// This is a cursor surface
|
||||||
|
// TODO: granular
|
||||||
|
self.pinnacle.space.outputs().cloned().collect()
|
||||||
|
} else if self.pinnacle.dnd_icon.as_ref() == Some(surface) {
|
||||||
|
// This is a dnd icon
|
||||||
|
// TODO: granular
|
||||||
|
self.pinnacle.space.outputs().cloned().collect()
|
||||||
} else if let Some(output) = self
|
} else if let Some(output) = self
|
||||||
.pinnacle
|
.pinnacle
|
||||||
.space
|
.space
|
||||||
|
@ -912,7 +928,8 @@ impl OutputManagementHandler for State {
|
||||||
OutputConfiguration::Disabled => {
|
OutputConfiguration::Disabled => {
|
||||||
self.pinnacle.set_output_enabled(&output, false);
|
self.pinnacle.set_output_enabled(&output, false);
|
||||||
// TODO: split
|
// TODO: split
|
||||||
self.backend.set_output_powered(&output, false);
|
self.backend
|
||||||
|
.set_output_powered(&output, &self.pinnacle.loop_handle, false);
|
||||||
}
|
}
|
||||||
OutputConfiguration::Enabled {
|
OutputConfiguration::Enabled {
|
||||||
mode,
|
mode,
|
||||||
|
@ -923,7 +940,8 @@ impl OutputManagementHandler for State {
|
||||||
} => {
|
} => {
|
||||||
self.pinnacle.set_output_enabled(&output, true);
|
self.pinnacle.set_output_enabled(&output, true);
|
||||||
// TODO: split
|
// TODO: split
|
||||||
self.backend.set_output_powered(&output, true);
|
self.backend
|
||||||
|
.set_output_powered(&output, &self.pinnacle.loop_handle, true);
|
||||||
|
|
||||||
self.capture_snapshots_on_output(&output, []);
|
self.capture_snapshots_on_output(&output, []);
|
||||||
|
|
||||||
|
@ -985,7 +1003,8 @@ impl OutputPowerManagementHandler for State {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_mode(&mut self, output: &Output, powered: bool) {
|
fn set_mode(&mut self, output: &Output, powered: bool) {
|
||||||
self.backend.set_output_powered(output, powered);
|
self.backend
|
||||||
|
.set_output_powered(output, &self.pinnacle.loop_handle, powered);
|
||||||
|
|
||||||
if powered {
|
if powered {
|
||||||
self.schedule_render(output);
|
self.schedule_render(output);
|
||||||
|
|
|
@ -7,17 +7,14 @@ use std::{
|
||||||
|
|
||||||
use smithay::{
|
use smithay::{
|
||||||
backend::renderer::element::{
|
backend::renderer::element::{
|
||||||
self,
|
surface::WaylandSurfaceRenderElement, utils::RescaleRenderElement, Element,
|
||||||
surface::WaylandSurfaceRenderElement,
|
|
||||||
texture::{TextureBuffer, TextureRenderElement},
|
|
||||||
utils::RescaleRenderElement,
|
|
||||||
},
|
},
|
||||||
desktop::Space,
|
desktop::Space,
|
||||||
reexports::calloop::{
|
reexports::calloop::{
|
||||||
timer::{TimeoutAction, Timer},
|
timer::{TimeoutAction, Timer},
|
||||||
LoopHandle,
|
LoopHandle,
|
||||||
},
|
},
|
||||||
utils::{Logical, Point, Scale, Serial, Transform},
|
utils::{Logical, Point, Scale, Serial},
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
|
@ -26,7 +23,7 @@ use crate::{
|
||||||
texture::CommonTextureRenderElement, util::snapshot::RenderSnapshot, AsGlesRenderer,
|
texture::CommonTextureRenderElement, util::snapshot::RenderSnapshot, AsGlesRenderer,
|
||||||
PRenderer,
|
PRenderer,
|
||||||
},
|
},
|
||||||
state::State,
|
state::{State, WithState},
|
||||||
window::WindowElement,
|
window::WindowElement,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -53,7 +50,10 @@ pub enum SnapshotTarget {
|
||||||
/// Render a window.
|
/// Render a window.
|
||||||
Window(WindowElement),
|
Window(WindowElement),
|
||||||
/// Render a snapshot.
|
/// Render a snapshot.
|
||||||
Snapshot(LayoutSnapshot),
|
Snapshot {
|
||||||
|
snapshot: LayoutSnapshot,
|
||||||
|
window: WindowElement,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A layout transaction.
|
/// A layout transaction.
|
||||||
|
@ -174,28 +174,18 @@ impl LayoutTransaction {
|
||||||
.map(SnapshotRenderElement::Window)
|
.map(SnapshotRenderElement::Window)
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
SnapshotTarget::Snapshot(snapshot) => {
|
SnapshotTarget::Snapshot { snapshot, window } => {
|
||||||
let Some((texture, loc)) = snapshot.texture(renderer.as_gles_renderer()) else {
|
let elem = snapshot.render_elements(renderer, scale, alpha);
|
||||||
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,
|
|
||||||
);
|
|
||||||
|
|
||||||
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![elem]
|
||||||
|
} else {
|
||||||
vec![SnapshotRenderElement::Snapshot(
|
vec![]
|
||||||
RescaleRenderElement::from_element(common, loc, scale),
|
}
|
||||||
)]
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -13,6 +13,7 @@ use smithay::{
|
||||||
utils::{Logical, Point, Transform},
|
utils::{Logical, Point, Transform},
|
||||||
wayland::session_lock::LockSurface,
|
wayland::session_lock::LockSurface,
|
||||||
};
|
};
|
||||||
|
use tracing::debug;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
backend::BackendData,
|
backend::BackendData,
|
||||||
|
@ -333,6 +334,8 @@ impl Pinnacle {
|
||||||
|
|
||||||
/// Completely remove an output, for example when a monitor is unplugged
|
/// Completely remove an output, for example when a monitor is unplugged
|
||||||
pub fn remove_output(&mut self, output: &Output) {
|
pub fn remove_output(&mut self, output: &Output) {
|
||||||
|
debug!("Removing output {}", output.name());
|
||||||
|
|
||||||
let global = self.outputs.shift_remove(output);
|
let global = self.outputs.shift_remove(output);
|
||||||
if let Some(mut global) = global {
|
if let Some(mut global) = global {
|
||||||
if let Some(global) = global.take() {
|
if let Some(global) = global.take() {
|
||||||
|
@ -346,6 +349,8 @@ impl Pinnacle {
|
||||||
|
|
||||||
self.space.unmap_output(output);
|
self.space.unmap_output(output);
|
||||||
|
|
||||||
|
self.output_focus_stack.remove(output);
|
||||||
|
|
||||||
self.gamma_control_manager_state.output_removed(output);
|
self.gamma_control_manager_state.output_removed(output);
|
||||||
|
|
||||||
self.output_power_management_state.output_removed(output);
|
self.output_power_management_state.output_removed(output);
|
||||||
|
|
|
@ -29,7 +29,7 @@ use smithay::{
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
backend::{udev::UdevRenderer, Backend},
|
backend::{udev::UdevRenderer, Backend},
|
||||||
layout::transaction::{LayoutTransaction, SnapshotRenderElement, SnapshotTarget},
|
layout::transaction::SnapshotRenderElement,
|
||||||
pinnacle_render_elements,
|
pinnacle_render_elements,
|
||||||
state::{State, WithState},
|
state::{State, WithState},
|
||||||
window::WindowElement,
|
window::WindowElement,
|
||||||
|
@ -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.
|
/// Generate render elements for the given output.
|
||||||
///
|
///
|
||||||
/// Render elements will be pulled from the provided windows,
|
/// Render elements will be pulled from the provided windows,
|
||||||
|
@ -377,7 +339,7 @@ pub fn output_render_elements<R: PRenderer + AsGlesRenderer>(
|
||||||
state
|
state
|
||||||
.layout_transaction
|
.layout_transaction
|
||||||
.as_ref()
|
.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
|
fullscreen_and_up_elements = fs_and_up_elements
|
||||||
.into_iter()
|
.into_iter()
|
||||||
|
@ -388,6 +350,9 @@ pub fn output_render_elements<R: PRenderer + AsGlesRenderer>(
|
||||||
.map(OutputRenderElement::from)
|
.map(OutputRenderElement::from)
|
||||||
.collect();
|
.collect();
|
||||||
} else {
|
} else {
|
||||||
|
for window in windows.iter() {
|
||||||
|
window.with_state_mut(|state| state.offscreen_elem_id.take());
|
||||||
|
}
|
||||||
(fullscreen_and_up_elements, rest_of_window_elements) =
|
(fullscreen_and_up_elements, rest_of_window_elements) =
|
||||||
window_render_elements::<R>(output, &windows, space, renderer, scale);
|
window_render_elements::<R>(output, &windows, space, renderer, scale);
|
||||||
}
|
}
|
||||||
|
@ -443,7 +408,7 @@ impl State {
|
||||||
pub fn schedule_render(&mut self, output: &Output) {
|
pub fn schedule_render(&mut self, output: &Output) {
|
||||||
match &mut self.backend {
|
match &mut self.backend {
|
||||||
Backend::Udev(udev) => {
|
Backend::Udev(udev) => {
|
||||||
udev.schedule_render(&self.pinnacle.loop_handle, output);
|
udev.schedule_render(output);
|
||||||
}
|
}
|
||||||
Backend::Winit(winit) => {
|
Backend::Winit(winit) => {
|
||||||
winit.schedule_render();
|
winit.schedule_render();
|
||||||
|
|
|
@ -210,7 +210,10 @@ pub fn capture_snapshots_on_output(
|
||||||
1.0,
|
1.0,
|
||||||
);
|
);
|
||||||
|
|
||||||
snapshot.map(SnapshotTarget::Snapshot)
|
snapshot.map(|ss| SnapshotTarget::Snapshot {
|
||||||
|
snapshot: ss,
|
||||||
|
window: win.clone(),
|
||||||
|
})
|
||||||
} else {
|
} else {
|
||||||
Some(SnapshotTarget::Window(win))
|
Some(SnapshotTarget::Window(win))
|
||||||
}
|
}
|
||||||
|
|
385
src/state.rs
385
src/state.rs
|
@ -2,7 +2,12 @@
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
api::signal::SignalState,
|
api::signal::SignalState,
|
||||||
backend::{self, udev::Udev, winit::Winit, Backend},
|
backend::{
|
||||||
|
self,
|
||||||
|
udev::{SurfaceDmabufFeedback, Udev},
|
||||||
|
winit::Winit,
|
||||||
|
Backend,
|
||||||
|
},
|
||||||
cli::{self, Cli},
|
cli::{self, Cli},
|
||||||
config::Config,
|
config::Config,
|
||||||
cursor::CursorState,
|
cursor::CursorState,
|
||||||
|
@ -23,8 +28,19 @@ use anyhow::Context;
|
||||||
use indexmap::IndexMap;
|
use indexmap::IndexMap;
|
||||||
use pinnacle_api_defs::pinnacle::v0alpha1::ShutdownWatchResponse;
|
use pinnacle_api_defs::pinnacle::v0alpha1::ShutdownWatchResponse;
|
||||||
use smithay::{
|
use smithay::{
|
||||||
desktop::{PopupManager, Space},
|
backend::renderer::element::{
|
||||||
input::{keyboard::XkbConfig, Seat, SeatState},
|
default_primary_scanout_output_compare, utils::select_dmabuf_feedback, Id,
|
||||||
|
PrimaryScanoutOutput, RenderElementState, 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,
|
output::Output,
|
||||||
reexports::{
|
reexports::{
|
||||||
calloop::{generic::Generic, Interest, LoopHandle, LoopSignal, Mode, PostAction},
|
calloop::{generic::Generic, Interest, LoopHandle, LoopSignal, Mode, PostAction},
|
||||||
|
@ -37,10 +53,11 @@ use smithay::{
|
||||||
},
|
},
|
||||||
utils::{Clock, Monotonic},
|
utils::{Clock, Monotonic},
|
||||||
wayland::{
|
wayland::{
|
||||||
compositor::{self, CompositorClientState, CompositorState},
|
compositor::{
|
||||||
|
self, with_surface_tree_downward, CompositorClientState, CompositorState, SurfaceData,
|
||||||
|
},
|
||||||
cursor_shape::CursorShapeManagerState,
|
cursor_shape::CursorShapeManagerState,
|
||||||
dmabuf::DmabufFeedback,
|
fractional_scale::{with_fractional_scale, FractionalScaleManagerState},
|
||||||
fractional_scale::FractionalScaleManagerState,
|
|
||||||
idle_notify::IdleNotifierState,
|
idle_notify::IdleNotifierState,
|
||||||
keyboard_shortcuts_inhibit::KeyboardShortcutsInhibitState,
|
keyboard_shortcuts_inhibit::KeyboardShortcutsInhibitState,
|
||||||
output::OutputManagerState,
|
output::OutputManagerState,
|
||||||
|
@ -72,7 +89,8 @@ use std::{
|
||||||
cell::RefCell,
|
cell::RefCell,
|
||||||
collections::{HashMap, HashSet},
|
collections::{HashMap, HashSet},
|
||||||
path::PathBuf,
|
path::PathBuf,
|
||||||
sync::Arc,
|
sync::{Arc, Mutex},
|
||||||
|
time::Duration,
|
||||||
};
|
};
|
||||||
use sysinfo::{ProcessRefreshKind, RefreshKind};
|
use sysinfo::{ProcessRefreshKind, RefreshKind};
|
||||||
use tracing::{info, warn};
|
use tracing::{info, warn};
|
||||||
|
@ -83,6 +101,12 @@ use crate::input::InputState;
|
||||||
#[cfg(feature = "testing")]
|
#[cfg(feature = "testing")]
|
||||||
use crate::backend::dummy::Dummy;
|
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.
|
/// The main state of the application.
|
||||||
pub struct State {
|
pub struct State {
|
||||||
/// Which backend is currently running
|
/// Which backend is currently running
|
||||||
|
@ -197,14 +221,13 @@ impl State {
|
||||||
pub fn on_event_loop_cycle_completion(&mut self) {
|
pub fn on_event_loop_cycle_completion(&mut self) {
|
||||||
self.pinnacle.fixup_z_layering();
|
self.pinnacle.fixup_z_layering();
|
||||||
self.pinnacle.space.refresh();
|
self.pinnacle.space.refresh();
|
||||||
|
self.pinnacle.cursor_state.cleanup();
|
||||||
self.pinnacle.popup_manager.cleanup();
|
self.pinnacle.popup_manager.cleanup();
|
||||||
self.update_pointer_focus();
|
self.update_pointer_focus();
|
||||||
foreign_toplevel::refresh(self);
|
foreign_toplevel::refresh(self);
|
||||||
self.pinnacle.refresh_idle_inhibit();
|
self.pinnacle.refresh_idle_inhibit();
|
||||||
|
|
||||||
if let Backend::Winit(winit) = &mut self.backend {
|
self.backend.render_scheduled_outputs(&mut self.pinnacle);
|
||||||
winit.render_if_scheduled(&mut self.pinnacle);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "snowcap")]
|
#[cfg(feature = "snowcap")]
|
||||||
if self
|
if self
|
||||||
|
@ -221,15 +244,14 @@ impl State {
|
||||||
|
|
||||||
// FIXME: Don't poll this every cycle
|
// FIXME: Don't poll this every cycle
|
||||||
for output in self.pinnacle.space.outputs().cloned().collect::<Vec<_>>() {
|
for output in self.pinnacle.space.outputs().cloned().collect::<Vec<_>>() {
|
||||||
output.with_state_mut(|state| {
|
if output.with_state_mut(|state| {
|
||||||
if state
|
state
|
||||||
.layout_transaction
|
.layout_transaction
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.is_some_and(|ts| ts.ready())
|
.is_some_and(|ts| ts.ready())
|
||||||
{
|
}) {
|
||||||
self.schedule_render(&output);
|
self.schedule_render(&output);
|
||||||
}
|
}
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
self.pinnacle
|
self.pinnacle
|
||||||
|
@ -463,6 +485,335 @@ impl Pinnacle {
|
||||||
stop_signal.stop();
|
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);
|
||||||
|
|
||||||
|
if current_primary_output.as_ref() != Some(output) {
|
||||||
|
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) {
|
||||||
|
// If the window is tiled and on an active tag, we'll unconditionally send frames.
|
||||||
|
// This works around an issue where snapshots taken with no windows prevent
|
||||||
|
// frames from being sent for new tiled windows until something else causes a render.
|
||||||
|
//
|
||||||
|
// FIXME: Currently this means that `WindowElementState::offscreen_elem_id` is redundant
|
||||||
|
let throttle = if window.is_on_active_tag_on_output(output)
|
||||||
|
&& window.with_state(|state| state.window_state.is_tiled())
|
||||||
|
{
|
||||||
|
Some(Duration::ZERO)
|
||||||
|
} else {
|
||||||
|
FRAME_CALLBACK_THROTTLE
|
||||||
|
};
|
||||||
|
|
||||||
|
window.send_frame(output, now, 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns a custom primary scanout output comparison function that, in addition to performing
|
||||||
|
/// the [`default_primary_scanout_output_compare`], checks if the returned output actually
|
||||||
|
/// exists. If it doesn't, it returns the new output.
|
||||||
|
///
|
||||||
|
/// This is needed because when turning a monitor off and on, windows will *still* have the old
|
||||||
|
/// output as the primary scanout output. For whatever reason, clones of that now-defunct
|
||||||
|
/// output still exist somewhere, causing the default compare function to choose it over the
|
||||||
|
/// new output for the monitor. This is a workaround for that.
|
||||||
|
fn primary_scanout_output_compare(
|
||||||
|
&self,
|
||||||
|
) -> impl for<'a> Fn(
|
||||||
|
&'a Output,
|
||||||
|
&'a RenderElementState,
|
||||||
|
&'a Output,
|
||||||
|
&'a RenderElementState,
|
||||||
|
) -> &'a Output
|
||||||
|
+ '_ {
|
||||||
|
|current_output, current_state, next_output, next_state| {
|
||||||
|
let new_op = default_primary_scanout_output_compare(
|
||||||
|
current_output,
|
||||||
|
current_state,
|
||||||
|
next_output,
|
||||||
|
next_state,
|
||||||
|
);
|
||||||
|
|
||||||
|
if self.outputs.contains_key(new_op) {
|
||||||
|
new_op
|
||||||
|
} else {
|
||||||
|
next_output
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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 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(
|
||||||
|
// Update the primary scanout using the snapshot of the window, if there is one.
|
||||||
|
// Otherwise just use the ids of this window's surfaces.
|
||||||
|
//
|
||||||
|
// Without this, the element id of the snapshot would be different from all
|
||||||
|
// this window's surfaces, preventing their primary scanout outputs from
|
||||||
|
// properly updating and potentially causing rendering to get stuck.
|
||||||
|
offscreen_id.clone().unwrap_or_else(|| Id::from(surface)),
|
||||||
|
output,
|
||||||
|
render_element_states,
|
||||||
|
self.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,
|
||||||
|
self.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());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(lock_surface) = output.with_state(|state| state.lock_surface.clone()) {
|
||||||
|
with_surface_tree_downward(
|
||||||
|
lock_surface.wl_surface(),
|
||||||
|
(),
|
||||||
|
|_, _, _| compositor::TraversalAction::DoChildren(()),
|
||||||
|
|surface, states, _| {
|
||||||
|
update_surface_primary_scanout_output(
|
||||||
|
surface,
|
||||||
|
output,
|
||||||
|
states,
|
||||||
|
render_element_states,
|
||||||
|
self.primary_scanout_output_compare(),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|_, _, _| true,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(dnd) = self.dnd_icon.as_ref() {
|
||||||
|
with_surface_tree_downward(
|
||||||
|
dnd,
|
||||||
|
(),
|
||||||
|
|_, _, _| compositor::TraversalAction::DoChildren(()),
|
||||||
|
|surface, states, _| {
|
||||||
|
update_surface_primary_scanout_output(
|
||||||
|
surface,
|
||||||
|
output,
|
||||||
|
states,
|
||||||
|
render_element_states,
|
||||||
|
self.primary_scanout_output_compare(),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|_, _, _| true,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if let CursorImageStatus::Surface(surface) = self.cursor_state.cursor_image() {
|
||||||
|
with_surface_tree_downward(
|
||||||
|
surface,
|
||||||
|
(),
|
||||||
|
|_, _, _| compositor::TraversalAction::DoChildren(()),
|
||||||
|
|surface, states, _| {
|
||||||
|
update_surface_primary_scanout_output(
|
||||||
|
surface,
|
||||||
|
output,
|
||||||
|
states,
|
||||||
|
render_element_states,
|
||||||
|
self.primary_scanout_output_compare(),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|_, _, _| true,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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 {
|
impl State {
|
||||||
|
@ -537,12 +888,6 @@ impl ClientData for ClientState {
|
||||||
fn disconnected(&self, _client_id: ClientId, _reason: DisconnectReason) {}
|
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]
|
/// A trait meant to be used in types with a [`UserDataMap`][smithay::utils::user_data::UserDataMap]
|
||||||
/// to get user-defined state.
|
/// to get user-defined state.
|
||||||
pub trait WithState {
|
pub trait WithState {
|
||||||
|
|
|
@ -4,6 +4,7 @@ use std::sync::atomic::{AtomicU32, Ordering};
|
||||||
|
|
||||||
use indexmap::IndexSet;
|
use indexmap::IndexSet;
|
||||||
use smithay::{
|
use smithay::{
|
||||||
|
backend::renderer::element::Id,
|
||||||
desktop::{space::SpaceElement, WindowSurface},
|
desktop::{space::SpaceElement, WindowSurface},
|
||||||
reexports::wayland_protocols::xdg::shell::server::xdg_toplevel,
|
reexports::wayland_protocols::xdg::shell::server::xdg_toplevel,
|
||||||
utils::{Logical, Point, Serial, Size},
|
utils::{Logical, Point, Serial, Size},
|
||||||
|
@ -206,6 +207,17 @@ pub struct WindowElementState {
|
||||||
pub decoration_mode: Option<DecorationMode>,
|
pub decoration_mode: Option<DecorationMode>,
|
||||||
pub floating_loc: Option<Point<f64, Logical>>,
|
pub floating_loc: Option<Point<f64, Logical>>,
|
||||||
pub floating_size: Option<Size<i32, Logical>>,
|
pub floating_size: Option<Size<i32, Logical>>,
|
||||||
|
|
||||||
|
/// The id of a snapshot element if any.
|
||||||
|
///
|
||||||
|
/// When updating the primary scanout output, Smithay looks at the ids of all elements drawn on
|
||||||
|
/// screen. If it matches the ids of this window's elements, the primary output is updated.
|
||||||
|
/// However, when a snapshot is rendering, the snapshot's element id is different from this
|
||||||
|
/// window's ids. Therefore, we clone that snapshot's id into this field and use it to update
|
||||||
|
/// the primary output when necessary.
|
||||||
|
///
|
||||||
|
/// See [`Pinnacle::update_primary_scanout_output`] for more details.
|
||||||
|
pub offscreen_elem_id: Option<Id>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl WindowElement {
|
impl WindowElement {
|
||||||
|
@ -425,6 +437,7 @@ impl WindowElementState {
|
||||||
snapshot: None,
|
snapshot: None,
|
||||||
snapshot_hook_id: None,
|
snapshot_hook_id: None,
|
||||||
decoration_mode: None,
|
decoration_mode: None,
|
||||||
|
offscreen_elem_id: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue