From d1462f81072017491e6998a219366f5c8353773c Mon Sep 17 00:00:00 2001 From: Julian Orth Date: Sun, 1 Sep 2024 20:23:04 +0200 Subject: [PATCH] video: add udmabuf allocator --- .builds/test.yml | 4 +- src/allocator.rs | 57 +++++ src/backends/metal/video.rs | 21 +- src/backends/x.rs | 1 + src/cli/screenshot.rs | 148 +++++++---- src/drm_feedback.rs | 10 +- src/gfx_api.rs | 9 +- src/gfx_apis/gl/renderer/context.rs | 13 +- src/gfx_apis/vulkan.rs | 15 +- src/gfx_apis/vulkan/device.rs | 4 +- src/ifs/jay_compositor.rs | 37 ++- src/ifs/jay_render_ctx.rs | 14 +- src/ifs/jay_screencast.rs | 31 +-- src/ifs/jay_screenshot.rs | 26 ++ src/ifs/wl_drm.rs | 4 +- src/ifs/wl_surface.rs | 5 +- src/it/test_backend.rs | 131 ++++++---- src/it/test_client.rs | 10 +- src/it/test_gfx_api.rs | 59 ++--- src/it/test_ifs/test_jay_compositor.rs | 15 +- src/it/test_ifs/test_registry.rs | 2 +- src/it/test_ifs/test_screenshot.rs | 61 ++++- src/it/testrun.rs | 11 +- src/it/tests/t0013_graphics_initialized.rs | 2 +- src/it/tests/t0031_syncobj.rs | 10 +- src/it/tests/t0035_scanout_feedback.rs | 2 +- src/main.rs | 2 + src/portal/ptr_gui.rs | 13 +- src/screenshoter.rs | 26 +- src/state.rs | 18 +- src/udmabuf.rs | 271 +++++++++++++++++++++ src/video/gbm.rs | 92 ++++++- wire/jay_screenshot.txt | 16 ++ 33 files changed, 882 insertions(+), 258 deletions(-) create mode 100644 src/allocator.rs create mode 100644 src/udmabuf.rs diff --git a/.builds/test.yml b/.builds/test.yml index 2d962d79..390dcaab 100644 --- a/.builds/test.yml +++ b/.builds/test.yml @@ -7,9 +7,7 @@ tasks: sudo pacman -S --noconfirm rustup libinput pango mesa libxkbcommon xorg-xwayland adwaita-icon-theme libxcursor cmake shaderc rustup toolchain install stable - configure: | - sudo rmmod bochs - sudo modprobe vkms - sudo chmod o+rw /dev/dri/card* + sudo chmod o+r /dev/udmabuf - build: | cd jay cargo build --features it diff --git a/src/allocator.rs b/src/allocator.rs new file mode 100644 index 00000000..e71d86dc --- /dev/null +++ b/src/allocator.rs @@ -0,0 +1,57 @@ +use { + crate::{ + format::Format, + video::{ + dmabuf::{DmaBuf, DmaBufIds}, + drm::Drm, + Modifier, + }, + }, + std::{error::Error, rc::Rc}, + thiserror::Error, +}; + +#[derive(Debug, Error)] +#[error(transparent)] +pub struct AllocatorError(#[from] pub Box); + +bitflags! { + BufferUsage: u32; + BO_USE_SCANOUT = 1 << 0, + BO_USE_CURSOR = 1 << 1, + BO_USE_RENDERING = 1 << 2, + BO_USE_WRITE = 1 << 3, + BO_USE_LINEAR = 1 << 4, + BO_USE_PROTECTED = 1 << 5, +} + +pub trait Allocator { + fn drm(&self) -> Option<&Drm>; + fn create_bo( + &self, + dma_buf_ids: &DmaBufIds, + width: i32, + height: i32, + format: &'static Format, + modifiers: &[Modifier], + usage: BufferUsage, + ) -> Result, AllocatorError>; + fn import_dmabuf( + &self, + dmabuf: &DmaBuf, + usage: BufferUsage, + ) -> Result, AllocatorError>; +} + +pub trait BufferObject { + fn dmabuf(&self) -> &DmaBuf; + fn map_read(self: Rc) -> Result, AllocatorError>; + fn map_write(self: Rc) -> Result, AllocatorError>; +} + +pub trait MappedBuffer { + unsafe fn data(&self) -> &[u8]; + #[cfg_attr(not(test), allow(dead_code))] + fn data_ptr(&self) -> *mut u8; + fn stride(&self) -> i32; +} diff --git a/src/backends/metal/video.rs b/src/backends/metal/video.rs index 406b01a8..45339187 100644 --- a/src/backends/metal/video.rs +++ b/src/backends/metal/video.rs @@ -1,5 +1,6 @@ use { crate::{ + allocator::BufferObject, async_engine::{Phase, SpawnedFuture}, backend::{ BackendDrmDevice, BackendDrmLease, BackendDrmLessee, BackendEvent, Connector, @@ -73,6 +74,7 @@ pub struct PendingDrmDevice { pub struct MetalRenderContext { pub dev_id: DrmDeviceId, pub gfx: Rc, + pub gbm: Rc, } pub struct MetalDrmDevice { @@ -91,7 +93,7 @@ pub struct MetalDrmDevice { pub cursor_width: u64, pub cursor_height: u64, pub supports_async_commit: bool, - pub gbm: GbmDevice, + pub gbm: Rc, pub handle_events: HandleEvents, pub ctx: CloneCell>, pub on_change: OnChange, @@ -2162,6 +2164,11 @@ impl MetalBackend { } } + let gbm = match GbmDevice::new(master) { + Ok(g) => Rc::new(g), + Err(e) => return Err(MetalError::GbmDevice(e)), + }; + let gfx = match self.state.create_gfx_context(master, None) { Ok(r) => r, Err(e) => return Err(MetalError::CreateRenderContex(e)), @@ -2169,13 +2176,9 @@ impl MetalBackend { let ctx = Rc::new(MetalRenderContext { dev_id: pending.id, gfx, + gbm: gbm.clone(), }); - let gbm = match GbmDevice::new(master) { - Ok(g) => g, - Err(e) => return Err(MetalError::GbmDevice(e)), - }; - let mut is_nvidia = false; let mut is_amd = false; match gbm.drm.version() { @@ -2547,7 +2550,8 @@ impl MetalBackend { } fn set_gfx_api(&self, dev: &MetalDrmDevice, api: GfxApi) { - if dev.ctx.get().gfx.gfx_api() == api { + let old_ctx = dev.ctx.get(); + if old_ctx.gfx.gfx_api() == api { return; } let gfx = match self.state.create_gfx_context(&dev.master, Some(api)) { @@ -2566,6 +2570,7 @@ impl MetalBackend { dev.ctx.set(Rc::new(MetalRenderContext { dev_id: dev.id, gfx, + gbm: old_ctx.gbm.clone(), })); if dev.is_render_device() { self.make_render_device(dev, true); @@ -2836,7 +2841,7 @@ impl MetalBackend { return Err(MetalError::MissingRenderModifier(format.name)); } usage = GBM_BO_USE_RENDERING | GBM_BO_USE_LINEAR; - let render_bo = render_ctx.gfx.gbm().create_bo( + let render_bo = render_ctx.gbm.create_bo( &self.state.dma_buf_ids, width, height, diff --git a/src/backends/x.rs b/src/backends/x.rs index a2c008ef..082b89e5 100644 --- a/src/backends/x.rs +++ b/src/backends/x.rs @@ -1,5 +1,6 @@ use { crate::{ + allocator::BufferObject, async_engine::{Phase, SpawnedFuture}, backend::{ AxisSource, Backend, BackendDrmDevice, BackendEvent, Connector, ConnectorEvent, diff --git a/src/cli/screenshot.rs b/src/cli/screenshot.rs index 42379582..994628f5 100644 --- a/src/cli/screenshot.rs +++ b/src/cli/screenshot.rs @@ -1,23 +1,30 @@ use { crate::{ + allocator::{Allocator, AllocatorError, BO_USE_LINEAR, BO_USE_RENDERING}, cli::{GlobalArgs, ScreenshotArgs, ScreenshotFormat}, format::XRGB8888, tools::tool_client::{with_tool_client, Handle, ToolClient}, + udmabuf::{Udmabuf, UdmabufError}, utils::{errorfmt::ErrorFmt, queue::AsyncQueue, windows::WindowsExt}, video::{ dmabuf::{DmaBuf, DmaBufIds, DmaBufPlane, PlaneVec}, - drm::Drm, - gbm::{GbmDevice, GBM_BO_USE_LINEAR, GBM_BO_USE_RENDERING}, + drm::{Drm, DrmError}, + gbm::{GbmDevice, GbmError}, }, wire::{ jay_compositor::TakeScreenshot, - jay_screenshot::{Dmabuf, Error}, + jay_screenshot::{Dmabuf, Dmabuf2, DrmDev, Error, Plane}, }, }, chrono::Local, jay_algorithms::qoi::xrgb8888_encode_qoi, png::{BitDepth, ColorType, Encoder, SrgbRenderingIntent}, - std::rc::Rc, + std::{ + cell::{Cell, RefCell}, + rc::Rc, + }, + thiserror::Error, + uapi::OwnedFd, }; pub fn main(global: GlobalArgs, args: ScreenshotArgs) { @@ -47,17 +54,62 @@ async fn run(screenshot: Rc) { Error::handle(tc, sid, result.clone(), |res, err| { res.push(Err(err.msg.to_owned())); }); - Dmabuf::handle(tc, sid, result.clone(), |res, buf| { - res.push(Ok(buf)); + Dmabuf::handle(tc, sid, result.clone(), |res, ev| { + let mut planes = PlaneVec::new(); + planes.push(DmaBufPlane { + offset: ev.offset, + stride: ev.stride, + fd: ev.fd, + }); + let buf = DmaBuf { + id: DmaBufIds::default().next(), + width: ev.width as _, + height: ev.height as _, + format: XRGB8888, + modifier: ((ev.modifier_hi as u64) << 32) | (ev.modifier_lo as u64), + planes, + }; + res.push(Ok((buf, Some(ev.drm_dev)))); + }); + let drm_dev = Rc::new(Cell::new(None)); + let planes = Rc::new(RefCell::new(PlaneVec::new())); + DrmDev::handle(tc, sid, drm_dev.clone(), |res, buf| { + res.set(Some(buf.drm_dev)); }); - let buf = match result.pop().await { + Plane::handle(tc, sid, planes.clone(), |res, buf| { + res.borrow_mut().push(DmaBufPlane { + offset: buf.offset, + stride: buf.stride, + fd: buf.fd, + }); + }); + Dmabuf2::handle( + tc, + sid, + (drm_dev, planes, result.clone()), + |(dev, planes, res), ev| { + let buf = DmaBuf { + id: DmaBufIds::default().next(), + width: ev.width, + height: ev.height, + format: XRGB8888, + modifier: ev.modifier, + planes: planes.take(), + }; + res.push(Ok((buf, dev.take()))) + }, + ); + let (buf, drm_dev) = match result.pop().await { Ok(b) => b, Err(e) => { fatal!("Could not take a screenshot: {}", e); } }; let format = screenshot.args.format; - let data = buf_to_bytes(&DmaBufIds::default(), &buf, format); + let data = match buf_to_bytes(drm_dev.as_ref(), &buf, format) { + Ok(d) => d, + Err(e) => fatal!("{}", ErrorFmt(e)), + }; let filename = match &screenshot.args.filename { Some(f) => f.clone(), _ => { @@ -74,48 +126,48 @@ async fn run(screenshot: Rc) { } } -pub fn buf_to_bytes(dma_buf_ids: &DmaBufIds, buf: &Dmabuf, format: ScreenshotFormat) -> Vec { - let drm = match Drm::reopen(buf.drm_dev.raw(), false) { - Ok(drm) => drm, - Err(e) => { - fatal!("Could not open the drm device: {}", ErrorFmt(e)); - } - }; - let gbm = match GbmDevice::new(&drm) { - Ok(g) => g, - Err(e) => { - fatal!("Could not create a gbm device: {}", ErrorFmt(e)); - } - }; - let mut planes = PlaneVec::new(); - planes.push(DmaBufPlane { - offset: buf.offset, - stride: buf.stride, - fd: buf.fd.clone(), - }); - let dmabuf = DmaBuf { - id: dma_buf_ids.next(), - width: buf.width as _, - height: buf.height as _, - format: XRGB8888, - modifier: (buf.modifier_hi as u64) << 32 | (buf.modifier_lo as u64), - planes, - }; - let bo = match gbm.import_dmabuf(&dmabuf, GBM_BO_USE_LINEAR | GBM_BO_USE_RENDERING) { - Ok(bo) => Rc::new(bo), - Err(e) => { - fatal!("Could not import screenshot dmabuf: {}", ErrorFmt(e)); - } - }; - let bo_map = match bo.map_read() { - Ok(map) => map, - Err(e) => { - fatal!("Could not map dmabuf: {}", ErrorFmt(e)); +#[derive(Debug, Error)] +pub enum ScreenshotError { + #[error("Could not open the drm device")] + OpenDrmDevice(#[source] DrmError), + #[error("Could not create a gbm device")] + CreateGbmDevice(#[source] GbmError), + #[error("Could not create a udmabuf allocator")] + CreateUdmabuf(#[source] UdmabufError), + #[error("Could not import a dmabuf")] + ImportDmabuf(#[source] AllocatorError), + #[error("Could not map a dmabuf")] + MapDmabuf(#[source] AllocatorError), +} + +pub fn buf_to_bytes( + drm_dev: Option<&Rc>, + buf: &DmaBuf, + format: ScreenshotFormat, +) -> Result, ScreenshotError> { + let allocator: Rc = match drm_dev { + Some(drm_dev) => { + let drm = Drm::reopen(drm_dev.raw(), false).map_err(ScreenshotError::OpenDrmDevice)?; + GbmDevice::new(&drm) + .map(Rc::new) + .map_err(ScreenshotError::CreateGbmDevice)? } + None => Udmabuf::new() + .map(Rc::new) + .map_err(ScreenshotError::CreateUdmabuf)?, }; + let bo = allocator + .import_dmabuf(buf, BO_USE_LINEAR | BO_USE_RENDERING) + .map_err(ScreenshotError::ImportDmabuf)?; + let bo_map = bo.map_read().map_err(ScreenshotError::MapDmabuf)?; let data = unsafe { bo_map.data() }; if format == ScreenshotFormat::Qoi { - return xrgb8888_encode_qoi(data, buf.width, buf.height, bo_map.stride() as u32); + return Ok(xrgb8888_encode_qoi( + data, + buf.width as _, + buf.height as _, + bo_map.stride() as u32, + )); } let mut out = vec![]; @@ -128,12 +180,12 @@ pub fn buf_to_bytes(dma_buf_ids: &DmaBufIds, buf: &Dmabuf, format: ScreenshotFor image_data.extend_from_slice(&[pixel[2], pixel[1], pixel[0], 255]) } } - let mut encoder = Encoder::new(&mut out, buf.width, buf.height); + let mut encoder = Encoder::new(&mut out, buf.width as _, buf.height as _); encoder.set_color(ColorType::Rgba); encoder.set_depth(BitDepth::Eight); encoder.set_srgb(SrgbRenderingIntent::Perceptual); let mut writer = encoder.write_header().unwrap(); writer.write_image_data(&image_data).unwrap(); } - out + Ok(out) } diff --git a/src/drm_feedback.rs b/src/drm_feedback.rs index 6a1251b2..f998ddc8 100644 --- a/src/drm_feedback.rs +++ b/src/drm_feedback.rs @@ -36,9 +36,11 @@ impl DrmFeedback { ids: &DrmFeedbackIds, render_ctx: &dyn GfxContext, ) -> Result { - let main_device = uapi::fstat(render_ctx.gbm().drm.raw()) - .map_err(OsError::from)? - .st_rdev; + let drm = match render_ctx.allocator().drm() { + Some(drm) => drm.raw(), + _ => return Err(DrmFeedbackError::NoDrmDevice), + }; + let main_device = uapi::fstat(drm).map_err(OsError::from)?.st_rdev; let (data, index_map) = create_fd_data(render_ctx); let mut memfd = uapi::memfd_create("drm_feedback", c::MFD_CLOEXEC | c::MFD_ALLOW_SEALING).unwrap(); @@ -118,4 +120,6 @@ fn create_fd_data(ctx: &dyn GfxContext) -> (Vec, AHashMap<(u32, Modifier), u pub enum DrmFeedbackError { #[error("Could not stat drm device")] Stat(#[from] OsError), + #[error("Graphics API does not have a DRM device")] + NoDrmDevice, } diff --git a/src/gfx_api.rs b/src/gfx_api.rs index 043e0172..9ebdd9c0 100644 --- a/src/gfx_api.rs +++ b/src/gfx_api.rs @@ -1,5 +1,6 @@ use { crate::{ + allocator::Allocator, cursor::Cursor, damage::DamageVisualizer, fixed::Fixed, @@ -11,7 +12,7 @@ use { theme::Color, tree::{Node, OutputNode}, utils::{clonecell::UnsafeCellCloneSafe, transform_ext::TransformExt}, - video::{dmabuf::DmaBuf, drm::sync_obj::SyncObjCtx, gbm::GbmDevice, Modifier}, + video::{dmabuf::DmaBuf, drm::sync_obj::SyncObjCtx, Modifier}, }, ahash::AHashMap, indexmap::IndexSet, @@ -533,7 +534,7 @@ pub trait GfxTexture: Debug { pub trait GfxContext: Debug { fn reset_status(&self) -> Option; - fn render_node(&self) -> Rc; + fn render_node(&self) -> Option>; fn formats(&self) -> Rc>; @@ -554,7 +555,7 @@ pub trait GfxContext: Debug { damage: Option<&[Rect]>, ) -> Result, GfxError>; - fn gbm(&self) -> &GbmDevice; + fn allocator(&self) -> Rc; fn gfx_api(&self) -> GfxApi; @@ -566,7 +567,7 @@ pub trait GfxContext: Debug { format: &'static Format, ) -> Result, GfxError>; - fn sync_obj_ctx(&self) -> &Rc; + fn sync_obj_ctx(&self) -> Option<&Rc>; } #[derive(Debug)] diff --git a/src/gfx_apis/gl/renderer/context.rs b/src/gfx_apis/gl/renderer/context.rs index e5c4b969..4d9568af 100644 --- a/src/gfx_apis/gl/renderer/context.rs +++ b/src/gfx_apis/gl/renderer/context.rs @@ -1,5 +1,6 @@ use { crate::{ + allocator::Allocator, format::{Format, XRGB8888}, gfx_api::{ BufferResvUser, GfxApiOpt, GfxContext, GfxError, GfxFormat, GfxFramebuffer, GfxImage, @@ -240,8 +241,8 @@ impl GfxContext for GlRenderContext { self.reset_status() } - fn render_node(&self) -> Rc { - self.render_node() + fn render_node(&self) -> Option> { + Some(self.render_node()) } fn formats(&self) -> Rc> { @@ -278,8 +279,8 @@ impl GfxContext for GlRenderContext { .map_err(|e| e.into()) } - fn gbm(&self) -> &GbmDevice { - &self.gbm + fn allocator(&self) -> Rc { + self.gbm.clone() } fn gfx_api(&self) -> GfxApi { @@ -299,7 +300,7 @@ impl GfxContext for GlRenderContext { Ok(Rc::new(Framebuffer { ctx: self, gl: fb })) } - fn sync_obj_ctx(&self) -> &Rc { - &self.sync_ctx + fn sync_obj_ctx(&self) -> Option<&Rc> { + Some(&self.sync_ctx) } } diff --git a/src/gfx_apis/vulkan.rs b/src/gfx_apis/vulkan.rs index dc91bf95..56d81677 100644 --- a/src/gfx_apis/vulkan.rs +++ b/src/gfx_apis/vulkan.rs @@ -17,6 +17,7 @@ mod util; use { crate::{ + allocator::Allocator, async_engine::AsyncEngine, format::Format, gfx_api::{ @@ -31,7 +32,7 @@ use { video::{ dmabuf::DmaBuf, drm::{sync_obj::SyncObjCtx, Drm, DrmError}, - gbm::{GbmDevice, GbmError}, + gbm::GbmError, }, }, ahash::AHashMap, @@ -209,8 +210,8 @@ impl GfxContext for Context { None } - fn render_node(&self) -> Rc { - self.0.device.render_node.clone() + fn render_node(&self) -> Option> { + Some(self.0.device.render_node.clone()) } fn formats(&self) -> Rc> { @@ -255,8 +256,8 @@ impl GfxContext for Context { Ok(tex as _) } - fn gbm(&self) -> &GbmDevice { - &self.0.device.gbm + fn allocator(&self) -> Rc { + self.0.device.gbm.clone() } fn gfx_api(&self) -> GfxApi { @@ -276,8 +277,8 @@ impl GfxContext for Context { Ok(fb) } - fn sync_obj_ctx(&self) -> &Rc { - &self.0.device.sync_ctx + fn sync_obj_ctx(&self) -> Option<&Rc> { + Some(&self.0.device.sync_ctx) } } diff --git a/src/gfx_apis/vulkan/device.rs b/src/gfx_apis/vulkan/device.rs index 78e7f577..8209d200 100644 --- a/src/gfx_apis/vulkan/device.rs +++ b/src/gfx_apis/vulkan/device.rs @@ -49,7 +49,7 @@ use { pub struct VulkanDevice { pub(super) physical_device: PhysicalDevice, pub(super) render_node: Rc, - pub(super) gbm: GbmDevice, + pub(super) gbm: Rc, pub(super) sync_ctx: Rc, pub(super) instance: Rc, pub(super) device: Device, @@ -276,7 +276,7 @@ impl VulkanInstance { physical_device: phy_dev, render_node, sync_ctx: Rc::new(SyncObjCtx::new(gbm.drm.fd())), - gbm, + gbm: Rc::new(gbm), instance: self.clone(), device, external_memory_fd, diff --git a/src/ifs/jay_compositor.rs b/src/ifs/jay_compositor.rs index 51d011ae..d9152077 100644 --- a/src/ifs/jay_compositor.rs +++ b/src/ifs/jay_compositor.rs @@ -32,6 +32,7 @@ use { }; pub const CREATE_EI_SESSION_SINCE: Version = Version(5); +pub const SCREENSHOT_SPLITUP_SINCE: Version = Version(6); pub struct JayCompositorGlobal { name: GlobalName, @@ -69,7 +70,7 @@ impl Global for JayCompositorGlobal { } fn version(&self) -> u32 { - 5 + 6 } fn required_caps(&self) -> ClientCaps { @@ -117,16 +118,30 @@ impl JayCompositor { match take_screenshot(&self.client.state, include_cursor) { Ok(s) => { let dmabuf = s.bo.dmabuf(); - let plane = &dmabuf.planes[0]; - ss.send_dmabuf( - &s.drm, - &plane.fd, - dmabuf.width, - dmabuf.height, - plane.offset, - plane.stride, - dmabuf.modifier, - ); + if self.version < SCREENSHOT_SPLITUP_SINCE { + if let Some(drm) = &s.drm { + let plane = &dmabuf.planes[0]; + ss.send_dmabuf( + drm, + &plane.fd, + dmabuf.width, + dmabuf.height, + plane.offset, + plane.stride, + dmabuf.modifier, + ); + } else { + ss.send_error("Buffer has no associated DRM device"); + } + } else { + if let Some(drm) = &s.drm { + ss.send_drm_dev(drm); + } + for plane in &dmabuf.planes { + ss.send_plane(plane); + } + ss.send_dmabuf2(dmabuf); + } } Err(e) => { let msg = ErrorFmt(e).to_string(); diff --git a/src/ifs/jay_render_ctx.rs b/src/ifs/jay_render_ctx.rs index 724e0967..f15e7129 100644 --- a/src/ifs/jay_render_ctx.rs +++ b/src/ifs/jay_render_ctx.rs @@ -21,10 +21,16 @@ impl JayRenderCtx { pub fn send_render_ctx(&self, ctx: Option>) { let mut fd = None; if let Some(ctx) = ctx { - match ctx.gbm().drm.dup_render() { - Ok(d) => fd = Some(d.fd().clone()), - Err(e) => { - log::error!("Could not dup drm fd: {}", ErrorFmt(e)); + let allocator = ctx.allocator(); + match allocator.drm() { + Some(drm) => match drm.dup_render() { + Ok(d) => fd = Some(d.fd().clone()), + Err(e) => { + log::error!("Could not dup drm fd: {}", ErrorFmt(e)); + } + }, + None => { + log::error!("Allocator does not have a DRM device"); } } } else { diff --git a/src/ifs/jay_screencast.rs b/src/ifs/jay_screencast.rs index 91811100..120874d6 100644 --- a/src/ifs/jay_screencast.rs +++ b/src/ifs/jay_screencast.rs @@ -1,5 +1,6 @@ use { crate::{ + allocator::{AllocatorError, BufferObject, BO_USE_LINEAR, BO_USE_RENDERING}, client::{Client, ClientError}, format::XRGB8888, gfx_api::{GfxContext, GfxError, GfxFramebuffer, GfxTexture}, @@ -16,17 +17,11 @@ use { numcell::NumCell, option_ext::OptionExt, }, - video::{ - dmabuf::DmaBuf, - gbm::{GbmBo, GbmError, GBM_BO_USE_LINEAR, GBM_BO_USE_RENDERING}, - Modifier, INVALID_MODIFIER, LINEAR_MODIFIER, - }, + video::{dmabuf::DmaBuf, INVALID_MODIFIER, LINEAR_MODIFIER}, wire::{jay_screencast::*, JayScreencastId}, }, ahash::AHashSet, - indexmap::{indexset, IndexSet}, jay_config::video::Transform, - once_cell::sync::Lazy, std::{ cell::{Cell, RefCell}, ops::DerefMut, @@ -108,7 +103,7 @@ struct Pending { } struct ScreencastBuffer { - _bo: GbmBo, + _bo: Rc, dmabuf: DmaBuf, fb: Rc, free: bool, @@ -381,31 +376,27 @@ impl JayScreencast { if width == 0 || height == 0 { continue; } - let mut usage = GBM_BO_USE_RENDERING; + let mut usage = BO_USE_RENDERING; let modifiers = match self.linear.get() { true if format.write_modifiers.contains(&LINEAR_MODIFIER) => { - static MODS: Lazy> = - Lazy::new(|| indexset![LINEAR_MODIFIER]); - &MODS + vec![LINEAR_MODIFIER] } true if format.write_modifiers.contains(&INVALID_MODIFIER) => { - usage |= GBM_BO_USE_LINEAR; - static MODS: Lazy> = - Lazy::new(|| indexset![INVALID_MODIFIER]); - &MODS + usage |= BO_USE_LINEAR; + vec![INVALID_MODIFIER] } true => return Err(JayScreencastError::Modifier), false if format.write_modifiers.is_empty() => { return Err(JayScreencastError::XRGB8888Writing) } - false => &format.write_modifiers, + false => format.write_modifiers.iter().copied().collect(), }; - let buffer = ctx.gbm().create_bo( + let buffer = ctx.allocator().create_bo( &self.client.state.dma_buf_ids, width, height, XRGB8888, - modifiers, + &modifiers, usage, )?; let fb = ctx.clone().dmabuf_img(buffer.dmabuf())?.to_framebuffer()?; @@ -681,7 +672,7 @@ pub enum JayScreencastError { #[error("Buffer index {0} is out-of-bounds")] OutOfBounds(u32), #[error(transparent)] - GbmError(#[from] GbmError), + AllocatorError(#[from] AllocatorError), #[error(transparent)] GfxError(#[from] GfxError), #[error("Render context does not support XRGB8888 format")] diff --git a/src/ifs/jay_screenshot.rs b/src/ifs/jay_screenshot.rs index 5695067f..05d1b150 100644 --- a/src/ifs/jay_screenshot.rs +++ b/src/ifs/jay_screenshot.rs @@ -3,6 +3,7 @@ use { client::Client, leaks::Tracker, object::{Object, Version}, + video::dmabuf::{DmaBuf, DmaBufPlane}, wire::{jay_screenshot::*, JayScreenshotId}, }, std::{convert::Infallible, rc::Rc}, @@ -45,6 +46,31 @@ impl JayScreenshot { msg, }); } + + pub fn send_drm_dev(&self, drm: &Rc) { + self.client.event(DrmDev { + self_id: self.id, + drm_dev: drm.clone(), + }) + } + + pub fn send_plane(&self, plane: &DmaBufPlane) { + self.client.event(Plane { + self_id: self.id, + fd: plane.fd.clone(), + offset: plane.offset, + stride: plane.stride, + }) + } + + pub fn send_dmabuf2(&self, buf: &DmaBuf) { + self.client.event(Dmabuf2 { + self_id: self.id, + width: buf.width, + height: buf.height, + modifier: buf.modifier, + }) + } } impl JayScreenshotRequestHandler for JayScreenshot { diff --git a/src/ifs/wl_drm.rs b/src/ifs/wl_drm.rs index 3f6c453f..212ad660 100644 --- a/src/ifs/wl_drm.rs +++ b/src/ifs/wl_drm.rs @@ -43,7 +43,9 @@ impl WlDrmGlobal { track!(client, obj); client.add_client_obj(&obj)?; if let Some(rc) = client.state.render_ctx.get() { - obj.send_device(&rc.render_node()); + if let Some(rn) = rc.render_node() { + obj.send_device(&rn); + } obj.send_capabilities(PRIME); } Ok(()) diff --git a/src/ifs/wl_surface.rs b/src/ifs/wl_surface.rs index 049c5034..3ab3e797 100644 --- a/src/ifs/wl_surface.rs +++ b/src/ifs/wl_surface.rs @@ -202,7 +202,10 @@ impl Drop for SurfaceBuffer { log::error!("Cannot signal release point because there is no render context"); return; }; - let ctx = ctx.sync_obj_ctx(); + let Some(ctx) = ctx.sync_obj_ctx() else { + log::error!("Cannot signal release point because there is no syncobj context"); + return; + }; if sync_files.is_not_empty() { let res = ctx.import_sync_files( &release.sync_obj, diff --git a/src/it/test_backend.rs b/src/it/test_backend.rs index 7acb55f9..489ca9b6 100644 --- a/src/it/test_backend.rs +++ b/src/it/test_backend.rs @@ -1,5 +1,6 @@ use { crate::{ + allocator::Allocator, async_engine::SpawnedFuture, backend::{ AxisSource, Backend, BackendEvent, Connector, ConnectorEvent, ConnectorId, @@ -15,14 +16,18 @@ use { test_error::TestResult, test_gfx_api::TestGfxCtx, test_utils::test_expected_event::TEEH, }, state::State, + udmabuf::Udmabuf, utils::{ - clonecell::CloneCell, copyhashmap::CopyHashMap, on_change::OnChange, oserror::OsError, - syncqueue::SyncQueue, + clonecell::CloneCell, copyhashmap::CopyHashMap, errorfmt::ErrorFmt, + on_change::OnChange, oserror::OsError, syncqueue::SyncQueue, + }, + video::{ + drm::{ConnectorType, Drm}, + gbm::{GbmDevice, GbmError}, }, - video::drm::{ConnectorType, Drm}, }, bstr::ByteSlice, - std::{any::Any, cell::Cell, io, os::unix::ffi::OsStrExt, pin::Pin, rc::Rc}, + std::{any::Any, cell::Cell, error::Error, io, os::unix::ffi::OsStrExt, pin::Pin, rc::Rc}, thiserror::Error, uapi::c, }; @@ -37,6 +42,10 @@ pub enum TestBackendError { OpenDrmNode(String, #[source] OsError), #[error("Could not create a render context")] RenderContext(#[source] GfxError), + #[error("Could not create a gbm device")] + CreateGbmDevice(#[source] GbmError), + #[error("Could not create any allocator")] + CreateAllocator, } pub struct TestBackend { @@ -124,17 +133,21 @@ impl TestBackend { } } - pub fn install_render_context(&self) -> TestResult { + pub fn install_render_context(&self, prefer_udmabuf: bool) -> TestResult { if self.render_context_installed.get() { return Ok(()); } - self.create_render_context()?; + self.create_render_context(prefer_udmabuf)?; self.render_context_installed.set(true); Ok(()) } pub fn install_default(&self) -> TestResult { - self.install_render_context()?; + self.install_default2(true) + } + + pub fn install_default2(&self, prefer_udmabuf: bool) -> TestResult { + self.install_render_context(prefer_udmabuf)?; self.state .backend_events .push(BackendEvent::NewConnector(self.default_connector.clone())); @@ -150,47 +163,35 @@ impl TestBackend { Ok(()) } - fn create_render_context(&self) -> Result<(), TestBackendError> { - let dri = match std::fs::read_dir("/dev/dri") { - Ok(d) => d, - Err(e) => return Err(TestBackendError::ReadDri(e)), - }; - let mut files = vec![]; - for f in dri { - let f = match f { - Ok(f) => f, - Err(e) => return Err(TestBackendError::ReadDri(e)), + fn create_render_context(&self, prefer_udmabuf: bool) -> Result<(), TestBackendError> { + macro_rules! constructor { + ($c:expr) => { + (&|| { + $c.map(|a| Rc::new(a) as Rc) + .map_err(|e| Box::new(e) as Box) + }) as &dyn Fn() -> Result, Box> }; - files.push(f.path()); } - let node = 'node: { - for f in &files { - if let Some(file) = f.file_name() { - if file.as_bytes().starts_with_str("renderD") { - break 'node f; - } + let udmabuf = ("udmabuf", constructor!(Udmabuf::new())); + let gbm = ("GBM", constructor!(create_gbm_allocator())); + let allocators = match prefer_udmabuf { + true => [udmabuf, gbm], + false => [gbm, udmabuf], + }; + let mut allocator = None::>; + for (name, f) in allocators { + match f() { + Ok(a) => { + allocator = Some(a); + break; } - } - for f in &files { - if let Some(file) = f.file_name() { - if file.as_bytes().starts_with_str("card") { - break 'node f; - } + Err(e) => { + log::error!("Could not create {name} allocator: {}", ErrorFmt(&*e)); } } - return Err(TestBackendError::NoDrmNode); - }; - let file = match uapi::open(node.as_path(), c::O_RDWR | c::O_CLOEXEC, 0) { - Ok(f) => Rc::new(f), - Err(e) => { - return Err(TestBackendError::OpenDrmNode( - node.as_os_str().as_bytes().as_bstr().to_string(), - e.into(), - )) - } - }; - let drm = Drm::open_existing(file); - let ctx = match TestGfxCtx::new(&drm) { + } + let allocator = allocator.ok_or(TestBackendError::CreateAllocator)?; + let ctx = match TestGfxCtx::new(allocator) { Ok(ctx) => ctx, Err(e) => return Err(TestBackendError::RenderContext(e)), }; @@ -199,6 +200,50 @@ impl TestBackend { } } +fn create_gbm_allocator() -> Result { + let dri = match std::fs::read_dir("/dev/dri") { + Ok(d) => d, + Err(e) => return Err(TestBackendError::ReadDri(e)), + }; + let mut files = vec![]; + for f in dri { + let f = match f { + Ok(f) => f, + Err(e) => return Err(TestBackendError::ReadDri(e)), + }; + files.push(f.path()); + } + let node = 'node: { + for f in &files { + if let Some(file) = f.file_name() { + if file.as_bytes().starts_with_str("renderD") { + break 'node f; + } + } + } + for f in &files { + if let Some(file) = f.file_name() { + if file.as_bytes().starts_with_str("card") { + break 'node f; + } + } + } + return Err(TestBackendError::NoDrmNode); + }; + let file = match uapi::open(node.as_path(), c::O_RDWR | c::O_CLOEXEC, 0) { + Ok(f) => Rc::new(f), + Err(e) => { + return Err(TestBackendError::OpenDrmNode( + node.as_os_str().as_bytes().as_bstr().to_string(), + e.into(), + )) + } + }; + let drm = Drm::open_existing(file); + let gbm = GbmDevice::new(&drm).map_err(TestBackendError::CreateGbmDevice)?; + Ok(gbm) +} + impl Backend for TestBackend { fn run(self: Rc) -> SpawnedFuture>> { let future = (self.test_future)(&self.state); diff --git a/src/it/test_client.rs b/src/it/test_client.rs index fdfbad9b..7bb2c4cc 100644 --- a/src/it/test_client.rs +++ b/src/it/test_client.rs @@ -25,7 +25,7 @@ use { pub struct TestClient { pub run: Rc, - pub server: Rc, + pub _server: Rc, pub tran: Rc, pub registry: Rc, pub jc: Rc, @@ -92,12 +92,8 @@ impl TestClient { } pub async fn take_screenshot(&self, include_cursor: bool) -> Result, TestError> { - let dmabuf = self.jc.take_screenshot(include_cursor).await?; - let qoi = buf_to_bytes( - &self.server.state.dma_buf_ids, - &dmabuf, - ScreenshotFormat::Qoi, - ); + let (dmabuf, dev) = self.jc.take_screenshot(include_cursor).await?; + let qoi = buf_to_bytes(dev.as_ref(), &dmabuf, ScreenshotFormat::Qoi)?; Ok(qoi) } diff --git a/src/it/test_gfx_api.rs b/src/it/test_gfx_api.rs index 4dd2f5b4..032fa402 100644 --- a/src/it/test_gfx_api.rs +++ b/src/it/test_gfx_api.rs @@ -1,5 +1,6 @@ use { crate::{ + allocator::{Allocator, AllocatorError, BufferObject, BufferUsage}, format::{Format, ARGB8888, XRGB8888}, gfx_api::{ CopyTexture, FillRect, FramebufferRect, GfxApiOpt, GfxContext, GfxError, GfxFormat, @@ -7,12 +8,7 @@ use { }, rect::Rect, theme::Color, - video::{ - dmabuf::DmaBuf, - drm::{sync_obj::SyncObjCtx, Drm, DrmError}, - gbm::{GbmBo, GbmDevice, GbmError}, - LINEAR_MODIFIER, - }, + video::{dmabuf::DmaBuf, drm::sync_obj::SyncObjCtx, LINEAR_MODIFIER}, }, ahash::AHashMap, indexmap::IndexSet, @@ -32,15 +28,9 @@ use { #[derive(Error, Debug)] enum TestGfxError { #[error("Could not map dmabuf")] - MapDmaBuf(#[source] GbmError), + MapDmaBuf(#[source] AllocatorError), #[error("Could not import dmabuf")] - ImportDmaBuf(#[source] GbmError), - #[error("Could not create a gbm device")] - CreateGbmDevice(#[source] GbmError), - #[error("Could not retrieve the render node path")] - GetRenderNode(#[source] DrmError), - #[error("Drm device does not have a render node")] - NoRenderNode, + ImportDmaBuf(#[source] AllocatorError), } impl From for GfxError { @@ -51,19 +41,11 @@ impl From for GfxError { pub struct TestGfxCtx { formats: Rc>, - sync_obj_ctx: Rc, - gbm: GbmDevice, - render_node: Rc, + allocator: Rc, } impl TestGfxCtx { - pub fn new(drm: &Drm) -> Result, GfxError> { - let render_node = drm - .get_render_node() - .map_err(TestGfxError::GetRenderNode)? - .ok_or(TestGfxError::NoRenderNode)?; - let gbm = GbmDevice::new(drm).map_err(TestGfxError::CreateGbmDevice)?; - let ctx = Rc::new(SyncObjCtx::new(drm.fd())); + pub fn new(allocator: Rc) -> Result, GfxError> { let mut modifiers = IndexSet::new(); modifiers.insert(LINEAR_MODIFIER); let mut formats = AHashMap::new(); @@ -79,9 +61,7 @@ impl TestGfxCtx { } Ok(Rc::new(Self { formats: Rc::new(formats), - sync_obj_ctx: ctx, - gbm, - render_node: Rc::new(render_node), + allocator, })) } } @@ -97,8 +77,8 @@ impl GfxContext for TestGfxCtx { None } - fn render_node(&self) -> Rc { - self.render_node.clone() + fn render_node(&self) -> Option> { + None } fn formats(&self) -> Rc> { @@ -109,9 +89,8 @@ impl GfxContext for TestGfxCtx { Ok(Rc::new(TestGfxImage::DmaBuf(TestDmaBufGfxImage { buf: buf.clone(), bo: self - .gbm - .import_dmabuf(buf, 0) - .map(Rc::new) + .allocator + .import_dmabuf(buf, BufferUsage::none()) .map_err(TestGfxError::ImportDmaBuf)?, }))) } @@ -142,8 +121,8 @@ impl GfxContext for TestGfxCtx { }))) } - fn gbm(&self) -> &GbmDevice { - &self.gbm + fn allocator(&self) -> Rc { + self.allocator.clone() } fn gfx_api(&self) -> GfxApi { @@ -170,8 +149,8 @@ impl GfxContext for TestGfxCtx { })) } - fn sync_obj_ctx(&self) -> &Rc { - &self.sync_obj_ctx + fn sync_obj_ctx(&self) -> Option<&Rc> { + None } } @@ -195,7 +174,7 @@ struct TestShmGfxImage { struct TestDmaBufGfxImage { buf: DmaBuf, - bo: Rc, + bo: Rc, } impl TestGfxImage { @@ -242,7 +221,7 @@ impl TestGfxImage { ); } TestGfxImage::DmaBuf(d) => { - let map = d.bo.map_read().map_err(TestGfxError::MapDmaBuf)?; + let map = d.bo.clone().map_read().map_err(TestGfxError::MapDmaBuf)?; unsafe { copy( map.stride(), @@ -478,7 +457,7 @@ impl GfxFramebuffer for TestGfxFb { s.format, ), TestGfxImage::DmaBuf(d) => { - let map = d.bo.map_read().map_err(TestGfxError::MapDmaBuf)?; + let map = d.bo.clone().map_read().map_err(TestGfxError::MapDmaBuf)?; copy( map.data_ptr(), d.buf.width, @@ -511,7 +490,7 @@ impl GfxFramebuffer for TestGfxFb { s.format, )?, TestGfxImage::DmaBuf(d) => { - let map = d.bo.map_write().map_err(TestGfxError::MapDmaBuf)?; + let map = d.bo.clone().map_write().map_err(TestGfxError::MapDmaBuf)?; apply( map.data_ptr(), d.buf.width, diff --git a/src/it/test_ifs/test_jay_compositor.rs b/src/it/test_ifs/test_jay_compositor.rs index 53887664..71298877 100644 --- a/src/it/test_ifs/test_jay_compositor.rs +++ b/src/it/test_ifs/test_jay_compositor.rs @@ -9,13 +9,14 @@ use { testrun::ParseFull, }, utils::{buffd::MsgParser, cell_ext::CellExt}, + video::dmabuf::DmaBuf, wire::{ jay_compositor::{self, *}, - jay_screenshot::Dmabuf, JayCompositorId, }, }, std::{cell::Cell, rc::Rc}, + uapi::OwnedFd, }; pub struct TestJayCompositor { @@ -49,10 +50,16 @@ impl TestJayCompositor { Ok(()) } - pub async fn take_screenshot(&self, include_cursor: bool) -> Result { + pub async fn take_screenshot( + &self, + include_cursor: bool, + ) -> Result<(DmaBuf, Option>), TestError> { let js = Rc::new(TestJayScreenshot { id: self.tran.id(), - result: Cell::new(None), + state: self.tran.run.state.clone(), + drm_dev: Default::default(), + planes: Default::default(), + result: Default::default(), }); self.tran.send(TakeScreenshot2 { self_id: self.id, @@ -62,7 +69,7 @@ impl TestJayCompositor { self.tran.add_obj(js.clone())?; self.tran.sync().await; match js.result.take() { - Some(Ok(res)) => Ok(res), + Some(Ok(res)) => Ok((res, js.drm_dev.take())), Some(Err(res)) => bail!("Compositor could not take a screenshot: {}", res), None => bail!("Compositor did not send a screenshot"), } diff --git a/src/it/test_ifs/test_registry.rs b/src/it/test_ifs/test_registry.rs index 13286ee4..cead68b6 100644 --- a/src/it/test_ifs/test_registry.rs +++ b/src/it/test_ifs/test_registry.rs @@ -165,7 +165,7 @@ impl TestRegistry { get_jay_compositor, jay_compositor, jay_compositor, - 1, + 6, TestJayCompositor ); create_singleton!(get_compositor, compositor, wl_compositor, 6, TestCompositor); diff --git a/src/it/test_ifs/test_screenshot.rs b/src/it/test_ifs/test_screenshot.rs index e3d21d32..e1ccc1d7 100644 --- a/src/it/test_ifs/test_screenshot.rs +++ b/src/it/test_ifs/test_screenshot.rs @@ -1,21 +1,44 @@ use { crate::{ + format::XRGB8888, it::{test_error::TestError, test_object::TestObject, testrun::ParseFull}, + state::State, utils::buffd::MsgParser, + video::dmabuf::{DmaBuf, DmaBufPlane, PlaneVec}, wire::{jay_screenshot::*, JayScreenshotId}, }, - std::cell::Cell, + std::{ + cell::{Cell, RefCell}, + rc::Rc, + }, + uapi::OwnedFd, }; pub struct TestJayScreenshot { pub id: JayScreenshotId, - pub result: Cell>>, + pub state: Rc, + pub drm_dev: Cell>>, + pub planes: RefCell>, + pub result: Cell>>, } impl TestJayScreenshot { fn handle_dmabuf(&self, parser: MsgParser<'_, '_>) -> Result<(), TestError> { let ev = Dmabuf::parse_full(parser)?; - self.result.set(Some(Ok(ev))); + let mut planes = PlaneVec::new(); + planes.push(DmaBufPlane { + offset: ev.offset, + stride: ev.stride, + fd: ev.fd, + }); + self.result.set(Some(Ok(DmaBuf { + id: self.state.dma_buf_ids.next(), + width: ev.width as _, + height: ev.height as _, + format: XRGB8888, + modifier: ((ev.modifier_hi as u64) << 32) | (ev.modifier_lo as u64), + planes, + }))); Ok(()) } @@ -24,6 +47,35 @@ impl TestJayScreenshot { self.result.set(Some(Err(ev.msg.to_string()))); Ok(()) } + + fn handle_drm_dev(&self, parser: MsgParser<'_, '_>) -> Result<(), TestError> { + let ev = DrmDev::parse_full(parser)?; + self.drm_dev.set(Some(ev.drm_dev)); + Ok(()) + } + + fn handle_plane(&self, parser: MsgParser<'_, '_>) -> Result<(), TestError> { + let ev = Plane::parse_full(parser)?; + self.planes.borrow_mut().push(DmaBufPlane { + offset: ev.offset, + stride: ev.stride, + fd: ev.fd, + }); + Ok(()) + } + + fn handle_dmabuf2(&self, parser: MsgParser<'_, '_>) -> Result<(), TestError> { + let ev = Dmabuf2::parse_full(parser)?; + self.result.set(Some(Ok(DmaBuf { + id: self.state.dma_buf_ids.next(), + width: ev.width as _, + height: ev.height as _, + format: XRGB8888, + modifier: ev.modifier, + planes: self.planes.take(), + }))); + Ok(()) + } } test_object! { @@ -31,6 +83,9 @@ test_object! { DMABUF => handle_dmabuf, ERROR => handle_error, + DRM_DEV => handle_drm_dev, + PLANE => handle_plane, + DMABUF2 => handle_dmabuf2, } impl TestObject for TestJayScreenshot {} diff --git a/src/it/testrun.rs b/src/it/testrun.rs index ad3a5b6f..d9b99ff0 100644 --- a/src/it/testrun.rs +++ b/src/it/testrun.rs @@ -79,7 +79,7 @@ impl TestRun { let client = self.state.clients.get(client_id)?; Ok(Rc::new(TestClient { run: self.clone(), - server: client, + _server: client, tran, jc, comp: registry.get_compositor().await?, @@ -106,7 +106,14 @@ impl TestRun { } pub async fn create_default_setup(&self) -> Result { - self.backend.install_default()?; + self.create_default_setup2(true).await + } + + pub async fn create_default_setup2( + &self, + prefer_udmabuf: bool, + ) -> Result { + self.backend.install_default2(prefer_udmabuf)?; let seat = self.get_seat("default")?; self.state.eng.yield_now().await; let output = match self.state.root.outputs.lock().values().next() { diff --git a/src/it/tests/t0013_graphics_initialized.rs b/src/it/tests/t0013_graphics_initialized.rs index 78176f19..5fcc7f4f 100644 --- a/src/it/tests/t0013_graphics_initialized.rs +++ b/src/it/tests/t0013_graphics_initialized.rs @@ -10,7 +10,7 @@ async fn test(run: Rc) -> TestResult { tassert!(!run.cfg.graphics_initialized.get()); - run.backend.install_render_context()?; + run.backend.install_render_context(true)?; tassert!(run.cfg.graphics_initialized.get()); diff --git a/src/it/tests/t0031_syncobj.rs b/src/it/tests/t0031_syncobj.rs index a0889ca5..9a15c4ee 100644 --- a/src/it/tests/t0031_syncobj.rs +++ b/src/it/tests/t0031_syncobj.rs @@ -11,7 +11,7 @@ use { testcase!(); async fn test(run: Rc) -> TestResult { - let _ds = run.create_default_setup().await?; + let _ds = run.create_default_setup2(false).await?; struct Waiter(Cell); impl SyncObjWaiter for Waiter { @@ -23,7 +23,11 @@ async fn test(run: Rc) -> TestResult { let waiter = Rc::new(Waiter(Cell::new(false))); let eng = run.state.render_ctx.get().unwrap(); - let syncobj = match eng.sync_obj_ctx().create_sync_obj() { + let Some(ctx) = eng.sync_obj_ctx() else { + log::warn!("Cannot test explicit sync on this system: render context does not support sync objects"); + return Ok(()); + }; + let syncobj = match ctx.create_sync_obj() { Ok(s) => Rc::new(s), Err(e) => { log::warn!("Cannot test explicit sync on this system: {}", ErrorFmt(e)); @@ -56,7 +60,7 @@ async fn test(run: Rc) -> TestResult { client.sync().await; tassert_eq!(waiter.0.get(), false); - eng.sync_obj_ctx().signal(&syncobj, SyncObjPoint(1))?; + ctx.signal(&syncobj, SyncObjPoint(1))?; client.sync().await; tassert_eq!(waiter.0.get(), true); diff --git a/src/it/tests/t0035_scanout_feedback.rs b/src/it/tests/t0035_scanout_feedback.rs index 25585a24..80e74841 100644 --- a/src/it/tests/t0035_scanout_feedback.rs +++ b/src/it/tests/t0035_scanout_feedback.rs @@ -12,7 +12,7 @@ use { testcase!(); async fn test(run: Rc) -> TestResult { - let ds = run.create_default_setup().await?; + let ds = run.create_default_setup2(false).await?; let scanout_feedback = { let Some(base_fb) = run.state.drm_feedback.get() else { diff --git a/src/main.rs b/src/main.rs index e4e72a12..5e3872b5 100644 --- a/src/main.rs +++ b/src/main.rs @@ -43,6 +43,7 @@ mod macros; #[macro_use] mod leaks; mod acceptor; +mod allocator; mod async_engine; mod backend; mod backends; @@ -91,6 +92,7 @@ mod time; mod tools; mod tree; mod udev; +mod udmabuf; mod user_session; mod utils; mod version; diff --git a/src/portal/ptr_gui.rs b/src/portal/ptr_gui.rs index 7ad3c413..c6d8d0a6 100644 --- a/src/portal/ptr_gui.rs +++ b/src/portal/ptr_gui.rs @@ -1,5 +1,6 @@ use { crate::{ + allocator::{BufferObject, BO_USE_RENDERING}, async_engine::{Phase, SpawnedFuture}, cursor::KnownCursor, fixed::Fixed, @@ -15,7 +16,6 @@ use { asyncevent::AsyncEvent, clonecell::CloneCell, copyhashmap::CopyHashMap, errorfmt::ErrorFmt, hash_map_ext::HashMapExt, rc_eq::rc_eq, }, - video::gbm::{GbmBo, GBM_BO_USE_RENDERING}, wire::{ wp_fractional_scale_v1::PreferredScale, zwlr_layer_surface_v1::Configure, ZwpLinuxBufferParamsV1Id, @@ -722,14 +722,15 @@ impl WindowData { log::error!("Render context cannot render to ARGB8888 format"); return; } + let modifiers: Vec<_> = format.write_modifiers.iter().copied().collect(); for _ in 0..NUM_BUFFERS { - let bo = match ctx.ctx.gbm().create_bo( + let bo = match ctx.ctx.allocator().create_bo( &self.dpy.state.dma_buf_ids, width, height, ARGB8888, - &format.write_modifiers, - GBM_BO_USE_RENDERING, + &modifiers, + BO_USE_RENDERING, ) { Ok(b) => b, Err(e) => { @@ -844,13 +845,13 @@ pub struct GuiBuffer { pub wl: Rc, pub window: Rc, pub fb: Rc, - pub _bo: Option, + pub _bo: Option>, pub free: Cell, pub _size: (i32, i32), } struct GuiBufferPending { - pub bo: Cell>, + pub bo: Cell>>, pub window: Rc, pub fb: Rc, pub params: Rc, diff --git a/src/screenshoter.rs b/src/screenshoter.rs index 4f9454c9..f0211d76 100644 --- a/src/screenshoter.rs +++ b/src/screenshoter.rs @@ -1,14 +1,11 @@ use { crate::{ + allocator::{AllocatorError, BufferObject, BO_USE_LINEAR, BO_USE_RENDERING}, format::XRGB8888, gfx_api::GfxError, scale::Scale, state::State, - video::{ - drm::DrmError, - gbm::{GbmBo, GbmError, GBM_BO_USE_LINEAR, GBM_BO_USE_RENDERING}, - INVALID_MODIFIER, LINEAR_MODIFIER, - }, + video::{drm::DrmError, INVALID_MODIFIER, LINEAR_MODIFIER}, }, jay_config::video::Transform, std::{ops::Deref, rc::Rc}, @@ -23,7 +20,7 @@ pub enum ScreenshooterError { #[error("Display is empty")] EmptyDisplay, #[error(transparent)] - GbmError(#[from] GbmError), + AllocatorError(#[from] AllocatorError), #[error(transparent)] RenderError(#[from] GfxError), #[error(transparent)] @@ -35,8 +32,8 @@ pub enum ScreenshooterError { } pub struct Screenshot { - pub drm: Rc, - pub bo: GbmBo, + pub drm: Option>, + pub bo: Rc, } pub fn take_screenshot( @@ -52,18 +49,18 @@ pub fn take_screenshot( return Err(ScreenshooterError::EmptyDisplay); } let formats = ctx.formats(); - let mut usage = GBM_BO_USE_RENDERING; + let mut usage = BO_USE_RENDERING; let modifiers = match formats.get(&XRGB8888.drm) { None => return Err(ScreenshooterError::XRGB8888), Some(f) if f.write_modifiers.contains(&LINEAR_MODIFIER) => &[LINEAR_MODIFIER], Some(f) if f.write_modifiers.contains(&INVALID_MODIFIER) => { - usage |= GBM_BO_USE_LINEAR; + usage |= BO_USE_LINEAR; &[INVALID_MODIFIER] } Some(_) => return Err(ScreenshooterError::Linear), }; - let gbm = ctx.gbm(); - let bo = gbm.create_bo( + let allocator = ctx.allocator(); + let bo = allocator.create_bo( &state.dma_buf_ids, extents.width(), extents.height(), @@ -83,6 +80,9 @@ pub fn take_screenshot( false, Transform::None, )?; - let drm = gbm.drm.dup_render()?.fd().clone(); + let drm = match allocator.drm() { + Some(drm) => Some(drm.dup_render()?.fd().clone()), + _ => None, + }; Ok(Screenshot { drm, bo }) } diff --git a/src/state.rs b/src/state.rs index 3564d9d1..941e37dd 100644 --- a/src/state.rs +++ b/src/state.rs @@ -429,7 +429,7 @@ impl State { self.cursors.set(None); self.drm_feedback.set(None); self.wait_for_sync_obj - .set_ctx(ctx.as_ref().map(|c| c.sync_obj_ctx().clone())); + .set_ctx(ctx.as_ref().and_then(|c| c.sync_obj_ctx().cloned())); 'handle_new_feedback: { if let Some(ctx) = &ctx { @@ -506,10 +506,12 @@ impl State { if !self.render_ctx_ever_initialized.replace(true) { self.add_global(&Rc::new(WlDrmGlobal::new(self.globals.name()))); self.add_global(&Rc::new(ZwpLinuxDmabufV1Global::new(self.globals.name()))); - if ctx.sync_obj_ctx().supports_async_wait() && self.explicit_sync_enabled.get() { - self.add_global(&Rc::new(WpLinuxDrmSyncobjManagerV1Global::new( - self.globals.name(), - ))); + if let Some(ctx) = ctx.sync_obj_ctx() { + if ctx.supports_async_wait() && self.explicit_sync_enabled.get() { + self.add_global(&Rc::new(WpLinuxDrmSyncobjManagerV1Global::new( + self.globals.name(), + ))); + } } if let Some(config) = self.config.get() { config.graphics_initialized(); @@ -1042,7 +1044,11 @@ impl State { log::error!("Cannot signal sync obj point because there is no render context"); return; }; - if let Err(e) = ctx.sync_obj_ctx().signal(sync_obj, point) { + let Some(ctx) = ctx.sync_obj_ctx() else { + log::error!("Cannot signal sync obj point because there is no syncobj context"); + return; + }; + if let Err(e) = ctx.signal(sync_obj, point) { log::error!("Could not signal sync obj: {}", ErrorFmt(e)); } } diff --git a/src/udmabuf.rs b/src/udmabuf.rs new file mode 100644 index 00000000..c20b66ac --- /dev/null +++ b/src/udmabuf.rs @@ -0,0 +1,271 @@ +use { + crate::{ + allocator::{Allocator, AllocatorError, BufferObject, BufferUsage, MappedBuffer}, + format::Format, + utils::{oserror::OsError, page_size::page_size}, + video::{ + dmabuf::{DmaBuf, DmaBufIds, DmaBufPlane, PlaneVec}, + drm::Drm, + Modifier, LINEAR_MODIFIER, + }, + }, + std::{ptr, rc::Rc}, + thiserror::Error, + uapi::{ + c, + c::{ + ioctl, mmap, munmap, F_SEAL_SHRINK, MAP_SHARED, MFD_ALLOW_SEALING, O_RDONLY, PROT_READ, + PROT_WRITE, + }, + map_err, open, OwnedFd, _IOW, + }, +}; + +#[derive(Debug, Error)] +pub enum UdmabufError { + #[error("Could not open /dev/udmabuf")] + Open(#[source] OsError), + #[error("Only the linear modifier can be allocated")] + Modifier, + #[error("Format {0} is not supported")] + Format(&'static str), + #[error("Could not create a memfd")] + Memfd(#[source] OsError), + #[error("Size calculation overflowed")] + Overflow, + #[error("Could not resize the memfd")] + Truncate(#[source] OsError), + #[error("Could not seal the memfd")] + Seal(#[source] OsError), + #[error("Could not create a dmabuf")] + CreateDmabuf(#[source] OsError), + #[error("Only a single plane is supported")] + Planes, + #[error("Stride is invalid")] + Stride, + #[error("Could not stat the dmabuf")] + Stat(#[source] OsError), + #[error("Dmabuf is too small for required size")] + Size, + #[error("Could not map dmabuf")] + Map(#[source] OsError), +} + +pub struct Udmabuf { + fd: OwnedFd, +} + +impl Udmabuf { + pub fn new() -> Result { + let fd = match open("/dev/udmabuf", O_RDONLY, 0) { + Ok(b) => b, + Err(e) => return Err(UdmabufError::Open(e.into())), + }; + Ok(Self { fd }) + } +} + +impl Allocator for Udmabuf { + fn drm(&self) -> Option<&Drm> { + None + } + + fn create_bo( + &self, + dma_buf_ids: &DmaBufIds, + width: i32, + height: i32, + format: &'static Format, + modifiers: &[Modifier], + _usage: BufferUsage, + ) -> Result, AllocatorError> { + if !modifiers.contains(&LINEAR_MODIFIER) { + return Err(UdmabufError::Modifier.into()); + } + let Some(shm_info) = &format.shm_info else { + return Err(UdmabufError::Format(format.name).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_mask = 255; + let stride = (width * shm_info.bpp as u64 + stride_mask) & !stride_mask; + 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 mut cmd = udmabuf_create { + memfd: memfd.raw() as u32, + flags: 0, + offset: 0, + size: size as u64, + }; + let dmabuf = unsafe { ioctl(self.fd.raw(), UDMABUF_CREATE, &mut cmd) }; + let dmabuf = match map_err!(dmabuf) { + Ok(d) => OwnedFd::new(d), + Err(e) => return Err(UdmabufError::CreateDmabuf(e.into()).into()), + }; + 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, + }; + Ok(Rc::new(UdmabufBo { + buf: dmabuf, + size: size as _, + })) + } + + fn import_dmabuf( + &self, + dmabuf: &DmaBuf, + _usage: BufferUsage, + ) -> Result, AllocatorError> { + if dmabuf.planes.len() != 1 { + return Err(UdmabufError::Planes.into()); + } + if dmabuf.modifier != LINEAR_MODIFIER { + return Err(UdmabufError::Modifier.into()); + } + let plane = &dmabuf.planes[0]; + let Some(shm_info) = &dmabuf.format.shm_info else { + return Err(UdmabufError::Format(dmabuf.format.name).into()); + }; + let height = dmabuf.height as u64; + let width = dmabuf.width as u64; + let stride = plane.stride as u64; + let offset = plane.offset as u64; + if height > 1 << 16 || width > 1 << 16 { + return Err(UdmabufError::Overflow.into()); + } + if stride < width * shm_info.bpp as u64 { + return Err(UdmabufError::Stride.into()); + } + let size = offset + stride * height; + if usize::try_from(size).is_err() { + return Err(UdmabufError::Overflow.into()); + } + let stat = match uapi::fstat(plane.fd.raw()) { + Ok(s) => s, + Err(e) => return Err(UdmabufError::Stat(e.into()).into()), + }; + if (stat.st_size as u64) < size { + return Err(UdmabufError::Size.into()); + } + Ok(Rc::new(UdmabufBo { + buf: dmabuf.clone(), + size: size as usize, + })) + } +} + +struct UdmabufBo { + buf: DmaBuf, + size: usize, +} + +impl BufferObject for UdmabufBo { + fn dmabuf(&self) -> &DmaBuf { + &self.buf + } + + fn map_read(self: Rc) -> Result, AllocatorError> { + self.map_write() + } + + fn map_write(self: Rc) -> Result, AllocatorError> { + let plane = &self.buf.planes[0]; + unsafe { + let res = mmap( + ptr::null_mut(), + self.size, + PROT_READ | PROT_WRITE, + MAP_SHARED, + plane.fd.raw(), + 0, + ); + if res == c::MAP_FAILED { + return Err(UdmabufError::Map(OsError::default()).into()); + } + let offset = plane.offset as _; + let data = + std::slice::from_raw_parts_mut((res as *mut u8).add(offset), self.size - offset); + Ok(Box::new(UdmabufMap { + data, + stride: plane.stride as _, + ptr: res, + len: self.size, + _bo: self, + })) + } + } +} + +struct UdmabufMap { + _bo: Rc, + data: *mut [u8], + stride: i32, + ptr: *mut c::c_void, + len: usize, +} + +impl Drop for UdmabufMap { + fn drop(&mut self) { + unsafe { + let res = munmap(self.ptr, self.len); + if let Err(e) = map_err!(res) { + log::error!("Could not unmap udmabuf: {}", OsError::from(e)); + } + } + } +} + +impl MappedBuffer for UdmabufMap { + unsafe fn data(&self) -> &[u8] { + &*self.data + } + + fn data_ptr(&self) -> *mut u8 { + self.data as _ + } + + fn stride(&self) -> i32 { + self.stride + } +} + +impl From for AllocatorError { + fn from(value: UdmabufError) -> Self { + Self(Box::new(value)) + } +} + +#[allow(non_camel_case_types)] +#[repr(C)] +struct udmabuf_create { + memfd: u32, + flags: u32, + offset: u64, + size: u64, +} + +const UDMABUF_CREATE: u64 = _IOW::(b'u' as u64, 0x42); diff --git a/src/video/gbm.rs b/src/video/gbm.rs index 8cf8cbb9..3be4fdd3 100644 --- a/src/video/gbm.rs +++ b/src/video/gbm.rs @@ -2,6 +2,10 @@ use { crate::{ + allocator::{ + Allocator, AllocatorError, BufferObject, BufferUsage, MappedBuffer, BO_USE_CURSOR, + BO_USE_LINEAR, BO_USE_PROTECTED, BO_USE_RENDERING, BO_USE_SCANOUT, BO_USE_WRITE, + }, format::{formats, Format}, utils::oserror::OsError, video::{ @@ -38,6 +42,12 @@ pub enum GbmError { NoModifier, } +impl From for AllocatorError { + fn from(value: GbmError) -> Self { + Self(Box::new(value)) + } +} + pub type Device = u8; type Bo = u8; @@ -151,17 +161,16 @@ pub struct GbmBoMap { stride: i32, } -impl GbmBoMap { - pub unsafe fn data(&self) -> &[u8] { +impl MappedBuffer for GbmBoMap { + unsafe fn data(&self) -> &[u8] { &*self.data } - #[cfg_attr(not(feature = "it"), allow(dead_code))] - pub fn data_ptr(&self) -> *mut u8 { + fn data_ptr(&self) -> *mut u8 { self.data as _ } - pub fn stride(&self) -> i32 { + fn stride(&self) -> i32 { self.stride } } @@ -290,6 +299,56 @@ impl GbmDevice { } } +impl Allocator for GbmDevice { + fn drm(&self) -> Option<&Drm> { + Some(&self.drm) + } + + fn create_bo( + &self, + dma_buf_ids: &DmaBufIds, + width: i32, + height: i32, + format: &'static Format, + modifiers: &[Modifier], + usage: BufferUsage, + ) -> Result, AllocatorError> { + let usage = map_usage(usage); + self.create_bo(dma_buf_ids, width, height, format, modifiers, usage) + .map(|v| Rc::new(v) as _) + .map_err(|v| v.into()) + } + + fn import_dmabuf( + &self, + dmabuf: &DmaBuf, + usage: BufferUsage, + ) -> Result, AllocatorError> { + let usage = map_usage(usage); + self.import_dmabuf(dmabuf, usage) + .map(|v| Rc::new(v) as _) + .map_err(|v| v.into()) + } +} + +fn map_usage(usage: BufferUsage) -> u32 { + let mut gbm = 0; + macro_rules! map { + ($bu:ident to $gbu:ident) => { + if usage.contains($bu) { + gbm |= $gbu; + } + }; + } + map!(BO_USE_SCANOUT to GBM_BO_USE_SCANOUT); + map!(BO_USE_CURSOR to GBM_BO_USE_CURSOR); + map!(BO_USE_RENDERING to GBM_BO_USE_RENDERING); + map!(BO_USE_WRITE to GBM_BO_USE_WRITE); + map!(BO_USE_LINEAR to GBM_BO_USE_LINEAR); + map!(BO_USE_PROTECTED to GBM_BO_USE_PROTECTED); + gbm +} + impl Drop for GbmDevice { fn drop(&mut self) { unsafe { @@ -299,15 +358,10 @@ impl Drop for GbmDevice { } impl GbmBo { - pub fn dmabuf(&self) -> &DmaBuf { - &self.dmabuf - } - pub fn map_read(self: &Rc) -> Result { self.map2(GBM_BO_TRANSFER_READ) } - #[cfg_attr(not(feature = "it"), allow(dead_code))] pub fn map_write(self: &Rc) -> Result { self.map2(GBM_BO_TRANSFER_READ_WRITE) } @@ -340,6 +394,24 @@ impl GbmBo { } } +impl BufferObject for GbmBo { + fn dmabuf(&self) -> &DmaBuf { + &self.dmabuf + } + + fn map_read(self: Rc) -> Result, AllocatorError> { + GbmBo::map_read(&self) + .map(|v| Box::new(v) as _) + .map_err(|v| v.into()) + } + + fn map_write(self: Rc) -> Result, AllocatorError> { + GbmBo::map_write(&self) + .map(|v| Box::new(v) as _) + .map_err(|v| v.into()) + } +} + impl Drop for GbmBoMap { fn drop(&mut self) { unsafe { diff --git a/wire/jay_screenshot.txt b/wire/jay_screenshot.txt index a4f77050..ba6432e1 100644 --- a/wire/jay_screenshot.txt +++ b/wire/jay_screenshot.txt @@ -14,3 +14,19 @@ event dmabuf { event error { msg: str, } + +event drm_dev (since = 6) { + drm_dev: fd, +} + +event plane (since = 6) { + fd: fd, + offset: u32, + stride: u32, +} + +event dmabuf2 (since = 6) { + width: i32, + height: i32, + modifier: pod(u64), +}