From f2a0221c9e8f816396f95ded11ef3bf3944ea72f Mon Sep 17 00:00:00 2001 From: Julian Orth Date: Fri, 13 Feb 2026 17:10:09 +0100 Subject: [PATCH] metal: refactor buffer allocation --- src/backends/metal.rs | 135 +---- src/backends/metal/allocator.rs | 938 ++++++++++++++++++++++++++++++ src/backends/metal/present.rs | 24 +- src/backends/metal/transaction.rs | 33 +- src/backends/metal/video.rs | 460 +-------------- 5 files changed, 1000 insertions(+), 590 deletions(-) create mode 100644 src/backends/metal/allocator.rs diff --git a/src/backends/metal.rs b/src/backends/metal.rs index 978e0b98..de329aa3 100644 --- a/src/backends/metal.rs +++ b/src/backends/metal.rs @@ -1,3 +1,4 @@ +mod allocator; mod input; mod monitor; mod present; @@ -12,13 +13,15 @@ use { InputDeviceClickMethod, InputDeviceGroupId, InputDeviceId, InputEvent, KeyState, Leds, TransformMatrix, transaction::BackendConnectorTransactionError, }, - backends::metal::video::{ - MetalDrmDeviceData, MetalLeaseData, MetalRenderContext, PendingDrmDevice, - PersistentDisplayData, + backends::metal::{ + allocator::{RenderBufferError, ScanoutBufferError, ScanoutBufferErrors}, + video::{ + MetalDrmDeviceData, MetalLeaseData, MetalRenderContext, PendingDrmDevice, + PersistentDisplayData, + }, }, dbus::{DbusError, SignalHandler}, drm_feedback::DrmFeedback, - format::Format, gfx_api::{GfxError, SyncFile}, ifs::{ wl_output::OutputId, @@ -49,15 +52,14 @@ use { smallmap::SmallMap, syncqueue::SyncQueue, }, - video::{Modifier, drm::DrmError, gbm::GbmError}, + video::{drm::DrmError, gbm::GbmError}, }, bstr::ByteSlice, - indexmap::IndexSet, std::{ cell::{Cell, RefCell}, error::Error, ffi::{CStr, CString}, - fmt::{Debug, Display, Formatter}, + fmt::{Debug, Formatter}, future::pending, rc::Rc, }, @@ -105,8 +107,6 @@ pub enum MetalError { DeviceResumeSignalHandler(#[source] DbusError), #[error("Could not render the frame")] RenderFrame(#[source] GfxError), - #[error("Could not copy frame to output device")] - CopyToOutput(#[source] GfxError), #[error("Could not perform atomic commit")] Commit(#[source] DrmError), #[error("The present configuration is out of date")] @@ -117,119 +117,14 @@ pub enum MetalError { CalculateDrmState(#[source] BackendConnectorTransactionError), #[error("Could not calculate DRM change set")] CalculateDrmChange(#[source] BackendConnectorTransactionError), - #[error("Could not create plane buffer")] + #[error("Could not create non-prime plane buffer")] AllocateScanoutBuffer(#[source] Box), -} - -#[derive(Debug, Error)] -pub enum ScanoutBufferErrorKind { - #[error("Scanout device: The format is not supported")] - SodUnsupportedFormat, - #[error( - "Scanout device: The intersection of the modifiers supported by the plane and modifiers writable by the gfx API is empty" - )] - SodNoWritableModifier, - #[error("Scanout device: Buffer allocation failed")] - SodBufferAllocation(#[source] GbmError), - #[error("Scanout device: addfb2 failed")] - SodAddfb2(#[source] DrmError), - #[error("Scanout device: Could not import SCANOUT buffer into the gfx API")] - SodImportSodImage(#[source] GfxError), - #[error("Scanout device: Could not turn imported SCANOUT buffer into gfx API FB")] - SodImportFb(#[source] GfxError), - #[error("Scanout device: Could not clear SCANOUT buffer")] - SodClear(#[source] GfxError), - #[error("Scanout device: Could not turn imported SCANOUT buffer into gfx API texture")] - SodImportSodTexture(#[source] GfxError), - #[error("Render device: The format is not supported")] + #[error("Could not create non-prime plane buffer")] + AllocateScanoutBufferPrime(#[source] ScanoutBufferErrors), + #[error("Could not copy frame to output device")] + CopyToDev(#[source] RenderBufferError), + #[error("The render device does not support the format")] RenderUnsupportedFormat, - #[error( - "Render device: The intersection of the modifiers readable by the scanout device and modifiers writable by the gfx API is empty" - )] - RenderNoWritableModifier, - #[error("Render device: Buffer allocation failed")] - RenderBufferAllocation(#[source] GbmError), - #[error("Render device: Could not import RENDER buffer into the gfx API")] - RenderImportImage(#[source] GfxError), - #[error("Render device: Could not turn imported RENDER buffer into gfx API FB")] - RenderImportFb(#[source] GfxError), - #[error("Render device: Could not clear RENDER buffer")] - RenderClear(#[source] GfxError), - #[error("Render device: Could not turn imported RENDER buffer into gfx API texture")] - RenderImportRenderTexture(#[source] GfxError), - #[error("Scanout device: Could not import RENDER buffer into the gfx API")] - SodImportRenderImage(#[source] GfxError), - #[error("Scanout device: Could not turn imported RENDER buffer into gfx API texture")] - SodImportRenderTexture(#[source] GfxError), -} - -#[derive(Debug)] -pub struct ScanoutBufferError { - dev: String, - format: &'static Format, - plane_modifiers: IndexSet, - width: i32, - height: i32, - cursor: bool, - dev_gfx_write_modifiers: Option>, - dev_gfx_read_modifiers: Option>, - dev_modifiers_possible: Option>, - dev_usage: Option, - dev_modifier: Option, - render_name: Option, - render_gfx_write_modifiers: Option>, - render_modifiers_possible: Option>, - render_usage: Option, - render_modifier: Option, - kind: ScanoutBufferErrorKind, -} - -impl Display for ScanoutBufferError { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - writeln!(f)?; - writeln!(f, "scanout device: {}", self.dev)?; - writeln!(f, "format: {}", self.format.name)?; - 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.dev_gfx_write_modifiers { - writeln!(f, "scanout gfx writable modifiers: {:x?}", v)?; - } - if let Some(v) = &self.dev_modifiers_possible { - writeln!(f, "scanout dev possible modifiers: {:x?}", v)?; - } - if let Some(v) = &self.dev_usage { - writeln!(f, "scanout dev gbm usage: {:x}", v)?; - } - if let Some(v) = &self.dev_modifier { - writeln!(f, "scanout dev modifier: {:x}", v)?; - } - if let Some(v) = &self.render_name { - writeln!(f, "render device: {}", v)?; - } - if let Some(v) = &self.render_gfx_write_modifiers { - writeln!(f, "render gfx writable modifiers: {:x?}", v)?; - } - if let Some(v) = &self.dev_gfx_read_modifiers { - writeln!(f, "scanout gfx readable modifiers: {:x?}", v)?; - } - if let Some(v) = &self.render_modifiers_possible { - writeln!(f, "render dev possible modifiers: {:x?}", v)?; - } - if let Some(v) = &self.render_usage { - writeln!(f, "render dev gbm usage: {:x}", v)?; - } - if let Some(v) = &self.render_modifier { - writeln!(f, "render dev modifier: {:x}", v)?; - } - Ok(()) - } -} - -impl Error for ScanoutBufferError { - fn source(&self) -> Option<&(dyn Error + 'static)> { - Some(&self.kind) - } } pub struct MetalBackend { diff --git a/src/backends/metal/allocator.rs b/src/backends/metal/allocator.rs new file mode 100644 index 00000000..958299bb --- /dev/null +++ b/src/backends/metal/allocator.rs @@ -0,0 +1,938 @@ +use { + crate::{ + allocator::BufferObject, + backends::metal::{ + MetalBackend, MetalError, + video::{MetalDrmDevice, MetalRenderContext}, + }, + cmm::cmm_description::ColorDescription, + format::Format, + gfx_api::{ + AcquireSync, GfxBlendBuffer, GfxError, GfxFormat, GfxFramebuffer, GfxTexture, + GfxWriteModifier, ReleaseSync, SyncFile, needs_render_usage, + }, + rect::{DamageQueue, Rect}, + utils::{errorfmt::ErrorFmt, rc_eq::rc_eq}, + video::{ + Modifier, + dmabuf::DmaBuf, + drm::{DrmError, DrmFramebuffer}, + gbm::{GBM_BO_USE_LINEAR, GBM_BO_USE_RENDERING, GBM_BO_USE_SCANOUT, GbmBo, GbmError}, + }, + }, + arrayvec::ArrayVec, + bstr::ByteSlice, + indexmap::{IndexMap, IndexSet}, + isnt::std_1::primitive::IsntSliceExt, + linearize::{Linearize, LinearizeExt, StaticMap}, + log::Level, + run_on_drop::on_drop, + std::{ + cell::{Cell, RefCell}, + error::Error, + fmt::{self, Debug, Display, Formatter}, + rc::Rc, + sync::LazyLock, + }, + thiserror::Error, +}; + +#[derive(Debug)] +pub struct RenderBuffer { + pub width: i32, + pub height: i32, + pub locked: Cell, + pub format: &'static Format, + pub drm: Rc, + pub damage_queue: DamageQueue, + pub blend_buffer: Option>, + pub render: RenderBufferRender, + pub dev_ctx: Rc, + pub prime: RenderBufferPrime, +} + +#[derive(Debug)] +pub struct RenderBufferRender { + pub ctx: Rc, + pub bo: GbmBo, + pub tex: Rc, + pub fb: Rc, +} + +#[derive(Debug)] +pub enum RenderBufferPrime { + None, + Sampling { + dev_bo: GbmBo, + dev_fb: Rc, + // Import of the render dmabuf into the dev ctx. + dev_render_tex: Rc, + }, +} + +#[derive(Debug, Error)] +pub enum RenderBufferError { + #[error("Cannot copy between buffers of different size")] + NotSameSize, + #[error(transparent)] + GfxError(#[from] GfxError), + #[error("Could not copy frame to output device")] + CopyToOutput(#[source] GfxError), +} + +#[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 copy_to_dev( + &self, + cd: &Rc, + sync_file: Option, + ) -> Result { + match &self.prime { + RenderBufferPrime::None => Ok(RenderBufferCopy { + render_block: None, + present_block: sync_file, + }), + RenderBufferPrime::Sampling { + dev_render_tex, + dev_fb, + .. + } => dev_fb + .copy_texture( + AcquireSync::Unnecessary, + ReleaseSync::Explicit, + cd, + dev_render_tex, + cd, + None, + AcquireSync::from_sync_file(sync_file), + ReleaseSync::None, + 0, + 0, + ) + .map_err(RenderBufferError::CopyToOutput) + .map(RenderBufferCopy::for_both), + } + } + + pub fn damage_full(&self) { + let rect = Rect::new_sized_saturating(0, 0, self.width, self.height); + self.damage_queue.clear_all(); + self.damage_queue.damage(&[rect]); + } + + pub fn clear(&self, cd: &Rc) -> Result, RenderBufferError> { + match &self.prime { + RenderBufferPrime::None => self + .render + .fb + .clear(AcquireSync::Unnecessary, ReleaseSync::Explicit, cd) + .map_err(Into::into), + RenderBufferPrime::Sampling { dev_fb, .. } => dev_fb + .clear(AcquireSync::Unnecessary, ReleaseSync::Explicit, cd) + .map_err(Into::into), + } + } + + pub fn copy_to_new( + &self, + new: &Self, + cd: &Rc, + ) -> Result, RenderBufferError> { + let old = self; + + if (old.width, old.height) != (new.width, new.height) { + return Err(RenderBufferError::NotSameSize); + } + + let copy_texture_impl = |fb: &Rc, tex: &Rc| { + fb.copy_texture( + AcquireSync::Unnecessary, + ReleaseSync::Explicit, + cd, + tex, + cd, + None, + AcquireSync::Unnecessary, + ReleaseSync::Explicit, + 0, + 0, + ) + }; + let copy_texture = |new_ctx: &Rc, + fb: &Rc, + old_ctx: &Rc, + tex: &Rc, + dma_buf: &DmaBuf| { + if rc_eq(&new_ctx.gfx, &old_ctx.gfx) { + return copy_texture_impl(fb, tex); + } + let tex = new_ctx.gfx.clone().dmabuf_img(dma_buf)?.to_texture()?; + copy_texture_impl(fb, &tex) + }; + + let sf = match &old.prime { + RenderBufferPrime::None => match &new.prime { + RenderBufferPrime::None => copy_texture( + &new.render.ctx, + &new.render.fb, + &old.render.ctx, + &old.render.tex, + old.render.bo.dmabuf(), + )?, + RenderBufferPrime::Sampling { + dev_fb: new_dev_fb, .. + } => copy_texture( + &new.dev_ctx, + new_dev_fb, + &old.render.ctx, + &old.render.tex, + old.render.bo.dmabuf(), + )?, + }, + RenderBufferPrime::Sampling { + dev_render_tex: old_dev_render_tex, + dev_bo: old_dev_bo, + .. + } => match &new.prime { + RenderBufferPrime::None => copy_texture( + &new.render.ctx, + &new.render.fb, + &old.dev_ctx, + old_dev_render_tex, + old_dev_bo.dmabuf(), + )?, + RenderBufferPrime::Sampling { + dev_fb: new_dev_fb, .. + } => copy_texture( + &new.dev_ctx, + &new_dev_fb, + &old.dev_ctx, + old_dev_render_tex, + old_dev_bo.dmabuf(), + )?, + }, + }; + Ok(sf) + } + + pub fn dev_bo(&self) -> &GbmBo { + match &self.prime { + RenderBufferPrime::None => &self.render.bo, + RenderBufferPrime::Sampling { dev_bo, .. } => dev_bo, + } + } +} + +struct Builder<'a> { + slf: &'a MetalBackend, + dev: &'a Rc, + dev_ctx: &'a Rc, + format: &'static Format, + render_fmt: &'a GfxFormat, + plane_modifiers: &'a IndexSet, + width: i32, + height: i32, + render_ctx: &'a Rc, + cursor: bool, + blend_buffer: Option<&'a Rc>, +} + +struct BoAllocationSettings { + modifiers: Vec, + usage: u32, +} + +struct NoPrime { + allocation_settings: BoAllocationSettings, +} + +struct PrimeSampling { + render_allocation_settings: BoAllocationSettings, + dev_allocation_settings: BoAllocationSettings, +} + +impl MetalBackend { + pub fn create_scanout_buffers( + &self, + dev: &Rc, + format: &'static Format, + plane_modifiers: &IndexSet, + width: i32, + height: i32, + render_ctx: &Rc, + cursor: bool, + ) -> Result<[RenderBuffer; N], MetalError> { + let Some(render_fmt) = render_ctx.gfx.formats().get(&format.drm) else { + return Err(MetalError::RenderUnsupportedFormat); + }; + let mut blend_buffer = None; + if !cursor { + match render_ctx.gfx.acquire_blend_buffer(width, height) { + Ok(bb) => blend_buffer = Some(bb), + Err(e) => { + log::warn!("Could not create blend buffer: {}", ErrorFmt(e)); + } + } + } + let builder = Builder { + slf: self, + dev, + dev_ctx: &dev.ctx.get(), + format, + render_fmt, + plane_modifiers, + width, + height, + render_ctx, + cursor, + blend_buffer: blend_buffer.as_ref(), + }; + if render_ctx.dev_id == dev.id { + return wrap_error(&builder, None, |dbg| { + let prepared = &builder.prepare_prime_none(dbg)?; + self.create_scanout_buffers_(|damage| { + builder.create_prime_none(prepared, damage, dbg) + }) + }) + .map_err(MetalError::AllocateScanoutBuffer); + } + let mut errors = ScanoutBufferErrors::default(); + for &method in &*PRIME_METHODS { + let res = wrap_error(&builder, Some(method), |dbg| { + macro_rules! x { + ($prepare:ident, $create:ident $(,)?) => {{ + let prepared = &builder.$prepare(dbg)?; + self.create_scanout_buffers_(|damage| { + builder.$create(prepared, damage, dbg) + }) + }}; + } + match method { + PrimeMethod::Sampling => { + x!(prepare_prime_sampling, create_prime_sampling) + } + } + }); + match res { + Err(e) => errors.errors.push(e), + Ok(b) => { + if errors.errors.is_not_empty() { + log::warn!("Preferred prime methods failed"); + let debug = log::log_enabled!(Level::Debug); + for error in &errors.errors { + let Some(method) = error.prime else { + continue; + }; + if debug { + log::warn!("- {method}: {}", ErrorFmt(error)); + } else { + log::warn!("- {method}: {}", ErrorFmt(&error.kind)); + } + } + } + return Ok(b); + } + } + } + Err(MetalError::AllocateScanoutBufferPrime(errors)) + } + + fn create_scanout_buffers_( + &self, + allocate: impl Fn(DamageQueue) -> Result, + ) -> Result<[RenderBuffer; N], ScanoutBufferErrorKind> { + let mut damage_queue = ArrayVec::from(DamageQueue::new::()); + let mut array = ArrayVec::<_, N>::new(); + for _ in 0..N { + let damage_queue = damage_queue.pop().unwrap(); + array.push(allocate(damage_queue)?); + } + if let Some(buffer) = array.first() { + buffer.damage_full(); + } + Ok(array.into_inner().unwrap()) + } +} + +#[derive(Debug, Error)] +pub enum ScanoutBufferErrorKind { + #[error("Scanout device: The format is not supported")] + SodUnsupportedFormat, + #[error("Scanout device: Buffer allocation failed")] + SodBufferAllocation(#[source] GbmError), + #[error("Scanout device: addfb2 failed")] + SodAddfb2(#[source] DrmError), + #[error("Scanout device: Could not import SCANOUT buffer into the gfx API")] + SodImportSodImage(#[source] GfxError), + #[error("Scanout device: Could not turn imported SCANOUT buffer into gfx API FB")] + SodImportFb(#[source] GfxError), + #[error("Render device: The intersection of render/sample/sod_sample modifiers is empty")] + RenderWriteReadSodReadIntersection, + #[error("Scanout device: The intersection of render/sample/plane modifiers is empty")] + SodWriteReadPlaneIntersection, + #[error("Scanout device: The intersection of render/plane modifiers is empty")] + SodWritePlaneIntersection, + #[error("Render device: Buffer allocation failed")] + RenderBufferAllocation(#[source] GbmError), + #[error("Render device: Could not import RENDER buffer into the gfx API")] + RenderImportImage(#[source] GfxError), + #[error("Render device: Could not turn imported RENDER buffer into gfx API FB")] + RenderImportFb(#[source] GfxError), + #[error("Render device: Could not clear RENDER buffer")] + RenderClear(#[source] GfxError), + #[error("Render device: Could not turn imported RENDER buffer into gfx API texture")] + RenderImportRenderTexture(#[source] GfxError), + #[error("Scanout device: Could not import RENDER buffer into the gfx API")] + SodImportRenderImage(#[source] GfxError), + #[error("Scanout device: Could not turn imported RENDER buffer into gfx API texture")] + SodImportRenderTexture(#[source] GfxError), +} + +#[derive(Default, Debug)] +pub struct ScanoutBufferErrors { + #[expect(clippy::vec_box)] + errors: Vec>, +} + +#[derive(Debug)] +pub struct ScanoutBufferError { + dev: String, + render_name: Option, + format: &'static Format, + plane_modifiers: IndexSet, + width: i32, + height: i32, + cursor: bool, + dbg: RenderBufferAllocationDebug, + kind: ScanoutBufferErrorKind, + prime: Option, +} + +#[derive(Copy, Clone, Linearize)] +pub enum PrimeMethod { + Sampling, +} + +impl PrimeMethod { + pub fn name(self) -> &'static str { + match self { + PrimeMethod::Sampling => "direct-sampling", + } + } +} + +impl Display for PrimeMethod { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + f.write_str(self.name()) + } +} + +impl Debug for PrimeMethod { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + f.write_str(self.name()) + } +} + +impl RenderBufferPrime { + pub fn method(&self) -> Option { + let method = match self { + RenderBufferPrime::None => return None, + RenderBufferPrime::Sampling { .. } => PrimeMethod::Sampling, + }; + Some(method) + } +} + +#[derive(Default, Debug)] +struct RenderBufferAllocationDebug { + dev_gfx_write_modifiers: Option>, + dev_gfx_read_modifiers: Option>, + dev_modifiers_possible: Option>, + dev_usage: Option, + dev_modifier: Option, + render_gfx_write_modifiers: Option>, + render_gfx_read_modifiers: Option>, + render_modifiers_possible: Option>, + render_usage: Option, + render_modifier: Option, +} + +impl Display for ScanoutBufferErrors { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + for (idx, error) in self.errors.iter().enumerate() { + if idx > 0 { + writeln!(f, "\n------")?; + } + write!(f, "{}", ErrorFmt(error))?; + } + Ok(()) + } +} + +impl Error for ScanoutBufferErrors {} + +impl Display for ScanoutBufferError { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + writeln!(f)?; + if let Some(v) = self.prime { + writeln!(f, "prime type: {}", v)?; + } + writeln!(f, "scanout device: {}", self.dev)?; + writeln!(f, "format: {}", self.format.name)?; + 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_gfx_write_modifiers { + writeln!(f, "scanout gfx writable modifiers: {:x?}", v)?; + } + if let Some(v) = &self.dbg.dev_modifiers_possible { + writeln!(f, "scanout dev possible modifiers: {:x?}", v)?; + } + if let Some(v) = &self.dbg.dev_usage { + writeln!(f, "scanout dev gbm usage: {:x}", v)?; + } + if let Some(v) = &self.dbg.dev_modifier { + writeln!(f, "scanout dev modifier: {:x}", v)?; + } + if let Some(v) = &self.render_name { + writeln!(f, "render device: {}", v)?; + } + if let Some(v) = &self.dbg.render_gfx_write_modifiers { + writeln!(f, "render gfx writable modifiers: {:x?}", v)?; + } + if let Some(v) = &self.dbg.render_gfx_read_modifiers { + writeln!(f, "render gfx readable modifiers: {:x?}", v)?; + } + if let Some(v) = &self.dbg.dev_gfx_read_modifiers { + writeln!(f, "scanout gfx readable modifiers: {:x?}", v)?; + } + if let Some(v) = &self.dbg.render_modifiers_possible { + writeln!(f, "render dev possible modifiers: {:x?}", v)?; + } + if let Some(v) = &self.dbg.render_usage { + writeln!(f, "render dev gbm usage: {:x}", v)?; + } + if let Some(v) = &self.dbg.render_modifier { + writeln!(f, "render dev modifier: {:x}", v)?; + } + Ok(()) + } +} + +impl Error for ScanoutBufferError { + fn source(&self) -> Option<&(dyn Error + 'static)> { + Some(&self.kind) + } +} + +fn wrap_error( + common: &Builder<'_>, + prime: Option, + f: impl FnOnce(&RefCell) -> Result, +) -> Result> { + let dbg = Default::default(); + f(&dbg) + .map_err(|kind| ScanoutBufferError { + dev: common.dev.devnode.as_bytes().as_bstr().to_string(), + render_name: (common.dev.id != common.render_ctx.dev_id) + .then(|| common.render_ctx.devnode.as_bytes().as_bstr().to_string()), + format: common.format, + plane_modifiers: common.plane_modifiers.clone(), + width: common.width, + height: common.height, + cursor: common.cursor, + dbg: dbg.into_inner(), + kind, + prime, + }) + .map_err(Box::new) +} + +impl BoAllocationSettings { + fn new1( + common: &Builder<'_>, + modifiers: &IndexMap, + scanout: bool, + rendering: bool, + usage_out: &mut Option, + ) -> Self { + let needs_render_usage = rendering && needs_render_usage(modifiers.values().copied()); + Self::new3( + common, + modifiers.keys(), + scanout, + needs_render_usage, + usage_out, + ) + } + + #[expect(dead_code)] + fn new2<'a>( + common: &Builder<'_>, + modifiers: impl IntoIterator + Clone, + fmt: &GfxFormat, + scanout: bool, + rendering: bool, + usage_out: &mut Option, + ) -> Self { + let needs_render_usage = rendering + && needs_render_usage( + modifiers + .clone() + .into_iter() + .filter_map(|m| fmt.write_modifiers.get(m)), + ); + Self::new3(common, modifiers, scanout, needs_render_usage, usage_out) + } + + fn new3<'a>( + common: &Builder<'_>, + modifiers: impl IntoIterator, + scanout: bool, + needs_render_usage: bool, + usage_out: &mut Option, + ) -> Self { + let mut usage = 0; + if scanout { + usage |= GBM_BO_USE_SCANOUT; + if common.cursor { + usage |= GBM_BO_USE_LINEAR; + } + } + if needs_render_usage { + usage |= GBM_BO_USE_RENDERING; + } + *usage_out = Some(usage); + Self { + modifiers: modifiers.into_iter().copied().collect(), + usage, + } + } +} + +impl Builder<'_> { + fn create( + &self, + drm: Rc, + damage_queue: DamageQueue, + render: RenderBufferRender, + prime: RenderBufferPrime, + ) -> Result { + let Self { + dev_ctx, + format, + width, + height, + blend_buffer, + .. + } = *self; + Ok(RenderBuffer { + width, + height, + locked: Cell::new(true), + format, + drm, + damage_queue, + blend_buffer: blend_buffer.cloned(), + render, + dev_ctx: dev_ctx.clone(), + prime, + }) + } + + fn create_bo( + &self, + ctx: &MetalRenderContext, + settings: &BoAllocationSettings, + ) -> Result { + ctx.gbm.create_bo( + &self.slf.state.dma_buf_ids, + self.width, + self.height, + self.format, + &settings.modifiers, + settings.usage, + ) + } + + fn create_dev_bo( + &self, + settings: &BoAllocationSettings, + dbg: &RefCell, + ) -> Result<(GbmBo, Rc), ScanoutBufferErrorKind> { + let bo = self + .create_bo(self.dev_ctx, settings) + .map_err(ScanoutBufferErrorKind::SodBufferAllocation)?; + let send_dev_modifier = on_drop(|| { + dbg.borrow_mut().dev_modifier = Some(bo.dmabuf().modifier); + }); + let drm = self + .dev + .master + .add_fb(bo.dmabuf(), None) + .map(Rc::new) + .map_err(ScanoutBufferErrorKind::SodAddfb2)?; + send_dev_modifier.forget(); + Ok((bo, drm)) + } + + fn create_render_buffer_render( + &self, + settings: &BoAllocationSettings, + dbg: &RefCell, + ) -> Result { + let Self { render_ctx, .. } = *self; + let bo = self + .create_bo(render_ctx, settings) + .map_err(ScanoutBufferErrorKind::RenderBufferAllocation)?; + let send_render_modifier = on_drop(|| { + dbg.borrow_mut().render_modifier = Some(bo.dmabuf().modifier); + }); + let img = render_ctx + .gfx + .clone() + .dmabuf_img(bo.dmabuf()) + .map_err(ScanoutBufferErrorKind::RenderImportImage)?; + let tex = img + .clone() + .to_texture() + .map_err(ScanoutBufferErrorKind::RenderImportRenderTexture)?; + let fb = img + .to_framebuffer() + .map_err(ScanoutBufferErrorKind::RenderImportFb)?; + fb.clear( + AcquireSync::Unnecessary, + ReleaseSync::None, + self.slf.state.color_manager.srgb_gamma22(), + ) + .map_err(ScanoutBufferErrorKind::RenderClear)?; + send_render_modifier.forget(); + Ok(RenderBufferRender { + ctx: render_ctx.clone(), + bo, + tex, + fb, + }) + } + + fn prepare_prime_none( + &self, + dbg: &RefCell, + ) -> Result { + let dbg = &mut *dbg.borrow_mut(); + let Self { + render_fmt, + plane_modifiers, + .. + } = *self; + let modifiers: IndexMap<_, _> = render_fmt + .write_modifiers + .iter() + .map(|(m, v)| (*m, v)) + .filter(|(m, _)| plane_modifiers.contains(m)) + .filter(|(m, _)| render_fmt.read_modifiers.contains(m)) + .collect(); + dbg.dev_gfx_write_modifiers = Some(render_modifiers(render_fmt)); + dbg.dev_gfx_read_modifiers = Some(sample_modifiers(render_fmt)); + dbg.dev_modifiers_possible = Some(modifiers.keys().copied().collect()); + if modifiers.is_empty() { + return Err(ScanoutBufferErrorKind::SodWriteReadPlaneIntersection); + } + let allocation_settings = + BoAllocationSettings::new1(self, &modifiers, true, true, &mut dbg.render_usage); + Ok(NoPrime { + allocation_settings, + }) + } + + fn create_prime_none( + &self, + prepared: &NoPrime, + damage_queue: DamageQueue, + dbg: &RefCell, + ) -> Result { + let NoPrime { + allocation_settings, + } = prepared; + let Self { dev, .. } = *self; + let render = self.create_render_buffer_render(allocation_settings, dbg)?; + let send_dev_modifier = on_drop(|| { + dbg.borrow_mut().dev_modifier = Some(render.bo.dmabuf().modifier); + }); + let drm = dev + .master + .add_fb(render.bo.dmabuf(), None) + .map(Rc::new) + .map_err(ScanoutBufferErrorKind::SodAddfb2)?; + send_dev_modifier.forget(); + let prime = RenderBufferPrime::None; + self.create(drm, damage_queue, render, prime) + } + + fn prepare_prime_sampling( + &self, + dbg: &RefCell, + ) -> Result { + let dbg = &mut *dbg.borrow_mut(); + let Self { + dev_ctx, + format, + render_fmt, + plane_modifiers, + .. + } = *self; + let Some(dev_fmt) = dev_ctx.gfx.formats().get(&format.drm) else { + return Err(ScanoutBufferErrorKind::SodUnsupportedFormat); + }; + let render_modifiers_possible: IndexMap<_, _> = render_fmt + .write_modifiers + .iter() + .filter(|(m, _)| render_fmt.read_modifiers.contains(*m)) + .filter(|(m, _)| dev_fmt.read_modifiers.contains(*m)) + .map(|(m, v)| (*m, v)) + .collect(); + dbg.dev_gfx_read_modifiers = Some(sample_modifiers(dev_fmt)); + dbg.render_gfx_write_modifiers = Some(render_modifiers(render_fmt)); + dbg.render_gfx_read_modifiers = Some(sample_modifiers(render_fmt)); + dbg.render_modifiers_possible = Some(render_modifiers_possible.keys().copied().collect()); + if render_modifiers_possible.is_empty() { + return Err(ScanoutBufferErrorKind::RenderWriteReadSodReadIntersection); + } + let dev_modifiers_possible: IndexMap<_, _> = dev_fmt + .write_modifiers + .iter() + .filter(|(m, _)| plane_modifiers.contains(*m)) + .map(|(m, v)| (*m, v)) + .collect(); + dbg.dev_gfx_write_modifiers = Some(render_modifiers(dev_fmt)); + dbg.dev_modifiers_possible = Some(dev_modifiers_possible.keys().copied().collect()); + if dev_modifiers_possible.is_empty() { + return Err(ScanoutBufferErrorKind::SodWritePlaneIntersection); + } + let render_allocation_settings = BoAllocationSettings::new1( + self, + &render_modifiers_possible, + false, + true, + &mut dbg.render_usage, + ); + let dev_allocation_settings = BoAllocationSettings::new1( + self, + &dev_modifiers_possible, + true, + true, + &mut dbg.dev_usage, + ); + Ok(PrimeSampling { + render_allocation_settings, + dev_allocation_settings, + }) + } + + fn create_prime_sampling( + &self, + prepared: &PrimeSampling, + damage_queue: DamageQueue, + dbg: &RefCell, + ) -> Result { + let PrimeSampling { + render_allocation_settings, + dev_allocation_settings, + } = prepared; + let Self { dev_ctx, .. } = *self; + let render = self.create_render_buffer_render(render_allocation_settings, dbg)?; + let send_render_modifier = on_drop(|| { + dbg.borrow_mut().render_modifier = Some(render.bo.dmabuf().modifier); + }); + let dev_render_tex = dev_ctx + .gfx + .clone() + .dmabuf_img(render.bo.dmabuf()) + .map_err(ScanoutBufferErrorKind::SodImportRenderImage)? + .to_texture() + .map_err(ScanoutBufferErrorKind::SodImportRenderTexture)?; + let (dev_bo, drm) = self.create_dev_bo(dev_allocation_settings, dbg)?; + let send_dev_modifier = on_drop(|| { + dbg.borrow_mut().dev_modifier = Some(dev_bo.dmabuf().modifier); + }); + let dev_fb = dev_ctx + .gfx + .clone() + .dmabuf_img(dev_bo.dmabuf()) + .map_err(ScanoutBufferErrorKind::SodImportSodImage)? + .to_framebuffer() + .map_err(ScanoutBufferErrorKind::SodImportFb)?; + send_dev_modifier.forget(); + send_render_modifier.forget(); + let prime = RenderBufferPrime::Sampling { + dev_bo, + dev_fb, + dev_render_tex, + }; + self.create(drm, damage_queue, render, prime) + } +} + +const JAY_PRIME_METHODS: &str = "JAY_PRIME_METHODS"; + +type PrimeMethods = ArrayVec; + +static PRIME_METHODS: LazyLock = LazyLock::new(prime_methods); + +fn prime_methods() -> PrimeMethods { + let mut res = PrimeMethods::new(); + let mut seen = StaticMap::<_, bool>::default(); + let mut apply = |method: PrimeMethod, allow: bool| { + if !seen[method] { + seen[method] = true; + if allow { + res.push(method); + } + } + }; + if let Ok(var) = std::env::var(JAY_PRIME_METHODS) { + for mut name in var.split(",") { + name = name.trim(); + if name.is_empty() { + continue; + } + let mut allow = true; + if let Some(m) = name.strip_prefix("-") { + name = m; + allow = false; + } + let Some(method) = PrimeMethod::variants().find(|m| m.name() == name) else { + log::warn!("Unknown prime method {}", name); + continue; + }; + apply(method, allow); + } + } + for method in PrimeMethod::variants() { + apply(method, true); + } + log::info!("Prime methods: {:?}", res); + res +} + +fn sample_modifiers(fmt: &GfxFormat) -> Vec { + fmt.read_modifiers.iter().copied().collect() +} + +fn render_modifiers(fmt: &GfxFormat) -> Vec { + fmt.write_modifiers.keys().copied().collect() +} diff --git a/src/backends/metal/present.rs b/src/backends/metal/present.rs index 7bc70427..b1493ea8 100644 --- a/src/backends/metal/present.rs +++ b/src/backends/metal/present.rs @@ -3,11 +3,9 @@ use { backend::Connector, backends::metal::{ MetalError, + allocator::{RenderBuffer, RenderBufferCopy}, transaction::{DrmConnectorState, DrmPlaneState}, - video::{ - MetalConnector, MetalCrtc, MetalHardwareCursorChange, MetalPlane, RenderBuffer, - RenderBufferCopy, - }, + video::{MetalConnector, MetalCrtc, MetalHardwareCursorChange, MetalPlane}, }, cmm::cmm_description::ColorDescription, gfx_api::{ @@ -557,7 +555,11 @@ 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)?.present_block; + let sf = c + .cursor_buffer + .copy_to_dev(cd, sf) + .map_err(MetalError::CopyToDev)? + .present_block; self.cursor_sync_file.set(sf); } let mut cursor_changed = false; @@ -588,13 +590,12 @@ impl MetalConnector { } let buffer_idx = (front_buffer % buffers.len() as u64) as usize; let buffer = &buffers[buffer_idx]; - let (width, height) = buffer.dev_fb.physical_size(); CursorProgrammingType::Enable { fb: buffer.drm.clone(), x: self.cursor_x.get(), y: self.cursor_y.get(), - width, - height, + width: buffer.width, + height: buffer.height, swap, } } else { @@ -862,7 +863,8 @@ impl MetalConnector { match &direct_scanout_data { None => { let sf = buffer - .render_fb() + .render + .fb .perform_render_pass( AcquireSync::Unnecessary, ReleaseSync::Explicit, @@ -873,9 +875,9 @@ impl MetalConnector { blend_cd, ) .map_err(MetalError::RenderFrame)?; - copy = buffer.copy_to_dev(cd, sf)?; + copy = buffer.copy_to_dev(cd, sf).map_err(MetalError::CopyToDev)?; fb = buffer.drm.clone(); - tex = buffer.render_tex.clone(); + tex = buffer.render.tex.clone(); } Some(dsd) => { let sf = match &dsd.acquire_sync { diff --git a/src/backends/metal/transaction.rs b/src/backends/metal/transaction.rs index 417fdd24..78fb7237 100644 --- a/src/backends/metal/transaction.rs +++ b/src/backends/metal/transaction.rs @@ -8,9 +8,11 @@ use { BackendConnectorTransactionError, BackendPreparedConnectorTransaction, }, }, - backends::metal::video::{ - FrontState, MetalConnector, MetalCrtc, MetalDrmDeviceData, MetalPlane, PlaneType, - RenderBuffer, + backends::metal::{ + allocator::RenderBuffer, + video::{ + FrontState, MetalConnector, MetalCrtc, MetalDrmDeviceData, MetalPlane, PlaneType, + }, }, format::{ARGB8888, Format}, gfx_api::SyncFile, @@ -464,7 +466,7 @@ impl MetalDeviceTransaction { if b[0].width != width || b[0].height != height || b[0].format != format { discard!(); } - if !rc_eq(render_ctx, &b[0].render_ctx) { + if !rc_eq(render_ctx, &b[0].render.ctx) { discard!(); } if !rc_eq(&dev_ctx, &b[0].dev_ctx) { @@ -472,7 +474,7 @@ impl MetalDeviceTransaction { } let modifiers = &plane.obj.formats.get(&format.drm).unwrap().modifiers; for b in &**b { - if !modifiers.contains(&b.dev_bo.dmabuf().modifier) { + if !modifiers.contains(&b.dev_bo().dmabuf().modifier) { discard!(); } } @@ -517,7 +519,20 @@ impl MetalDeviceTransaction { *plane_id = DrmPlane::NONE; continue; } - let buffers = Rc::new(res?); + let res = res?; + if let Some(method) = res[0].prime.method() { + let plane = match plane.obj.ty { + PlaneType::Overlay => "overlay", + PlaneType::Primary => "primary", + PlaneType::Cursor => "cursor", + }; + let dev = slf.dev.dev.devnode.as_bytes().as_bstr(); + let connector = connector.obj.kernel_id(); + log::info!( + "Using prime method {method} for {dev} {connector} ({plane})", + ); + } + let buffers = Rc::new(res); plane.new.buffers = Some(buffers.clone()); new_buffers = Some(buffers.clone()); buffers @@ -572,7 +587,7 @@ impl MetalDeviceTransaction { match prev.copy_to_new(new_buffer, &cd) { Ok(sf) => Ok(sf), Err(e) => { - log::warn!("Could not copy from old buffer: {}", ErrorFmt(&*e)); + log::warn!("Could not copy from old buffer: {}", ErrorFmt(e)); new_buffer.clear(&cd) } } @@ -582,7 +597,7 @@ impl MetalDeviceTransaction { match res { Ok(sf) => sync_files.extend(sf), Err(e) => { - log::warn!("Could not clear new buffer: {}", ErrorFmt(&*e)); + log::warn!("Could not clear new buffer: {}", ErrorFmt(e)); } } } else { @@ -623,7 +638,7 @@ impl MetalDeviceTransaction { Err(e) => { log::error!( "Could not black out old buffer: {}", - ErrorFmt(&*e), + ErrorFmt(e), ); } } diff --git a/src/backends/metal/video.rs b/src/backends/metal/video.rs index 0faa76bc..89fd53f8 100644 --- a/src/backends/metal/video.rs +++ b/src/backends/metal/video.rs @@ -1,6 +1,5 @@ use { crate::{ - allocator::BufferObject, async_engine::{Phase, SpawnedFuture}, backend::{ BackendColorSpace, BackendConnectorState, BackendDrmDevice, BackendDrmLease, @@ -14,7 +13,8 @@ use { }, }, backends::metal::{ - MetalBackend, MetalError, ScanoutBufferError, ScanoutBufferErrorKind, + MetalBackend, MetalError, + allocator::RenderBuffer, present::{ DEFAULT_POST_COMMIT_MARGIN, DEFAULT_PRE_COMMIT_MARGIN, DirectScanoutCache, POST_COMMIT_MARGIN_DELTA, PresentFb, @@ -25,15 +25,11 @@ use { drm_feedback::DrmFeedback, edid::{CtaDataBlock, Descriptor, EdidExtension}, format::{Format, XRGB8888}, - gfx_api::{ - AcquireSync, GfxBlendBuffer, GfxContext, GfxFramebuffer, GfxTexture, ReleaseSync, - SyncFile, needs_render_usage, - }, + gfx_api::{GfxContext, GfxFramebuffer, SyncFile}, ifs::{ wl_output::OutputId, wp_presentation_feedback::{KIND_HW_COMPLETION, KIND_VSYNC, KIND_ZERO_COPY}, }, - rect::{DamageQueue, Rect}, state::State, tree::OutputNode, udev::UdevDevice, @@ -41,32 +37,29 @@ use { asyncevent::AsyncEvent, binary_search_map::BinarySearchMap, bitflags::BitflagsExt, cell_ext::CellExt, clonecell::CloneCell, copyhashmap::CopyHashMap, errorfmt::ErrorFmt, geometric_decay::GeometricDecay, numcell::NumCell, on_change::OnChange, - opaque_cell::OpaqueCell, ordered_float::F64, oserror::OsError, rc_eq::rc_eq, + opaque_cell::OpaqueCell, ordered_float::F64, oserror::OsError, }, video::{ INVALID_MODIFIER, Modifier, dmabuf::DmaBufId, drm::{ ConnectorStatus, ConnectorType, DRM_CLIENT_CAP_ATOMIC, DrmBlob, DrmConnector, - DrmCrtc, DrmEncoder, DrmError, DrmEvent, DrmFb, DrmFramebuffer, DrmLease, - DrmMaster, DrmModeInfo, DrmObject, DrmPlane, DrmProperty, DrmPropertyDefinition, - DrmPropertyType, DrmVersion, HDMI_EOTF_TRADITIONAL_GAMMA_SDR, drm_mode_modeinfo, + DrmCrtc, DrmEncoder, DrmError, DrmEvent, DrmFb, DrmLease, DrmMaster, DrmModeInfo, + DrmObject, DrmPlane, DrmProperty, DrmPropertyDefinition, DrmPropertyType, + DrmVersion, HDMI_EOTF_TRADITIONAL_GAMMA_SDR, drm_mode_modeinfo, hdr_output_metadata, }, - gbm::{GBM_BO_USE_LINEAR, GBM_BO_USE_RENDERING, GBM_BO_USE_SCANOUT, GbmBo, GbmDevice}, + gbm::GbmDevice, }, }, ahash::{AHashMap, AHashSet}, - arrayvec::ArrayVec, bstr::{BString, ByteSlice}, - indexmap::{IndexMap, IndexSet, indexset}, + indexmap::{IndexSet, indexset}, isnt::std_1::collections::IsntHashMapExt, jay_config::video::GfxApi, - run_on_drop::on_drop, std::{ cell::{Cell, RefCell}, collections::hash_map::Entry, - error::Error, ffi::CString, fmt::{Debug, Formatter}, mem, @@ -588,7 +581,7 @@ impl HardwareCursorUpdate for MetalHardwareCursorChange<'_> { } fn get_buffer(&self) -> Rc { - self.cursor_buffer.render_fb() + self.cursor_buffer.render.fb.clone() } fn set_position(&mut self, x: i32, y: i32) { @@ -2603,315 +2596,6 @@ impl MetalBackend { Ok(()) } - pub fn create_scanout_buffers( - &self, - dev: &Rc, - format: &'static Format, - plane_modifiers: &IndexSet, - width: i32, - height: i32, - render_ctx: &Rc, - cursor: bool, - ) -> Result<[RenderBuffer; N], MetalError> { - let mut blend_buffer = None; - if !cursor { - match render_ctx.gfx.acquire_blend_buffer(width, height) { - Ok(bb) => blend_buffer = Some(bb), - Err(e) => { - log::warn!("Could not create blend buffer: {}", ErrorFmt(e)); - } - } - } - let mut damage_queue = ArrayVec::from(DamageQueue::new::()); - let mut create = || { - self.create_scanout_buffer( - dev, - format, - plane_modifiers, - width, - height, - render_ctx, - cursor, - damage_queue.pop().unwrap(), - blend_buffer.clone(), - ) - }; - let mut array = ArrayVec::<_, N>::new(); - for _ in 0..N { - array.push(create()?); - } - if let Some(buffer) = array.first() { - buffer.damage_full(); - } - Ok(array.into_inner().unwrap()) - } - - fn create_scanout_buffer( - &self, - dev: &Rc, - format: &'static Format, - plane_modifiers: &IndexSet, - width: i32, - height: i32, - render_ctx: &Rc, - cursor: bool, - damage_queue: DamageQueue, - blend_buffer: Option>, - ) -> Result { - let mut dev_gfx_write_modifiers = None; - let mut dev_gfx_read_modifiers = None; - let mut dev_modifiers_possible = None; - let mut dev_usage = None; - let mut dev_modifier = None; - let mut render_name = None; - let mut render_gfx_write_modifiers = None; - let mut render_modifiers_possible = None; - let mut render_usage = None; - let mut render_modifier = None; - self.create_scanout_buffer_( - dev, - format, - plane_modifiers, - width, - height, - render_ctx, - cursor, - damage_queue, - blend_buffer, - &mut dev_gfx_write_modifiers, - &mut dev_gfx_read_modifiers, - &mut dev_modifiers_possible, - &mut dev_usage, - &mut dev_modifier, - &mut render_name, - &mut render_gfx_write_modifiers, - &mut render_modifiers_possible, - &mut render_usage, - &mut render_modifier, - ) - .map_err(|kind| ScanoutBufferError { - dev: dev.devnode.as_bytes().as_bstr().to_string(), - format, - plane_modifiers: plane_modifiers.clone(), - width, - height, - cursor, - dev_gfx_write_modifiers, - dev_gfx_read_modifiers, - dev_modifiers_possible, - dev_usage, - dev_modifier, - render_name, - render_gfx_write_modifiers, - render_modifiers_possible, - render_usage, - render_modifier, - kind, - }) - .map_err(Box::new) - .map_err(MetalError::AllocateScanoutBuffer) - } - - fn create_scanout_buffer_( - &self, - dev: &Rc, - format: &'static Format, - plane_modifiers: &IndexSet, - width: i32, - height: i32, - render_ctx: &Rc, - cursor: bool, - damage_queue: DamageQueue, - blend_buffer: Option>, - dbg_dev_gfx_write_modifiers: &mut Option>, - dbg_dev_gfx_read_modifiers: &mut Option>, - dbg_dev_modifiers_possible: &mut Option>, - dbg_dev_usage: &mut Option, - dbg_dev_modifier: &mut Option, - dbg_render_name: &mut Option, - dbg_render_gfx_write_modifiers: &mut Option>, - dbg_render_modifiers_possible: &mut Option>, - dbg_render_usage: &mut Option, - dbg_render_modifier: &mut Option, - ) -> Result { - let dev_ctx = dev.ctx.get(); - let dev_gfx_formats = dev_ctx.gfx.formats(); - let Some(dev_gfx_format) = dev_gfx_formats.get(&format.drm) else { - return Err(ScanoutBufferErrorKind::SodUnsupportedFormat); - }; - let send_dev_gfx_write_modifiers = on_drop(|| { - *dbg_dev_gfx_write_modifiers = - Some(dev_gfx_format.write_modifiers.keys().copied().collect()) - }); - let possible_modifiers: IndexMap<_, _> = dev_gfx_format - .write_modifiers - .iter() - .filter(|(m, _)| plane_modifiers.contains(*m)) - .map(|(m, v)| (*m, v)) - .collect(); - let send_dev_modifiers_possible = on_drop(|| { - *dbg_dev_modifiers_possible = Some(possible_modifiers.keys().copied().collect()) - }); - if possible_modifiers.is_empty() { - return Err(ScanoutBufferErrorKind::SodNoWritableModifier); - } - let mut usage = GBM_BO_USE_RENDERING | GBM_BO_USE_SCANOUT; - if !needs_render_usage(possible_modifiers.values().copied()) { - usage &= !GBM_BO_USE_RENDERING; - } - if cursor { - usage |= GBM_BO_USE_LINEAR; - }; - *dbg_dev_usage = Some(usage); - let dev_bo = dev.gbm.create_bo( - &self.state.dma_buf_ids, - width, - height, - format, - possible_modifiers.keys(), - usage, - ); - let dev_bo = match dev_bo { - Ok(b) => b, - Err(e) => return Err(ScanoutBufferErrorKind::SodBufferAllocation(e)), - }; - *dbg_dev_modifier = Some(dev_bo.dmabuf().modifier); - let drm_fb = match dev.master.add_fb(dev_bo.dmabuf(), None) { - Ok(fb) => Rc::new(fb), - Err(e) => return Err(ScanoutBufferErrorKind::SodAddfb2(e)), - }; - let dev_img = match dev_ctx.gfx.clone().dmabuf_img(dev_bo.dmabuf()) { - Ok(img) => img, - Err(e) => return Err(ScanoutBufferErrorKind::SodImportSodImage(e)), - }; - let dev_fb = match dev_img.clone().to_framebuffer() { - Ok(fb) => fb, - Err(e) => return Err(ScanoutBufferErrorKind::SodImportFb(e)), - }; - dev_fb - .clear( - AcquireSync::Unnecessary, - ReleaseSync::None, - self.state.color_manager.srgb_gamma22(), - ) - .map_err(ScanoutBufferErrorKind::SodClear)?; - let render_gfx_formats; - let render_possible_modifiers: IndexMap<_, _>; - let mut send_render_dev_name = None; - let mut send_render_gfx_write_modifiers = None; - let mut send_dev_gfx_read_modifiers = None; - let mut send_render_possible_modifiers = None; - let (dev_tex, render_tex, render_fb, render_bo) = if dev.id == render_ctx.dev_id { - let render_tex = match dev_img.to_texture() { - Ok(fb) => fb, - Err(e) => return Err(ScanoutBufferErrorKind::SodImportSodTexture(e)), - }; - (None, render_tex, None, None) - } else { - send_render_dev_name = Some(on_drop(|| { - *dbg_render_name = Some(render_ctx.devnode.as_bytes().as_bstr().to_string()); - })); - // Create a _bridge_ BO in the render device - render_gfx_formats = render_ctx.gfx.formats(); - let render_gfx_format = match render_gfx_formats.get(&format.drm) { - None => return Err(ScanoutBufferErrorKind::RenderUnsupportedFormat), - Some(f) => f, - }; - send_render_gfx_write_modifiers = Some(on_drop(|| { - *dbg_render_gfx_write_modifiers = - Some(render_gfx_format.write_modifiers.keys().copied().collect()) - })); - send_dev_gfx_read_modifiers = Some(on_drop(|| { - *dbg_dev_gfx_read_modifiers = Some(dev_gfx_format.read_modifiers.clone()); - })); - render_possible_modifiers = render_gfx_format - .write_modifiers - .iter() - .filter(|(m, _)| dev_gfx_format.read_modifiers.contains(*m)) - .map(|(m, v)| (*m, v)) - .collect(); - send_render_possible_modifiers = Some(on_drop(|| { - *dbg_render_modifiers_possible = - Some(render_possible_modifiers.keys().copied().collect()) - })); - if render_possible_modifiers.is_empty() { - return Err(ScanoutBufferErrorKind::RenderNoWritableModifier); - } - usage = GBM_BO_USE_RENDERING | GBM_BO_USE_LINEAR; - if !needs_render_usage(render_possible_modifiers.values().copied()) { - usage &= !GBM_BO_USE_RENDERING; - } - *dbg_render_usage = Some(usage); - let render_bo = render_ctx.gbm.create_bo( - &self.state.dma_buf_ids, - width, - height, - format, - render_possible_modifiers.keys(), - usage, - ); - let render_bo = match render_bo { - Ok(b) => b, - Err(e) => return Err(ScanoutBufferErrorKind::RenderBufferAllocation(e)), - }; - *dbg_render_modifier = Some(render_bo.dmabuf().modifier); - let render_img = match render_ctx.gfx.clone().dmabuf_img(render_bo.dmabuf()) { - Ok(img) => img, - Err(e) => return Err(ScanoutBufferErrorKind::RenderImportImage(e)), - }; - let render_fb = match render_img.clone().to_framebuffer() { - Ok(fb) => fb, - Err(e) => return Err(ScanoutBufferErrorKind::RenderImportFb(e)), - }; - render_fb - .clear( - AcquireSync::Unnecessary, - ReleaseSync::None, - self.state.color_manager.srgb_gamma22(), - ) - .map_err(ScanoutBufferErrorKind::RenderClear)?; - let render_tex = match render_img.to_texture() { - Ok(fb) => fb, - Err(e) => return Err(ScanoutBufferErrorKind::RenderImportRenderTexture(e)), - }; - - // Import the bridge BO into the current device - let dev_img = match dev_ctx.gfx.clone().dmabuf_img(render_bo.dmabuf()) { - Ok(img) => img, - Err(e) => return Err(ScanoutBufferErrorKind::SodImportRenderImage(e)), - }; - let dev_tex = match dev_img.to_texture() { - Ok(fb) => fb, - Err(e) => return Err(ScanoutBufferErrorKind::SodImportRenderTexture(e)), - }; - - (Some(dev_tex), render_tex, Some(render_fb), Some(render_bo)) - }; - send_dev_gfx_write_modifiers.forget(); - send_dev_modifiers_possible.forget(); - send_render_dev_name.map(|o| o.forget()); - send_render_gfx_write_modifiers.map(|o| o.forget()); - send_dev_gfx_read_modifiers.map(|o| o.forget()); - send_render_possible_modifiers.map(|o| o.forget()); - Ok(RenderBuffer { - width, - height, - locked: Cell::new(true), - format, - dev_ctx, - render_ctx: render_ctx.clone(), - drm: drm_fb, - damage_queue, - dev_bo, - _render_bo: render_bo, - blend_buffer, - dev_fb, - dev_tex, - render_tex, - render_fb, - }) - } - fn start_connector(&self, connector: &Rc, log_mode: bool) { let dd = &*connector.display.borrow(); self.send_connected(connector, dd); @@ -2938,127 +2622,3 @@ impl MetalBackend { connector.schedule_present(); } } - -#[derive(Debug)] -pub struct RenderBuffer { - pub width: i32, - pub height: i32, - pub locked: Cell, - pub format: &'static Format, - pub dev_ctx: Rc, - pub render_ctx: Rc, - pub drm: Rc, - pub damage_queue: DamageQueue, - pub dev_bo: GbmBo, - pub _render_bo: Option, - pub blend_buffer: Option>, - // ctx = dev - // buffer location = dev - pub dev_fb: Rc, - // ctx = dev - // buffer location = render - pub dev_tex: Option>, - // ctx = render - // buffer location = render - pub render_tex: Rc, - // ctx = render - // buffer location = render - 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 - .clone() - .unwrap_or_else(|| self.dev_fb.clone()) - } - - pub fn copy_to_dev( - &self, - cd: &Rc, - sync_file: Option, - ) -> Result { - let Some(tex) = &self.dev_tex else { - return Ok(RenderBufferCopy { - render_block: None, - present_block: sync_file, - }); - }; - self.dev_fb - .copy_texture( - AcquireSync::Unnecessary, - ReleaseSync::Explicit, - cd, - tex, - cd, - None, - AcquireSync::from_sync_file(sync_file), - ReleaseSync::None, - 0, - 0, - ) - .map_err(MetalError::CopyToOutput) - .map(RenderBufferCopy::for_both) - } - - pub fn damage_full(&self) { - let dmabuf = self.dev_bo.dmabuf(); - let rect = Rect::new_sized_saturating(0, 0, dmabuf.width, dmabuf.height); - self.damage_queue.clear_all(); - self.damage_queue.damage(&[rect]); - } - - pub fn clear(&self, cd: &Rc) -> Result, Box> { - self.dev_fb - .clear(AcquireSync::Unnecessary, ReleaseSync::Explicit, cd) - .map_err(Into::into) - } - - pub fn copy_to_new( - &self, - new: &Self, - cd: &Rc, - ) -> Result, Box> { - let old = self; - let copy_texture = |new: &Rc, old: &Rc| { - new.copy_texture( - AcquireSync::Unnecessary, - ReleaseSync::Explicit, - cd, - old, - cd, - None, - AcquireSync::Unnecessary, - ReleaseSync::Explicit, - 0, - 0, - ) - .map_err(Into::into) - }; - if rc_eq(&old.dev_ctx, &new.dev_ctx) { - return copy_texture(&new.dev_fb, old.dev_tex.as_ref().unwrap_or(&old.render_tex)); - } - let tex = new - .dev_ctx - .gfx - .clone() - .dmabuf_img(old.dev_bo.dmabuf())? - .to_texture()?; - copy_texture(&new.dev_fb, &tex) - } -}