mirror of
https://github.com/pinnacle-comp/pinnacle.git
synced 2024-12-26 21:58:10 +01:00
Add render scheduling
This commit is contained in:
parent
698d13bfda
commit
26ce7309de
10 changed files with 493 additions and 416 deletions
|
@ -7,10 +7,11 @@ use std::{
|
|||
ffi::OsString,
|
||||
os::fd::FromRawFd,
|
||||
path::Path,
|
||||
time::Duration,
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
|
||||
use anyhow::Context;
|
||||
use calloop::Idle;
|
||||
use smithay::{
|
||||
backend::{
|
||||
allocator::{
|
||||
|
@ -28,10 +29,7 @@ use smithay::{
|
|||
libinput::{LibinputInputBackend, LibinputSessionInterface},
|
||||
renderer::{
|
||||
damage::{self},
|
||||
element::{
|
||||
surface::WaylandSurfaceRenderElement, texture::TextureBuffer, RenderElement,
|
||||
RenderElementStates,
|
||||
},
|
||||
element::{texture::TextureBuffer, RenderElement, RenderElementStates},
|
||||
gles::{GlesRenderer, GlesTexture},
|
||||
multigpu::{gbm::GbmGlesBackend, GpuManager, MultiRenderer, MultiTexture},
|
||||
Bind, ExportMem, ImportDma, ImportEgl, ImportMemWl, Offscreen, Renderer,
|
||||
|
@ -53,10 +51,7 @@ use smithay::{
|
|||
output::{Output, PhysicalProperties, Subpixel},
|
||||
reexports::{
|
||||
ash::vk::ExtPhysicalDeviceDrmFn,
|
||||
calloop::{
|
||||
timer::{TimeoutAction, Timer},
|
||||
EventLoop, LoopHandle, RegistrationToken,
|
||||
},
|
||||
calloop::{EventLoop, LoopHandle, RegistrationToken},
|
||||
drm::{
|
||||
self,
|
||||
control::{connector, crtc, ModeTypeFlags},
|
||||
|
@ -88,7 +83,7 @@ use crate::{
|
|||
ConnectorSavedState,
|
||||
},
|
||||
output::OutputName,
|
||||
render::{pointer::PointerElement, take_presentation_feedback, CustomRenderElements},
|
||||
render::{pointer::PointerElement, take_presentation_feedback},
|
||||
state::{CalloopData, State, SurfaceDmabufFeedback, WithState},
|
||||
window::WindowElement,
|
||||
};
|
||||
|
@ -128,6 +123,8 @@ pub struct Udev {
|
|||
pointer_images: Vec<(xcursor::parser::Image, TextureBuffer<MultiTexture>)>,
|
||||
pointer_element: PointerElement<MultiTexture>,
|
||||
pointer_image: crate::cursor::Cursor,
|
||||
|
||||
last_vblank_time: Instant,
|
||||
}
|
||||
|
||||
impl Backend {
|
||||
|
@ -142,6 +139,30 @@ impl Backend {
|
|||
}
|
||||
}
|
||||
|
||||
impl Udev {
|
||||
pub fn schedule_render(&mut self, loop_handle: &LoopHandle<CalloopData>, output: &Output) {
|
||||
let Some(surface) = render_surface_for_output(output, &mut self.backends) else {
|
||||
return;
|
||||
};
|
||||
// tracing::debug!(state = ?surface.render_state, "scheduling render");
|
||||
|
||||
match &surface.render_state {
|
||||
RenderState::Idle => {
|
||||
let output = output.clone();
|
||||
let token = loop_handle.insert_idle(move |data| {
|
||||
data.state.render_surface(&output);
|
||||
});
|
||||
|
||||
surface.render_state = RenderState::Scheduled(token);
|
||||
}
|
||||
RenderState::Scheduled(_) => (),
|
||||
RenderState::WaitingForVblank { dirty: _ } => {
|
||||
surface.render_state = RenderState::WaitingForVblank { dirty: true }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl BackendData for Udev {
|
||||
fn seat_name(&self) -> String {
|
||||
self.session.seat()
|
||||
|
@ -205,6 +226,8 @@ pub fn run_udev() -> anyhow::Result<()> {
|
|||
pointer_image: crate::cursor::Cursor::load(),
|
||||
pointer_images: Vec::new(),
|
||||
pointer_element: PointerElement::default(),
|
||||
|
||||
last_vblank_time: Instant::now(),
|
||||
};
|
||||
|
||||
let display_handle = display.handle();
|
||||
|
@ -314,9 +337,10 @@ pub fn run_udev() -> anyhow::Result<()> {
|
|||
}
|
||||
|
||||
for output in data.state.space.outputs().cloned().collect::<Vec<_>>() {
|
||||
data.state
|
||||
.loop_handle
|
||||
.insert_idle(move |data| data.state.render_surface(&output));
|
||||
data.state.schedule_render(&output);
|
||||
// data.state
|
||||
// .loop_handle
|
||||
// .insert_idle(move |data| data.state.render_surface(&output));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -533,6 +557,26 @@ struct DrmSurfaceDmabufFeedback {
|
|||
scanout_feedback: DmabufFeedback,
|
||||
}
|
||||
|
||||
/// The state of a [`RenderSurface`].
|
||||
#[derive(Debug)]
|
||||
enum RenderState {
|
||||
/// No render is scheduled.
|
||||
Idle,
|
||||
/// A render has been queued.
|
||||
Scheduled(
|
||||
/// The idle token from a render being scheduled.
|
||||
/// This is used to cancel renders if, for example,
|
||||
/// the output being rendered is removed.
|
||||
Idle<'static>,
|
||||
),
|
||||
/// A frame was rendered and scheduled and we are waiting for vblank.
|
||||
WaitingForVblank {
|
||||
/// A render was scheduled while waiting for vblank.
|
||||
/// In this case, another render will be scheduled once vblank happens.
|
||||
dirty: bool,
|
||||
},
|
||||
}
|
||||
|
||||
/// Render surface for an output.
|
||||
struct RenderSurface {
|
||||
/// The output global id.
|
||||
|
@ -549,6 +593,7 @@ struct RenderSurface {
|
|||
/// The thing rendering elements and queueing frames.
|
||||
compositor: GbmDrmCompositor,
|
||||
dmabuf_feedback: Option<DrmSurfaceDmabufFeedback>,
|
||||
render_state: RenderState,
|
||||
}
|
||||
|
||||
impl Drop for RenderSurface {
|
||||
|
@ -630,17 +675,22 @@ impl State {
|
|||
|
||||
let registration_token = self
|
||||
.loop_handle
|
||||
.insert_source(
|
||||
notifier,
|
||||
move |event, metadata, data: &mut CalloopData| match event {
|
||||
DrmEvent::VBlank(crtc) => {
|
||||
data.state.frame_finish(node, crtc, metadata);
|
||||
.insert_source(notifier, move |event, metadata, data| match event {
|
||||
DrmEvent::VBlank(crtc) => {
|
||||
{
|
||||
let udev = data.state.backend.udev_mut();
|
||||
let then = udev.last_vblank_time;
|
||||
let now = Instant::now();
|
||||
let diff = now.duration_since(then);
|
||||
tracing::debug!(time = diff.as_secs_f64(), "Time since last vblank");
|
||||
udev.last_vblank_time = now;
|
||||
}
|
||||
DrmEvent::Error(error) => {
|
||||
tracing::error!("{:?}", error);
|
||||
}
|
||||
},
|
||||
)
|
||||
data.state.on_vblank(node, crtc, metadata);
|
||||
}
|
||||
DrmEvent::Error(error) => {
|
||||
tracing::error!("{:?}", error);
|
||||
}
|
||||
})
|
||||
.expect("failed to insert drm notifier into event loop");
|
||||
|
||||
let render_node = EGLDevice::device_for_display(
|
||||
|
@ -867,12 +917,11 @@ impl State {
|
|||
global: Some(global),
|
||||
compositor,
|
||||
dmabuf_feedback,
|
||||
render_state: RenderState::Idle,
|
||||
};
|
||||
|
||||
device.surfaces.insert(crtc, surface);
|
||||
|
||||
self.schedule_initial_render(node, crtc, self.loop_handle.clone());
|
||||
|
||||
// If there is saved connector state, the connector was previously plugged in.
|
||||
// In this case, restore its tags and location.
|
||||
// TODO: instead of checking the connector, check the monitor's edid info instead
|
||||
|
@ -1025,7 +1074,7 @@ impl State {
|
|||
}
|
||||
|
||||
/// Mark [`OutputPresentationFeedback`]s as presented and schedule a new render on idle.
|
||||
fn frame_finish(
|
||||
fn on_vblank(
|
||||
&mut self,
|
||||
dev_id: DrmNode,
|
||||
crtc: crtc::Handle,
|
||||
|
@ -1052,7 +1101,7 @@ impl State {
|
|||
return;
|
||||
};
|
||||
|
||||
let schedule_render = match surface
|
||||
match surface
|
||||
.compositor
|
||||
.frame_submitted()
|
||||
.map_err(SwapBuffersError::from)
|
||||
|
@ -1089,66 +1138,51 @@ impl State {
|
|||
flags,
|
||||
);
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
Err(err) => {
|
||||
tracing::warn!("Error during rendering: {:?}", err);
|
||||
match err {
|
||||
SwapBuffersError::AlreadySwapped => true,
|
||||
// If the device has been deactivated do not reschedule, this will be done
|
||||
// by session resume
|
||||
SwapBuffersError::TemporaryFailure(err)
|
||||
if matches!(
|
||||
err.downcast_ref::<DrmError>(),
|
||||
Some(&DrmError::DeviceInactive)
|
||||
) =>
|
||||
{
|
||||
false
|
||||
}
|
||||
SwapBuffersError::TemporaryFailure(err) => matches!(
|
||||
err.downcast_ref::<DrmError>(),
|
||||
Some(&DrmError::Access {
|
||||
source: drm::SystemError::PermissionDenied,
|
||||
..
|
||||
})
|
||||
),
|
||||
SwapBuffersError::ContextLost(err) => panic!("Rendering loop lost: {}", err),
|
||||
if let SwapBuffersError::ContextLost(err) = err {
|
||||
panic!("Rendering loop lost: {}", err)
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if schedule_render {
|
||||
// Anvil had some stuff here about delaying a render to reduce latency,
|
||||
// but it introduces visible hitching when scrolling, so I'm removing it here.
|
||||
//
|
||||
// If latency is a problem then future me can deal with it :)
|
||||
self.loop_handle.insert_idle(move |data| {
|
||||
data.state.render_surface(&output);
|
||||
});
|
||||
let RenderState::WaitingForVblank { dirty } = surface.render_state else {
|
||||
unreachable!();
|
||||
};
|
||||
|
||||
surface.render_state = RenderState::Idle;
|
||||
|
||||
if dirty {
|
||||
self.schedule_render(&output);
|
||||
} else {
|
||||
for window in self.windows.iter() {
|
||||
window.send_frame(&output, self.clock.now(), Some(Duration::ZERO), |_, _| {
|
||||
Some(output.clone())
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// if schedule_render {
|
||||
// // Anvil had some stuff here about delaying a render to reduce latency,
|
||||
// // but it introduces visible hitching when scrolling, so I'm removing it here.
|
||||
// //
|
||||
// // If latency is a problem then future me can deal with it :)
|
||||
// self.loop_handle.insert_idle(move |data| {
|
||||
// data.state.render_surface(&output);
|
||||
// });
|
||||
// }
|
||||
}
|
||||
|
||||
/// Render to the [`RenderSurface`] associated with the given `output`.
|
||||
fn render_surface(&mut self, output: &Output) {
|
||||
let udev = self.backend.udev_mut();
|
||||
|
||||
let Some(UdevOutputData {
|
||||
device_id,
|
||||
crtc,
|
||||
mode: _,
|
||||
}) = output.user_data().get()
|
||||
else {
|
||||
let Some(surface) = render_surface_for_output(output, &mut udev.backends) else {
|
||||
return;
|
||||
};
|
||||
|
||||
let Some(surface) = udev
|
||||
.backends
|
||||
.get_mut(device_id)
|
||||
.and_then(|device| device.surfaces.get_mut(crtc))
|
||||
else {
|
||||
return;
|
||||
};
|
||||
assert!(matches!(surface.render_state, RenderState::Scheduled(_)));
|
||||
|
||||
// TODO get scale from the rendersurface when supporting HiDPI
|
||||
let frame = udev.pointer_image.get_image(
|
||||
|
@ -1231,114 +1265,91 @@ impl State {
|
|||
&self.clock,
|
||||
);
|
||||
|
||||
let reschedule = match &result {
|
||||
Ok(has_rendered) => !has_rendered,
|
||||
Err(err) => {
|
||||
tracing::warn!("Error during rendering: {:?}", err);
|
||||
match err {
|
||||
SwapBuffersError::AlreadySwapped => false,
|
||||
SwapBuffersError::TemporaryFailure(err) => !matches!(
|
||||
err.downcast_ref::<DrmError>(),
|
||||
Some(&DrmError::DeviceInactive)
|
||||
| Some(&DrmError::Access {
|
||||
source: drm::SystemError::PermissionDenied,
|
||||
..
|
||||
})
|
||||
),
|
||||
SwapBuffersError::ContextLost(err) => panic!("Rendering loop lost: {}", err),
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if reschedule {
|
||||
let Some(data) = output.user_data().get::<UdevOutputData>() else {
|
||||
unreachable!()
|
||||
};
|
||||
|
||||
// Literally no idea if this refresh time calculation is doing anything, but we're
|
||||
// gonna keep it here because I already added the stuff for it
|
||||
let refresh_time = if let Some(mode) = data.mode {
|
||||
self::utils::refresh_time(mode)
|
||||
} else {
|
||||
let output_refresh = match output.current_mode() {
|
||||
Some(mode) => mode.refresh,
|
||||
None => {
|
||||
return;
|
||||
}
|
||||
};
|
||||
Duration::from_millis((1_000_000f32 / output_refresh as f32) as u64)
|
||||
};
|
||||
|
||||
// If reschedule is true we either hit a temporary failure or more likely rendering
|
||||
// did not cause any damage on the output. In this case we just re-schedule a repaint
|
||||
// after approx. one frame to re-test for damage.
|
||||
tracing::trace!(
|
||||
"reschedule repaint timer with delay {:?} on {:?}",
|
||||
refresh_time,
|
||||
crtc,
|
||||
);
|
||||
let timer = Timer::from_duration(refresh_time);
|
||||
let output = output.clone();
|
||||
self.loop_handle
|
||||
.insert_source(timer, move |_, _, data| {
|
||||
data.state.render_surface(&output);
|
||||
TimeoutAction::Drop
|
||||
})
|
||||
.expect("failed to schedule frame timer");
|
||||
match result {
|
||||
Ok(true) => surface.render_state = RenderState::WaitingForVblank { dirty: false },
|
||||
Ok(false) | Err(_) => surface.render_state = RenderState::Idle,
|
||||
}
|
||||
|
||||
// let reschedule = match &result {
|
||||
// Ok(has_rendered) => !has_rendered,
|
||||
// Err(err) => {
|
||||
// tracing::warn!("Error during rendering: {:?}", err);
|
||||
// match err {
|
||||
// SwapBuffersError::AlreadySwapped => false,
|
||||
// SwapBuffersError::TemporaryFailure(err) => !matches!(
|
||||
// err.downcast_ref::<DrmError>(),
|
||||
// Some(&DrmError::DeviceInactive)
|
||||
// | Some(&DrmError::Access {
|
||||
// source: drm::SystemError::PermissionDenied,
|
||||
// ..
|
||||
// })
|
||||
// ),
|
||||
// SwapBuffersError::ContextLost(err) => panic!("Rendering loop lost: {}", err),
|
||||
// }
|
||||
// }
|
||||
// };
|
||||
//
|
||||
// if reschedule {
|
||||
// tracing::debug!("rescheduling due to no dmg or error");
|
||||
// let Some(data) = output.user_data().get::<UdevOutputData>() else {
|
||||
// unreachable!()
|
||||
// };
|
||||
//
|
||||
// // Literally no idea if this refresh time calculation is doing anything, but we're
|
||||
// // gonna keep it here because I already added the stuff for it
|
||||
// let refresh_time = if let Some(mode) = data.mode {
|
||||
// self::utils::refresh_time(mode)
|
||||
// } else {
|
||||
// let output_refresh = match output.current_mode() {
|
||||
// Some(mode) => mode.refresh,
|
||||
// None => {
|
||||
// return;
|
||||
// }
|
||||
// };
|
||||
// Duration::from_millis((1_000_000f32 / output_refresh as f32) as u64)
|
||||
// };
|
||||
//
|
||||
// // If reschedule is true we either hit a temporary failure or more likely rendering
|
||||
// // did not cause any damage on the output. In this case we just re-schedule a repaint
|
||||
// // after approx. one frame to re-test for damage.
|
||||
// tracing::trace!(
|
||||
// "reschedule repaint timer with delay {:?} on {}",
|
||||
// refresh_time,
|
||||
// output.name(),
|
||||
// );
|
||||
// let timer = Timer::from_duration(refresh_time);
|
||||
// let output = output.clone();
|
||||
// self.loop_handle
|
||||
// .insert_source(timer, move |_, _, data| {
|
||||
// data.state.render_surface(&output);
|
||||
// TimeoutAction::Drop
|
||||
// })
|
||||
// .expect("failed to schedule frame timer");
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
/// Do an initial render that renders nothing to the screen.
|
||||
///
|
||||
/// If that render failed, schedule another one.
|
||||
fn schedule_initial_render(
|
||||
&mut self,
|
||||
node: DrmNode,
|
||||
crtc: crtc::Handle,
|
||||
evt_handle: LoopHandle<'static, CalloopData>,
|
||||
) {
|
||||
let udev = self.backend.udev_mut();
|
||||
fn render_surface_for_output<'a>(
|
||||
output: &Output,
|
||||
backends: &'a mut HashMap<DrmNode, UdevBackendData>,
|
||||
) -> Option<&'a mut RenderSurface> {
|
||||
let UdevOutputData {
|
||||
device_id,
|
||||
crtc,
|
||||
mode: _,
|
||||
} = output.user_data().get()?;
|
||||
|
||||
let Some(surface) = udev
|
||||
.backends
|
||||
.get_mut(&node)
|
||||
.and_then(|device| device.surfaces.get_mut(&crtc))
|
||||
else {
|
||||
return;
|
||||
};
|
||||
|
||||
let node = surface.render_node;
|
||||
let result = {
|
||||
let mut renderer = udev
|
||||
.gpu_manager
|
||||
.single_renderer(&node)
|
||||
.expect("failed to create MultiRenderer");
|
||||
initial_render(surface, &mut renderer)
|
||||
};
|
||||
|
||||
if let Err(err) = result {
|
||||
match err {
|
||||
SwapBuffersError::AlreadySwapped => {}
|
||||
SwapBuffersError::TemporaryFailure(err) => {
|
||||
// TODO dont reschedule after 3(?) retries
|
||||
tracing::warn!("Failed to submit page_flip: {}", err);
|
||||
let handle = evt_handle.clone();
|
||||
evt_handle.insert_idle(move |data| {
|
||||
data.state.schedule_initial_render(node, crtc, handle)
|
||||
});
|
||||
}
|
||||
SwapBuffersError::ContextLost(err) => panic!("Rendering loop lost: {}", err),
|
||||
}
|
||||
}
|
||||
}
|
||||
backends
|
||||
.get_mut(device_id)
|
||||
.and_then(|device| device.surfaces.get_mut(crtc))
|
||||
}
|
||||
|
||||
/// Render windows, layers, and everything else needed to the given [`RenderSurface`].
|
||||
/// Also queues the frame for scanout.
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn render_surface<'a>(
|
||||
surface: &'a mut RenderSurface,
|
||||
renderer: &mut UdevRenderer<'a, '_>,
|
||||
fn render_surface(
|
||||
surface: &mut RenderSurface,
|
||||
renderer: &mut UdevRenderer<'_, '_>,
|
||||
output: &Output,
|
||||
|
||||
space: &Space<WindowElement>,
|
||||
|
@ -1354,6 +1365,8 @@ fn render_surface<'a>(
|
|||
|
||||
clock: &Clock<Monotonic>,
|
||||
) -> Result<bool, SwapBuffersError> {
|
||||
use smithay::reexports::wayland_protocols::xdg::shell::server::xdg_toplevel;
|
||||
|
||||
let pending_wins = windows
|
||||
.iter()
|
||||
.filter(|win| win.alive())
|
||||
|
@ -1367,6 +1380,16 @@ fn render_surface<'a>(
|
|||
};
|
||||
pending_size || win.with_state(|state| !state.loc_request_state.is_idle())
|
||||
})
|
||||
.filter(|win| {
|
||||
if let WindowElement::Wayland(win) = win {
|
||||
!win.toplevel()
|
||||
.current_state()
|
||||
.states
|
||||
.contains(xdg_toplevel::State::Resizing)
|
||||
} else {
|
||||
true
|
||||
}
|
||||
})
|
||||
.map(|win| {
|
||||
(
|
||||
win.class().unwrap_or("None".to_string()),
|
||||
|
@ -1389,7 +1412,10 @@ fn render_surface<'a>(
|
|||
.queue_frame(None)
|
||||
.map_err(Into::<SwapBuffersError>::into)?;
|
||||
|
||||
tracing::debug!("queued no frame");
|
||||
|
||||
// TODO: still draw the cursor here
|
||||
surface.render_state = RenderState::WaitingForVblank { dirty: false };
|
||||
|
||||
return Ok(true);
|
||||
}
|
||||
|
@ -1417,7 +1443,6 @@ fn render_surface<'a>(
|
|||
|
||||
let time = clock.now();
|
||||
|
||||
// Send frames to the cursor surface to get it to update correctly
|
||||
if let CursorImageStatus::Surface(surf) = cursor_status {
|
||||
send_frames_surface_tree(surf, output, time, Some(Duration::ZERO), |_, _| None);
|
||||
}
|
||||
|
@ -1446,20 +1471,3 @@ fn render_surface<'a>(
|
|||
|
||||
Ok(res.rendered)
|
||||
}
|
||||
|
||||
/// Renders nothing to the given [`RenderSurface`].
|
||||
fn initial_render(
|
||||
surface: &mut RenderSurface,
|
||||
renderer: &mut UdevRenderer<'_, '_>,
|
||||
) -> Result<(), SwapBuffersError> {
|
||||
render_frame::<_, CustomRenderElements<_, WaylandSurfaceRenderElement<_>>, GlesTexture>(
|
||||
&mut surface.compositor,
|
||||
renderer,
|
||||
&[],
|
||||
[0.6, 0.6, 0.6, 1.0],
|
||||
)?;
|
||||
surface.compositor.queue_frame(None)?;
|
||||
surface.compositor.reset_buffers();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
@ -1,31 +0,0 @@
|
|||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
use std::time::Duration;
|
||||
|
||||
use smithay::reexports::drm::control::{Mode, ModeFlags};
|
||||
|
||||
// From niri:
|
||||
// https://github.com/YaLTeR/niri/blob/ba0a6d6b8868cc6348ad1b20f683a95d5909df6b/src/backend/tty.rs#L900-L922
|
||||
pub fn refresh_time(mode: 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 = (numerator + denominator / 2) / denominator;
|
||||
Duration::from_nanos(refresh_interval)
|
||||
}
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
use std::{ffi::OsString, time::Duration};
|
||||
|
||||
use calloop::LoopHandle;
|
||||
use smithay::{
|
||||
backend::{
|
||||
egl::EGLDevice,
|
||||
|
@ -43,6 +44,7 @@ pub struct Winit {
|
|||
pub damage_tracker: OutputDamageTracker,
|
||||
pub dmabuf_state: (DmabufState, DmabufGlobal, Option<DmabufFeedback>),
|
||||
pub full_redraw: u8,
|
||||
render_state: RenderState,
|
||||
}
|
||||
|
||||
impl BackendData for Winit {
|
||||
|
@ -57,6 +59,18 @@ impl BackendData for Winit {
|
|||
fn early_import(&mut self, _surface: &WlSurface) {}
|
||||
}
|
||||
|
||||
impl Backend {
|
||||
fn winit(&self) -> &Winit {
|
||||
let Backend::Winit(winit) = self else { unreachable!() };
|
||||
winit
|
||||
}
|
||||
|
||||
fn winit_mut(&mut self) -> &mut Winit {
|
||||
let Backend::Winit(winit) = self else { unreachable!() };
|
||||
winit
|
||||
}
|
||||
}
|
||||
|
||||
/// Start Pinnacle as a window in a graphical environment.
|
||||
pub fn run_winit() -> anyhow::Result<()> {
|
||||
let mut event_loop: EventLoop<CalloopData> = EventLoop::try_new()?;
|
||||
|
@ -155,6 +169,7 @@ pub fn run_winit() -> anyhow::Result<()> {
|
|||
damage_tracker: OutputDamageTracker::from_output(&output),
|
||||
dmabuf_state,
|
||||
full_redraw: 0,
|
||||
render_state: RenderState::Idle,
|
||||
}),
|
||||
display,
|
||||
event_loop.get_signal(),
|
||||
|
@ -163,13 +178,13 @@ pub fn run_winit() -> anyhow::Result<()> {
|
|||
|
||||
state.focus_state.focused_output = Some(output.clone());
|
||||
|
||||
let Backend::Winit(backend) = &mut state.backend else {
|
||||
unreachable!()
|
||||
};
|
||||
let winit = state.backend.winit_mut();
|
||||
|
||||
winit.backend.window().set_title("Pinnacle");
|
||||
|
||||
state
|
||||
.shm_state
|
||||
.update_formats(backend.backend.renderer().shm_formats());
|
||||
.update_formats(winit.backend.renderer().shm_formats());
|
||||
|
||||
state.space.map_output(&output, (0, 0));
|
||||
|
||||
|
@ -183,13 +198,10 @@ pub fn run_winit() -> anyhow::Result<()> {
|
|||
tracing::error!("Failed to start XWayland: {err}");
|
||||
}
|
||||
|
||||
let mut pointer_element = PointerElement::<GlesTexture>::new();
|
||||
|
||||
let insert_ret =
|
||||
state
|
||||
.loop_handle
|
||||
.insert_source(Timer::immediate(), move |_instant, _metadata, data| {
|
||||
let display_handle = &mut data.display_handle;
|
||||
let state = &mut data.state;
|
||||
|
||||
let result = winit_evt_loop.dispatch_new_events(|event| match event {
|
||||
|
@ -214,7 +226,9 @@ pub fn run_winit() -> anyhow::Result<()> {
|
|||
WinitEvent::Input(input_evt) => {
|
||||
state.process_input_event(input_evt);
|
||||
}
|
||||
WinitEvent::Refresh => {}
|
||||
WinitEvent::Refresh => {
|
||||
state.schedule_render(&output);
|
||||
}
|
||||
});
|
||||
|
||||
match result {
|
||||
|
@ -224,186 +238,216 @@ pub fn run_winit() -> anyhow::Result<()> {
|
|||
}
|
||||
};
|
||||
|
||||
if let CursorImageStatus::Surface(surface) = &state.cursor_status {
|
||||
if !surface.alive() {
|
||||
state.cursor_status = CursorImageStatus::default_named();
|
||||
}
|
||||
}
|
||||
|
||||
let cursor_visible = !matches!(state.cursor_status, CursorImageStatus::Surface(_));
|
||||
|
||||
pointer_element.set_status(state.cursor_status.clone());
|
||||
|
||||
let pending_wins = state
|
||||
.windows
|
||||
.iter()
|
||||
.filter(|win| win.alive())
|
||||
.filter(|win| {
|
||||
let pending_size = if let WindowElement::Wayland(win) = win {
|
||||
let current_state = win.toplevel().current_state();
|
||||
win.toplevel()
|
||||
.with_pending_state(|state| state.size != current_state.size)
|
||||
} else {
|
||||
false
|
||||
};
|
||||
pending_size || win.with_state(|state| !state.loc_request_state.is_idle())
|
||||
})
|
||||
.map(|win| {
|
||||
(
|
||||
win.class().unwrap_or("None".to_string()),
|
||||
win.title().unwrap_or("None".to_string()),
|
||||
win.with_state(|state| state.loc_request_state.clone()),
|
||||
)
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
if !pending_wins.is_empty() {
|
||||
// tracing::debug!("Skipping frame, waiting on {pending_wins:?}");
|
||||
let op_clone = output.clone();
|
||||
state.loop_handle.insert_idle(move |dt| {
|
||||
for win in dt.state.windows.iter() {
|
||||
win.send_frame(
|
||||
&op_clone,
|
||||
dt.state.clock.now(),
|
||||
Some(Duration::ZERO),
|
||||
surface_primary_scanout_output,
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
state.space.refresh();
|
||||
state.popup_manager.cleanup();
|
||||
display_handle
|
||||
.flush_clients()
|
||||
.expect("failed to flush client buffers");
|
||||
|
||||
// TODO: still draw the cursor here
|
||||
|
||||
return TimeoutAction::ToDuration(Duration::from_millis(1));
|
||||
}
|
||||
|
||||
let Backend::Winit(backend) = &mut state.backend else {
|
||||
unreachable!()
|
||||
};
|
||||
let full_redraw = &mut backend.full_redraw;
|
||||
*full_redraw = full_redraw.saturating_sub(1);
|
||||
|
||||
state.focus_state.fix_up_focus(&mut state.space);
|
||||
|
||||
let output_render_elements = crate::render::generate_render_elements(
|
||||
&output,
|
||||
backend.backend.renderer(),
|
||||
&state.space,
|
||||
&state.focus_state.focus_stack,
|
||||
&state.override_redirect_windows,
|
||||
state.pointer_location,
|
||||
&mut state.cursor_status,
|
||||
state.dnd_icon.as_ref(),
|
||||
// state.seat.input_method(),
|
||||
&mut pointer_element,
|
||||
None,
|
||||
);
|
||||
|
||||
let render_res = backend.backend.bind().and_then(|_| {
|
||||
let age = if *full_redraw > 0 {
|
||||
0
|
||||
} else {
|
||||
backend.backend.buffer_age().unwrap_or(0)
|
||||
};
|
||||
|
||||
let renderer = backend.backend.renderer();
|
||||
|
||||
backend
|
||||
.damage_tracker
|
||||
.render_output(renderer, age, &output_render_elements, [0.6, 0.6, 0.6, 1.0])
|
||||
.map_err(|err| match err {
|
||||
damage::Error::Rendering(err) => err.into(),
|
||||
damage::Error::OutputNoMode(_) => todo!(),
|
||||
})
|
||||
});
|
||||
|
||||
match render_res {
|
||||
Ok(render_output_result) => {
|
||||
let has_rendered = render_output_result.damage.is_some();
|
||||
if let Some(damage) = render_output_result.damage {
|
||||
// tracing::debug!("damage rects are {damage:?}");
|
||||
if let Err(err) = backend.backend.submit(Some(&damage)) {
|
||||
tracing::warn!("{}", err);
|
||||
}
|
||||
}
|
||||
|
||||
backend.backend.window().set_cursor_visible(cursor_visible);
|
||||
|
||||
let time = state.clock.now();
|
||||
|
||||
// Send frames to the cursor surface so it updates correctly
|
||||
if let CursorImageStatus::Surface(surf) = &state.cursor_status {
|
||||
if let Some(op) = state.focus_state.focused_output.as_ref() {
|
||||
send_frames_surface_tree(
|
||||
surf,
|
||||
op,
|
||||
time,
|
||||
Some(Duration::ZERO),
|
||||
|_, _| None,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
super::post_repaint(
|
||||
&output,
|
||||
&render_output_result.states,
|
||||
&state.space,
|
||||
None,
|
||||
time.into(),
|
||||
);
|
||||
|
||||
if has_rendered {
|
||||
let mut output_presentation_feedback = take_presentation_feedback(
|
||||
&output,
|
||||
&state.space,
|
||||
&render_output_result.states,
|
||||
);
|
||||
output_presentation_feedback.presented(
|
||||
time,
|
||||
output
|
||||
.current_mode()
|
||||
.map(|mode| {
|
||||
Duration::from_secs_f64(1000f64 / mode.refresh as f64)
|
||||
})
|
||||
.unwrap_or_default(),
|
||||
0,
|
||||
wp_presentation_feedback::Kind::Vsync,
|
||||
);
|
||||
}
|
||||
}
|
||||
Err(err) => {
|
||||
tracing::warn!("{}", err);
|
||||
}
|
||||
}
|
||||
|
||||
state.space.refresh();
|
||||
state.popup_manager.cleanup();
|
||||
display_handle
|
||||
.flush_clients()
|
||||
.expect("failed to flush client buffers");
|
||||
|
||||
TimeoutAction::ToDuration(Duration::from_micros(((1.0 / 144.0) * 1000000.0) as u64))
|
||||
});
|
||||
|
||||
if let Err(err) = insert_ret {
|
||||
anyhow::bail!("Failed to insert winit events into event loop: {err}");
|
||||
}
|
||||
|
||||
let frame_time = Duration::from_micros(((1.0 / 144.0) * 1000000.0) as u64);
|
||||
let refresh_timer = Timer::from_duration(frame_time);
|
||||
state
|
||||
.loop_handle
|
||||
.insert_source(refresh_timer, move |instant, _, data| {
|
||||
let winit = data.state.backend.winit();
|
||||
|
||||
winit.backend.window().request_redraw();
|
||||
|
||||
let frame_time = winit
|
||||
.backend
|
||||
.window()
|
||||
.current_monitor()
|
||||
.and_then(|monitor| monitor.refresh_rate_millihertz())
|
||||
.map(|rate| Duration::from_secs_f64(1000.0 / rate as f64))
|
||||
.unwrap_or(frame_time);
|
||||
|
||||
TimeoutAction::ToInstant(instant + frame_time)
|
||||
})
|
||||
.expect("failed to insert render timer into event loop");
|
||||
|
||||
event_loop.run(
|
||||
Some(Duration::from_micros(((1.0 / 144.0) * 1000000.0) as u64)),
|
||||
&mut CalloopData {
|
||||
display_handle,
|
||||
state,
|
||||
},
|
||||
|_data| {
|
||||
// println!("{}", _data.state.space.elements().count());
|
||||
|data| {
|
||||
data.state.space.refresh();
|
||||
data.state.popup_manager.cleanup();
|
||||
data.display_handle
|
||||
.flush_clients()
|
||||
.expect("failed to flush client buffers");
|
||||
},
|
||||
)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
enum RenderState {
|
||||
Idle,
|
||||
Scheduled,
|
||||
}
|
||||
|
||||
impl Winit {
|
||||
pub fn schedule_render(&mut self, loop_handle: &LoopHandle<CalloopData>, output: &Output) {
|
||||
match &self.render_state {
|
||||
RenderState::Idle => {
|
||||
let output = output.clone();
|
||||
loop_handle.insert_idle(move |data| data.state.render_window(&output));
|
||||
|
||||
self.render_state = RenderState::Scheduled;
|
||||
}
|
||||
RenderState::Scheduled => (),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl State {
|
||||
fn render_window(&mut self, output: &Output) {
|
||||
let winit = self.backend.winit_mut();
|
||||
|
||||
assert!(matches!(winit.render_state, RenderState::Scheduled));
|
||||
|
||||
let pending_wins = self
|
||||
.windows
|
||||
.iter()
|
||||
.filter(|win| win.alive())
|
||||
.filter(|win| {
|
||||
let pending_size = if let WindowElement::Wayland(win) = win {
|
||||
let current_state = win.toplevel().current_state();
|
||||
win.toplevel()
|
||||
.with_pending_state(|state| state.size != current_state.size)
|
||||
} else {
|
||||
false
|
||||
};
|
||||
pending_size || win.with_state(|state| !state.loc_request_state.is_idle())
|
||||
})
|
||||
.map(|win| {
|
||||
(
|
||||
win.class().unwrap_or("None".to_string()),
|
||||
win.title().unwrap_or("None".to_string()),
|
||||
win.with_state(|state| state.loc_request_state.clone()),
|
||||
)
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
if !pending_wins.is_empty() {
|
||||
// tracing::debug!("Skipping frame, waiting on {pending_wins:?}");
|
||||
let op_clone = output.clone();
|
||||
self.loop_handle.insert_idle(move |dt| {
|
||||
for win in dt.state.windows.iter() {
|
||||
win.send_frame(
|
||||
&op_clone,
|
||||
dt.state.clock.now(),
|
||||
Some(Duration::ZERO),
|
||||
surface_primary_scanout_output,
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
// TODO: still draw the cursor here
|
||||
|
||||
winit.render_state = RenderState::Idle;
|
||||
return;
|
||||
}
|
||||
let full_redraw = &mut winit.full_redraw;
|
||||
*full_redraw = full_redraw.saturating_sub(1);
|
||||
|
||||
self.focus_state.fix_up_focus(&mut self.space);
|
||||
|
||||
if let CursorImageStatus::Surface(surface) = &self.cursor_status {
|
||||
if !surface.alive() {
|
||||
self.cursor_status = CursorImageStatus::default_named();
|
||||
}
|
||||
}
|
||||
|
||||
let cursor_visible = !matches!(self.cursor_status, CursorImageStatus::Surface(_));
|
||||
|
||||
let mut pointer_element = PointerElement::<GlesTexture>::new();
|
||||
pointer_element.set_status(self.cursor_status.clone());
|
||||
|
||||
let output_render_elements = crate::render::generate_render_elements(
|
||||
output,
|
||||
winit.backend.renderer(),
|
||||
&self.space,
|
||||
&self.focus_state.focus_stack,
|
||||
&self.override_redirect_windows,
|
||||
self.pointer_location,
|
||||
&mut self.cursor_status,
|
||||
self.dnd_icon.as_ref(),
|
||||
// self.seat.input_method(),
|
||||
&mut pointer_element,
|
||||
None,
|
||||
);
|
||||
|
||||
let render_res = winit.backend.bind().and_then(|_| {
|
||||
let age = if *full_redraw > 0 {
|
||||
0
|
||||
} else {
|
||||
winit.backend.buffer_age().unwrap_or(0)
|
||||
};
|
||||
|
||||
let renderer = winit.backend.renderer();
|
||||
|
||||
winit
|
||||
.damage_tracker
|
||||
.render_output(renderer, age, &output_render_elements, [0.6, 0.6, 0.6, 1.0])
|
||||
.map_err(|err| match err {
|
||||
damage::Error::Rendering(err) => err.into(),
|
||||
damage::Error::OutputNoMode(_) => todo!(),
|
||||
})
|
||||
});
|
||||
|
||||
match render_res {
|
||||
Ok(render_output_result) => {
|
||||
let has_rendered = render_output_result.damage.is_some();
|
||||
if let Some(damage) = render_output_result.damage {
|
||||
// tracing::debug!("damage rects are {damage:?}");
|
||||
if let Err(err) = winit.backend.submit(Some(&damage)) {
|
||||
tracing::warn!("{}", err);
|
||||
}
|
||||
}
|
||||
|
||||
winit.backend.window().set_cursor_visible(cursor_visible);
|
||||
|
||||
let time = self.clock.now();
|
||||
|
||||
// Send frames to the cursor surface so it updates correctly
|
||||
if let CursorImageStatus::Surface(surf) = &self.cursor_status {
|
||||
if let Some(op) = self.focus_state.focused_output.as_ref() {
|
||||
send_frames_surface_tree(surf, op, time, Some(Duration::ZERO), |_, _| None);
|
||||
}
|
||||
}
|
||||
|
||||
super::post_repaint(
|
||||
output,
|
||||
&render_output_result.states,
|
||||
&self.space,
|
||||
None,
|
||||
time.into(),
|
||||
);
|
||||
|
||||
if has_rendered {
|
||||
let mut output_presentation_feedback = take_presentation_feedback(
|
||||
output,
|
||||
&self.space,
|
||||
&render_output_result.states,
|
||||
);
|
||||
output_presentation_feedback.presented(
|
||||
time,
|
||||
output
|
||||
.current_mode()
|
||||
.map(|mode| Duration::from_secs_f64(1000f64 / mode.refresh as f64))
|
||||
.unwrap_or_default(),
|
||||
0,
|
||||
wp_presentation_feedback::Kind::Vsync,
|
||||
);
|
||||
}
|
||||
}
|
||||
Err(err) => {
|
||||
tracing::warn!("{}", err);
|
||||
}
|
||||
}
|
||||
winit.render_state = RenderState::Idle;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,7 +10,7 @@ use smithay::{
|
|||
delegate_compositor, delegate_data_device, delegate_fractional_scale, delegate_layer_shell,
|
||||
delegate_output, delegate_presentation, delegate_primary_selection, delegate_relative_pointer,
|
||||
delegate_seat, delegate_shm, delegate_viewporter,
|
||||
desktop::{self, layer_map_for_output, PopupKind, WindowSurfaceType},
|
||||
desktop::{self, find_popup_root_surface, layer_map_for_output, PopupKind, WindowSurfaceType},
|
||||
input::{pointer::CursorImageStatus, Seat, SeatHandler, SeatState},
|
||||
output::Output,
|
||||
reexports::{
|
||||
|
@ -105,11 +105,12 @@ impl CompositorHandler for State {
|
|||
utils::on_commit_buffer_handler::<Self>(surface);
|
||||
self.backend.early_import(surface);
|
||||
|
||||
let mut root = surface.clone();
|
||||
while let Some(parent) = compositor::get_parent(&root) {
|
||||
root = parent;
|
||||
}
|
||||
|
||||
if !compositor::is_sync_subsurface(surface) {
|
||||
let mut root = surface.clone();
|
||||
while let Some(parent) = compositor::get_parent(&root) {
|
||||
root = parent;
|
||||
}
|
||||
if let Some(win @ WindowElement::Wayland(window)) = &self.window_for_surface(&root) {
|
||||
// tracing::debug!("window commit thing {:?}", win.class());
|
||||
window.on_commit();
|
||||
|
@ -129,9 +130,41 @@ impl CompositorHandler for State {
|
|||
|
||||
crate::grab::resize_grab::handle_commit(self, surface);
|
||||
|
||||
// if let Some(window) = self.window_for_surface(surface) {
|
||||
// tracing::debug!("commit on window {:?}", window.class());
|
||||
// }
|
||||
// `surface` is a root window
|
||||
let Some(output) = self
|
||||
.window_for_surface(surface)
|
||||
.and_then(|win| win.output(self))
|
||||
.or_else(|| {
|
||||
// `surface` is a descendant of a root window
|
||||
self.window_for_surface(&root)
|
||||
.and_then(|win| win.output(self))
|
||||
})
|
||||
.or_else(|| {
|
||||
// `surface` is a popup
|
||||
self.popup_manager
|
||||
.find_popup(surface)
|
||||
.and_then(|popup| find_popup_root_surface(&popup).ok())
|
||||
.and_then(|surf| self.window_for_surface(&surf))
|
||||
.and_then(|win| win.output(self))
|
||||
})
|
||||
.or_else(|| {
|
||||
// `surface` is a layer surface
|
||||
self.space
|
||||
.outputs()
|
||||
.find(|op| {
|
||||
let layer_map = layer_map_for_output(op);
|
||||
layer_map
|
||||
.layer_for_surface(surface, WindowSurfaceType::ALL)
|
||||
.is_some()
|
||||
})
|
||||
.cloned()
|
||||
})
|
||||
// TODO: cursor surface and dnd icon
|
||||
else {
|
||||
return;
|
||||
};
|
||||
|
||||
self.schedule_render(&output);
|
||||
}
|
||||
|
||||
fn client_compositor_state<'a>(&self, client: &'a Client) -> &'a CompositorClientState {
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
use std::time::Duration;
|
||||
|
||||
use smithay::{
|
||||
delegate_xdg_shell,
|
||||
desktop::{
|
||||
|
@ -150,6 +148,8 @@ impl XdgShellHandler for State {
|
|||
.get_keyboard()
|
||||
.expect("Seat had no keyboard")
|
||||
.set_focus(self, focus, SERIAL_COUNTER.next_serial());
|
||||
|
||||
self.schedule_render(&output);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -749,14 +749,6 @@ impl XdgShellHandler for State {
|
|||
state.loc_request_state =
|
||||
LocationRequestState::Acknowledged(new_loc);
|
||||
});
|
||||
if let Some(op) = window.output(self) {
|
||||
window.send_frame(
|
||||
&op,
|
||||
self.clock.now(),
|
||||
Some(Duration::ZERO),
|
||||
|_, _| Some(op.clone()),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
Configure::Popup(_) => todo!(),
|
||||
|
|
|
@ -218,6 +218,8 @@ impl XwmHandler for CalloopData {
|
|||
.get_keyboard()
|
||||
.expect("Seat had no keyboard")
|
||||
.set_focus(&mut self.state, focus, SERIAL_COUNTER.next_serial());
|
||||
|
||||
self.state.schedule_render(&output);
|
||||
}
|
||||
}
|
||||
if !window.is_override_redirect() {
|
||||
|
@ -267,6 +269,8 @@ impl XwmHandler for CalloopData {
|
|||
.get_keyboard()
|
||||
.expect("Seat had no keyboard")
|
||||
.set_focus(&mut self.state, focus, SERIAL_COUNTER.next_serial());
|
||||
|
||||
self.state.schedule_render(&output);
|
||||
}
|
||||
}
|
||||
tracing::debug!("destroyed x11 window");
|
||||
|
|
16
src/input.rs
16
src/input.rs
|
@ -657,8 +657,8 @@ impl State {
|
|||
let surface_under = self.surface_under(self.pointer_location);
|
||||
|
||||
// tracing::info!("{:?}", self.pointer_location);
|
||||
if let Some(ptr) = self.seat.get_pointer() {
|
||||
ptr.motion(
|
||||
if let Some(pointer) = self.seat.get_pointer() {
|
||||
pointer.motion(
|
||||
self,
|
||||
surface_under.clone(),
|
||||
&MotionEvent {
|
||||
|
@ -668,7 +668,7 @@ impl State {
|
|||
},
|
||||
);
|
||||
|
||||
ptr.relative_motion(
|
||||
pointer.relative_motion(
|
||||
self,
|
||||
surface_under,
|
||||
&RelativeMotionEvent {
|
||||
|
@ -678,7 +678,15 @@ impl State {
|
|||
},
|
||||
);
|
||||
|
||||
ptr.frame(self);
|
||||
pointer.frame(self);
|
||||
|
||||
self.schedule_render(
|
||||
&self
|
||||
.focus_state
|
||||
.focused_output
|
||||
.clone()
|
||||
.expect("no focused output"),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -31,7 +31,11 @@ use smithay::{
|
|||
xwayland::X11Surface,
|
||||
};
|
||||
|
||||
use crate::{state::WithState, window::WindowElement};
|
||||
use crate::{
|
||||
backend::Backend,
|
||||
state::{State, WithState},
|
||||
window::WindowElement,
|
||||
};
|
||||
|
||||
use self::pointer::{PointerElement, PointerRenderElement};
|
||||
|
||||
|
@ -400,3 +404,12 @@ pub fn take_presentation_feedback(
|
|||
|
||||
output_presentation_feedback
|
||||
}
|
||||
|
||||
impl State {
|
||||
pub fn schedule_render(&mut self, output: &Output) {
|
||||
match &mut self.backend {
|
||||
Backend::Winit(winit) => winit.schedule_render(&self.loop_handle, output),
|
||||
Backend::Udev(udev) => udev.schedule_render(&self.loop_handle, output),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,7 +10,7 @@ use smithay::{
|
|||
},
|
||||
ImportAll, Renderer, Texture,
|
||||
},
|
||||
input::pointer::{CursorIcon, CursorImageStatus},
|
||||
input::pointer::CursorImageStatus,
|
||||
render_elements,
|
||||
utils::{Physical, Point, Scale},
|
||||
};
|
||||
|
|
|
@ -147,6 +147,12 @@ impl State {
|
|||
|
||||
let Some(output) = window.output(self) else { return };
|
||||
self.update_windows(&output);
|
||||
|
||||
// Sometimes toggling won't change the window size,
|
||||
// causing no commit.
|
||||
//
|
||||
// Schedule a render in case the window moves.
|
||||
self.schedule_render(&output);
|
||||
}
|
||||
Msg::ToggleFullscreen { window_id } => {
|
||||
let Some(window) = window_id.window(self) else { return };
|
||||
|
|
Loading…
Reference in a new issue