Crop windows, also pause rendering on a pending size

This is a series of attempts at preventing flickering. We wrap every window render element in a CropRenderElement so that windows don't render at the incorrect size for a frame. Additionally, we also pause rendering also when the window has a pending size different from the current. Fun fact: Firefox (and by extension Librewolf) renders content to child subsurfaces, so the root surface doesn't get most commits.
This commit is contained in:
Ottatop 2023-09-25 03:15:13 -05:00
parent 9442d721dd
commit 16787092a7
6 changed files with 130 additions and 60 deletions

View file

@ -29,7 +29,10 @@ use smithay::{
libinput::{LibinputInputBackend, LibinputSessionInterface}, libinput::{LibinputInputBackend, LibinputSessionInterface},
renderer::{ renderer::{
damage::{self, OutputDamageTracker}, damage::{self, OutputDamageTracker},
element::{texture::TextureBuffer, RenderElement, RenderElementStates}, element::{
surface::WaylandSurfaceRenderElement, texture::TextureBuffer, RenderElement,
RenderElementStates,
},
gles::{GlesRenderer, GlesTexture}, gles::{GlesRenderer, GlesTexture},
multigpu::{gbm::GbmGlesBackend, GpuManager, MultiRenderer, MultiTexture}, multigpu::{gbm::GbmGlesBackend, GpuManager, MultiRenderer, MultiTexture},
sync::SyncPoint, sync::SyncPoint,
@ -1480,7 +1483,15 @@ fn render_surface<'a>(
let pending_wins = windows let pending_wins = windows
.iter() .iter()
.filter(|win| win.alive()) .filter(|win| win.alive())
.filter(|win| win.with_state(|state| !state.loc_request_state.is_idle())) .filter(|win| {
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
}
})
.map(|win| { .map(|win| {
( (
win.class().unwrap_or("None".to_string()), win.class().unwrap_or("None".to_string()),
@ -1491,7 +1502,7 @@ fn render_surface<'a>(
.collect::<Vec<_>>(); .collect::<Vec<_>>();
if !pending_wins.is_empty() { if !pending_wins.is_empty() {
// tracing::debug!("Skipping frame, waiting on {pending_wins:?}"); tracing::debug!("Skipping frame, waiting on {pending_wins:?}");
for win in windows.iter() { for win in windows.iter() {
win.send_frame(output, clock.now(), Some(Duration::ZERO), |_, _| { win.send_frame(output, clock.now(), Some(Duration::ZERO), |_, _| {
Some(output.clone()) Some(output.clone())
@ -1509,14 +1520,14 @@ fn render_surface<'a>(
} }
let output_render_elements = crate::render::generate_render_elements( let output_render_elements = crate::render::generate_render_elements(
output,
renderer,
space, space,
windows, windows,
override_redirect_windows, override_redirect_windows,
pointer_location, pointer_location,
cursor_status, cursor_status,
dnd_icon, dnd_icon,
renderer,
output,
input_method, input_method,
pointer_element, pointer_element,
Some(pointer_image), Some(pointer_image),
@ -1567,7 +1578,7 @@ fn initial_render(
) -> Result<(), SwapBuffersError> { ) -> Result<(), SwapBuffersError> {
surface surface
.compositor .compositor
.render_frame::<_, CustomRenderElements<_>, GlesTexture>( .render_frame::<_, CustomRenderElements<_, WaylandSurfaceRenderElement<_>>, GlesTexture>(
renderer, renderer,
&[], &[],
[0.6, 0.6, 0.6, 1.0], [0.6, 0.6, 0.6, 1.0],

View file

@ -36,6 +36,7 @@ use smithay::{
use crate::{ use crate::{
render::{pointer::PointerElement, take_presentation_feedback}, render::{pointer::PointerElement, take_presentation_feedback},
state::{Backend, CalloopData, State, WithState}, state::{Backend, CalloopData, State, WithState},
window::WindowElement,
}; };
use super::BackendData; use super::BackendData;
@ -241,7 +242,16 @@ pub fn run_winit() -> anyhow::Result<()> {
.windows .windows
.iter() .iter()
.filter(|win| win.alive()) .filter(|win| win.alive())
.filter(|win| win.with_state(|state| !state.loc_request_state.is_idle())) .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| { .map(|win| {
( (
win.class().unwrap_or("None".to_string()), win.class().unwrap_or("None".to_string()),
@ -253,14 +263,17 @@ pub fn run_winit() -> anyhow::Result<()> {
if !pending_wins.is_empty() { if !pending_wins.is_empty() {
// tracing::debug!("Skipping frame, waiting on {pending_wins:?}"); // tracing::debug!("Skipping frame, waiting on {pending_wins:?}");
for win in state.windows.iter() { let op_clone = output.clone();
state.loop_handle.insert_idle(move |dt| {
for win in dt.state.windows.iter() {
win.send_frame( win.send_frame(
&output, &op_clone,
state.clock.now(), dt.state.clock.now(),
Some(Duration::ZERO), Some(Duration::ZERO),
surface_primary_scanout_output, surface_primary_scanout_output,
); );
} }
});
state.space.refresh(); state.space.refresh();
state.popup_manager.cleanup(); state.popup_manager.cleanup();
@ -282,14 +295,14 @@ pub fn run_winit() -> anyhow::Result<()> {
state.focus_state.fix_up_focus(&mut state.space); state.focus_state.fix_up_focus(&mut state.space);
let output_render_elements = crate::render::generate_render_elements( let output_render_elements = crate::render::generate_render_elements(
&output,
backend.backend.renderer(),
&state.space, &state.space,
&state.focus_state.focus_stack, &state.focus_state.focus_stack,
&state.override_redirect_windows, &state.override_redirect_windows,
state.pointer_location, state.pointer_location,
&mut state.cursor_status, &mut state.cursor_status,
state.dnd_icon.as_ref(), state.dnd_icon.as_ref(),
backend.backend.renderer(),
&output,
state.seat.input_method(), state.seat.input_method(),
&mut pointer_element, &mut pointer_element,
None, None,
@ -306,7 +319,7 @@ pub fn run_winit() -> anyhow::Result<()> {
backend backend
.damage_tracker .damage_tracker
.render_output(renderer, age, &output_render_elements, [0.5, 0.5, 0.5, 1.0]) .render_output(renderer, age, &output_render_elements, [0.6, 0.6, 0.6, 1.0])
.map_err(|err| match err { .map_err(|err| match err {
damage::Error::Rendering(err) => err.into(), damage::Error::Rendering(err) => err.into(),
damage::Error::OutputNoMode(_) => todo!(), damage::Error::OutputNoMode(_) => todo!(),
@ -327,7 +340,7 @@ pub fn run_winit() -> anyhow::Result<()> {
let time = state.clock.now(); let time = state.clock.now();
// Send frames to the cursor surface so it updates in xwayland // Send frames to the cursor surface so it updates correctly
if let CursorImageStatus::Surface(surf) = &state.cursor_status { if let CursorImageStatus::Surface(surf) = &state.cursor_status {
if let Some(op) = state.focus_state.focused_output.as_ref() { if let Some(op) = state.focus_state.focused_output.as_ref() {
send_frames_surface_tree( send_frames_surface_tree(
@ -378,7 +391,7 @@ pub fn run_winit() -> anyhow::Result<()> {
.flush_clients() .flush_clients()
.expect("failed to flush client buffers"); .expect("failed to flush client buffers");
TimeoutAction::ToDuration(Duration::from_millis(1)) TimeoutAction::ToDuration(Duration::from_micros(((1.0 / 144.0) * 1000000.0) as u64))
}); });
if let Err(err) = insert_ret { if let Err(err) = insert_ret {
@ -386,7 +399,7 @@ pub fn run_winit() -> anyhow::Result<()> {
} }
event_loop.run( event_loop.run(
Some(Duration::from_millis(1)), Some(Duration::from_micros(((1.0 / 144.0) * 1000000.0) as u64)),
&mut CalloopData { display, state }, &mut CalloopData { display, state },
|_data| { |_data| {
// println!("{}", _data.state.space.elements().count()); // println!("{}", _data.state.space.elements().count());

View file

@ -86,6 +86,19 @@ impl PointerGrab<State> for MoveSurfaceGrab<State> {
return; return;
} }
if state
.space
.element_geometry(&self.window)
.is_some_and(|geo| {
state
.space
.element_geometry(&window_under)
.is_some_and(|geo2| geo.overlaps(geo2))
})
{
return;
}
let is_floating = let is_floating =
window_under.with_state(|state| state.floating_or_tiled.is_floating()); window_under.with_state(|state| state.floating_or_tiled.is_floating());
@ -101,6 +114,7 @@ impl PointerGrab<State> for MoveSurfaceGrab<State> {
return; return;
} }
tracing::debug!("Swapping window positions");
state.swap_window_positions(&self.window, &window_under); state.swap_window_positions(&self.window, &window_under);
} }
} else { } else {

View file

@ -95,8 +95,7 @@ impl CompositorHandler for State {
} }
fn commit(&mut self, surface: &WlSurface) { fn commit(&mut self, surface: &WlSurface) {
// tracing::debug!("commit on surface {:?}", surface); // tracing::debug!("commit on surface {surface:?}");
X11Wm::commit_hook::<CalloopData>(surface); X11Wm::commit_hook::<CalloopData>(surface);
utils::on_commit_buffer_handler::<Self>(surface); utils::on_commit_buffer_handler::<Self>(surface);
@ -107,8 +106,16 @@ impl CompositorHandler for State {
while let Some(parent) = compositor::get_parent(&root) { while let Some(parent) = compositor::get_parent(&root) {
root = parent; root = parent;
} }
if let Some(WindowElement::Wayland(window)) = self.window_for_surface(surface) { if let Some(win @ WindowElement::Wayland(window)) = &self.window_for_surface(&root) {
// tracing::debug!("window commit thing {:?}", win.class());
window.on_commit(); window.on_commit();
win.with_state(|state| {
if let LocationRequestState::Acknowledged(new_pos) = state.loc_request_state {
tracing::debug!("Mapping Acknowledged window");
state.loc_request_state = LocationRequestState::Idle;
self.space.map_element(win.clone(), new_pos, false);
}
});
} }
}; };
@ -118,15 +125,9 @@ impl CompositorHandler for State {
crate::grab::resize_grab::handle_commit(self, surface); crate::grab::resize_grab::handle_commit(self, surface);
if let Some(window) = self.window_for_surface(surface) { // if let Some(window) = self.window_for_surface(surface) {
window.with_state(|state| { // tracing::debug!("commit on window {:?}", window.class());
if let LocationRequestState::Acknowledged(new_pos) = state.loc_request_state { // }
tracing::debug!("Mapping Acknowledged window");
state.loc_request_state = LocationRequestState::Idle;
self.space.map_element(window.clone(), new_pos, false);
}
});
}
} }
fn client_compositor_state<'a>(&self, client: &'a Client) -> &'a CompositorClientState { fn client_compositor_state<'a>(&self, client: &'a Client) -> &'a CompositorClientState {

View file

@ -1,3 +1,5 @@
use std::time::Duration;
use smithay::{ use smithay::{
delegate_xdg_shell, delegate_xdg_shell,
desktop::{ desktop::{
@ -736,20 +738,30 @@ impl XdgShellHandler for State {
fn ack_configure(&mut self, surface: WlSurface, configure: Configure) { fn ack_configure(&mut self, surface: WlSurface, configure: Configure) {
if let Some(window) = self.window_for_surface(&surface) { if let Some(window) = self.window_for_surface(&surface) {
window.with_state(|state| { if let LocationRequestState::Requested(serial, new_loc) =
if let LocationRequestState::Requested(serial, new_loc) = state.loc_request_state { window.with_state(|state| state.loc_request_state.clone())
{
match &configure { match &configure {
Configure::Toplevel(configure) => { Configure::Toplevel(configure) => {
if configure.serial >= serial { if configure.serial >= serial {
tracing::debug!("acked configure, new loc is {:?}", new_loc); tracing::debug!("acked configure, new loc is {:?}", new_loc);
window.with_state(|state| {
state.loc_request_state = state.loc_request_state =
LocationRequestState::Acknowledged(new_loc); 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!(), Configure::Popup(_) => todo!(),
} }
} }
});
} }
} }

View file

@ -5,8 +5,8 @@ use std::sync::Mutex;
use smithay::{ use smithay::{
backend::renderer::{ backend::renderer::{
element::{ element::{
self, surface::WaylandSurfaceRenderElement, texture::TextureBuffer, AsRenderElements, self, surface::WaylandSurfaceRenderElement, texture::TextureBuffer,
RenderElementStates, Wrap, utils::CropRenderElement, AsRenderElements, RenderElementStates, Wrap,
}, },
ImportAll, ImportMem, Renderer, Texture, ImportAll, ImportMem, Renderer, Texture,
}, },
@ -38,16 +38,17 @@ use self::pointer::{PointerElement, PointerRenderElement};
pub mod pointer; pub mod pointer;
render_elements! { render_elements! {
pub CustomRenderElements<R> where R: ImportAll + ImportMem; pub CustomRenderElements<R, E> where R: ImportAll + ImportMem;
Pointer=PointerRenderElement<R>, Pointer = PointerRenderElement<R>,
Surface=WaylandSurfaceRenderElement<R>, Surface = WaylandSurfaceRenderElement<R>,
Crop = CropRenderElement<E>,
} }
render_elements! { render_elements! {
pub OutputRenderElements<R, E> where R: ImportAll + ImportMem; pub OutputRenderElements<R, E> where R: ImportAll + ImportMem;
Space=SpaceRenderElements<R, E>, Space=SpaceRenderElements<R, E>,
Window=Wrap<E>, Window=Wrap<E>,
Custom=CustomRenderElements<R>, Custom=CustomRenderElements<R, E>,
} }
impl<R> AsRenderElements<R> for WindowElement impl<R> AsRenderElements<R> for WindowElement
@ -136,27 +137,35 @@ where
} }
/// Get the render_elements for the provided tags. /// Get the render_elements for the provided tags.
fn tag_render_elements<R, C>( fn tag_render_elements<R>(
windows: &[WindowElement], windows: &[WindowElement],
space: &Space<WindowElement>, space: &Space<WindowElement>,
renderer: &mut R, renderer: &mut R,
scale: Scale<f64>, scale: Scale<f64>,
) -> Vec<C> ) -> Vec<CustomRenderElements<R, WaylandSurfaceRenderElement<R>>>
where where
R: Renderer + ImportAll + ImportMem, R: Renderer + ImportAll + ImportMem,
<R as Renderer>::TextureId: 'static, <R as Renderer>::TextureId: 'static,
C: From<WaylandSurfaceRenderElement<R>>,
{ {
let elements = windows let elements = windows
.iter() .iter()
.rev() // rev because I treat the focus stack backwards vs how the renderer orders it .rev() // rev because I treat the focus stack backwards vs how the renderer orders it
.filter(|win| win.is_on_active_tag(space.outputs())) .filter(|win| win.is_on_active_tag(space.outputs()))
.flat_map(|win| { .map(|win| {
// subtract win.geometry().loc to align decorations correctly // subtract win.geometry().loc to align decorations correctly
let loc = (space.element_location(win).unwrap_or((0, 0).into()) let loc = (space.element_location(win).unwrap_or((0, 0).into())
- win.geometry().loc) - win.geometry().loc)
.to_physical(1); .to_physical((scale.x.round() as i32, scale.x.round() as i32));
win.render_elements::<C>(renderer, loc, scale, 1.0) (win.render_elements::<WaylandSurfaceRenderElement<R>>(renderer, loc, scale, 1.0), space.element_geometry(win))
}).flat_map(|(elems, rect)| {
match rect {
Some(rect) => {
elems.into_iter().filter_map(|elem| {
CropRenderElement::from_element(elem, scale, rect.to_physical_precise_down(scale))
}).map(CustomRenderElements::from).collect::<Vec<_>>()
},
None => elems.into_iter().map(CustomRenderElements::from).collect(),
}
}) })
.collect::<Vec<_>>(); .collect::<Vec<_>>();
@ -165,14 +174,14 @@ where
#[allow(clippy::too_many_arguments)] #[allow(clippy::too_many_arguments)]
pub fn generate_render_elements<R, T>( pub fn generate_render_elements<R, T>(
output: &Output,
renderer: &mut R,
space: &Space<WindowElement>, space: &Space<WindowElement>,
windows: &[WindowElement], windows: &[WindowElement],
override_redirect_windows: &[X11Surface], override_redirect_windows: &[X11Surface],
pointer_location: Point<f64, Logical>, pointer_location: Point<f64, Logical>,
cursor_status: &mut CursorImageStatus, cursor_status: &mut CursorImageStatus,
dnd_icon: Option<&WlSurface>, dnd_icon: Option<&WlSurface>,
renderer: &mut R,
output: &Output,
input_method: &InputMethodHandle, input_method: &InputMethodHandle,
pointer_element: &mut PointerElement<T>, pointer_element: &mut PointerElement<T>,
pointer_image: Option<&TextureBuffer<T>>, pointer_image: Option<&TextureBuffer<T>>,
@ -187,7 +196,7 @@ where
.expect("called output_geometry on an unmapped output"); .expect("called output_geometry on an unmapped output");
let scale = Scale::from(output.current_scale().fractional_scale()); let scale = Scale::from(output.current_scale().fractional_scale());
let mut custom_render_elements: Vec<CustomRenderElements<_>> = Vec::new(); let mut custom_render_elements: Vec<CustomRenderElements<_, _>> = Vec::new();
// draw input method surface if any // draw input method surface if any
let rectangle = input_method.coordinates(); let rectangle = input_method.coordinates();
let position = Point::from(( let position = Point::from((
@ -317,8 +326,7 @@ where
overlay, overlay,
} = layer_render_elements(output, renderer, scale); } = layer_render_elements(output, renderer, scale);
let window_render_elements: Vec<WaylandSurfaceRenderElement<R>> = let window_render_elements = tag_render_elements::<R>(windows, space, renderer, scale);
tag_render_elements(windows, space, renderer, scale);
let mut output_render_elements = let mut output_render_elements =
Vec::<OutputRenderElements<R, WaylandSurfaceRenderElement<R>>>::new(); Vec::<OutputRenderElements<R, WaylandSurfaceRenderElement<R>>>::new();
@ -335,8 +343,19 @@ where
overlay overlay
.into_iter() .into_iter()
.chain(top) .chain(top)
.chain(window_render_elements) .map(CustomRenderElements::from)
.chain(bottom) .map(OutputRenderElements::from),
);
output_render_elements.extend(
window_render_elements
.into_iter()
.map(OutputRenderElements::from),
);
output_render_elements.extend(
bottom
.into_iter()
.chain(background) .chain(background)
.map(CustomRenderElements::from) .map(CustomRenderElements::from)
.map(OutputRenderElements::from), .map(OutputRenderElements::from),