From 4193457fc4c9e67357191ffcee5a98b21e9af587 Mon Sep 17 00:00:00 2001 From: Will Temple Date: Mon, 23 Mar 2026 00:29:19 -0400 Subject: [PATCH] Back out of wp_viewporter --- lib/ui_platform_wayland/src/lib.rs | 146 ++++++----------------------- 1 file changed, 31 insertions(+), 115 deletions(-) diff --git a/lib/ui_platform_wayland/src/lib.rs b/lib/ui_platform_wayland/src/lib.rs index 9e31275..b6ba4c0 100644 --- a/lib/ui_platform_wayland/src/lib.rs +++ b/lib/ui_platform_wayland/src/lib.rs @@ -40,7 +40,6 @@ use wayland_client::{ use wayland_protocols::wp::cursor_shape::v1::client::{ wp_cursor_shape_device_v1, wp_cursor_shape_manager_v1, }; -use wayland_protocols::wp::viewporter::client::{wp_viewport, wp_viewporter}; use wayland_protocols::wp::primary_selection::zv1::client::{ zwp_primary_selection_device_manager_v1, zwp_primary_selection_device_v1, zwp_primary_selection_offer_v1, zwp_primary_selection_source_v1, @@ -157,9 +156,6 @@ struct State { clipboard_device: Option, cursor_shape_manager: Option, cursor_shape_device: Option, - /// Surface viewport for compositor-side scaling during resize. When set, the compositor - /// scales the last-committed buffer to the destination size without a GPU re-render. - viewport: Option, primary_selection_manager: Option, primary_selection_device: Option, @@ -316,7 +312,6 @@ impl WaylandWindow { }, ); let cursor_shape_manager = globals.bind(&qh, 1..=2, ()).ok(); - let viewporter: Option = globals.bind(&qh, 1..=1, ()).ok(); let primary_selection_manager = globals.bind(&qh, 1..=1, ()).ok(); let primary_selection_device = primary_selection_manager.as_ref().map( |manager: &zwp_primary_selection_device_manager_v1::ZwpPrimarySelectionDeviceManagerV1| { @@ -324,7 +319,6 @@ impl WaylandWindow { }, ); let surface = compositor.create_surface(&qh, ()); - let viewport = viewporter.as_ref().map(|vp| vp.get_viewport(&surface, &qh, ())); let xdg_surface = wm_base.get_xdg_surface(&surface, &qh, ()); let toplevel = xdg_surface.get_toplevel(&qh, ()); toplevel.set_title(spec.title.clone()); @@ -369,7 +363,6 @@ impl WaylandWindow { clipboard_device, cursor_shape_manager, cursor_shape_device: None, - viewport, primary_selection_manager, primary_selection_device, qh, @@ -685,29 +678,6 @@ impl WaylandWindow { self.state.frame_callback = None; } - /// Set the compositor-side destination size for the surface viewport. - /// The compositor will scale the last-committed buffer to this size without a GPU re-render. - /// Call `clear_viewport_destination` before the next GPU render so the buffer size is used. - /// Returns `true` if the viewport was set (compositor supports wp_viewporter), `false` if - /// the caller must fall back to a GPU render for the preview. - fn set_viewport_destination(&mut self, width: u32, height: u32) -> bool { - if let Some(vp) = self.state.viewport.as_ref() { - vp.set_destination(width as i32, height as i32); - self.state._surface.commit(); - true - } else { - false - } - } - - /// Remove the compositor-side destination override. Must be called before the next GPU - /// render so the compositor uses the buffer's natural dimensions. - fn clear_viewport_destination(&mut self) { - if let Some(vp) = self.state.viewport.as_ref() { - vp.set_destination(-1, -1); - } - } - fn flush_connection(&mut self) -> Result<(), Box> { self.state._connection.flush()?; Ok(()) @@ -1137,6 +1107,7 @@ fn spawn_window_worker( internal_tx, keyboard_repeat_timer: None, pending_viewport_since: None, + pending_swapchain_size: None, })); // Task 1: Wayland-fd watcher. Waits for the compositor to buffer events (frame @@ -1339,11 +1310,11 @@ fn spawn_window_worker( ); state_ref.pending_viewport = Some(current_viewport); state_ref.pending_viewport_since.get_or_insert_with(Instant::now); - // Emit the Configured event BEFORE resizing the GPU - // swapchain so the app thread starts layout immediately. - // renderer.resize() then runs concurrently with layout - // (different CPU threads), cutting the critical path from - // resize_gpu + layout to max(resize_gpu, layout). + // Emit configure BEFORE touching the GPU so the app + // thread starts layout immediately in parallel. + // The actual swapchain recreation is deferred to just + // before the next renderer.render() call so rapid + // configures only pay one recreation. if state_ref .latest_scene .as_ref() @@ -1354,17 +1325,8 @@ fn spawn_window_worker( } else { state_ref.pending_viewport = None; } - let t_resize = std::time::Instant::now(); - state_ref.renderer.resize(frame.width, frame.height); - let resize_gpu_us = t_resize.elapsed().as_micros(); - debug!( - target: "ruin_ui_platform_wayland::perf", - window_id = state_ref.window_id.raw(), - width = frame.width, - height = frame.height, - resize_gpu_us, - "renderer swapchain resized" - ); + state_ref.pending_swapchain_size = + Some((frame.width, frame.height)); state_ref.window.request_redraw(); } if !state_ref.opened_emitted { @@ -1376,11 +1338,10 @@ fn spawn_window_worker( let scene = state_ref.latest_scene.clone(); if let Some(scene) = scene.as_ref() { if scene.logical_size != current_viewport { - // Resize case: render the preview immediately. - // Drop any pending frame callback — vsync pacing - // doesn't matter for a placeholder preview, and - // waiting for it (potentially 16–150ms) is the - // primary source of visible resize lag. + // Size mismatch: hold the last committed buffer and + // wait for the app to produce a correctly-sized scene. + // Clear any pending frame callback so we can render + // the new scene as soon as it arrives. state_ref.window.clear_frame_callback(); debug!( target: "ruin_ui_platform_wayland::resize", @@ -1390,76 +1351,33 @@ fn spawn_window_worker( scene_height = scene.logical_size.height, viewport_width = current_viewport.width, viewport_height = current_viewport.height, - "scene size does not match current viewport" + "scene size does not match current viewport; holding last buffer" ); state_ref.pending_viewport = Some(current_viewport); state_ref.pending_viewport_since.get_or_insert_with(Instant::now); - // Use wp_viewport to scale the last-committed buffer - // to the new size without any GPU work. This lets - // the compositor composite immediately after - // ack_configure, before a new GPU frame is ready. - // Falls back to a GPU stretch render when the - // compositor does not advertise wp_viewporter. - let viewport_set = state_ref.window - .set_viewport_destination( - current_viewport.width as u32, - current_viewport.height as u32, - ); - let preview_ok = if viewport_set { - true - } else { - let mut preview_scene = scene.clone(); - preview_scene.logical_size = current_viewport; - state_ref.renderer.render(&preview_scene).is_ok() - }; - if preview_ok { - match state_ref.window.flush_connection() { - Ok(()) => { - let preview_lag_us = state_ref - .pending_viewport_since - .map(|t| t.elapsed().as_micros()); - debug!( - target: "ruin_ui_platform_wayland::resize", - window_id = state_ref.window_id.raw(), - scene_version = scene.version, - width = current_viewport.width, - height = current_viewport.height, - preview_lag_us, - compositor_scaled = viewport_set, - "presented preview; waiting for correct scene" - ); - let _ = state_ref.event_tx.send( - PlatformEvent::FramePresented { - window_id: state_ref.window_id, - scene_version: scene.version, - item_count: scene.item_count(), - }, - ); - finish_presented_viewport_request( - &mut state_ref, - scene.logical_size, - ); - } - Err(error) => { - debug!( - target: "ruin_ui_platform_wayland::scene", - window_id = state_ref.window_id.raw(), - error = %error, - "failed to flush preview" - ); - state_ref.window.request_redraw(); - } - } - } + state_ref.window.request_redraw(); } else if !state_ref.window.presentation_ready() { // Correct scene is ready but the compositor // hasn't signalled vsync yet. Wait for it. state_ref.window.request_redraw(); } else { - // Correct scene: clear the viewport destination so - // the compositor uses the buffer's natural size. - // This must happen before wgpu commits the new buffer. - state_ref.window.clear_viewport_destination(); + // Correct scene: apply any deferred swapchain resize + // before rendering. Deferring from frame.resized means + // rapid configures pay one recreation instead of one + // per event. + if let Some((w, h)) = state_ref.pending_swapchain_size.take() { + let t_resize = std::time::Instant::now(); + state_ref.renderer.resize(w, h); + let resize_gpu_us = t_resize.elapsed().as_micros(); + debug!( + target: "ruin_ui_platform_wayland::perf", + window_id = state_ref.window_id.raw(), + width = w, + height = h, + resize_gpu_us, + "renderer swapchain resized (deferred)" + ); + } state_ref.window.arm_frame_callback(); let t_render = std::time::Instant::now(); match state_ref.renderer.render(scene) { @@ -1655,8 +1573,6 @@ impl Dispatch for State { } } -delegate_noop!(State: ignore wp_viewporter::WpViewporter); -delegate_noop!(State: ignore wp_viewport::WpViewport); delegate_noop!(State: ignore wl_compositor::WlCompositor); delegate_noop!(State: ignore wl_surface::WlSurface); delegate_noop!(State: ignore wl_data_device_manager::WlDataDeviceManager);