From a77929741a8c0c20a58f2e3be9969c6ab0987548 Mon Sep 17 00:00:00 2001 From: Julian Orth Date: Sun, 15 Feb 2026 19:04:25 +0100 Subject: [PATCH] metal: add support for copy-device based prime methods --- src/backends/metal/allocator.rs | 110 +++++++++++++++++++++++++++++++- src/backends/metal/monitor.rs | 1 + src/backends/metal/present.rs | 6 +- src/backends/metal/video.rs | 48 +++++++++++++- src/copy_device.rs | 7 -- src/state.rs | 1 - src/udmabuf.rs | 100 ++++++++++++++++++----------- 7 files changed, 223 insertions(+), 50 deletions(-) diff --git a/src/backends/metal/allocator.rs b/src/backends/metal/allocator.rs index 958299bb..3f5bc257 100644 --- a/src/backends/metal/allocator.rs +++ b/src/backends/metal/allocator.rs @@ -6,20 +6,22 @@ use { video::{MetalDrmDevice, MetalRenderContext}, }, cmm::cmm_description::ColorDescription, + copy_device::{CopyDevice, CopyDeviceError, CopyDeviceSupport}, format::Format, gfx_api::{ AcquireSync, GfxBlendBuffer, GfxError, GfxFormat, GfxFramebuffer, GfxTexture, GfxWriteModifier, ReleaseSync, SyncFile, needs_render_usage, }, - rect::{DamageQueue, Rect}, + rect::{DamageQueue, Rect, Region}, utils::{errorfmt::ErrorFmt, rc_eq::rc_eq}, video::{ - Modifier, + LINEAR_MODIFIER, Modifier, dmabuf::DmaBuf, drm::{DrmError, DrmFramebuffer}, gbm::{GBM_BO_USE_LINEAR, GBM_BO_USE_RENDERING, GBM_BO_USE_SCANOUT, GbmBo, GbmError}, }, }, + ahash::HashSet, arrayvec::ArrayVec, bstr::ByteSlice, indexmap::{IndexMap, IndexSet}, @@ -78,6 +80,10 @@ pub enum RenderBufferError { GfxError(#[from] GfxError), #[error("Could not copy frame to output device")] CopyToOutput(#[source] GfxError), + #[error("Could not create a copy device copy")] + CreateCopyDeviceCopy(#[source] CopyDeviceError), + #[error("Could not execute a copy device copy")] + ExecuteCopyDeviceCopy(#[source] CopyDeviceError), } #[derive(Default)] @@ -99,6 +105,7 @@ impl RenderBuffer { pub fn copy_to_dev( &self, cd: &Rc, + _region: Option<&Region>, sync_file: Option, ) -> Result { match &self.prime { @@ -158,6 +165,14 @@ impl RenderBuffer { return Err(RenderBufferError::NotSameSize); } + if let Some(dev) = new.dev_copy_device().or(old.dev_copy_device()) { + return dev + .create_copy(old.dev_bo().dmabuf(), new.dev_bo().dmabuf()) + .map_err(RenderBufferError::CreateCopyDeviceCopy)? + .execute(None, None) + .map_err(RenderBufferError::ExecuteCopyDeviceCopy); + } + let copy_texture_impl = |fb: &Rc, tex: &Rc| { fb.copy_texture( AcquireSync::Unnecessary, @@ -235,6 +250,13 @@ impl RenderBuffer { RenderBufferPrime::Sampling { dev_bo, .. } => dev_bo, } } + + pub fn dev_copy_device(&self) -> Option<&Rc> { + match &self.prime { + RenderBufferPrime::None => None, + RenderBufferPrime::Sampling { .. } => None, + } + } } struct Builder<'a> { @@ -459,11 +481,15 @@ impl RenderBufferPrime { #[derive(Default, Debug)] struct RenderBufferAllocationDebug { + dev_copy_src_modifiers: Option>, + dev_copy_dst_modifiers: Option>, dev_gfx_write_modifiers: Option>, dev_gfx_read_modifiers: Option>, dev_modifiers_possible: Option>, dev_usage: Option, dev_modifier: Option, + render_copy_src_modifiers: Option>, + render_copy_dst_modifiers: Option>, render_gfx_write_modifiers: Option>, render_gfx_read_modifiers: Option>, render_modifiers_possible: Option>, @@ -496,6 +522,12 @@ impl Display for ScanoutBufferError { writeln!(f, "plane modifiers: {:x?}", self.plane_modifiers)?; writeln!(f, "size: {}x{}", self.width, self.height)?; writeln!(f, "cursor: {}", self.cursor)?; + if let Some(v) = &self.dbg.dev_copy_src_modifiers { + writeln!(f, "scanout copy src modifiers: {:x?}", v)?; + } + if let Some(v) = &self.dbg.dev_copy_dst_modifiers { + writeln!(f, "scanout copy dst modifiers: {:x?}", v)?; + } if let Some(v) = &self.dbg.dev_gfx_write_modifiers { writeln!(f, "scanout gfx writable modifiers: {:x?}", v)?; } @@ -511,6 +543,12 @@ impl Display for ScanoutBufferError { if let Some(v) = &self.render_name { writeln!(f, "render device: {}", v)?; } + if let Some(v) = &self.dbg.render_copy_src_modifiers { + writeln!(f, "render copy src modifiers: {:x?}", v)?; + } + if let Some(v) = &self.dbg.render_copy_dst_modifiers { + writeln!(f, "render copy dst modifiers: {:x?}", v)?; + } if let Some(v) = &self.dbg.render_gfx_write_modifiers { writeln!(f, "render gfx writable modifiers: {:x?}", v)?; } @@ -729,6 +767,43 @@ impl Builder<'_> { }) } + fn copy_modifiers_iter(&self, support: &[CopyDeviceSupport]) -> impl Iterator { + let Builder { width, height, .. } = *self; + support + .iter() + .filter(move |s| s.max_width >= width as _ && s.max_height >= height as _) + .map(move |s| s.modifier) + } + + fn copy_modifiers(&self, support: &[CopyDeviceSupport]) -> Vec { + self.copy_modifiers_iter(support).collect() + } + + #[expect(dead_code)] + fn copy_src_modifiers(&self, dev: &CopyDevice) -> Vec { + self.copy_modifiers(dev.src_support(self.format)) + } + + #[expect(dead_code)] + fn copy_dst_modifiers(&self, dev: &CopyDevice) -> Vec { + self.copy_modifiers(dev.dst_support(self.format)) + } + + fn copy_supports_linear(&self, support: &[CopyDeviceSupport]) -> bool { + self.copy_modifiers_iter(support) + .any(|m| m == LINEAR_MODIFIER) + } + + #[expect(dead_code)] + fn copy_src_supports_linear(&self, dev: &CopyDevice) -> bool { + self.copy_supports_linear(dev.src_support(self.format)) + } + + #[expect(dead_code)] + fn copy_dst_supports_linear(&self, dev: &CopyDevice) -> bool { + self.copy_supports_linear(dev.dst_support(self.format)) + } + fn prepare_prime_none( &self, dbg: &RefCell, @@ -936,3 +1011,34 @@ fn sample_modifiers(fmt: &GfxFormat) -> Vec { fn render_modifiers(fmt: &GfxFormat) -> Vec { fmt.write_modifiers.keys().copied().collect() } + +fn intersect_modifiers<'a>( + left: impl IntoIterator, + right: impl IntoIterator, +) -> Vec { + let right: HashSet<_> = right.into_iter().copied().collect(); + left.into_iter() + .copied() + .filter(|m| right.contains(m)) + .collect() +} + +#[expect(dead_code)] +fn intersect_render_modifiers<'a>( + left: &'a GfxFormat, + right: impl IntoIterator, +) -> Vec { + intersect_modifiers( + left.write_modifiers + .keys() + .filter(|m| left.read_modifiers.contains(*m)), + right, + ) +} + +#[expect(dead_code)] +fn make_linear_only(modifiers: &mut Vec) { + if modifiers.contains(&LINEAR_MODIFIER) { + *modifiers = vec![LINEAR_MODIFIER]; + } +} diff --git a/src/backends/metal/monitor.rs b/src/backends/metal/monitor.rs index e5ca6daf..db9a85a2 100644 --- a/src/backends/metal/monitor.rs +++ b/src/backends/metal/monitor.rs @@ -131,6 +131,7 @@ impl MetalBackend { } fn handle_drm_device_removed(self: &Rc, dev: &Rc) { + self.state.copy_device_registry.remove(dev.dev.devnum); log::info!("Device removed: {}", dev.dev.devnode.to_bytes().as_bstr()); } diff --git a/src/backends/metal/present.rs b/src/backends/metal/present.rs index b1493ea8..a4edf006 100644 --- a/src/backends/metal/present.rs +++ b/src/backends/metal/present.rs @@ -557,7 +557,7 @@ impl MetalConnector { if let Some(sf) = c.cursor_swap_buffer.take() { let sf = c .cursor_buffer - .copy_to_dev(cd, sf) + .copy_to_dev(cd, None, sf) .map_err(MetalError::CopyToDev)? .present_block; self.cursor_sync_file.set(sf); @@ -875,7 +875,9 @@ impl MetalConnector { blend_cd, ) .map_err(MetalError::RenderFrame)?; - copy = buffer.copy_to_dev(cd, sf).map_err(MetalError::CopyToDev)?; + copy = buffer + .copy_to_dev(cd, Some(&latched.damage), sf) + .map_err(MetalError::CopyToDev)?; fb = buffer.drm.clone(); tex = buffer.render.tex.clone(); } diff --git a/src/backends/metal/video.rs b/src/backends/metal/video.rs index 89fd53f8..1598f478 100644 --- a/src/backends/metal/video.rs +++ b/src/backends/metal/video.rs @@ -22,6 +22,7 @@ use { transaction::{DrmConnectorState, DrmCrtcState, DrmPlaneState, MetalDeviceTransaction}, }, cmm::{cmm_description::ColorDescription, cmm_primaries::Primaries}, + copy_device::{CopyDevice, CopyDeviceRegistry}, drm_feedback::DrmFeedback, edid::{CtaDataBlock, Descriptor, EdidExtension}, format::{Format, XRGB8888}, @@ -58,7 +59,7 @@ use { isnt::std_1::collections::IsntHashMapExt, jay_config::video::GfxApi, std::{ - cell::{Cell, RefCell}, + cell::{Cell, OnceCell, RefCell}, collections::hash_map::Entry, ffi::CString, fmt::{Debug, Formatter}, @@ -84,6 +85,19 @@ pub struct MetalRenderContext { pub gfx: Rc, pub gbm: Rc, pub devnode: CString, + pub copy_device: Rc, +} + +pub struct CopyDeviceHolder { + pub registry: Rc, + pub devnum: dev_t, + pub dev: OnceCell>>, +} + +impl Debug for CopyDeviceHolder { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + f.debug_struct("CopyDeviceHolder").finish_non_exhaustive() + } } pub struct MetalDrmDevice { @@ -105,6 +119,8 @@ pub struct MetalDrmDevice { pub gbm: Rc, pub handle_events: HandleEvents, pub ctx: CloneCell>, + #[expect(dead_code)] + pub copy_device: Rc, pub on_change: OnChange, pub direct_scanout_enabled: Cell>, pub is_nvidia: bool, @@ -1987,6 +2003,12 @@ impl MetalBackend { Err(e) => return Err(MetalError::GbmDevice(e)), }; + let copy_device = Rc::new(CopyDeviceHolder { + registry: self.state.copy_device_registry.clone(), + devnum: pending.devnum, + dev: Default::default(), + }); + let gfx = match self.state.create_gfx_context(master, None) { Ok(r) => r, Err(e) => return Err(MetalError::CreateRenderContex(e)), @@ -1996,6 +2018,7 @@ impl MetalBackend { gfx, gbm: gbm.clone(), devnode: pending.devnode.clone(), + copy_device: copy_device.clone(), }); let mut is_nvidia = false; @@ -2037,6 +2060,7 @@ impl MetalBackend { handle_events: Cell::new(None), }, ctx: CloneCell::new(ctx), + copy_device, on_change: Default::default(), direct_scanout_enabled: Default::default(), is_nvidia, @@ -2462,6 +2486,7 @@ impl MetalBackend { gfx, gbm: old_ctx.gbm.clone(), devnode: old_ctx.devnode.clone(), + copy_device: old_ctx.copy_device.clone(), })); if dev.is_render_device() { self.make_render_device(dev, true); @@ -2622,3 +2647,24 @@ impl MetalBackend { connector.schedule_present(); } } + +impl CopyDeviceHolder { + #[expect(dead_code)] + pub fn get(&self) -> Option> { + self.dev + .get_or_init( + || match self.registry.get(self.devnum)?.create_device().map(Some) { + Ok(d) => d, + Err(e) => { + log::error!( + "Could not get copy device for {}: {}", + self.devnum, + ErrorFmt(e), + ); + None + } + }, + ) + .clone() + } +} diff --git a/src/copy_device.rs b/src/copy_device.rs index 512a297a..4acd68a8 100644 --- a/src/copy_device.rs +++ b/src/copy_device.rs @@ -629,12 +629,10 @@ impl PhysicalCopyDevice { Ok(dev) } - #[expect(dead_code)] pub fn src_support(&self, format: &Format) -> &[CopyDeviceSupport] { self.support(format, Dir::Src) } - #[expect(dead_code)] pub fn dst_support(&self, format: &Format) -> &[CopyDeviceSupport] { self.support(format, Dir::Dst) } @@ -646,7 +644,6 @@ impl PhysicalCopyDevice { .unwrap_or_default() } - #[expect(dead_code)] pub fn create_device(self: &Rc) -> Result, CopyDeviceError> { let instance = &self.instance.instance; let device = { @@ -1053,7 +1050,6 @@ impl CopyDevice { }) } - #[expect(dead_code)] pub fn create_copy( self: &Rc, src: &DmaBuf, @@ -1299,7 +1295,6 @@ impl CopyDeviceCopy { Ok(()) } - #[expect(dead_code)] pub fn execute( &self, sync_file: Option<&SyncFile>, @@ -1763,12 +1758,10 @@ impl CopyDeviceRegistry { } } - #[expect(dead_code)] pub fn remove(&self, dev: c::dev_t) { self.devs.remove(&dev); } - #[expect(dead_code)] pub fn get(&self, dev: c::dev_t) -> Option> { if let Some(dev) = self.devs.get(&dev) { return dev; diff --git a/src/state.rs b/src/state.rs index 01ebf3b4..790f9a9a 100644 --- a/src/state.rs +++ b/src/state.rs @@ -293,7 +293,6 @@ pub struct State { pub outputs_without_hc: NumCell, pub udmabuf: Rc, pub gfx_ctx_changed: EventSource, - #[expect(dead_code)] pub copy_device_registry: Rc, } diff --git a/src/udmabuf.rs b/src/udmabuf.rs index bb35c463..e398d692 100644 --- a/src/udmabuf.rs +++ b/src/udmabuf.rs @@ -115,6 +115,64 @@ impl Udmabuf { }; Ok(dmabuf) } + + #[expect(dead_code)] + pub fn create_dmabuf( + &self, + dma_buf_ids: &DmaBufIds, + width: i32, + height: i32, + format: &'static Format, + ) -> Result { + Ok(self.create_bo(dma_buf_ids, width, height, format)?.buf) + } + + fn create_bo( + &self, + dma_buf_ids: &DmaBufIds, + width: i32, + height: i32, + format: &'static Format, + ) -> Result { + let height = height as u64; + let width = width as u64; + if height > 1 << 16 || width > 1 << 16 { + return Err(UdmabufError::Overflow); + } + let stride = (width * format.bpp as u64).next_multiple_of(LINEAR_STRIDE_ALIGN); + let size_mask = page_size() as u64 - 1; + let size = (height * stride + size_mask) & !size_mask; + let memfd = match uapi::memfd_create("udmabuf", MFD_ALLOW_SEALING) { + Ok(f) => f, + Err(e) => return Err(UdmabufError::Memfd(e.into())), + }; + if let Err(e) = uapi::ftruncate(memfd.raw(), size as _) { + return Err(UdmabufError::Truncate(e.into())); + } + if let Err(e) = uapi::fcntl_add_seals(memfd.raw(), F_SEAL_SHRINK) { + return Err(UdmabufError::Seal(e.into())); + } + let dmabuf = self.create_dmabuf_from_memfd(&memfd, 0, size as _)?; + let mut planes = PlaneVec::new(); + planes.push(DmaBufPlane { + offset: 0, + stride: stride as _, + fd: Rc::new(dmabuf), + }); + let dmabuf = DmaBuf { + id: dma_buf_ids.next(), + width: width as _, + height: height as _, + format, + modifier: LINEAR_MODIFIER, + planes, + is_disjoint: Default::default(), + }; + Ok(UdmabufBo { + buf: dmabuf, + size: size as _, + }) + } } impl Allocator for Udmabuf { @@ -134,44 +192,12 @@ impl Allocator for Udmabuf { if !modifiers.contains(&LINEAR_MODIFIER) { return Err(UdmabufError::Modifier.into()); } - let height = height as u64; - let width = width as u64; - if height > 1 << 16 || width > 1 << 16 { - return Err(UdmabufError::Overflow.into()); - } - let stride = (width * format.bpp as u64).next_multiple_of(LINEAR_STRIDE_ALIGN); - let size_mask = page_size() as u64 - 1; - let size = (height * stride + size_mask) & !size_mask; - let memfd = match uapi::memfd_create("udmabuf", MFD_ALLOW_SEALING) { - Ok(f) => f, - Err(e) => return Err(UdmabufError::Memfd(e.into()).into()), - }; - if let Err(e) = uapi::ftruncate(memfd.raw(), size as _) { - return Err(UdmabufError::Truncate(e.into()).into()); - } - if let Err(e) = uapi::fcntl_add_seals(memfd.raw(), F_SEAL_SHRINK) { - return Err(UdmabufError::Seal(e.into()).into()); - } - let dmabuf = self.create_dmabuf_from_memfd(&memfd, 0, size as _)?; - let mut planes = PlaneVec::new(); - planes.push(DmaBufPlane { - offset: 0, - stride: stride as _, - fd: Rc::new(dmabuf), - }); - let dmabuf = DmaBuf { - id: dma_buf_ids.next(), - width: width as _, - height: height as _, + Ok(Rc::new(self.create_bo( + dma_buf_ids, + width, + height, format, - modifier: LINEAR_MODIFIER, - planes, - is_disjoint: Default::default(), - }; - Ok(Rc::new(UdmabufBo { - buf: dmabuf, - size: size as _, - })) + )?)) } fn import_dmabuf(