diff --git a/src/backends/metal.rs b/src/backends/metal.rs index 9c318349..3c900e73 100644 --- a/src/backends/metal.rs +++ b/src/backends/metal.rs @@ -18,6 +18,7 @@ use { }, dbus::{DbusError, SignalHandler}, drm_feedback::DrmFeedback, + format::Format, gfx_api::{GfxError, SyncFile}, ifs::{ wl_output::OutputId, @@ -48,14 +49,15 @@ use { smallmap::SmallMap, syncqueue::SyncQueue, }, - video::{drm::DrmError, gbm::GbmError}, + video::{Modifier, drm::DrmError, gbm::GbmError}, }, bstr::ByteSlice, + indexmap::IndexSet, std::{ cell::{Cell, RefCell}, error::Error, ffi::{CStr, CString}, - fmt::{Debug, Formatter}, + fmt::{Debug, Display, Formatter}, future::pending, rc::Rc, }, @@ -85,16 +87,6 @@ pub enum MetalError { UpdateProperties(#[source] DrmError), #[error("Could not create a render context")] CreateRenderContex(#[source] GfxError), - #[error("Could not allocate scanout buffer")] - ScanoutBuffer(#[source] GbmError), - #[error("addfb2 failed")] - Framebuffer(#[source] DrmError), - #[error("Could not import a framebuffer into the graphics API")] - ImportFb(#[source] GfxError), - #[error("Could not import a texture into the graphics API")] - ImportTexture(#[source] GfxError), - #[error("Could not import an image into the graphics API")] - ImportImage(#[source] GfxError), #[error("Could not perform modeset")] Modeset(#[source] BackendConnectorTransactionError), #[error("Could not enable atomic modesetting")] @@ -111,22 +103,12 @@ pub enum MetalError { DevicePauseSignalHandler(#[source] DbusError), #[error("Could not create device-resumed signal handler")] DeviceResumeSignalHandler(#[source] DbusError), - #[error("Device render context does not support required format {0}")] - MissingDevFormat(&'static str), - #[error("Render context does not support required format {0}")] - MissingRenderFormat(&'static str), - #[error("Device cannot scan out any buffers writable by its GFX API (format {0})")] - MissingDevModifier(&'static str), - #[error("Device GFX API cannot read any buffers writable by the render GFX API (format {0})")] - MissingRenderModifier(&'static str), #[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("Could not clear framebuffer")] - Clear(#[source] GfxError), #[error("The present configuration is out of date")] OutOfDate, #[error("Could not add connector to transaction")] @@ -135,6 +117,119 @@ pub enum MetalError { CalculateDrmState(#[source] BackendConnectorTransactionError), #[error("Could not calculate DRM change set")] CalculateDrmChange(#[source] BackendConnectorTransactionError), + #[error("Could not create 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")] + 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/video.rs b/src/backends/metal/video.rs index c3b59a02..0c04011c 100644 --- a/src/backends/metal/video.rs +++ b/src/backends/metal/video.rs @@ -14,7 +14,7 @@ use { }, }, backends::metal::{ - MetalBackend, MetalError, + MetalBackend, MetalError, ScanoutBufferError, ScanoutBufferErrorKind, present::{ DEFAULT_POST_COMMIT_MARGIN, DEFAULT_PRE_COMMIT_MARGIN, DirectScanoutCache, POST_COMMIT_MARGIN_DELTA, PresentFb, @@ -41,7 +41,7 @@ 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, + on_drop::OnDrop2, opaque_cell::OpaqueCell, ordered_float::F64, oserror::OsError, }, video::{ INVALID_MODIFIER, Modifier, @@ -88,6 +88,7 @@ pub struct MetalRenderContext { pub dev_id: DrmDeviceId, pub gfx: Rc, pub gbm: Rc, + pub devnode: CString, } pub struct MetalDrmDevice { @@ -2000,6 +2001,7 @@ impl MetalBackend { dev_id: pending.id, gfx, gbm: gbm.clone(), + devnode: pending.devnode.clone(), }); let mut is_nvidia = false; @@ -2465,6 +2467,7 @@ impl MetalBackend { dev_id: dev.id, gfx, gbm: old_ctx.gbm.clone(), + devnode: old_ctx.devnode.clone(), })); if dev.is_render_device() { self.make_render_device(dev, true); @@ -2647,25 +2650,102 @@ impl MetalBackend { 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 dev_gfx_format = match dev_gfx_formats.get(&format.drm) { - None => return Err(MetalError::MissingDevFormat(format.name)), - Some(f) => f, + let Some(dev_gfx_format) = dev_gfx_formats.get(&format.drm) else { + return Err(ScanoutBufferErrorKind::SodUnsupportedFormat); }; + let send_dev_gfx_write_modifiers = OnDrop2::new(|| { + *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 = OnDrop2::new(|| { + *dbg_dev_modifiers_possible = Some(possible_modifiers.keys().copied().collect()) + }); if possible_modifiers.is_empty() { - log::warn!("Scanout modifiers: {:?}", plane_modifiers); - log::warn!( - "DEV GFX modifiers: {:?}", - dev_gfx_format.write_modifiers.keys() - ); - return Err(MetalError::MissingDevModifier(format.name)); + return Err(ScanoutBufferErrorKind::SodNoWritableModifier); } let mut usage = GBM_BO_USE_RENDERING | GBM_BO_USE_SCANOUT; if !needs_render_usage(possible_modifiers.values().copied()) { @@ -2674,6 +2754,7 @@ impl MetalBackend { 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, @@ -2684,19 +2765,20 @@ impl MetalBackend { ); let dev_bo = match dev_bo { Ok(b) => b, - Err(e) => return Err(MetalError::ScanoutBuffer(e)), + 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(MetalError::Framebuffer(e)), + 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(MetalError::ImportImage(e)), + Err(e) => return Err(ScanoutBufferErrorKind::SodImportSodImage(e)), }; let dev_fb = match dev_img.clone().to_framebuffer() { Ok(fb) => fb, - Err(e) => return Err(MetalError::ImportFb(e)), + Err(e) => return Err(ScanoutBufferErrorKind::SodImportFb(e)), }; dev_fb .clear( @@ -2704,57 +2786,74 @@ impl MetalBackend { ReleaseSync::None, self.state.color_manager.srgb_gamma22(), ) - .map_err(MetalError::Clear)?; + .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(MetalError::ImportTexture(e)), + Err(e) => return Err(ScanoutBufferErrorKind::SodImportSodTexture(e)), }; (None, render_tex, None, None) } else { + send_render_dev_name = Some(OnDrop2::new(|| { + *dbg_render_name = Some(render_ctx.devnode.as_bytes().as_bstr().to_string()); + })); // Create a _bridge_ BO in the render device - let render_gfx_formats = render_ctx.gfx.formats(); + render_gfx_formats = render_ctx.gfx.formats(); let render_gfx_format = match render_gfx_formats.get(&format.drm) { - None => return Err(MetalError::MissingRenderFormat(format.name)), + None => return Err(ScanoutBufferErrorKind::RenderUnsupportedFormat), Some(f) => f, }; - let possible_modifiers: IndexMap<_, _> = render_gfx_format + send_render_gfx_write_modifiers = Some(OnDrop2::new(|| { + *dbg_render_gfx_write_modifiers = + Some(render_gfx_format.write_modifiers.keys().copied().collect()) + })); + send_dev_gfx_read_modifiers = Some(OnDrop2::new(|| { + *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(); - if possible_modifiers.is_empty() { - log::warn!( - "Render GFX modifiers: {:?}", - render_gfx_format.write_modifiers.keys() - ); - log::warn!("DEV GFX modifiers: {:?}", dev_gfx_format.read_modifiers); - return Err(MetalError::MissingRenderModifier(format.name)); + send_render_possible_modifiers = Some(OnDrop2::new(|| { + *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(possible_modifiers.values().copied()) { + 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, - possible_modifiers.keys(), + render_possible_modifiers.keys(), usage, ); let render_bo = match render_bo { Ok(b) => b, - Err(e) => return Err(MetalError::ScanoutBuffer(e)), + 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(MetalError::ImportImage(e)), + Err(e) => return Err(ScanoutBufferErrorKind::RenderImportImage(e)), }; let render_fb = match render_img.clone().to_framebuffer() { Ok(fb) => fb, - Err(e) => return Err(MetalError::ImportFb(e)), + Err(e) => return Err(ScanoutBufferErrorKind::RenderImportFb(e)), }; render_fb .clear( @@ -2762,24 +2861,30 @@ impl MetalBackend { ReleaseSync::None, self.state.color_manager.srgb_gamma22(), ) - .map_err(MetalError::Clear)?; + .map_err(ScanoutBufferErrorKind::RenderClear)?; let render_tex = match render_img.to_texture() { Ok(fb) => fb, - Err(e) => return Err(MetalError::ImportTexture(e)), + 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(MetalError::ImportImage(e)), + Err(e) => return Err(ScanoutBufferErrorKind::SodImportRenderImage(e)), }; let dev_tex = match dev_img.to_texture() { Ok(fb) => fb, - Err(e) => return Err(MetalError::ImportTexture(e)), + 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,