From 9ac9fb5623e5c5ef9c4eb03aefae471640096f19 Mon Sep 17 00:00:00 2001 From: Julian Orth Date: Sun, 15 Feb 2026 18:47:20 +0100 Subject: [PATCH 1/3] metal: discard buffers after failed ctx change --- src/backends/metal/video.rs | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/src/backends/metal/video.rs b/src/backends/metal/video.rs index 9b10c509..f879dae5 100644 --- a/src/backends/metal/video.rs +++ b/src/backends/metal/video.rs @@ -2443,7 +2443,7 @@ impl MetalBackend { self.default_feedback.set(fb); self.ctx.set(Some(ctx)); for dev in self.device_holder.drm_devices.lock().values() { - self.re_init_drm_device(&dev); + self.init_drm_device_after_gfx_ctx_change(&dev); for connector in dev.connectors.lock().values() { connector.send_hardware_cursor(); } @@ -2478,18 +2478,25 @@ impl MetalBackend { self.make_render_device(dev, true); } else { if let Some(dev) = self.device_holder.drm_devices.get(&dev.devnum) { - self.re_init_drm_device(&dev); + self.init_drm_device_after_gfx_ctx_change(&dev); } } } - fn re_init_drm_device(&self, dev: &Rc) { + fn init_drm_device_after_gfx_ctx_change(&self, dev: &Rc) { if let Err(e) = self.init_drm_device(dev) { log::error!( "Could not initialize drm device {}: {}", dev.dev.devnode.as_bytes().as_bstr(), ErrorFmt(e), ); + for connector in dev.connectors.lock().values() { + connector.buffers.take(); + connector.cursor_buffers.take(); + } + for plane in dev.dev.planes.values() { + plane.drm_state.borrow_mut().buffers.take(); + } } for connector in dev.connectors.lock().values() { if connector.connected() { From cc56632c687ddb5cbde37f7469f80a4d65ea95ba Mon Sep 17 00:00:00 2001 From: Julian Orth Date: Mon, 16 Feb 2026 13:40:08 +0100 Subject: [PATCH 2/3] cursor: merge set_sync_file/swap_buffer --- src/backend.rs | 3 +-- src/backends/metal/present.rs | 16 +++++++--------- src/backends/metal/video.rs | 11 +++-------- src/cursor_user.rs | 3 +-- 4 files changed, 12 insertions(+), 21 deletions(-) diff --git a/src/backend.rs b/src/backend.rs index 0b0eebdf..cf5490ce 100644 --- a/src/backend.rs +++ b/src/backend.rs @@ -183,8 +183,7 @@ pub trait HardwareCursorUpdate { fn set_enabled(&mut self, enabled: bool); fn get_buffer(&self) -> Rc; fn set_position(&mut self, x: i32, y: i32); - fn swap_buffer(&mut self); - fn set_sync_file(&mut self, sync_file: Option); + fn swap_buffer(&mut self, sync_file: Option); fn size(&self) -> (i32, i32); } diff --git a/src/backends/metal/present.rs b/src/backends/metal/present.rs index d6e162a7..08df0615 100644 --- a/src/backends/metal/present.rs +++ b/src/backends/metal/present.rs @@ -522,24 +522,22 @@ impl MetalConnector { let buffer_idx = ((connector_drm_state.cursor_fb_idx + 1) % buffers.len() as u64) as usize; let mut c = MetalHardwareCursorChange { cursor_enabled: self.cursor_enabled.get(), - cursor_swap_buffer: false, + cursor_swap_buffer: None, cursor_x: self.cursor_x.get(), cursor_y: self.cursor_y.get(), cursor_buffer: &buffers[buffer_idx], - sync_file: None, cursor_size: (self.dev.cursor_width as _, self.dev.cursor_height as _), }; self.state.present_hardware_cursor(node, &mut c); - if c.cursor_swap_buffer { - c.sync_file = c.cursor_buffer.copy_to_dev(cd, c.sync_file)?; - } - self.cursor_swap_buffer.set(c.cursor_swap_buffer); - if c.sync_file.is_some() { - self.cursor_sync_file.set(c.sync_file); + let swap_buffers = c.cursor_swap_buffer.is_some(); + self.cursor_swap_buffer.set(swap_buffers); + if let Some(sf) = c.cursor_swap_buffer.take() { + let sf = c.cursor_buffer.copy_to_dev(cd, sf)?; + self.cursor_sync_file.set(sf); } let mut cursor_changed = false; cursor_changed |= self.cursor_enabled.replace(c.cursor_enabled) != c.cursor_enabled; - cursor_changed |= c.cursor_swap_buffer; + cursor_changed |= swap_buffers; cursor_changed |= self.cursor_x.replace(c.cursor_x) != c.cursor_x; cursor_changed |= self.cursor_y.replace(c.cursor_y) != c.cursor_y; if cursor_changed { diff --git a/src/backends/metal/video.rs b/src/backends/metal/video.rs index f879dae5..0aa3bfb3 100644 --- a/src/backends/metal/video.rs +++ b/src/backends/metal/video.rs @@ -557,12 +557,11 @@ pub struct MetalHardwareCursor { } pub struct MetalHardwareCursorChange<'a> { - pub cursor_swap_buffer: bool, + pub cursor_swap_buffer: Option>, pub cursor_enabled: bool, pub cursor_x: i32, pub cursor_y: i32, pub cursor_buffer: &'a RenderBuffer, - pub sync_file: Option, pub cursor_size: (i32, i32), } @@ -596,12 +595,8 @@ impl HardwareCursorUpdate for MetalHardwareCursorChange<'_> { self.cursor_y = y; } - fn swap_buffer(&mut self) { - self.cursor_swap_buffer = true; - } - - fn set_sync_file(&mut self, sync_file: Option) { - self.sync_file = sync_file; + fn swap_buffer(&mut self, sync_file: Option) { + self.cursor_swap_buffer = Some(sync_file); } fn size(&self) -> (i32, i32) { diff --git a/src/cursor_user.rs b/src/cursor_user.rs index 19310269..b014728b 100644 --- a/src/cursor_user.rs +++ b/src/cursor_user.rs @@ -515,8 +515,7 @@ impl CursorUser { ); match res { Ok(sync_file) => { - hc.set_sync_file(sync_file); - hc.swap_buffer(); + hc.swap_buffer(sync_file); } Err(e) => { log::error!("Could not render hardware cursor: {}", ErrorFmt(e)); From fbf32f44ce879cc20a115dd75c03742b83448de8 Mon Sep 17 00:00:00 2001 From: Julian Orth Date: Fri, 13 Feb 2026 17:19:22 +0100 Subject: [PATCH 3/3] metal: block screencopies behind cross-device copies --- src/backends/metal/present.rs | 50 ++++++++++++++++++++++++++--------- src/backends/metal/video.rs | 23 ++++++++++++++-- 2 files changed, 59 insertions(+), 14 deletions(-) diff --git a/src/backends/metal/present.rs b/src/backends/metal/present.rs index 08df0615..7bc70427 100644 --- a/src/backends/metal/present.rs +++ b/src/backends/metal/present.rs @@ -6,6 +6,7 @@ use { transaction::{DrmConnectorState, DrmPlaneState}, video::{ MetalConnector, MetalCrtc, MetalHardwareCursorChange, MetalPlane, RenderBuffer, + RenderBufferCopy, }, }, cmm::cmm_description::ColorDescription, @@ -71,7 +72,7 @@ pub struct PresentFb { fb: Rc, tex: Rc, direct_scanout_data: Option, - sync_file: Option, + copy: RenderBufferCopy, pub locked: bool, } @@ -103,6 +104,12 @@ pub const DEFAULT_PRE_COMMIT_MARGIN: u64 = 16_000_000; // 16ms pub const DEFAULT_POST_COMMIT_MARGIN: u64 = 1_500_000; // 1.5ms; pub const POST_COMMIT_MARGIN_DELTA: u64 = 500_000; // 500us +#[derive(Copy, Clone)] +enum PresentFbWait { + Render, + Scanout, +} + impl MetalConnector { pub fn schedule_present(&self) { self.present_trigger.trigger(); @@ -227,6 +234,13 @@ impl MetalConnector { direct_scanout_id = fb.direct_scanout_data.as_ref().map(|d| d.dma_buf_id); present_fb = Some(fb); } + self.await_present_fb(present_fb.as_mut(), PresentFbWait::Render) + .await; + // perform_screencopies should return a sync file that we wait on before + // presentation since during screencopy the buffer layout might be mutated which + // could interfere with scanout. However, perform_screencopies just uses the + // current PresentFb if present_fb is None, potentially mutating the fb that is + // currently being scanned out, which would render such a wait absurd. self.perform_screencopies(&present_fb, &node, &cd); if let Some(sync_file) = self.cursor_sync_file.take() && let Err(e) = self.state.ring.readable(&sync_file).await @@ -236,7 +250,8 @@ impl MetalConnector { ErrorFmt(e) ); } - self.await_present_fb(present_fb.as_mut()).await; + self.await_present_fb(present_fb.as_mut(), PresentFbWait::Scanout) + .await; let mut changed_planes = ArrayVec::new(); let mut res = self.program_connector( version, @@ -259,7 +274,8 @@ impl MetalConnector { false, )?; present_fb = Some(fb); - self.await_present_fb(present_fb.as_mut()).await; + self.await_present_fb(present_fb.as_mut(), PresentFbWait::Scanout) + .await; res = self.program_connector( version, &crtc, @@ -330,17 +346,26 @@ impl MetalConnector { } } - async fn await_present_fb(&self, new_fb: Option<&mut PresentFb>) { + async fn await_present_fb(&self, new_fb: Option<&mut PresentFb>, wait: PresentFbWait) { + use PresentFbWait as W; let Some(fb) = new_fb else { return; }; - let Some(sync_file) = fb.sync_file.take() else { + let field = match wait { + W::Render => &mut fb.copy.render_block, + W::Scanout => &mut fb.copy.present_block, + }; + let Some(sync_file) = field.take() else { return; }; if let Err(e) = self.state.ring.readable(&sync_file).await { + let name = match wait { + W::Render => "render", + W::Scanout => "scanout", + }; log::error!( - "Could not wait for primary sync file to complete: {}", - ErrorFmt(e) + "Could not wait for primary {name} sync file to complete: {}", + ErrorFmt(e), ); } } @@ -532,7 +557,7 @@ impl MetalConnector { let swap_buffers = c.cursor_swap_buffer.is_some(); self.cursor_swap_buffer.set(swap_buffers); if let Some(sf) = c.cursor_swap_buffer.take() { - let sf = c.cursor_buffer.copy_to_dev(cd, sf)?; + let sf = c.cursor_buffer.copy_to_dev(cd, sf)?.present_block; self.cursor_sync_file.set(sf); } let mut cursor_changed = false; @@ -831,7 +856,7 @@ impl MetalConnector { }; log::debug!("{} direct scanout on {}", change, self.kernel_id()); } - let sync_file; + let copy; let fb; let tex; match &direct_scanout_data { @@ -848,17 +873,18 @@ impl MetalConnector { blend_cd, ) .map_err(MetalError::RenderFrame)?; - sync_file = buffer.copy_to_dev(cd, sf)?; + copy = buffer.copy_to_dev(cd, sf)?; fb = buffer.drm.clone(); tex = buffer.render_tex.clone(); } Some(dsd) => { - sync_file = match &dsd.acquire_sync { + let sf = match &dsd.acquire_sync { AcquireSync::None => None, AcquireSync::Implicit => None, AcquireSync::SyncFile { sync_file } => Some(sync_file.clone()), AcquireSync::Unnecessary => None, }; + copy = RenderBufferCopy::for_both(sf); fb = dsd.fb.clone(); tex = dsd.tex.clone(); } @@ -867,7 +893,7 @@ impl MetalConnector { fb, tex, direct_scanout_data, - sync_file, + copy, locked: latched.locked, }) } diff --git a/src/backends/metal/video.rs b/src/backends/metal/video.rs index 0aa3bfb3..16e8eb93 100644 --- a/src/backends/metal/video.rs +++ b/src/backends/metal/video.rs @@ -2965,6 +2965,21 @@ pub struct RenderBuffer { pub render_fb: Option>, } +#[derive(Default)] +pub struct RenderBufferCopy { + pub render_block: Option, + pub present_block: Option, +} + +impl RenderBufferCopy { + pub fn for_both(sf: Option) -> Self { + Self { + render_block: sf.clone(), + present_block: sf, + } + } +} + impl RenderBuffer { pub fn render_fb(&self) -> Rc { self.render_fb @@ -2976,9 +2991,12 @@ impl RenderBuffer { &self, cd: &Rc, sync_file: Option, - ) -> Result, MetalError> { + ) -> Result { let Some(tex) = &self.dev_tex else { - return Ok(sync_file); + return Ok(RenderBufferCopy { + render_block: None, + present_block: sync_file, + }); }; self.dev_fb .copy_texture( @@ -2994,6 +3012,7 @@ impl RenderBuffer { 0, ) .map_err(MetalError::CopyToOutput) + .map(RenderBufferCopy::for_both) } pub fn damage_full(&self) {