From cbfc6e95df9c15a159c747b57aa4c29e544722bf Mon Sep 17 00:00:00 2001 From: Till Wegmueller Date: Sat, 4 Apr 2026 18:45:31 +0200 Subject: [PATCH] Add rendering pipeline for client surfaces via OutputDamageTracker - Create render module using Smithay's desktop::space::render_output for damage-tracked frame rendering to the Winit backend window - Store backend and damage_tracker in CalloopData so the Winit event callback can trigger rendering on Redraw events - Send initial xdg toplevel configure on first commit so clients can begin drawing - Send frame callbacks after each render so clients schedule redraws --- crates/wrsrvd/src/handlers/compositor.rs | 17 ++++ crates/wrsrvd/src/main.rs | 26 ++++-- crates/wrsrvd/src/render.rs | 102 +++++++++++++++++++++++ 3 files changed, 138 insertions(+), 7 deletions(-) create mode 100644 crates/wrsrvd/src/render.rs diff --git a/crates/wrsrvd/src/handlers/compositor.rs b/crates/wrsrvd/src/handlers/compositor.rs index a3369ab..cd0de12 100644 --- a/crates/wrsrvd/src/handlers/compositor.rs +++ b/crates/wrsrvd/src/handlers/compositor.rs @@ -7,6 +7,7 @@ use smithay::{ wayland::{ buffer::BufferHandler, compositor::{CompositorClientState, CompositorHandler, CompositorState}, + seat::WaylandFocus, shm::{ShmHandler, ShmState}, }, }; @@ -44,6 +45,22 @@ impl CompositorHandler for WayRay { fn commit(&mut self, surface: &WlSurface) { trace!(?surface, "surface commit"); smithay::backend::renderer::utils::on_commit_buffer_handler::(surface); + + // If this surface belongs to an xdg toplevel that hasn't received + // its initial configure yet, send it now so the client can start + // drawing. + if let Some(window) = self + .space + .elements() + .find(|w| w.wl_surface().map(|s| s.into_owned()) == Some(surface.clone())) + .cloned() + { + if let Some(toplevel) = window.toplevel() { + if !toplevel.is_initial_configure_sent() { + toplevel.send_configure(); + } + } + } } } diff --git a/crates/wrsrvd/src/main.rs b/crates/wrsrvd/src/main.rs index 7370ef9..5a63215 100644 --- a/crates/wrsrvd/src/main.rs +++ b/crates/wrsrvd/src/main.rs @@ -1,5 +1,6 @@ mod errors; mod handlers; +mod render; mod state; use std::sync::Arc; @@ -11,8 +12,8 @@ use crate::state::WayRay; use miette::Result; use smithay::{ backend::{ - renderer::gles::GlesRenderer, - winit::{self, WinitEvent}, + renderer::{damage::OutputDamageTracker, gles::GlesRenderer}, + winit::{self, WinitEvent, WinitGraphicsBackend}, }, output::{Mode, Output, PhysicalProperties, Subpixel}, reexports::wayland_server::Display, @@ -25,6 +26,8 @@ use tracing::info; struct CalloopData { state: WayRay, display: Display, + backend: WinitGraphicsBackend, + damage_tracker: OutputDamageTracker, } fn main() -> Result<()> { @@ -118,7 +121,7 @@ fn main() -> Result<()> { // Insert the Winit event loop as a calloop source. loop_handle - .insert_source(winit_event_loop, move |event, _, _data| match event { + .insert_source(winit_event_loop, move |event, _, data| match event { WinitEvent::Resized { size, scale_factor } => { info!(?size, scale_factor, "window resized"); } @@ -129,7 +132,11 @@ fn main() -> Result<()> { // Input handling is Task 7 -- ignore for now. } WinitEvent::Redraw => { - // Rendering is Task 6 -- ignore for now. + render::render_output_frame( + &mut data.state, + &mut data.backend, + &mut data.damage_tracker, + ); } WinitEvent::CloseRequested => { info!("close requested, shutting down"); @@ -138,10 +145,15 @@ fn main() -> Result<()> { }) .map_err(|e| errors::WayRayError::EventLoop(Box::new(e.error)))?; - // Keep a handle to the backend (needed for rendering in Task 6). - let _backend = backend; + // Create a damage tracker for efficient rendering. + let damage_tracker = OutputDamageTracker::from_output(&output); - let mut calloop_data = CalloopData { state, display }; + let mut calloop_data = CalloopData { + state, + display, + backend, + damage_tracker, + }; info!("entering main event loop"); diff --git a/crates/wrsrvd/src/render.rs b/crates/wrsrvd/src/render.rs new file mode 100644 index 0000000..87188f0 --- /dev/null +++ b/crates/wrsrvd/src/render.rs @@ -0,0 +1,102 @@ +use smithay::{ + backend::{ + renderer::{ + damage::OutputDamageTracker, + element::texture::TextureRenderElement, + gles::{GlesRenderer, GlesTexture}, + }, + winit::WinitGraphicsBackend, + }, + desktop::{Window, space::render_output}, +}; +use tracing::warn; + +use crate::state::WayRay; + +/// Dark grey clear color for the compositor background. +const CLEAR_COLOR: [f32; 4] = [0.1, 0.1, 0.1, 1.0]; + +/// Render the compositor space to the Winit backend window. +/// +/// Uses `OutputDamageTracker` for efficient re-rendering: only +/// damaged regions are redrawn each frame. +/// +/// Returns `true` if any damage was present and submitted. +pub fn render_output_frame( + state: &mut WayRay, + backend: &mut WinitGraphicsBackend, + damage_tracker: &mut OutputDamageTracker, +) -> bool { + let output = state.output.clone(); + + // Get buffer age before bind (avoids borrow conflict). + let age = backend.buffer_age().unwrap_or(0); + + // Render within a block so framebuffer is dropped before submit. + let render_damage = { + let (renderer, mut framebuffer) = match backend.bind() { + Ok(pair) => pair, + Err(err) => { + warn!(?err, "failed to bind winit backend for rendering"); + return false; + } + }; + + // The empty custom elements slice needs a concrete type. + let custom_elements: &[TextureRenderElement] = &[]; + + let render_result = render_output::<_, _, Window, _>( + &output, + renderer, + &mut framebuffer, + 1.0, // alpha + age, + [&state.space], + custom_elements, + damage_tracker, + CLEAR_COLOR, + ); + + match render_result { + Ok(result) => { + // Clone the damage rectangles so we can use them after + // the framebuffer is dropped. + Ok(result.damage.cloned()) + } + Err(err) => { + warn!(?err, "damage tracker render failed"); + Err(()) + } + } + }; + // framebuffer is now dropped, backend is no longer borrowed. + + match render_damage { + Ok(damage) => { + let has_damage = damage.is_some(); + + let submit_result = if let Some(ref rects) = damage { + backend.submit(Some(rects)) + } else { + backend.submit(None) + }; + + if let Err(err) = submit_result { + warn!(?err, "failed to submit frame"); + return false; + } + + // Send frame callbacks to all mapped surfaces so clients + // know they can draw the next frame. + let time = state.clock.now(); + for window in state.space.elements() { + window.send_frame(&output, time, Some(std::time::Duration::ZERO), |_, _| { + Some(output.clone()) + }); + } + + has_damage + } + Err(()) => false, + } +}