Merge pull request #69 from Ottatop/fix_rendering

Fix rendering (hopefully)
This commit is contained in:
Ottatop 2023-09-08 22:54:51 -05:00 committed by GitHub
commit 68c47f15ae
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
14 changed files with 276 additions and 170 deletions

View file

@ -1333,18 +1333,19 @@ impl State {
};
let result = render_surface(
&mut self.cursor_status,
&self.space,
&self.windows,
self.dnd_icon.as_ref(),
&self.focus_state.focus_stack,
surface,
&mut renderer,
&self.space,
&output,
self.seat.input_method(),
self.pointer_location,
&pointer_image,
&mut backend.pointer_element,
&mut self.cursor_status,
self.dnd_icon.as_ref(),
self.pointer_location,
&self.clock,
&self.focus_state.focus_stack,
);
let reschedule = match &result {
Ok(has_rendered) => !has_rendered,
@ -1437,30 +1438,32 @@ impl State {
#[allow(clippy::too_many_arguments)]
fn render_surface<'a>(
cursor_status: &mut CursorImageStatus,
space: &Space<WindowElement>,
windows: &[WindowElement],
dnd_icon: Option<&WlSurface>,
focus_stack: &[WindowElement],
surface: &'a mut SurfaceData,
renderer: &mut UdevRenderer<'a, '_>,
space: &Space<WindowElement>,
output: &Output,
input_method: &InputMethodHandle,
pointer_location: Point<f64, Logical>,
pointer_image: &TextureBuffer<MultiTexture>,
pointer_element: &mut PointerElement<MultiTexture>,
cursor_status: &mut CursorImageStatus,
dnd_icon: Option<&WlSurface>,
pointer_location: Point<f64, Logical>,
clock: &Clock<Monotonic>,
focus_stack: &[WindowElement],
) -> Result<bool, SwapBuffersError> {
let output_render_elements = crate::render::generate_render_elements(
renderer,
space,
output,
input_method,
windows,
pointer_location,
pointer_element,
Some(pointer_image),
cursor_status,
dnd_icon,
focus_stack,
renderer,
output,
input_method,
pointer_element,
Some(pointer_image),
);
let res = surface.compositor.render_frame::<_, _, GlesTexture>(

View file

@ -246,17 +246,20 @@ pub fn run_winit() -> anyhow::Result<()> {
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(
backend.backend.renderer(),
&state.space,
&output,
state.seat.input_method(),
&state.windows,
state.pointer_location,
&mut pointer_element,
None,
&mut state.cursor_status,
state.dnd_icon.as_ref(),
&state.focus_state.focus_stack,
backend.backend.renderer(),
&output,
state.seat.input_method(),
&mut pointer_element,
None,
);
let render_res = backend.backend.bind().and_then(|_| {
@ -281,6 +284,7 @@ pub fn run_winit() -> anyhow::Result<()> {
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);
}

View file

@ -50,9 +50,7 @@ impl FocusState {
/// to back to correct their z locations.
pub fn fix_up_focus(&self, space: &mut Space<WindowElement>) {
for win in self.focus_stack.iter() {
if let Some(loc) = space.element_location(win) {
space.map_element(win.clone(), loc, false);
}
space.raise_element(win, false);
}
}
}

View file

@ -121,6 +121,7 @@ impl CompositorHandler for State {
if let Some(window) = self.window_for_surface(surface) {
window.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(window.clone(), new_pos, false);
}
@ -128,7 +129,8 @@ impl CompositorHandler for State {
}
// correct focus layering
self.focus_state.fix_up_focus(&mut self.space);
// TODO: maybe do this at the end of every event loop cycle instead?
// self.focus_state.fix_up_focus(&mut self.space);
}
fn client_compositor_state<'a>(&self, client: &'a Client) -> &'a CompositorClientState {

View file

@ -72,6 +72,8 @@ impl XdgShellHandler for State {
self.space.map_element(window.clone(), (0, 0), true);
let win_clone = window.clone();
// Let the initial configure happen before updating the windows
self.schedule(
move |_data| {
if let WindowElement::Wayland(window) = &win_clone {
@ -123,10 +125,8 @@ impl XdgShellHandler for State {
self.update_windows(&focused_output);
}
// let mut windows: Vec<Window> = self.space.elements().cloned().collect();
// windows.retain(|window| window.toplevel() != &surface);
// Layouts::master_stack(self, windows, crate::layout::Direction::Left);
let focus = self.focus_state.current_focus().map(FocusTarget::Window);
self.seat
.get_keyboard()
.expect("Seat had no keyboard")
@ -145,7 +145,7 @@ impl XdgShellHandler for State {
crate::grab::move_grab::move_request_client(
self,
surface.wl_surface(),
&Seat::from_resource(&seat).expect("Couldn't get seat from WlSeat"),
&Seat::from_resource(&seat).expect("couldn't get seat from WlSeat"),
serial,
BUTTON_LEFT,
);
@ -162,7 +162,7 @@ impl XdgShellHandler for State {
crate::grab::resize_grab::resize_request_client(
self,
surface.wl_surface(),
&Seat::from_resource(&seat).expect("Couldn't get seat from WlSeat"),
&Seat::from_resource(&seat).expect("couldn't get seat from WlSeat"),
serial,
edges.into(),
BUTTON_LEFT,
@ -183,7 +183,7 @@ impl XdgShellHandler for State {
}
fn grab(&mut self, surface: PopupSurface, seat: WlSeat, serial: Serial) {
let seat: Seat<Self> = Seat::from_resource(&seat).expect("Couldn't get seat from WlSeat");
let seat: Seat<Self> = Seat::from_resource(&seat).expect("couldn't get seat from WlSeat");
let popup_kind = PopupKind::Xdg(surface);
if let Some(root) = find_popup_root_surface(&popup_kind).ok().and_then(|root| {
self.window_for_surface(&root)

View file

@ -73,6 +73,7 @@ impl State {
}
}
/// Get the [`FocusTarget`] under `point`.
pub fn surface_under<P>(&self, point: P) -> Option<(FocusTarget, Point<i32, Logical>)>
where
P: Into<Point<f64, Logical>>,
@ -101,9 +102,12 @@ impl State {
});
// I think I'm going a bit too far with the functional stuff
// The topmost fullscreen window
top_fullscreen_window
.map(|window| (FocusTarget::from(window.clone()), output_geo.loc))
.or_else(|| {
// The topmost layer surface in Overlay or Top
layers
.layer_under(wlr_layer::Layer::Overlay, point)
.or_else(|| layers.layer_under(wlr_layer::Layer::Top, point))
@ -113,11 +117,27 @@ impl State {
})
})
.or_else(|| {
// The topmost window
self.space
.element_under(point)
.map(|(window, loc)| (window.clone().into(), loc))
.elements()
.rev()
.filter(|win| win.is_on_active_tag(self.space.outputs()))
.find_map(|win| {
let loc = self
.space
.element_location(win)
.expect("called elem loc on unmapped win")
- win.geometry().loc;
if win.is_in_input_region(&(point - loc.to_f64())) {
Some((win.clone().into(), loc))
} else {
None
}
})
})
.or_else(|| {
// The topmost layer surface in Bottom or Background
layers
.layer_under(wlr_layer::Layer::Bottom, point)
.or_else(|| layers.layer_under(wlr_layer::Layer::Background, point))

View file

@ -4,21 +4,17 @@ use itertools::{Either, Itertools};
use smithay::{
desktop::layer_map_for_output,
output::Output,
reexports::wayland_server::Resource,
utils::{Logical, Point, Rectangle, Size},
wayland::compositor::{self, CompositorHandler},
utils::{IsAlive, Logical, Point, Rectangle, Size},
};
use crate::{
state::{State, WithState},
window::{
window_state::{FloatingOrTiled, FullscreenOrMaximized, LocationRequestState},
WindowElement, BLOCKER_COUNTER,
WindowElement,
},
};
// -------------------------------------------
impl State {
/// Compute the positions and sizes of tiled windows on
/// `output` according to the provided [`Layout`].
@ -55,9 +51,11 @@ impl State {
}
}
/// Compute tiled window locations and sizes, size maximized and fullscreen windows correctly,
/// and send configures and that cool stuff.
pub fn update_windows(&mut self, output: &Output) {
let Some(layout) = output.with_state(|state| {
state.focused_tags().next().cloned().map(|tag| tag.layout())
state.focused_tags().next().map(|tag| tag.layout())
}) else { return };
let (windows_on_foc_tags, mut windows_not_on_foc_tags): (Vec<_>, _) =
@ -68,6 +66,7 @@ impl State {
})
});
// Don't unmap windows that aren't on `output` (that would clear all other monitors)
windows_not_on_foc_tags.retain(|win| win.output(self) == Some(output.clone()));
let tiled_windows = windows_on_foc_tags
@ -122,12 +121,19 @@ impl State {
WindowElement::Wayland(win) => {
// If the above didn't cause any change to size or other state, simply
// map the window.
if !win.toplevel().has_pending_changes() {
let current_state = win.toplevel().current_state();
let is_pending = win
.toplevel()
.with_pending_state(|state| state.size != current_state.size);
// for whatever reason vscode on wayland likes to have pending state
// (like tiled states) but not commit, so we check for just the size
// here
if !is_pending {
tracing::debug!("No pending changes");
state.loc_request_state = LocationRequestState::Idle;
non_pending_wins.push((loc, window.clone()));
// TODO: wait for windows with pending state to ack and commit
// self.space.map_element(window.clone(), loc, false);
} else {
tracing::debug!("Pending changes");
let serial = win.toplevel().send_configure();
state.loc_request_state =
LocationRequestState::Requested(serial, loc);
@ -149,76 +155,30 @@ impl State {
});
}
BLOCKER_COUNTER.store(1, std::sync::atomic::Ordering::SeqCst);
tracing::debug!(
"blocker {}",
BLOCKER_COUNTER.load(std::sync::atomic::Ordering::SeqCst)
);
let start_time = self.clock.now();
// Pause rendering. Here we'll wait until all windows have ack'ed and committed,
// then resume rendering. This prevents flickering because some windows will commit before
// others.
//
// This *will* cause everything to freeze for a few frames, but it should'nt impact
// This *will* cause everything to freeze for a few frames, but it shouldn't impact
// anything meaningfully.
self.pause_rendering = true;
for (_loc, win) in pending_wins.iter() {
if let Some(surf) = win.wl_surface() {
tracing::debug!("adding blocker");
compositor::add_blocker(&surf, crate::window::WindowBlocker);
}
}
let pending_wins_clone = pending_wins.clone();
// schedule on all idle
self.schedule(
move |_data| {
pending_wins_clone.iter().all(|(_, win)| {
win.with_state(|state| state.loc_request_state.is_acknowledged())
})
},
move |data| {
// remove and trigger blockers
BLOCKER_COUNTER.store(0, std::sync::atomic::Ordering::SeqCst);
tracing::debug!(
"blocker {}",
BLOCKER_COUNTER.load(std::sync::atomic::Ordering::SeqCst)
);
for client in pending_wins
move |_dt| {
// tracing::debug!("Waiting for all to be idle");
let all_idle = pending_wins
.iter()
.filter_map(|(_, win)| win.wl_surface()?.client())
{
data.state
.client_compositor_state(&client)
.blocker_cleared(&mut data.state, &data.display.handle())
}
.filter(|(_, win)| win.alive())
.all(|(_, win)| win.with_state(|state| state.loc_request_state.is_idle()));
// schedule on all idle
data.state.schedule(
move |_dt| {
pending_wins.iter().all(|(_, win)| {
win.with_state(|state| state.loc_request_state.is_idle())
})
},
move |dt| {
for (loc, win) in non_pending_wins {
dt.state.space.map_element(win, loc, false);
}
for win in windows_not_on_foc_tags {
dt.state.space.unmap_elem(&win);
}
dt.state.pause_rendering = false;
let finish_time =
smithay::utils::Time::elapsed(&start_time, dt.state.clock.now());
tracing::debug!(
"spent {} microseconds not rendering",
finish_time.as_micros()
);
},
);
all_idle
},
move |dt| {
for (loc, win) in non_pending_wins {
dt.state.space.map_element(win, loc, false);
}
dt.state.pause_rendering = false;
},
);
}
@ -538,13 +498,36 @@ impl State {
}
}
// TODO: don't use the focused output, use the outputs the two windows are on
let output = self
.focus_state
.focused_output
.clone()
.expect("no focused output");
self.update_windows(&output);
// self.re_layout(&output);
let mut same_suggested_size = false;
if let WindowElement::Wayland(w1) = win1 {
if let WindowElement::Wayland(w2) = win2 {
if let Some(w1_size) = w1.toplevel().current_state().size {
if let Some(w2_size) = w2.toplevel().current_state().size {
same_suggested_size = w1_size == w2_size;
}
}
}
}
if same_suggested_size {
let win1_loc = self.space.element_location(win1);
let win2_loc = self.space.element_location(win2);
if let Some(win1_loc) = win1_loc {
if let Some(win2_loc) = win2_loc {
self.space.map_element(win1.clone(), win2_loc, false);
self.space.map_element(win2.clone(), win1_loc, false);
}
}
} else {
// TODO: don't use the focused output, use the outputs the two windows are on
let output = self
.focus_state
.focused_output
.clone()
.expect("no focused output");
self.update_windows(&output);
}
}
}

View file

@ -8,7 +8,7 @@
//! While Pinnacle is not a library, this documentation serves to guide those who want to
//! contribute or learn how building something like this works.
#![deny(unused_imports)] // gonna force myself to keep stuff clean
// #![deny(unused_imports)] // gonna force myself to keep stuff clean
#![warn(clippy::unwrap_used)]
use clap::Parser;

View file

@ -11,7 +11,8 @@ use smithay::{
ImportAll, ImportMem, Renderer, Texture,
},
desktop::{
space::{self, SpaceRenderElements, SurfaceTree},
layer_map_for_output,
space::{SpaceRenderElements, SurfaceTree},
Space,
},
input::pointer::{CursorImageAttributes, CursorImageStatus},
@ -22,10 +23,10 @@ use smithay::{
},
render_elements,
utils::{IsAlive, Logical, Physical, Point, Scale},
wayland::{compositor, input_method::InputMethodHandle},
wayland::{compositor, input_method::InputMethodHandle, shell::wlr_layer},
};
use crate::{state::WithState, window::WindowElement};
use crate::{state::WithState, tag::Tag, window::WindowElement};
use self::pointer::{PointerElement, PointerRenderElement};
@ -76,18 +77,71 @@ where
}
}
struct LayerRenderElements<R> {
background: Vec<WaylandSurfaceRenderElement<R>>,
bottom: Vec<WaylandSurfaceRenderElement<R>>,
top: Vec<WaylandSurfaceRenderElement<R>>,
overlay: Vec<WaylandSurfaceRenderElement<R>>,
}
fn layer_render_elements<R>(output: &Output, renderer: &mut R) -> LayerRenderElements<R>
where
R: Renderer + ImportAll,
<R as Renderer>::TextureId: 'static,
{
let layer_map = layer_map_for_output(output);
let mut overlay = vec![];
let mut top = vec![];
let mut bottom = vec![];
let mut background = vec![];
let layer_elements = layer_map
.layers()
.filter_map(|surface| {
layer_map
.layer_geometry(surface)
.map(|geo| (surface, geo.loc))
})
.map(|(surface, loc)| {
let render_elements = surface.render_elements::<WaylandSurfaceRenderElement<R>>(
renderer,
loc.to_physical(1),
Scale::from(1.0),
1.0,
);
(surface.layer(), render_elements)
});
for (layer, elements) in layer_elements {
match layer {
wlr_layer::Layer::Background => background.extend(elements),
wlr_layer::Layer::Bottom => bottom.extend(elements),
wlr_layer::Layer::Top => top.extend(elements),
wlr_layer::Layer::Overlay => overlay.extend(elements),
}
}
LayerRenderElements {
background,
bottom,
top,
overlay,
}
}
#[allow(clippy::too_many_arguments)]
pub fn generate_render_elements<R, T>(
renderer: &mut R,
space: &Space<WindowElement>,
output: &Output,
input_method: &InputMethodHandle,
windows: &[WindowElement],
pointer_location: Point<f64, Logical>,
pointer_element: &mut PointerElement<T>,
pointer_image: Option<&TextureBuffer<T>>,
cursor_status: &mut CursorImageStatus,
dnd_icon: Option<&WlSurface>,
focus_stack: &[WindowElement],
renderer: &mut R,
output: &Output,
input_method: &InputMethodHandle,
pointer_element: &mut PointerElement<T>,
pointer_image: Option<&TextureBuffer<T>>,
) -> Vec<OutputRenderElements<R, WaylandSurfaceRenderElement<R>>>
where
R: Renderer<TextureId = T> + ImportAll + ImportMem,
@ -210,24 +264,48 @@ where
output_render_elements
} else {
// render everything
let space_render_elements =
space::space_render_elements(renderer, [space], output, 1.0)
.expect("Failed to get render elements");
let LayerRenderElements {
background,
bottom,
top,
overlay,
} = layer_render_elements(output, renderer);
let window_render_elements: Vec<WaylandSurfaceRenderElement<R>> =
Tag::tag_render_elements(windows, space, renderer);
let mut output_render_elements =
Vec::<OutputRenderElements<_, WaylandSurfaceRenderElement<_>>>::new();
Vec::<OutputRenderElements<R, WaylandSurfaceRenderElement<R>>>::new();
// let space_render_elements =
// smithay::desktop::space::space_render_elements(renderer, [space], output, 1.0)
// .expect("failed to get space_render_elements");
// Elements render from top to bottom
output_render_elements.extend(
custom_render_elements
.into_iter()
.map(OutputRenderElements::from),
);
output_render_elements.extend(
space_render_elements
overlay
.into_iter()
.chain(top)
.chain(window_render_elements)
.chain(bottom)
.chain(background)
.map(CustomRenderElements::from)
.map(OutputRenderElements::from),
);
// output_render_elements.extend(
// space_render_elements
// .into_iter()
// .map(OutputRenderElements::from),
// );
output_render_elements
}
};

View file

@ -581,6 +581,9 @@ impl ApiState {
pub trait WithState {
type State;
/// Access data map state.
///
/// RefCell Safety: This function will panic if called within itself.
fn with_state<F, T>(&self, func: F) -> T
where
F: FnMut(&mut Self::State) -> T;

View file

@ -4,6 +4,7 @@ use async_process::Stdio;
use futures_lite::AsyncBufReadExt;
use smithay::{
desktop::space::SpaceElement,
utils::Rectangle,
wayland::{compositor, shell::xdg::XdgToplevelSurfaceData},
};
@ -68,7 +69,10 @@ impl State {
if let Some(height) = height {
window_size.h = height;
}
window.request_size_change(&mut self.space, window_loc, window_size);
window.change_geometry(Rectangle::from_loc_and_size(window_loc, window_size));
if let Some(output) = window.output(self) {
self.update_windows(&output);
}
}
Msg::MoveWindowToTag { window_id, tag_id } => {
let Some(window) = window_id.window(self) else { return };

View file

@ -7,11 +7,20 @@ use std::{
sync::atomic::{AtomicU32, Ordering},
};
use smithay::output::Output;
use smithay::{
backend::renderer::{
element::{surface::WaylandSurfaceRenderElement, AsRenderElements},
ImportAll, ImportMem, Renderer,
},
desktop::{space::SpaceElement, Space},
output::Output,
utils::Scale,
};
use crate::{
layout::Layout,
state::{State, WithState},
window::WindowElement,
};
static TAG_ID_COUNTER: AtomicU32 = AtomicU32::new(0);
@ -64,6 +73,7 @@ impl Eq for TagInner {}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Tag(Rc<RefCell<TagInner>>);
// RefCell Safety: These methods should never panic because they are all self-contained or Copy.
impl Tag {
pub fn id(&self) -> TagId {
self.0.borrow().id
@ -107,4 +117,31 @@ impl Tag {
.find(|output| output.with_state(|state| state.tags.iter().any(|tg| tg == self)))
.cloned()
}
/// Get the render_elements for the provided tags.
pub fn tag_render_elements<R, C>(
windows: &[WindowElement],
space: &Space<WindowElement>,
renderer: &mut R,
) -> Vec<C>
where
R: Renderer + ImportAll + ImportMem,
<R as Renderer>::TextureId: 'static,
C: From<WaylandSurfaceRenderElement<R>>,
{
let elements = windows
.iter()
.rev() // rev because I treat the focus stack backwards vs how the renderer orders it
.filter(|win| win.is_on_active_tag(space.outputs()))
.flat_map(|win| {
// subtract win.geometry().loc to align decorations correctly
let loc = (space.element_location(win).unwrap_or((0, 0).into())
- win.geometry().loc)
.to_physical(1);
win.render_elements::<C>(renderer, loc, Scale::from(1.0), 1.0)
})
.collect::<Vec<_>>();
elements
}
}

View file

@ -11,7 +11,7 @@ use smithay::{
take_presentation_feedback_surface_tree, under_from_surface_tree,
with_surfaces_surface_tree, OutputPresentationFeedback,
},
Space, Window, WindowSurfaceType,
Window, WindowSurfaceType,
},
input::{
keyboard::{KeyboardTarget, KeysymHandle, ModifiersState},
@ -23,7 +23,7 @@ use smithay::{
wayland_protocols::wp::presentation_time::server::wp_presentation_feedback,
wayland_server::protocol::wl_surface::WlSurface,
},
utils::{user_data::UserDataMap, IsAlive, Logical, Point, Rectangle, Serial, Size},
utils::{user_data::UserDataMap, IsAlive, Logical, Point, Rectangle, Serial},
wayland::{
compositor::{self, Blocker, BlockerState, SurfaceData},
dmabuf::DmabufFeedback,
@ -203,39 +203,6 @@ impl WindowElement {
});
}
/// Request a size and loc change.
pub fn request_size_change(
&self,
space: &mut Space<WindowElement>,
new_loc: Point<i32, Logical>,
new_size: Size<i32, Logical>,
) {
match self {
WindowElement::Wayland(window) => {
window.toplevel().with_pending_state(|state| {
state.size = Some(new_size);
});
self.with_state(|state| {
state.loc_request_state =
LocationRequestState::Requested(window.toplevel().send_configure(), new_loc)
});
}
WindowElement::X11(surface) => {
tracing::debug!("sending size change to x11 win");
surface
.configure(Rectangle::from_loc_and_size(new_loc, new_size))
.expect("failed to configure x11 win");
if !surface.is_override_redirect() {
surface
.set_mapped(true)
.expect("failed to set x11 win to mapped");
}
space.map_element(self.clone(), new_loc, false);
}
}
}
pub fn class(&self) -> Option<String> {
match self {
WindowElement::Wayland(window) => {
@ -279,6 +246,21 @@ impl WindowElement {
self.with_state(|st| st.tags.first().and_then(|tag| tag.output(state)))
}
/// RefCell Safety: This uses RefCells on both `self` and everything in `outputs`.
pub fn is_on_active_tag<'a>(&self, outputs: impl IntoIterator<Item = &'a Output>) -> bool {
let tags = outputs
.into_iter()
.flat_map(|op| op.with_state(|state| state.focused_tags().cloned().collect::<Vec<_>>()))
.collect::<Vec<_>>();
self.with_state(|state| {
state
.tags
.iter()
.any(|tag| tags.iter().any(|tag2| tag == tag2))
})
}
/// Returns `true` if the window element is [`Wayland`].
///
/// [`Wayland`]: WindowElement::Wayland

View file

@ -122,14 +122,6 @@ impl LocationRequestState {
pub fn is_idle(&self) -> bool {
matches!(self, Self::Idle)
}
/// Returns `true` if the location request state is [`Acknowledged`].
///
/// [`Acknowledged`]: LocationRequestState::Acknowledged
#[must_use]
pub fn is_acknowledged(&self) -> bool {
matches!(self, Self::Acknowledged(..))
}
}
impl WindowElement {