Refactor window ownership and immutable frame sampling

This commit is contained in:
Samuel Onoja committed 2026-10-01 18:03:01 +01:00
1 parent c0010ccbb6
commit 07bda5a21c
31 files changed
+1222 -623

No files matched your search

+176
View File
@@ -0,0 +1,176 @@
use ferese_core::OutputId;
use ferese_layout::WindowId;
use smithay::reexports::wayland_server::protocol::wl_surface::WlSurface;
use super::*;
/// Resource ownership follows the lifetime of a window, output, surface or GL
/// context. Desktop policy and animation values do not live in these caches.
#[derive(Default)]
pub(crate) struct RenderResources {
pub(super) windows: HashMap<WindowId, WindowBuffers>,
pub(super) contexts: HashMap<ErasedContextId, ContextPrograms>,
pub(super) outputs: HashMap<OutputId, OverviewScrim>,
pub(super) surfaces: HashMap<WlSurface, MaterialBuffers>,
}
#[derive(Default)]
pub(super) struct WindowBuffers {
pub borders: WindowBorderBuffers,
pub dim: WindowBorderBuffers,
pub resize_fill: WindowBorderBuffers,
pub shadow: WindowShadowBuffers,
snapshot: Option<ResizeSnapshot>,
}
#[derive(Default)]
pub(super) struct ContextPrograms {
pub rounded: Option<RoundedClipPrograms>,
pub rounded_warned: bool,
pub material: Option<MaterialProgram>,
pub blur: Option<BlurProgram>,
}
impl RenderResources {
pub fn remove_window(&mut self, id: WindowId) {
self.windows.remove(&id);
}
pub fn remove_output(&mut self, id: OutputId) {
self.outputs.remove(&id);
}
pub fn snapshot(&self, id: &WindowId) -> Option<&ResizeSnapshot> {
self.windows.get(id)?.snapshot.as_ref()
}
pub fn snapshots(&self) -> impl Iterator<Item = &ResizeSnapshot> {
self.windows.values().filter_map(|window| window.snapshot.as_ref())
}
pub fn set_snapshot(&mut self, id: WindowId, snapshot: ResizeSnapshot) {
self.windows.entry(id).or_default().snapshot = Some(snapshot);
}
pub fn clear_snapshot(&mut self, id: &WindowId) {
if let Some(window) = self.windows.get_mut(id) {
window.snapshot = None;
}
}
pub fn retain_snapshots(&mut self, mut keep: impl FnMut(&WindowId, &mut ResizeSnapshot) -> bool) {
for (id, window) in &mut self.windows {
if let Some(snapshot) = &mut window.snapshot
&& !keep(id, snapshot)
{
window.snapshot = None;
}
}
}
pub(super) fn clear_tint(&mut self, id: WindowId, resize_fill: bool) {
let Some(window) = self.windows.get_mut(&id) else {
return;
};
if resize_fill {
window.resize_fill.contexts.clear();
} else {
window.dim.contexts.clear();
}
}
pub fn forget_context(&mut self, context: &ErasedContextId) {
self.contexts.remove(context);
for window in self.windows.values_mut() {
window.borders.contexts.remove(context);
window.dim.contexts.remove(context);
window.resize_fill.contexts.remove(context);
window.shadow.contexts.remove(context);
if window
.snapshot
.as_ref()
.is_some_and(|snapshot| &snapshot.context == context)
{
window.snapshot = None;
}
}
for output in self.outputs.values_mut() {
for contexts in output.chrome.values_mut() {
contexts.remove(context);
}
}
for surface in self.surfaces.values_mut() {
surface.contexts.retain(|(id, _), _| id != context);
surface.captures.remove(context);
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn window_removal_releases_all_its_caches_without_removing_other_windows() {
let mut resources = RenderResources::default();
resources.windows.insert(WindowId(1), WindowBuffers::default());
resources.windows.insert(WindowId(2), WindowBuffers::default());
resources.remove_window(WindowId(1));
resources.clear_snapshot(&WindowId(1));
resources.clear_tint(WindowId(1), true);
assert!(!resources.windows.contains_key(&WindowId(1)));
assert!(resources.windows.contains_key(&WindowId(2)));
}
#[test]
#[ignore = "requires an EGL rendering device"]
fn context_removal_evicts_programs_and_snapshots_without_touching_another_gpu() {
use smithay::backend::egl::{EGLContext, EGLDevice, EGLDisplay};
let device = EGLDevice::enumerate().unwrap().last().expect("an EGL device");
let display = unsafe { EGLDisplay::new(device).unwrap() };
let mut first = unsafe { GlesRenderer::new(EGLContext::new(&display).unwrap()).unwrap() };
let mut second = unsafe { GlesRenderer::new(EGLContext::new(&display).unwrap()).unwrap() };
let first_id = first.context_id().erased();
let second_id = second.context_id().erased();
let mut resources = RenderResources::default();
for (id, renderer) in [(WindowId(1), &mut first), (WindowId(2), &mut second)] {
rounded_clip_program(&mut resources, renderer).unwrap();
material_program(&mut resources, renderer).unwrap();
blur_program(&mut resources, renderer).unwrap();
let texture = Offscreen::<GlesTexture>::create_buffer(renderer, Fourcc::Abgr8888, (16, 16).into()).unwrap();
resources.set_snapshot(
id,
ResizeSnapshot {
texture,
context: renderer.context_id().erased(),
id: Id::new(),
commit: CommitCounter::default(),
elapsed: Duration::ZERO,
last_tick: None,
scale: 1.0,
},
);
}
resources.forget_context(&first_id);
assert!(!resources.contexts.contains_key(&first_id));
assert!(resources.contexts.contains_key(&second_id));
assert!(resources.snapshot(&WindowId(1)).is_none());
assert!(resources.snapshot(&WindowId(2)).is_some());
assert_eq!(resources.snapshots().count(), 1);
// Reconnecting a context can build fresh resources after eviction.
rounded_clip_program(&mut resources, &mut first).unwrap();
assert!(resources.contexts.contains_key(&first_id));
}
}