anvil: Implement subsurface state caching

This commit is contained in:
Victor Berger 2020-04-21 20:53:55 +02:00 committed by Victor Berger
parent 315797ff43
commit d48c04fd7b
2 changed files with 152 additions and 40 deletions

View File

@ -311,7 +311,7 @@ impl<F: GLGraphicsBackend + 'static> GliumDrawer<F> {
if let Some(data) = attributes.user_data.get::<RefCell<SurfaceData>>() { if let Some(data) = attributes.user_data.get::<RefCell<SurfaceData>>() {
let mut data = data.borrow_mut(); let mut data = data.borrow_mut();
if data.texture.is_none() { if data.texture.is_none() {
if let Some(buffer) = data.buffer.take() { if let Some(buffer) = data.current_state.buffer.take() {
if let Ok(m) = self.texture_from_buffer(buffer.clone()) { if let Ok(m) = self.texture_from_buffer(buffer.clone()) {
// release the buffer if it was an SHM buffer // release the buffer if it was an SHM buffer
#[cfg(feature = "egl")] #[cfg(feature = "egl")]
@ -336,9 +336,9 @@ impl<F: GLGraphicsBackend + 'static> GliumDrawer<F> {
// Now, should we be drawn ? // Now, should we be drawn ?
if data.texture.is_some() { if data.texture.is_some() {
// if yes, also process the children // if yes, also process the children
if let Ok(subdata) = Role::<SubsurfaceRole>::data(role) { if Role::<SubsurfaceRole>::has(role) {
x += subdata.location.0; x += data.current_state.sub_location.0;
y += subdata.location.1; y += data.current_state.sub_location.1;
} }
TraversalAction::DoChildren((x, y)) TraversalAction::DoChildren((x, y))
} else { } else {
@ -352,12 +352,13 @@ impl<F: GLGraphicsBackend + 'static> GliumDrawer<F> {
}, },
|_surface, attributes, role, &(mut x, mut y)| { |_surface, attributes, role, &(mut x, mut y)| {
if let Some(ref data) = attributes.user_data.get::<RefCell<SurfaceData>>() { if let Some(ref data) = attributes.user_data.get::<RefCell<SurfaceData>>() {
if let Some(ref metadata) = data.borrow().texture { let data = data.borrow();
if let Some(ref metadata) = data.texture {
// we need to re-extract the subsurface offset, as the previous closure // we need to re-extract the subsurface offset, as the previous closure
// only passes it to our children // only passes it to our children
if let Ok(subdata) = Role::<SubsurfaceRole>::data(role) { if Role::<SubsurfaceRole>::has(role) {
x += subdata.location.0; x += data.current_state.sub_location.0;
y += subdata.location.1; y += data.current_state.sub_location.1;
} }
self.render_texture( self.render_texture(
frame, frame,

View File

@ -17,7 +17,10 @@ use smithay::{
}, },
utils::Rectangle, utils::Rectangle,
wayland::{ wayland::{
compositor::{compositor_init, BufferAssignment, CompositorToken, RegionAttributes, SurfaceEvent}, compositor::{
compositor_init, roles::Role, BufferAssignment, CompositorToken, RegionAttributes,
SubsurfaceRole, SurfaceEvent, TraversalAction,
},
data_device::DnDIconRole, data_device::DnDIconRole,
seat::{AxisFrame, CursorImageRole, GrabStartData, PointerGrab, PointerInnerHandle, Seat}, seat::{AxisFrame, CursorImageRole, GrabStartData, PointerGrab, PointerInnerHandle, Seat},
shell::{ shell::{
@ -29,7 +32,6 @@ use smithay::{
XdgSurfacePendingState, XdgSurfaceRole, XdgSurfacePendingState, XdgSurfaceRole,
}, },
}, },
SERIAL_COUNTER as SCOUNTER,
}, },
}; };
@ -640,13 +642,19 @@ impl Default for ResizeState {
} }
} }
#[derive(Default, Clone)]
pub struct CommitedState {
pub buffer: Option<wl_buffer::WlBuffer>,
pub input_region: Option<RegionAttributes>,
pub dimensions: Option<(i32, i32)>,
pub frame_callback: Option<wl_callback::WlCallback>,
pub sub_location: (i32, i32),
}
#[derive(Default)] #[derive(Default)]
pub struct SurfaceData { pub struct SurfaceData {
pub buffer: Option<wl_buffer::WlBuffer>,
pub texture: Option<crate::glium_drawer::TextureMetadata>, pub texture: Option<crate::glium_drawer::TextureMetadata>,
pub dimensions: Option<(i32, i32)>,
pub geometry: Option<Rectangle>, pub geometry: Option<Rectangle>,
pub input_region: Option<RegionAttributes>,
pub resize_state: ResizeState, pub resize_state: ResizeState,
/// Minimum width and height, as requested by the surface. /// Minimum width and height, as requested by the surface.
/// ///
@ -656,13 +664,63 @@ pub struct SurfaceData {
/// ///
/// `0` means unlimited. /// `0` means unlimited.
pub max_size: (i32, i32), pub max_size: (i32, i32),
pub frame_callback: Option<wl_callback::WlCallback>, pub current_state: CommitedState,
pub cached_state: Option<CommitedState>,
}
impl SurfaceData {
/// Apply a next state into the surface current state
pub fn apply_state(&mut self, next_state: CommitedState) {
if Self::merge_state(&mut self.current_state, next_state) {
self.texture = None;
}
}
/// Apply a next state into the cached state
pub fn apply_cache(&mut self, next_state: CommitedState) {
match self.cached_state {
Some(ref mut cached) => {
Self::merge_state(cached, next_state);
}
None => self.cached_state = Some(next_state),
}
}
/// Apply the current cached state if any
pub fn apply_from_cache(&mut self) {
if let Some(cached) = self.cached_state.take() {
self.apply_state(cached);
}
}
// merge the "next" state into the "into" state
//
// returns true if the texture cache should be invalidated
fn merge_state(into: &mut CommitedState, next: CommitedState) -> bool {
let mut new_buffer = false;
// release the previous buffer if relevant
if into.buffer != next.buffer {
if let Some(buffer) = into.buffer.take() {
buffer.release();
}
new_buffer = true;
}
// ping the previous callback if relevant
if into.frame_callback != next.frame_callback {
if let Some(callback) = into.frame_callback.take() {
callback.done(0);
}
}
*into = next;
new_buffer
}
} }
impl SurfaceData { impl SurfaceData {
/// Returns the size of the surface. /// Returns the size of the surface.
pub fn size(&self) -> Option<(i32, i32)> { pub fn size(&self) -> Option<(i32, i32)> {
self.dimensions self.current_state.dimensions
} }
/// Checks if the surface's input region contains the point. /// Checks if the surface's input region contains the point.
@ -688,16 +746,16 @@ impl SurfaceData {
} }
// If there's no input region, we're done. // If there's no input region, we're done.
if self.input_region.is_none() { if self.current_state.input_region.is_none() {
return true; return true;
} }
self.input_region.as_ref().unwrap().contains(point) self.current_state.input_region.as_ref().unwrap().contains(point)
} }
/// Send the frame callback if it had been requested /// Send the frame callback if it had been requested
pub fn send_frame(&mut self, serial: u32) { pub fn send_frame(&mut self, serial: u32) {
if let Some(callback) = self.frame_callback.take() { if let Some(callback) = self.current_state.frame_callback.take() {
callback.done(serial); callback.done(serial);
} }
} }
@ -721,7 +779,13 @@ fn surface_commit(
geometry = role.window_geometry; geometry = role.window_geometry;
}); });
let refresh = token.with_surface_data(surface, |attributes| { let sub_data = token
.with_role_data(surface, |role: &mut SubsurfaceRole| role.clone())
.ok();
let mut next_state = CommitedState::default();
let (refresh, apply_children) = token.with_surface_data(surface, |attributes| {
attributes attributes
.user_data .user_data
.insert_if_missing(|| RefCell::new(SurfaceData::default())); .insert_if_missing(|| RefCell::new(SurfaceData::default()));
@ -731,8 +795,20 @@ fn surface_commit(
.unwrap() .unwrap()
.borrow_mut(); .borrow_mut();
if let Some(ref cached_state) = data.cached_state {
// There is a pending state, accumulate into it
next_state = cached_state.clone();
} else {
// start from the current state
next_state = data.current_state.clone();
}
if let Some(ref data) = sub_data {
next_state.sub_location = data.location;
}
data.geometry = geometry; data.geometry = geometry;
data.input_region = attributes.input_region.clone(); next_state.input_region = attributes.input_region.clone();
data.min_size = min_size; data.min_size = min_size;
data.max_size = max_size; data.max_size = max_size;
@ -740,37 +816,72 @@ fn surface_commit(
match attributes.buffer.take() { match attributes.buffer.take() {
Some(BufferAssignment::NewBuffer { buffer, .. }) => { Some(BufferAssignment::NewBuffer { buffer, .. }) => {
// new contents // new contents
// TODO: handle hotspot coordinates next_state.dimensions = buffer_utils.dimensions(&buffer);
if let Some(old_buffer) = data.buffer.replace(buffer) { next_state.buffer = Some(buffer);
old_buffer.release();
}
data.texture = None;
// If this fails, the buffer will be discarded later by the drawing code.
data.dimensions = buffer_utils.dimensions(data.buffer.as_ref().unwrap());
} }
Some(BufferAssignment::Removed) => { Some(BufferAssignment::Removed) => {
// erase the contents // remove the contents
if let Some(old_buffer) = data.buffer.take() { next_state.buffer = None;
old_buffer.release(); next_state.dimensions = None;
}
data.texture = None;
data.dimensions = None;
} }
None => {} None => {}
} }
// process the frame callback if any if let Some(frame_cb) = attributes.frame_callback.take() {
if let Some(callback) = attributes.frame_callback.take() { if let Some(old_cb) = next_state.frame_callback.take() {
if let Some(old_callback) = data.frame_callback.take() { old_cb.done(0);
// fire the old unfired callback to clean it up
old_callback.done(SCOUNTER.next_serial());
} }
data.frame_callback = Some(callback); next_state.frame_callback = Some(frame_cb);
} }
window_map.borrow().find(surface) data.apply_cache(next_state);
let apply_children = if let Some(SubsurfaceRole { sync: true, .. }) = sub_data {
false
} else {
data.apply_from_cache();
true
};
(window_map.borrow().find(surface), apply_children)
}); });
// Apply the cached state of all sync children
if apply_children {
token.with_surface_tree_upward(
surface,
true,
|_, _, role, &is_root| {
// only process children if the surface is sync or we are the root
if is_root {
// we are the root
TraversalAction::DoChildren(false)
} else if let Ok(sub_data) = Role::<SubsurfaceRole>::data(role) {
if sub_data.sync || is_root {
TraversalAction::DoChildren(false)
} else {
// if we are not sync, we won't apply from cache and don't process
// the children
TraversalAction::SkipChildren
}
} else {
unreachable!();
}
},
|_, attributes, role, _| {
// only apply from cache if we are a sync subsurface
if let Ok(sub_data) = Role::<SubsurfaceRole>::data(role) {
if sub_data.sync {
if let Some(data) = attributes.user_data.get::<RefCell<SurfaceData>>() {
data.borrow_mut().apply_from_cache();
}
}
}
},
|_, _, _, _| true,
)
}
if let Some(toplevel) = refresh { if let Some(toplevel) = refresh {
let mut window_map = window_map.borrow_mut(); let mut window_map = window_map.borrow_mut();
window_map.refresh_toplevel(&toplevel); window_map.refresh_toplevel(&toplevel);