From 3635ae0104118cca8290b4b32c006d06dbd149ed Mon Sep 17 00:00:00 2001 From: Julian Orth Date: Sun, 18 Feb 2024 15:23:10 +0100 Subject: [PATCH 1/5] render: store underlying DmaBufs in textures --- src/gfx_api.rs | 1 + src/gfx_apis/gl/egl/display.rs | 4 +-- src/gfx_apis/gl/egl/image.rs | 6 ++--- src/gfx_apis/gl/gl/render_buffer.rs | 4 +-- src/gfx_apis/gl/gl/texture.rs | 6 ++--- src/gfx_apis/gl/renderer/framebuffer.rs | 2 +- src/gfx_apis/gl/renderer/image.rs | 4 +-- src/gfx_apis/gl/renderer/texture.rs | 5 ++++ src/gfx_apis/vulkan/image.rs | 35 +++++++++++++------------ src/gfx_apis/vulkan/renderer.rs | 6 ++--- 10 files changed, 38 insertions(+), 35 deletions(-) diff --git a/src/gfx_api.rs b/src/gfx_api.rs index 38fa1674..1df27473 100644 --- a/src/gfx_api.rs +++ b/src/gfx_api.rs @@ -267,6 +267,7 @@ pub trait GfxTexture: Debug { format: &'static Format, shm: &[Cell], ) -> Result<(), GfxError>; + fn dmabuf(&self) -> Option<&DmaBuf>; } pub trait GfxContext: Debug { diff --git a/src/gfx_apis/gl/egl/display.rs b/src/gfx_apis/gl/egl/display.rs index 0091893f..795bbacb 100644 --- a/src/gfx_apis/gl/egl/display.rs +++ b/src/gfx_apis/gl/egl/display.rs @@ -259,10 +259,8 @@ impl EglDisplay { Ok(Rc::new(EglImage { dpy: self.clone(), img, - width: buf.width, - height: buf.height, external_only: format.external_only, - format: buf.format, + dmabuf: buf.clone(), })) } } diff --git a/src/gfx_apis/gl/egl/image.rs b/src/gfx_apis/gl/egl/image.rs index ab2412b5..31cad564 100644 --- a/src/gfx_apis/gl/egl/image.rs +++ b/src/gfx_apis/gl/egl/image.rs @@ -1,11 +1,11 @@ use { crate::{ - format::Format, gfx_apis::gl::egl::{ display::EglDisplay, sys::{EGLImageKHR, EGL_FALSE}, PROCS, }, + video::dmabuf::DmaBuf, }, std::rc::Rc, }; @@ -13,10 +13,8 @@ use { pub struct EglImage { pub dpy: Rc, pub img: EGLImageKHR, - pub width: i32, - pub height: i32, pub external_only: bool, - pub format: &'static Format, + pub dmabuf: DmaBuf, } impl Drop for EglImage { diff --git a/src/gfx_apis/gl/gl/render_buffer.rs b/src/gfx_apis/gl/gl/render_buffer.rs index 7e582b9e..32dad288 100644 --- a/src/gfx_apis/gl/gl/render_buffer.rs +++ b/src/gfx_apis/gl/gl/render_buffer.rs @@ -60,8 +60,8 @@ impl GlRenderBuffer { _tex: None, ctx: self.ctx.clone(), fbo, - width: self.img.width, - height: self.img.height, + width: self.img.dmabuf.width, + height: self.img.dmabuf.height, }; if status != GL_FRAMEBUFFER_COMPLETE { return Err(RenderError::CreateFramebuffer); diff --git a/src/gfx_apis/gl/gl/texture.rs b/src/gfx_apis/gl/gl/texture.rs index cded2e9f..9397578d 100644 --- a/src/gfx_apis/gl/gl/texture.rs +++ b/src/gfx_apis/gl/gl/texture.rs @@ -56,10 +56,10 @@ impl GlTexture { ctx: ctx.clone(), img: Some(img.clone()), tex, - width: img.width, - height: img.height, + width: img.dmabuf.width, + height: img.dmabuf.height, external_only: img.external_only, - format: img.format, + format: img.dmabuf.format, }) } diff --git a/src/gfx_apis/gl/renderer/framebuffer.rs b/src/gfx_apis/gl/renderer/framebuffer.rs index 99dc5e5b..e1cd262c 100644 --- a/src/gfx_apis/gl/renderer/framebuffer.rs +++ b/src/gfx_apis/gl/renderer/framebuffer.rs @@ -120,6 +120,6 @@ impl GfxFramebuffer for Framebuffer { } fn format(&self) -> &'static Format { - self.gl.rb.img.format + self.gl.rb.img.dmabuf.format } } diff --git a/src/gfx_apis/gl/renderer/image.rs b/src/gfx_apis/gl/renderer/image.rs index a586f271..23f3d848 100644 --- a/src/gfx_apis/gl/renderer/image.rs +++ b/src/gfx_apis/gl/renderer/image.rs @@ -17,11 +17,11 @@ pub struct Image { impl Image { pub fn width(&self) -> i32 { - self.gl.width + self.gl.dmabuf.width } pub fn height(&self) -> i32 { - self.gl.height + self.gl.dmabuf.height } fn to_texture(self: &Rc) -> Result, RenderError> { diff --git a/src/gfx_apis/gl/renderer/texture.rs b/src/gfx_apis/gl/renderer/texture.rs index e6ac3480..8e1130f4 100644 --- a/src/gfx_apis/gl/renderer/texture.rs +++ b/src/gfx_apis/gl/renderer/texture.rs @@ -3,6 +3,7 @@ use { format::Format, gfx_api::{GfxError, GfxTexture}, gfx_apis::gl::{gl::texture::GlTexture, renderer::context::GlRenderContext, RenderError}, + video::dmabuf::DmaBuf, }, std::{ any::Any, @@ -58,4 +59,8 @@ impl GfxTexture for Texture { ) -> Result<(), GfxError> { Err(RenderError::UnsupportedOperation.into()) } + + fn dmabuf(&self) -> Option<&DmaBuf> { + self.gl.img.as_ref().map(|i| &i.dmabuf) + } } diff --git a/src/gfx_apis/vulkan/image.rs b/src/gfx_apis/vulkan/image.rs index 2176ddc6..52e1a7d2 100644 --- a/src/gfx_apis/vulkan/image.rs +++ b/src/gfx_apis/vulkan/image.rs @@ -8,10 +8,7 @@ use { }, theme::Color, utils::clonecell::CloneCell, - video::{ - dmabuf::{DmaBuf, DmaBufPlane, PlaneVec}, - Modifier, - }, + video::dmabuf::{DmaBuf, PlaneVec}, }, ash::vk::{ BindImageMemoryInfo, BindImagePlaneMemoryInfo, ComponentMapping, ComponentSwizzle, @@ -36,12 +33,10 @@ use { pub struct VulkanDmaBufImageTemplate { pub(super) renderer: Rc, - pub(super) format: &'static Format, pub(super) width: u32, pub(super) height: u32, - pub(super) modifier: Modifier, pub(super) disjoint: bool, - pub(super) planes: PlaneVec, + pub(super) dmabuf: DmaBuf, pub(super) render_max_extents: Option, pub(super) texture_max_extents: Option, } @@ -260,12 +255,10 @@ impl VulkanRenderer { } Ok(Rc::new(VulkanDmaBufImageTemplate { renderer: self.clone(), - format: dmabuf.format, width, height, - modifier: dmabuf.modifier, disjoint, - planes: dmabuf.planes.clone(), + dmabuf: dmabuf.clone(), render_max_extents: modifier.render_max_extents, texture_max_extents: modifier.texture_max_extents, })) @@ -332,6 +325,7 @@ impl VulkanDmaBufImageTemplate { } let image = { let plane_layouts: PlaneVec<_> = self + .dmabuf .planes .iter() .map(|p| SubresourceLayout { @@ -343,7 +337,7 @@ impl VulkanDmaBufImageTemplate { }) .collect(); let mut mod_info = ImageDrmFormatModifierExplicitCreateInfoEXT::builder() - .drm_format_modifier(self.modifier) + .drm_format_modifier(self.dmabuf.modifier) .plane_layouts(&plane_layouts) .build(); let mut memory_image_create_info = ExternalMemoryImageCreateInfo::builder() @@ -361,7 +355,7 @@ impl VulkanDmaBufImageTemplate { }; let create_info = ImageCreateInfo::builder() .image_type(ImageType::TYPE_2D) - .format(self.format.vk_format) + .format(self.dmabuf.format.vk_format) .mip_levels(1) .array_layers(1) .tiling(ImageTiling::DRM_FORMAT_MODIFIER_EXT) @@ -383,14 +377,14 @@ impl VulkanDmaBufImageTemplate { }; let destroy_image = OnDrop(|| unsafe { device.device.destroy_image(image, None) }); let num_device_memories = match self.disjoint { - true => self.planes.len(), + true => self.dmabuf.planes.len(), false => 1, }; let mut device_memories = PlaneVec::new(); let mut free_device_memories = PlaneVec::new(); let mut bind_image_plane_memory_infos = PlaneVec::new(); for plane_idx in 0..num_device_memories { - let dma_buf_plane = &self.planes[plane_idx]; + let dma_buf_plane = &self.dmabuf.planes[plane_idx]; let memory_fd_properties = unsafe { device.external_memory_fd.get_memory_fd_properties( ExternalMemoryHandleTypeFlags::DMA_BUF_EXT, @@ -467,8 +461,8 @@ impl VulkanDmaBufImageTemplate { } let res = unsafe { device.device.bind_image_memory2(&bind_image_memory_infos) }; res.map_err(VulkanError::BindImageMemory)?; - let texture_view = device.create_image_view(image, self.format, false)?; - let render_view = device.create_image_view(image, self.format, true)?; + let texture_view = device.create_image_view(image, self.dmabuf.format, false)?; + let render_view = device.create_image_view(image, self.dmabuf.format, true)?; free_device_memories.drain(..).for_each(mem::forget); mem::forget(destroy_image); Ok(Rc::new(VulkanImage { @@ -484,7 +478,7 @@ impl VulkanDmaBufImageTemplate { template: self.clone(), mems: device_memories, }), - format: self.format, + format: self.dmabuf.format, is_undefined: Cell::new(true), })) } @@ -579,4 +573,11 @@ impl GfxTexture for VulkanImage { .read_pixels(&self, x, y, width, height, stride, format, shm) .map_err(|e| e.into()) } + + fn dmabuf(&self) -> Option<&DmaBuf> { + match &self.ty { + VulkanImageMemory::DmaBuf(b) => Some(&b.template.dmabuf), + VulkanImageMemory::Internal(_) => None, + } + } } diff --git a/src/gfx_apis/vulkan/renderer.rs b/src/gfx_apis/vulkan/renderer.rs index 62eb2723..bd5d418e 100644 --- a/src/gfx_apis/vulkan/renderer.rs +++ b/src/gfx_apis/vulkan/renderer.rs @@ -532,7 +532,7 @@ impl VulkanRenderer { flag: u32| -> Result<(), VulkanError> { if let VulkanImageMemory::DmaBuf(buf) = &img.ty { - for plane in &buf.template.planes { + for plane in &buf.template.dmabuf.planes { let fd = dma_buf_export_sync_file(&plane.fd, flag) .map_err(VulkanError::IoctlExportSyncFile)?; let semaphore = self.allocate_semaphore()?; @@ -573,7 +573,7 @@ impl VulkanRenderer { }; let import = |img: &VulkanImage, flag: u32| { if let VulkanImageMemory::DmaBuf(buf) = &img.ty { - for plane in &buf.template.planes { + for plane in &buf.template.dmabuf.planes { let res = dma_buf_import_sync_file(&plane.fd, flag, &syncfile) .map_err(VulkanError::IoctlImportSyncFile); if let Err(e) = res { @@ -764,7 +764,7 @@ impl VulkanRenderer { let mut semaphores = vec![]; let mut semaphore_infos = vec![]; if let VulkanImageMemory::DmaBuf(buf) = &tex.ty { - for plane in &buf.template.planes { + for plane in &buf.template.dmabuf.planes { let fd = dma_buf_export_sync_file(&plane.fd, DMA_BUF_SYNC_READ) .map_err(VulkanError::IoctlExportSyncFile)?; let semaphore = self.allocate_semaphore()?; From fed2ceb8b5c43489215feb9bd3e464797ff72fde Mon Sep 17 00:00:00 2001 From: Julian Orth Date: Sun, 18 Feb 2024 15:28:07 +0100 Subject: [PATCH 2/5] drm: add unique identifiers to dmabufs --- src/backends/metal/video.rs | 24 ++++++++++++++++-------- src/backends/x.rs | 11 ++++++++--- src/cli/screenshot.rs | 7 ++++--- src/compositor.rs | 1 + src/ifs/jay_screencast.rs | 11 ++++++++--- src/ifs/wl_drm.rs | 1 + src/ifs/zwp_linux_buffer_params_v1.rs | 1 + src/it/test_client.rs | 2 +- src/portal.rs | 3 +++ src/portal/ptl_display.rs | 11 ++++++++++- src/portal/ptr_gui.rs | 1 + src/screenshoter.rs | 1 + src/state.rs | 3 ++- src/video/dmabuf.rs | 7 +++++-- src/video/gbm.rs | 8 +++++--- src/wl_usr.rs | 4 ++++ src/wl_usr/usr_ifs/usr_jay_screencast.rs | 1 + 17 files changed, 72 insertions(+), 25 deletions(-) diff --git a/src/backends/metal/video.rs b/src/backends/metal/video.rs index 03f15e06..5873eb7a 100644 --- a/src/backends/metal/video.rs +++ b/src/backends/metal/video.rs @@ -1692,9 +1692,14 @@ impl MetalBackend { if cursor { usage |= GBM_BO_USE_LINEAR; }; - let dev_bo = dev - .gbm - .create_bo(width, height, format, &possible_modifiers, usage); + let dev_bo = dev.gbm.create_bo( + &self.state.dma_buf_ids, + width, + height, + format, + &possible_modifiers, + usage, + ); let dev_bo = match dev_bo { Ok(b) => b, Err(e) => return Err(MetalError::ScanoutBuffer(e)), @@ -1740,11 +1745,14 @@ 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(width, height, format, &possible_modifiers, usage); + let render_bo = render_ctx.gfx.gbm().create_bo( + &self.state.dma_buf_ids, + width, + height, + format, + &possible_modifiers, + usage, + ); let render_bo = match render_bo { Ok(b) => b, Err(e) => return Err(MetalError::ScanoutBuffer(e)), diff --git a/src/backends/x.rs b/src/backends/x.rs index ce3b46de..f6131a79 100644 --- a/src/backends/x.rs +++ b/src/backends/x.rs @@ -393,9 +393,14 @@ impl XBackend { panic!("Neither linear nor invalid modifier is supported"); }; for image in &mut images { - let bo = self - .gbm - .create_bo(width, height, XRGB8888, modifier, usage)?; + let bo = self.gbm.create_bo( + &self.state.dma_buf_ids, + width, + height, + XRGB8888, + modifier, + usage, + )?; let dma = bo.dmabuf(); assert!(dma.planes.len() == 1); let plane = dma.planes.first().unwrap(); diff --git a/src/cli/screenshot.rs b/src/cli/screenshot.rs index 91222eba..865d5646 100644 --- a/src/cli/screenshot.rs +++ b/src/cli/screenshot.rs @@ -5,7 +5,7 @@ use { tools::tool_client::{with_tool_client, Handle, ToolClient}, utils::{errorfmt::ErrorFmt, queue::AsyncQueue}, video::{ - dmabuf::{DmaBuf, DmaBufPlane, PlaneVec}, + dmabuf::{DmaBuf, DmaBufIds, DmaBufPlane, PlaneVec}, drm::Drm, gbm::{GbmDevice, GBM_BO_USE_LINEAR, GBM_BO_USE_RENDERING}, }, @@ -55,7 +55,7 @@ async fn run(screenshot: Rc) { fatal!("Could not take a screenshot: {}", e); } }; - let data = buf_to_qoi(&buf); + let data = buf_to_qoi(&DmaBufIds::default(), &buf); let filename = screenshot .args .filename @@ -67,7 +67,7 @@ async fn run(screenshot: Rc) { } } -pub fn buf_to_qoi(buf: &Dmabuf) -> Vec { +pub fn buf_to_qoi(dma_buf_ids: &DmaBufIds, buf: &Dmabuf) -> Vec { let drm = match Drm::reopen(buf.drm_dev.raw(), false) { Ok(drm) => drm, Err(e) => { @@ -87,6 +87,7 @@ pub fn buf_to_qoi(buf: &Dmabuf) -> Vec { fd: buf.fd.clone(), }); let dmabuf = DmaBuf { + id: dma_buf_ids.next(), width: buf.width as _, height: buf.height as _, format: XRGB8888, diff --git a/src/compositor.rs b/src/compositor.rs index 05ab43cf..37bacd32 100644 --- a/src/compositor.rs +++ b/src/compositor.rs @@ -201,6 +201,7 @@ fn start_compositor2( default_gfx_api: Cell::new(GfxApi::OpenGl), activation_tokens: Default::default(), toplevel_lists: Default::default(), + dma_buf_ids: Default::default(), }); state.tracker.register(ClientId::from_raw(0)); create_dummy_output(&state); diff --git a/src/ifs/jay_screencast.rs b/src/ifs/jay_screencast.rs index 112a8a99..eea05d0c 100644 --- a/src/ifs/jay_screencast.rs +++ b/src/ifs/jay_screencast.rs @@ -236,9 +236,14 @@ impl JayScreencast { } false => &format.write_modifiers, }; - let buffer = - ctx.gbm() - .create_bo(mode.width, mode.height, XRGB8888, modifiers, usage)?; + let buffer = ctx.gbm().create_bo( + &self.client.state.dma_buf_ids, + mode.width, + mode.height, + XRGB8888, + modifiers, + usage, + )?; let fb = ctx.clone().dmabuf_img(buffer.dmabuf())?.to_framebuffer()?; buffers.push(ScreencastBuffer { dmabuf: buffer.dmabuf().clone(), diff --git a/src/ifs/wl_drm.rs b/src/ifs/wl_drm.rs index 4bf5a01c..b7f22a7c 100644 --- a/src/ifs/wl_drm.rs +++ b/src/ifs/wl_drm.rs @@ -119,6 +119,7 @@ impl WlDrm { None => return Err(WlDrmError::InvalidFormat(req.format)), }; let mut dmabuf = DmaBuf { + id: self.client.state.dma_buf_ids.next(), width: req.width, height: req.height, format, diff --git a/src/ifs/zwp_linux_buffer_params_v1.rs b/src/ifs/zwp_linux_buffer_params_v1.rs index fbfb38e5..4dcebf55 100644 --- a/src/ifs/zwp_linux_buffer_params_v1.rs +++ b/src/ifs/zwp_linux_buffer_params_v1.rs @@ -114,6 +114,7 @@ impl ZwpLinuxBufferParamsV1 { return Err(ZwpLinuxBufferParamsV1Error::InvalidModifier(modifier)); } let mut dmabuf = DmaBuf { + id: self.parent.client.state.dma_buf_ids.next(), width, height, format: format.format, diff --git a/src/it/test_client.rs b/src/it/test_client.rs index c9e28d88..e6eb7ced 100644 --- a/src/it/test_client.rs +++ b/src/it/test_client.rs @@ -85,7 +85,7 @@ impl TestClient { pub async fn take_screenshot(&self) -> Result, TestError> { let dmabuf = self.jc.take_screenshot().await?; - let qoi = buf_to_qoi(&dmabuf); + let qoi = buf_to_qoi(&self.server.state.dma_buf_ids, &dmabuf); Ok(qoi) } diff --git a/src/portal.rs b/src/portal.rs index 6b9ffb3e..18a2b383 100644 --- a/src/portal.rs +++ b/src/portal.rs @@ -23,6 +23,7 @@ use { copyhashmap::CopyHashMap, errorfmt::ErrorFmt, numcell::NumCell, run_toplevel::RunToplevel, xrd::xrd, }, + video::dmabuf::DmaBufIds, wheel::Wheel, wire_dbus::org, }, @@ -84,6 +85,7 @@ async fn run_async(eng: Rc, ring: Rc) { screencasts: Default::default(), next_id: NumCell::new(1), render_ctxs: Default::default(), + dma_buf_ids: Default::default(), }); let _root = { let obj = state @@ -143,6 +145,7 @@ struct PortalState { screencasts: CopyHashMap>, next_id: NumCell, render_ctxs: CopyHashMap>, + dma_buf_ids: Rc, } impl PortalState { diff --git a/src/portal/ptl_display.rs b/src/portal/ptl_display.rs index 7c16b11d..c6fc7939 100644 --- a/src/portal/ptl_display.rs +++ b/src/portal/ptl_display.rs @@ -247,7 +247,16 @@ async fn maybe_add_display(state: &Rc, name: &str) { _ => return, }; let path = format!("{}/{}", state.xrd, name); - let con = match UsrCon::new(&state.ring, &state.wheel, &state.eng, &path, num).await { + let con = match UsrCon::new( + &state.ring, + &state.wheel, + &state.eng, + &state.dma_buf_ids, + &path, + num, + ) + .await + { Ok(c) => c, Err(e) => { log::error!( diff --git a/src/portal/ptr_gui.rs b/src/portal/ptr_gui.rs index 93276069..668675f6 100644 --- a/src/portal/ptr_gui.rs +++ b/src/portal/ptr_gui.rs @@ -710,6 +710,7 @@ impl WindowData { } for _ in 0..NUM_BUFFERS { let bo = match ctx.ctx.gbm().create_bo( + &self.dpy.state.dma_buf_ids, width, height, ARGB8888, diff --git a/src/screenshoter.rs b/src/screenshoter.rs index 4732abfe..89d46146 100644 --- a/src/screenshoter.rs +++ b/src/screenshoter.rs @@ -60,6 +60,7 @@ pub fn take_screenshot(state: &State) -> Result }; let gbm = ctx.gbm(); let bo = gbm.create_bo( + &state.dma_buf_ids, extents.width(), extents.height(), XRGB8888, diff --git a/src/state.rs b/src/state.rs index 7db7be9e..a4fa07c9 100644 --- a/src/state.rs +++ b/src/state.rs @@ -50,7 +50,7 @@ use { linkedlist::LinkedList, numcell::NumCell, queue::AsyncQueue, refcounted::RefCounted, run_toplevel::RunToplevel, }, - video::drm::Drm, + video::{dmabuf::DmaBufIds, drm::Drm}, wheel::Wheel, wire::{ ExtForeignToplevelListV1Id, JayRenderCtxId, JaySeatEventsId, JayWorkspaceWatcherId, @@ -142,6 +142,7 @@ pub struct State { pub activation_tokens: CopyHashMap, pub toplevel_lists: CopyHashMap<(ClientId, ExtForeignToplevelListV1Id), Rc>, + pub dma_buf_ids: DmaBufIds, } // impl Drop for State { diff --git a/src/video/dmabuf.rs b/src/video/dmabuf.rs index 1f8fe7ad..c217bfba 100644 --- a/src/video/dmabuf.rs +++ b/src/video/dmabuf.rs @@ -5,15 +5,18 @@ use { uapi::{c::ioctl, OwnedFd, _IOW, _IOWR}, }; -#[derive(Clone)] +#[derive(Clone, Debug)] pub struct DmaBufPlane { pub offset: u32, pub stride: u32, pub fd: Rc, } -#[derive(Clone)] +linear_ids!(DmaBufIds, DmaBufId); + +#[derive(Debug, Clone)] pub struct DmaBuf { + pub id: DmaBufId, pub width: i32, pub height: i32, pub format: &'static Format, diff --git a/src/video/gbm.rs b/src/video/gbm.rs index 904f9330..3338e2fb 100644 --- a/src/video/gbm.rs +++ b/src/video/gbm.rs @@ -5,7 +5,7 @@ use { format::{formats, Format}, utils::oserror::OsError, video::{ - dmabuf::{DmaBuf, DmaBufPlane, PlaneVec}, + dmabuf::{DmaBuf, DmaBufIds, DmaBufPlane, PlaneVec}, drm::{Drm, DrmError}, Modifier, INVALID_MODIFIER, }, @@ -150,8 +150,9 @@ impl GbmBoMap { } } -unsafe fn export_bo(bo: *mut Bo) -> Result { +unsafe fn export_bo(dmabuf_ids: &DmaBufIds, bo: *mut Bo) -> Result { Ok(DmaBuf { + id: dmabuf_ids.next(), width: gbm_bo_get_width(bo) as _, height: gbm_bo_get_height(bo) as _, modifier: gbm_bo_get_modifier(bo), @@ -199,6 +200,7 @@ impl GbmDevice { pub fn create_bo<'a>( &self, + dma_buf_ids: &DmaBufIds, width: i32, height: i32, format: &Format, @@ -229,7 +231,7 @@ impl GbmDevice { return Err(GbmError::CreateBo(OsError::default())); } let bo = BoHolder { bo }; - let dma = export_bo(bo.bo)?; + let dma = export_bo(dma_buf_ids, bo.bo)?; Ok(GbmBo { bo, dmabuf: dma }) } } diff --git a/src/wl_usr.rs b/src/wl_usr.rs index a8a6ca24..1b2fc47a 100644 --- a/src/wl_usr.rs +++ b/src/wl_usr.rs @@ -20,6 +20,7 @@ use { oserror::OsError, vec_ext::VecExt, }, + video::dmabuf::DmaBufIds, wheel::Wheel, wire::wl_display, wl_usr::{ @@ -77,6 +78,7 @@ pub struct UsrCon { outgoing: Cell>>, pub owner: CloneCell>>, dead: Cell, + dma_buf_ids: Rc, } pub trait UsrConOwner { @@ -88,6 +90,7 @@ impl UsrCon { ring: &Rc, wheel: &Rc, eng: &Rc, + dma_buf_ids: &Rc, path: &str, server_id: u32, ) -> Result, UsrConError> { @@ -122,6 +125,7 @@ impl UsrCon { outgoing: Default::default(), owner: Default::default(), dead: Cell::new(false), + dma_buf_ids: dma_buf_ids.clone(), }); slf.objects.set( WL_DISPLAY_ID.into(), diff --git a/src/wl_usr/usr_ifs/usr_jay_screencast.rs b/src/wl_usr/usr_ifs/usr_jay_screencast.rs index 87917e43..6630aaad 100644 --- a/src/wl_usr/usr_ifs/usr_jay_screencast.rs +++ b/src/wl_usr/usr_ifs/usr_jay_screencast.rs @@ -123,6 +123,7 @@ impl UsrJayScreencast { _ => return Err(UsrJayScreencastError::UnknownFormat(ev.format)), }; self.pending_buffers.borrow_mut().push(DmaBuf { + id: self.con.dma_buf_ids.next(), width: ev.width, height: ev.height, format, From da84e9ec27e327ad06bd7db6af42494ab31e7182 Mon Sep 17 00:00:00 2001 From: Julian Orth Date: Sun, 18 Feb 2024 20:29:23 +0100 Subject: [PATCH 3/5] metal: implement direct scanout --- src/backend.rs | 4 + src/backends/metal.rs | 3 + src/backends/metal/video.rs | 309 ++++++++++++++++-- src/compositor.rs | 1 + src/drm_feedback.rs | 91 +++++- src/format.rs | 64 ++-- src/gfx_api.rs | 101 +++++- src/gfx_apis/gl/renderer/context.rs | 1 + src/gfx_apis/gl/renderer/image.rs | 1 + src/gfx_apis/gl/renderer/texture.rs | 7 +- src/gfx_apis/vulkan/image.rs | 9 +- src/ifs/wl_output.rs | 56 ++-- src/ifs/wl_surface.rs | 34 +- src/ifs/wl_surface/x_surface/xwindow.rs | 4 + .../wl_surface/xdg_surface/xdg_toplevel.rs | 9 +- src/ifs/zwlr_screencopy_frame_v1.rs | 9 + src/ifs/zwlr_screencopy_manager_v1.rs | 1 + src/ifs/zwp_linux_dmabuf_feedback_v1.rs | 35 +- src/ifs/zwp_linux_dmabuf_v1.rs | 18 +- src/renderer.rs | 9 + src/state.rs | 12 +- src/tree/output.rs | 2 +- src/tree/toplevel.rs | 7 +- src/tree/workspace.rs | 10 + src/video/drm.rs | 9 +- 25 files changed, 670 insertions(+), 136 deletions(-) diff --git a/src/backend.rs b/src/backend.rs index 75410dd9..53cf4b0e 100644 --- a/src/backend.rs +++ b/src/backend.rs @@ -1,6 +1,7 @@ use { crate::{ async_engine::SpawnedFuture, + drm_feedback::DrmFeedback, fixed::Fixed, gfx_api::GfxFramebuffer, ifs::wl_seat::wl_pointer::{CONTINUOUS, FINGER, HORIZONTAL_SCROLL, VERTICAL_SCROLL, WHEEL}, @@ -79,6 +80,9 @@ pub trait Connector { fn damage(&self); fn drm_dev(&self) -> Option; fn set_enabled(&self, enabled: bool); + fn drm_feedback(&self) -> Option> { + None + } } #[derive(Debug)] diff --git a/src/backends/metal.rs b/src/backends/metal.rs index 0d323120..6f18a7d7 100644 --- a/src/backends/metal.rs +++ b/src/backends/metal.rs @@ -11,6 +11,7 @@ use { }, backends::metal::video::{MetalDrmDeviceData, MetalRenderContext, PendingDrmDevice}, dbus::{DbusError, SignalHandler}, + drm_feedback::DrmFeedback, gfx_api::GfxError, libinput::{ consts::{ @@ -130,6 +131,7 @@ pub struct MetalBackend { pause_handler: Cell>, resume_handler: Cell>, ctx: CloneCell>>, + default_feedback: CloneCell>>, } impl Debug for MetalBackend { @@ -253,6 +255,7 @@ pub async fn create(state: &Rc) -> Result, MetalError> { pause_handler: Default::default(), resume_handler: Default::default(), ctx: Default::default(), + default_feedback: Default::default(), }); metal.pause_handler.set(Some({ let mtl = metal.clone(); diff --git a/src/backends/metal/video.rs b/src/backends/metal/video.rs index 5873eb7a..b0ff64be 100644 --- a/src/backends/metal/video.rs +++ b/src/backends/metal/video.rs @@ -6,12 +6,14 @@ use { ConnectorKernelId, DrmDeviceId, HardwareCursor, MonitorInfo, }, backends::metal::{MetalBackend, MetalError}, + drm_feedback::DrmFeedback, edid::Descriptor, format::{Format, ARGB8888, XRGB8888}, - gfx_api::{GfxContext, GfxFramebuffer, GfxTexture}, + gfx_api::{BufferPoints, GfxApiOpt, GfxContext, GfxFramebuffer, GfxRenderPass, GfxTexture}, ifs::wp_presentation_feedback::{KIND_HW_COMPLETION, KIND_VSYNC}, renderer::RenderResult, state::State, + tree::OutputNode, udev::UdevDevice, utils::{ asyncevent::AsyncEvent, bitflags::BitflagsExt, clonecell::CloneCell, @@ -19,6 +21,7 @@ use { oserror::OsError, syncqueue::SyncQueue, }, video::{ + dmabuf::DmaBufId, drm::{ drm_mode_modeinfo, Change, ConnectorStatus, ConnectorType, DrmBlob, DrmConnector, DrmCrtc, DrmEncoder, DrmError, DrmEvent, DrmFramebuffer, DrmMaster, DrmModeInfo, @@ -36,13 +39,14 @@ use { jay_config::video::GfxApi, std::{ cell::{Cell, RefCell}, + collections::VecDeque, ffi::CString, fmt::{Debug, Formatter}, mem, ops::DerefMut, - rc::Rc, + rc::{Rc, Weak}, }, - uapi::{c, c::dev_t}, + uapi::c::{self, dev_t}, }; pub struct PendingDrmDevice { @@ -202,6 +206,11 @@ pub struct MetalConnector { pub cursor_buffers: CloneCell>>, pub cursor_front_buffer: NumCell, pub cursor_swap_buffer: Cell, + + pub drm_feedback: CloneCell>>, + pub scanout_buffers: RefCell>, + pub active_framebuffers: RefCell>, + pub direct_scanout_active: Cell, } #[derive(Debug)] @@ -310,11 +319,39 @@ impl Debug for OnChange { } } +#[derive(Debug)] +pub struct DirectScanoutCache { + tex: Weak, + fb: Option>, +} + +#[derive(Debug)] +pub struct DirectScanoutData { + tex: Rc, + fb: Rc, + dma_buf_id: DmaBufId, + acquired: Cell, +} + +impl Drop for DirectScanoutData { + fn drop(&mut self) { + if self.acquired.replace(false) { + self.tex.reservations().release(); + } + } +} + +#[derive(Debug)] +pub struct PresentFb { + fb: Rc, + direct_scanout_data: Option, +} + impl MetalConnector { async fn present_loop(self: Rc) { loop { self.present_trigger.triggered().await; - self.present(); + let _ = self.present(true); } } @@ -351,49 +388,178 @@ impl MetalConnector { self.present_trigger.trigger(); } - pub fn present(&self) { + fn trim_scanout_cache(&self) { + self.scanout_buffers + .borrow_mut() + .retain(|_, buffer| buffer.tex.strong_count() > 0); + } + + fn prepare_direct_scanout( + &self, + pass: &GfxRenderPass, + plane: &Rc, + ) -> Option { + if pass.ops.len() != 1 { + return None; + } + let GfxApiOpt::CopyTexture(ct) = &pass.ops[0] else { + return None; + }; + if ct.source != BufferPoints::identity() { + return None; + } + if ct.target.x1 != 0.0 + || ct.target.y1 != 0.0 + || ct.target.x2 != plane.mode_w.get() as f32 + || ct.target.y2 != plane.mode_h.get() as f32 + { + return None; + } + let Some(dmabuf) = ct.tex.dmabuf() else { + return None; + }; + let mut cache = self.scanout_buffers.borrow_mut(); + if let Some(buffer) = cache.get(&dmabuf.id) { + return buffer.fb.as_ref().map(|fb| DirectScanoutData { + tex: buffer.tex.upgrade().unwrap(), + fb: fb.clone(), + dma_buf_id: dmabuf.id, + acquired: Default::default(), + }); + } + let format = 'format: { + if let Some(f) = plane.formats.get(&dmabuf.format.drm) { + break 'format f; + } + if let Some(opaque) = dmabuf.format.opaque { + if let Some(f) = plane.formats.get(&opaque.drm) { + break 'format f; + } + } + return None; + }; + if !format.modifiers.contains(&dmabuf.modifier) { + return None; + } + let data = match self.dev.master.add_fb(dmabuf, Some(format.format)) { + Ok(fb) => Some(DirectScanoutData { + tex: ct.tex.clone(), + fb: Rc::new(fb), + dma_buf_id: dmabuf.id, + acquired: Default::default(), + }), + Err(e) => { + log::debug!( + "Could not import dmabuf for direct scanout: {}", + ErrorFmt(e) + ); + None + } + }; + cache.insert( + dmabuf.id, + DirectScanoutCache { + tex: Rc::downgrade(&ct.tex), + fb: data.as_ref().map(|dsd| dsd.fb.clone()), + }, + ); + data + } + + fn prepare_present_fb( + &self, + rr: &mut RenderResult, + buffer: &RenderBuffer, + plane: &Rc, + output: &OutputNode, + try_direct_scanout: bool, + ) -> PresentFb { + self.trim_scanout_cache(); + let buffer_fb = buffer.render_fb(); + let render_hw_cursor = !self.cursor_enabled.get(); + let pass = buffer_fb.create_render_pass( + output, + &self.state, + Some(output.global.pos.get()), + Some(rr), + output.global.preferred_scale.get(), + render_hw_cursor, + ); + let try_direct_scanout = try_direct_scanout && !output.global.have_shm_screencopies(); + let mut direct_scanout_data = None; + if try_direct_scanout { + if let Some(dsd) = self.prepare_direct_scanout(&pass, plane) { + output.perform_screencopies(None, &dsd.tex, !render_hw_cursor); + direct_scanout_data = Some(dsd); + } + } + let direct_scanout_active = direct_scanout_data.is_some(); + if self.direct_scanout_active.replace(direct_scanout_active) != direct_scanout_active { + let change = match direct_scanout_active { + true => "Enabling", + false => "Disabling", + }; + log::debug!("{} direct scanout on {}", change, self.kernel_id()); + } + let fb = match &direct_scanout_data { + None => { + self.next_buffer.fetch_add(1); + buffer_fb.perform_render_pass(pass); + if let Some(tex) = &buffer.dev_tex { + buffer.dev_fb.copy_texture(tex, 0, 0); + } + output.perform_screencopies( + Some(&*buffer_fb), + &buffer.render_tex, + !render_hw_cursor, + ); + buffer.drm.clone() + } + Some(dsd) => dsd.fb.clone(), + }; + PresentFb { + fb, + direct_scanout_data, + } + } + + pub fn present(&self, try_direct_scanout: bool) -> Result<(), ()> { let crtc = match self.crtc.get() { Some(crtc) => crtc, - _ => return, + _ => return Ok(()), }; if (!self.has_damage.get() && !self.cursor_changed.get()) || !self.can_present.get() { - return; + return Ok(()); } if !crtc.active.value.get() { - return; + return Ok(()); } let plane = match self.primary_plane.get() { Some(p) => p, - _ => return, + _ => return Ok(()), }; let buffers = match self.buffers.get() { Some(b) => b, - _ => return, + _ => return Ok(()), }; let cursor = self.cursor_plane.get(); + let mut new_fb = None; let mut changes = self.master.change(); if self.has_damage.get() { if !self.backend.check_render_context(&self.dev) { - return; + return Ok(()); } - let buffer = &buffers[self.next_buffer.fetch_add(1) % buffers.len()]; if let Some(node) = self.state.root.outputs.get(&self.connector_id) { + let buffer = &buffers[self.next_buffer.get() % buffers.len()]; let mut rr = self.render_result.borrow_mut(); - let render_fb = buffer.render_fb(); - self.state.present_output( - &node, - &render_fb, - &buffer.render_tex, - &mut rr, - !self.cursor_enabled.get(), - ); - if let Some(tex) = &buffer.dev_tex { - buffer.dev_fb.copy_texture(tex, 0, 0); - } + let fb = + self.prepare_present_fb(&mut rr, buffer, &plane, &node, try_direct_scanout); + rr.dispatch_frame_requests(); + changes.change_object(plane.id, |c| { + c.change(plane.fb_id, fb.fb.id().0 as _); + }); + new_fb = Some(fb); } - changes.change_object(plane.id, |c| { - c.change(plane.fb_id, buffer.drm.id().0 as _); - }); } if self.cursor_changed.get() && cursor.is_some() { let plane = cursor.unwrap(); @@ -434,12 +600,63 @@ impl MetalConnector { DrmError::Atomic(OsError(c::EACCES)) => { log::debug!("Could not perform atomic commit, likely because we're no longer the DRM master"); } - _ => log::error!("Could not set plane framebuffer: {}", ErrorFmt(e)), + _ => 'handle_failure: { + if let Some(fb) = &new_fb { + if let Some(dsd) = &fb.direct_scanout_data { + if self.present(false).is_ok() { + let mut cache = self.scanout_buffers.borrow_mut(); + if let Some(buffer) = cache.remove(&dsd.dma_buf_id) { + cache.insert( + dsd.dma_buf_id, + DirectScanoutCache { + tex: buffer.tex, + fb: None, + }, + ); + } + break 'handle_failure; + } + } + } + log::error!("Could not set plane framebuffer: {}", ErrorFmt(e)); + } } + Err(()) } else { + if let Some(fb) = new_fb { + if let Some(dsd) = &fb.direct_scanout_data { + dsd.tex.reservations().acquire(); + dsd.acquired.set(true); + } + self.active_framebuffers.borrow_mut().push_back(fb); + } self.can_present.set(false); self.has_damage.set(false); self.cursor_changed.set(false); + Ok(()) + } + } + + pub fn update_drm_feedback(&self) { + let fb = self.compute_drm_feedback(); + self.drm_feedback.set(fb); + } + + fn compute_drm_feedback(&self) -> Option> { + let default = self.backend.default_feedback.get()?; + let plane = self.primary_plane.get()?; + let mut formats = vec![]; + for (format, info) in &plane.formats { + for modifier in &info.modifiers { + formats.push((*format, *modifier)); + } + } + match default.for_scanout(&self.state.drm_feedback_ids, self.dev.devnum, &formats) { + Ok(fb) => fb.map(Rc::new), + Err(e) => { + log::error!("Could not compute connector feedback: {}", ErrorFmt(e)); + None + } } } } @@ -488,6 +705,10 @@ impl Connector for MetalConnector { } } } + + fn drm_feedback(&self) -> Option> { + self.drm_feedback.get() + } } #[derive(Debug)] @@ -522,7 +743,7 @@ pub enum PlaneType { #[derive(Debug)] pub struct PlaneFormat { - _format: &'static Format, + format: &'static Format, modifiers: IndexSet, } @@ -538,6 +759,9 @@ pub struct MetalPlane { pub assigned: Cell, + pub mode_w: Cell, + pub mode_h: Cell, + pub crtc_id: MutableProperty, pub crtc_x: MutableProperty, pub crtc_y: MutableProperty, @@ -611,6 +835,10 @@ fn create_connector( cursor_changed: Cell::new(false), cursor_front_buffer: Default::default(), cursor_swap_buffer: Cell::new(false), + drm_feedback: Default::default(), + scanout_buffers: Default::default(), + active_framebuffers: Default::default(), + direct_scanout_active: Cell::new(false), }); let futures = ConnectorFutures { present: backend @@ -786,7 +1014,7 @@ fn create_plane(plane: DrmPlane, master: &Rc) -> Result) -> Result) -> Result return, }; connector.can_present.set(true); + { + let mut scanout_buffers = connector.active_framebuffers.borrow_mut(); + while scanout_buffers.len() > 1 { + scanout_buffers.pop_front(); + } + } if connector.has_damage.get() || connector.cursor_changed.get() { connector.schedule_present(); } @@ -1465,6 +1701,14 @@ impl MetalBackend { } let ctx = dev.ctx.get(); self.state.set_render_ctx(Some(ctx.gfx.clone())); + let fb = match DrmFeedback::new(&self.state.drm_feedback_ids, &*ctx.gfx) { + Ok(fb) => Some(Rc::new(fb)), + Err(e) => { + log::error!("Could not create feedback for new context: {}", ErrorFmt(e)); + None + } + }; + self.default_feedback.set(fb); self.ctx.set(Some(ctx)); for dev in self.device_holder.drm_devices.lock().values() { self.re_init_drm_device(&dev); @@ -1562,6 +1806,7 @@ impl MetalBackend { continue; } connector.send_hardware_cursor(); + connector.update_drm_feedback(); } Ok(()) } @@ -1704,7 +1949,7 @@ impl MetalBackend { Ok(b) => b, Err(e) => return Err(MetalError::ScanoutBuffer(e)), }; - let drm_fb = match dev.master.add_fb(dev_bo.dmabuf()) { + let drm_fb = match dev.master.add_fb(dev_bo.dmabuf(), None) { Ok(fb) => Rc::new(fb), Err(e) => return Err(MetalError::Framebuffer(e)), }; @@ -1917,6 +2162,8 @@ impl MetalBackend { c.change(primary_plane.src_h.id, (mode.vdisplay as u64) << 16); }); primary_plane.assigned.set(true); + primary_plane.mode_w.set(mode.hdisplay as _); + primary_plane.mode_h.set(mode.vdisplay as _); primary_plane.crtc_id.value.set(crtc.id); primary_plane.crtc_x.value.set(0); primary_plane.crtc_y.value.set(0); diff --git a/src/compositor.rs b/src/compositor.rs index 37bacd32..291b64d8 100644 --- a/src/compositor.rs +++ b/src/compositor.rs @@ -202,6 +202,7 @@ fn start_compositor2( activation_tokens: Default::default(), toplevel_lists: Default::default(), dma_buf_ids: Default::default(), + drm_feedback_ids: Default::default(), }); state.tracker.register(ClientId::from_raw(0)); create_dummy_output(&state); diff --git a/src/drm_feedback.rs b/src/drm_feedback.rs index 381ab2c0..6a1251b2 100644 --- a/src/drm_feedback.rs +++ b/src/drm_feedback.rs @@ -1,24 +1,45 @@ use { - crate::{gfx_api::GfxContext, utils::oserror::OsError}, + crate::{gfx_api::GfxContext, utils::oserror::OsError, video::Modifier}, + ahash::AHashMap, byteorder::{NativeEndian, WriteBytesExt}, std::{io::Write, rc::Rc}, thiserror::Error, uapi::{c, OwnedFd}, }; -pub struct DrmFeedback { +linear_ids!(DrmFeedbackIds, DrmFeedbackId); + +#[derive(Debug)] +pub struct DrmFeedbackShared { pub fd: Rc, pub size: usize, - pub indices: Vec, pub main_device: c::dev_t, + pub indices: AHashMap<(u32, Modifier), u16>, +} + +#[derive(Debug)] +pub struct DrmFeedback { + pub id: DrmFeedbackId, + pub shared: Rc, + pub tranches: Vec, +} + +#[derive(Clone, Debug)] +pub struct DrmFeedbackTranche { + pub device: c::dev_t, + pub indices: Vec, + pub scanout: bool, } impl DrmFeedback { - pub fn new(ctx: &dyn GfxContext) -> Result { - let dev_t = uapi::fstat(ctx.gbm().drm.raw()) + pub fn new( + ids: &DrmFeedbackIds, + render_ctx: &dyn GfxContext, + ) -> Result { + let main_device = uapi::fstat(render_ctx.gbm().drm.raw()) .map_err(OsError::from)? .st_rdev; - let data = create_fd_data(ctx); + 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(); memfd.write_all(&data).unwrap(); @@ -28,27 +49,69 @@ impl DrmFeedback { c::F_SEAL_SEAL | c::F_SEAL_GROW | c::F_SEAL_SHRINK | c::F_SEAL_WRITE, ) .unwrap(); - let num_indices = data.len() / 16; - let indices = (0..num_indices).map(|v| v as u16).collect(); Ok(Self { - fd: Rc::new(memfd), - size: data.len(), - indices, - main_device: dev_t, + id: ids.next(), + tranches: vec![DrmFeedbackTranche { + device: main_device, + indices: (0..index_map.len()).map(|v| v as u16).collect(), + scanout: false, + }], + shared: Rc::new(DrmFeedbackShared { + fd: Rc::new(memfd), + size: data.len(), + main_device, + indices: index_map, + }), }) } + + pub fn for_scanout( + &self, + ids: &DrmFeedbackIds, + devnum: c::dev_t, + formats: &[(u32, Modifier)], + ) -> Result, DrmFeedbackError> { + let mut tranches = vec![]; + { + let mut indices = vec![]; + for (format, modifier) in formats { + if let Some(idx) = self.shared.indices.get(&(*format, *modifier)) { + indices.push(*idx); + } + } + if indices.len() > 0 { + tranches.push(DrmFeedbackTranche { + device: devnum, + indices, + scanout: true, + }); + } else { + return Ok(None); + } + } + tranches.extend(self.tranches.iter().cloned()); + Ok(Some(Self { + id: ids.next(), + shared: self.shared.clone(), + tranches, + })) + } } -fn create_fd_data(ctx: &dyn GfxContext) -> Vec { +fn create_fd_data(ctx: &dyn GfxContext) -> (Vec, AHashMap<(u32, Modifier), u16>) { let mut vec = vec![]; + let mut map = AHashMap::new(); + let mut pos = 0; for (format, info) in &*ctx.formats() { for modifier in &info.read_modifiers { vec.write_u32::(*format).unwrap(); vec.write_u32::(0).unwrap(); vec.write_u64::(*modifier).unwrap(); + map.insert((*format, *modifier), pos); + pos += 1; } } - vec + (vec, map) } #[derive(Debug, Error)] diff --git a/src/format.rs b/src/format.rs index f59978fc..6cec1995 100644 --- a/src/format.rs +++ b/src/format.rs @@ -26,6 +26,7 @@ pub struct Format { pub has_alpha: bool, pub shm_supported: bool, pub pipewire: SpaVideoFormat, + pub opaque: Option<&'static Format>, } impl PartialEq for Format { @@ -87,7 +88,6 @@ pub fn map_wayland_format_id(id: u32) -> u32 { } } -#[allow(dead_code)] pub static ARGB8888: &Format = &Format { name: "argb8888", bpp: 4, @@ -100,6 +100,7 @@ pub static ARGB8888: &Format = &Format { has_alpha: true, shm_supported: true, pipewire: SPA_VIDEO_FORMAT_BGRA, + opaque: Some(XRGB8888), }; pub static XRGB8888: &Format = &Format { @@ -114,38 +115,43 @@ pub static XRGB8888: &Format = &Format { has_alpha: false, shm_supported: true, pipewire: SPA_VIDEO_FORMAT_BGRx, + opaque: None, +}; + +static ABGR8888: &Format = &Format { + name: "abgr8888", + bpp: 4, + gl_format: GL_RGBA, + gl_type: GL_UNSIGNED_BYTE, + vk_format: vk::Format::R8G8B8A8_UNORM, + drm: fourcc_code('A', 'B', '2', '4'), + wl_id: None, + external_only_guess: false, + has_alpha: true, + shm_supported: true, + pipewire: SPA_VIDEO_FORMAT_RGBA, + opaque: Some(XBGR8888), +}; + +static XBGR8888: &Format = &Format { + name: "xbgr8888", + bpp: 4, + gl_format: GL_RGBA, + gl_type: GL_UNSIGNED_BYTE, + vk_format: vk::Format::R8G8B8A8_UNORM, + drm: fourcc_code('X', 'B', '2', '4'), + wl_id: None, + external_only_guess: false, + has_alpha: false, + shm_supported: true, + pipewire: SPA_VIDEO_FORMAT_RGBx, + opaque: None, }; pub static FORMATS: &[Format] = &[ - *ARGB8888, - *XRGB8888, + *ARGB8888, *XRGB8888, *ABGR8888, + *XBGR8888, // *NV12, - Format { - name: "abgr8888", - bpp: 4, - gl_format: GL_RGBA, - gl_type: GL_UNSIGNED_BYTE, - vk_format: vk::Format::R8G8B8A8_UNORM, - drm: fourcc_code('A', 'B', '2', '4'), - wl_id: None, - external_only_guess: false, - has_alpha: true, - shm_supported: true, - pipewire: SPA_VIDEO_FORMAT_RGBA, - }, - Format { - name: "xbgr8888", - bpp: 4, - gl_format: GL_RGBA, - gl_type: GL_UNSIGNED_BYTE, - vk_format: vk::Format::R8G8B8A8_UNORM, - drm: fourcc_code('X', 'B', '2', '4'), - wl_id: None, - external_only_guess: false, - has_alpha: false, - shm_supported: true, - pipewire: SPA_VIDEO_FORMAT_RGBx, - }, // Format { // name: "nv12", // bpp: 1, // wrong but only used for shm diff --git a/src/gfx_api.rs b/src/gfx_api.rs index 1df27473..1d67dd9b 100644 --- a/src/gfx_api.rs +++ b/src/gfx_api.rs @@ -9,6 +9,7 @@ use { state::State, theme::Color, tree::Node, + utils::numcell::NumCell, video::{dmabuf::DmaBuf, gbm::GbmDevice, Modifier}, }, ahash::AHashMap, @@ -31,7 +32,12 @@ pub enum GfxApiOpt { CopyTexture(CopyTexture), } -#[derive(Default, Debug, Copy, Clone)] +pub struct GfxRenderPass { + pub ops: Vec, + pub clear: Option, +} + +#[derive(Default, Debug, Copy, Clone, PartialEq)] pub struct BufferPoint { pub x: f32, pub y: f32, @@ -41,9 +47,25 @@ impl BufferPoint { pub fn is_leq_1(&self) -> bool { self.x <= 1.0 && self.y <= 1.0 } + + pub fn top_left() -> Self { + Self { x: 0.0, y: 0.0 } + } + + pub fn top_right() -> Self { + Self { x: 1.0, y: 0.0 } + } + + pub fn bottom_left() -> Self { + Self { x: 0.0, y: 1.0 } + } + + pub fn bottom_right() -> Self { + Self { x: 1.0, y: 1.0 } + } } -#[derive(Default, Debug, Copy, Clone)] +#[derive(Default, Debug, Copy, Clone, PartialEq)] pub struct BufferPoints { pub top_left: BufferPoint, pub top_right: BufferPoint, @@ -79,6 +101,15 @@ impl BufferPoints { && self.bottom_left.is_leq_1() && self.bottom_right.is_leq_1() } + + pub fn identity() -> Self { + Self { + top_left: BufferPoint::top_left(), + top_right: BufferPoint::top_right(), + bottom_left: BufferPoint::bottom_left(), + bottom_right: BufferPoint::bottom_right(), + } + } } #[derive(Debug)] @@ -172,7 +203,7 @@ impl dyn GfxFramebuffer { self.render(ops, clear); } - pub fn render_node( + pub fn create_render_pass( &self, node: &dyn Node, state: &State, @@ -180,7 +211,7 @@ impl dyn GfxFramebuffer { result: Option<&mut RenderResult>, scale: Scale, render_hardware_cursor: bool, - ) { + ) -> GfxRenderPass { let mut ops = self.take_render_ops(); let (width, height) = self.size(); let mut renderer = Renderer { @@ -221,7 +252,34 @@ impl dyn GfxFramebuffer { } } let c = state.theme.colors.background.get(); - self.render(ops, Some(&c)); + GfxRenderPass { + ops, + clear: Some(c), + } + } + + pub fn perform_render_pass(&self, pass: GfxRenderPass) { + self.render(pass.ops, pass.clear.as_ref()) + } + + pub fn render_node( + &self, + node: &dyn Node, + state: &State, + cursor_rect: Option, + result: Option<&mut RenderResult>, + scale: Scale, + render_hardware_cursor: bool, + ) { + let pass = self.create_render_pass( + node, + state, + cursor_rect, + result, + scale, + render_hardware_cursor, + ); + self.perform_render_pass(pass); } pub fn render_hardware_cursor(&self, cursor: &dyn Cursor, state: &State, scale: Scale) { @@ -253,6 +311,38 @@ pub trait GfxImage { fn height(&self) -> i32; } +#[derive(Default)] +pub struct TextureReservations { + reservations: NumCell, + on_release: Cell>>, +} + +impl TextureReservations { + pub fn has_reservation(&self) -> bool { + self.reservations.get() != 0 + } + + pub fn acquire(&self) { + self.reservations.fetch_add(1); + } + + pub fn release(&self) { + if self.reservations.fetch_sub(1) == 1 { + if let Some(cb) = self.on_release.take() { + cb(); + } + } + } + + pub fn on_released(&self, cb: C) { + if self.has_reservation() { + self.on_release.set(Some(Box::new(cb))); + } else { + cb(); + } + } +} + pub trait GfxTexture: Debug { fn size(&self) -> (i32, i32); fn as_any(&self) -> &dyn Any; @@ -268,6 +358,7 @@ pub trait GfxTexture: Debug { shm: &[Cell], ) -> Result<(), GfxError>; fn dmabuf(&self) -> Option<&DmaBuf>; + fn reservations(&self) -> &TextureReservations; } pub trait GfxContext: Debug { diff --git a/src/gfx_apis/gl/renderer/context.rs b/src/gfx_apis/gl/renderer/context.rs index fc6adad6..7e633d7e 100644 --- a/src/gfx_apis/gl/renderer/context.rs +++ b/src/gfx_apis/gl/renderer/context.rs @@ -186,6 +186,7 @@ impl GlRenderContext { Ok(Rc::new(Texture { ctx: self.clone(), gl, + resv: Default::default(), })) } } diff --git a/src/gfx_apis/gl/renderer/image.rs b/src/gfx_apis/gl/renderer/image.rs index 23f3d848..c4a10b6b 100644 --- a/src/gfx_apis/gl/renderer/image.rs +++ b/src/gfx_apis/gl/renderer/image.rs @@ -28,6 +28,7 @@ impl Image { Ok(Rc::new(Texture { ctx: self.ctx.clone(), gl: GlTexture::import_img(&self.ctx.ctx, &self.gl)?, + resv: Default::default(), })) } diff --git a/src/gfx_apis/gl/renderer/texture.rs b/src/gfx_apis/gl/renderer/texture.rs index 8e1130f4..d2f54f05 100644 --- a/src/gfx_apis/gl/renderer/texture.rs +++ b/src/gfx_apis/gl/renderer/texture.rs @@ -1,7 +1,7 @@ use { crate::{ format::Format, - gfx_api::{GfxError, GfxTexture}, + gfx_api::{GfxError, GfxTexture, TextureReservations}, gfx_apis::gl::{gl::texture::GlTexture, renderer::context::GlRenderContext, RenderError}, video::dmabuf::DmaBuf, }, @@ -16,6 +16,7 @@ use { pub struct Texture { pub(in crate::gfx_apis::gl) ctx: Rc, pub(in crate::gfx_apis::gl) gl: GlTexture, + pub(in crate::gfx_apis::gl) resv: TextureReservations, } impl Debug for Texture { @@ -63,4 +64,8 @@ impl GfxTexture for Texture { fn dmabuf(&self) -> Option<&DmaBuf> { self.gl.img.as_ref().map(|i| &i.dmabuf) } + + fn reservations(&self) -> &TextureReservations { + &self.resv + } } diff --git a/src/gfx_apis/vulkan/image.rs b/src/gfx_apis/vulkan/image.rs index 52e1a7d2..88b40025 100644 --- a/src/gfx_apis/vulkan/image.rs +++ b/src/gfx_apis/vulkan/image.rs @@ -1,7 +1,7 @@ use { crate::{ format::Format, - gfx_api::{GfxApiOpt, GfxError, GfxFramebuffer, GfxImage, GfxTexture}, + gfx_api::{GfxApiOpt, GfxError, GfxFramebuffer, GfxImage, GfxTexture, TextureReservations}, gfx_apis::vulkan::{ allocator::VulkanAllocation, device::VulkanDevice, format::VulkanMaxExtents, renderer::VulkanRenderer, util::OnDrop, VulkanError, @@ -53,6 +53,7 @@ pub struct VulkanImage { pub(super) is_undefined: Cell, pub(super) ty: VulkanImageMemory, pub(super) render_ops: CloneCell>, + pub(super) resv: TextureReservations, } pub enum VulkanImageMemory { @@ -211,6 +212,7 @@ impl VulkanRenderer { is_undefined: Cell::new(true), ty: VulkanImageMemory::Internal(shm), render_ops: Default::default(), + resv: Default::default(), })) } @@ -480,6 +482,7 @@ impl VulkanDmaBufImageTemplate { }), format: self.dmabuf.format, is_undefined: Cell::new(true), + resv: Default::default(), })) } } @@ -580,4 +583,8 @@ impl GfxTexture for VulkanImage { VulkanImageMemory::Internal(_) => None, } } + + fn reservations(&self) -> &TextureReservations { + &self.resv + } } diff --git a/src/ifs/wl_output.rs b/src/ifs/wl_output.rs index f1e3a70b..f02f6b66 100644 --- a/src/ifs/wl_output.rs +++ b/src/ifs/wl_output.rs @@ -202,9 +202,13 @@ impl WlOutputGlobal { Ok(()) } + pub fn have_shm_screencopies(&self) -> bool { + self.pending_captures.iter().any(|c| c.is_shm.get()) + } + pub fn perform_screencopies( &self, - fb: &dyn GfxFramebuffer, + fb: Option<&dyn GfxFramebuffer>, tex: &Rc, render_hardware_cursors: bool, ) { @@ -232,12 +236,13 @@ impl WlOutputGlobal { wl_buffer.storage.borrow_mut().deref() { let acc = mem.access(|mem| { - fb.copy_to_shm( - rect.x1(), - rect.y1(), - rect.width(), - rect.height(), - XRGB8888, + tex.clone().read_pixels( + capture.rect.x1(), + capture.rect.y1(), + capture.rect.width(), + capture.rect.height(), + *stride, + wl_buffer.format, mem, ) }); @@ -249,24 +254,25 @@ impl WlOutputGlobal { } }; if res.is_err() { - let acc = mem.access(|mem| { - tex.clone().read_pixels( - capture.rect.x1(), - capture.rect.y1(), - capture.rect.width(), - capture.rect.height(), - *stride, - wl_buffer.format, - mem, - ) - }); - res = match acc { - Ok(res) => res, - Err(e) => { - capture.client.error(e); - continue; - } - }; + if let Some(fb) = fb { + let acc = mem.access(|mem| { + fb.copy_to_shm( + rect.x1(), + rect.y1(), + rect.width(), + rect.height(), + XRGB8888, + mem, + ) + }); + res = match acc { + Ok(res) => res, + Err(e) => { + capture.client.error(e); + continue; + } + }; + } } if let Err(e) = res { log::warn!("Could not read texture to memory: {}", ErrorFmt(e)); diff --git a/src/ifs/wl_surface.rs b/src/ifs/wl_surface.rs index 81f68c5d..710d2fa2 100644 --- a/src/ifs/wl_surface.rs +++ b/src/ifs/wl_surface.rs @@ -14,6 +14,7 @@ use { crate::{ backend::KeyState, client::{Client, ClientError, RequestParser}, + drm_feedback::DrmFeedback, fixed::Fixed, gfx_api::{BufferPoint, BufferPoints}, ifs::{ @@ -36,6 +37,7 @@ use { }, wp_content_type_v1::ContentType, wp_presentation_feedback::WpPresentationFeedback, + zwp_linux_dmabuf_feedback_v1::ZwpLinuxDmabufFeedbackV1, }, leaks::Tracker, object::Object, @@ -53,7 +55,10 @@ use { numcell::NumCell, smallmap::SmallMap, }, - wire::{wl_surface::*, WlOutputId, WlSurfaceId, ZwpIdleInhibitorV1Id}, + wire::{ + wl_surface::*, WlOutputId, WlSurfaceId, ZwpIdleInhibitorV1Id, + ZwpLinuxDmabufFeedbackV1Id, + }, xkbcommon::ModifierState, xwayland::XWaylandEvent, }, @@ -259,6 +264,7 @@ pub struct WlSurface { version: u32, pub has_content_type_manager: Cell, content_type: Cell>, + pub drm_feedback: CopyHashMap>, } impl Debug for WlSurface { @@ -411,6 +417,7 @@ impl WlSurface { version, has_content_type_manager: Default::default(), content_type: Default::default(), + drm_feedback: Default::default(), } } @@ -762,7 +769,23 @@ impl WlSurface { if let Some(buffer) = self.buffer.take() { old_raw_size = Some(buffer.rect); if !buffer.destroyed() { - buffer.send_release(); + 'handle_release: { + if let Some(tex) = buffer.texture.get() { + let resv = tex.reservations(); + if resv.has_reservation() { + let buffer = Rc::downgrade(&buffer); + resv.on_released(move || { + if let Some(buffer) = buffer.upgrade() { + if !buffer.destroyed() { + buffer.send_release(); + } + } + }); + break 'handle_release; + } + } + buffer.send_release(); + } } } if let Some(buffer) = buffer_change { @@ -1065,6 +1088,12 @@ impl WlSurface { tl.tl_data().request_attention(tl.tl_as_node()); } } + + pub fn send_feedback(&self, fb: &DrmFeedback) { + for consumer in self.drm_feedback.lock().values() { + consumer.send_feedback(fb); + } + } } object_base! { @@ -1100,6 +1129,7 @@ impl Object for WlSurface { self.fractional_scale.take(); self.tearing_control.take(); self.constraints.clear(); + self.drm_feedback.clear(); } } diff --git a/src/ifs/wl_surface/x_surface/xwindow.rs b/src/ifs/wl_surface/x_surface/xwindow.rs index 960e124c..daa452bc 100644 --- a/src/ifs/wl_surface/x_surface/xwindow.rs +++ b/src/ifs/wl_surface/x_surface/xwindow.rs @@ -424,6 +424,10 @@ impl ToplevelNode for Xwindow { self.display_link.borrow_mut().take(); self.x.surface.destroy_node(); } + + fn tl_scanout_surface(&self) -> Option> { + Some(self.x.surface.clone()) + } } impl StackedNode for Xwindow { diff --git a/src/ifs/wl_surface/xdg_surface/xdg_toplevel.rs b/src/ifs/wl_surface/xdg_surface/xdg_toplevel.rs index c49837cc..60a21d87 100644 --- a/src/ifs/wl_surface/xdg_surface/xdg_toplevel.rs +++ b/src/ifs/wl_surface/xdg_surface/xdg_toplevel.rs @@ -8,7 +8,10 @@ use { ifs::{ ext_foreign_toplevel_list_v1::ExtForeignToplevelListV1, wl_seat::{NodeSeatState, SeatId, WlSeatGlobal}, - wl_surface::xdg_surface::{XdgSurface, XdgSurfaceError, XdgSurfaceExt}, + wl_surface::{ + xdg_surface::{XdgSurface, XdgSurfaceError, XdgSurfaceExt}, + WlSurface, + }, }, leaks::Tracker, object::Object, @@ -544,6 +547,10 @@ impl ToplevelNode for XdgToplevel { // self.map_tiled() // } // } + + fn tl_scanout_surface(&self) -> Option> { + Some(self.xdg.surface.clone()) + } } impl XdgSurfaceExt for XdgToplevel { diff --git a/src/ifs/zwlr_screencopy_frame_v1.rs b/src/ifs/zwlr_screencopy_frame_v1.rs index 1b7c1af0..fbc35fe4 100644 --- a/src/ifs/zwlr_screencopy_frame_v1.rs +++ b/src/ifs/zwlr_screencopy_frame_v1.rs @@ -33,6 +33,7 @@ pub struct ZwlrScreencopyFrameV1 { pub with_damage: Cell, pub output_link: Cell>>>, pub buffer: Cell>>, + pub is_shm: Cell, pub version: u32, } @@ -119,6 +120,14 @@ impl ZwlrScreencopyFrameV1 { return Err(ZwlrScreencopyFrameV1Error::InvalidBufferStride); } } + let is_shm = match &*buffer.storage.borrow() { + None => false, + Some(s) => match s { + WlBufferStorage::Shm { .. } => true, + WlBufferStorage::Dmabuf(_) => false, + }, + }; + self.is_shm.set(is_shm); self.buffer.set(Some(buffer)); if !with_damage { self.output.connector.connector.damage(); diff --git a/src/ifs/zwlr_screencopy_manager_v1.rs b/src/ifs/zwlr_screencopy_manager_v1.rs index 581d528f..6f712f50 100644 --- a/src/ifs/zwlr_screencopy_manager_v1.rs +++ b/src/ifs/zwlr_screencopy_manager_v1.rs @@ -112,6 +112,7 @@ impl ZwlrScreencopyManagerV1 { with_damage: Cell::new(false), output_link: Cell::new(None), buffer: Cell::new(None), + is_shm: Cell::new(false), version: self.version, }); track!(self.client, frame); diff --git a/src/ifs/zwp_linux_dmabuf_feedback_v1.rs b/src/ifs/zwp_linux_dmabuf_feedback_v1.rs index 37e3ce61..f8828ce2 100644 --- a/src/ifs/zwp_linux_dmabuf_feedback_v1.rs +++ b/src/ifs/zwp_linux_dmabuf_feedback_v1.rs @@ -1,13 +1,14 @@ use { crate::{ client::{Client, ClientError}, - drm_feedback::DrmFeedback, + drm_feedback::{DrmFeedback, DrmFeedbackId}, + ifs::wl_surface::WlSurface, leaks::Tracker, object::Object, utils::buffd::{MsgParser, MsgParserError}, wire::{zwp_linux_dmabuf_feedback_v1::*, ZwpLinuxDmabufFeedbackV1Id}, }, - std::rc::Rc, + std::{cell::Cell, rc::Rc}, thiserror::Error, uapi::{c, OwnedFd}, }; @@ -19,24 +20,37 @@ pub struct ZwpLinuxDmabufFeedbackV1 { pub id: ZwpLinuxDmabufFeedbackV1Id, pub client: Rc, pub tracker: Tracker, + pub last_feedback: Cell>, + pub surface: Option>, } impl ZwpLinuxDmabufFeedbackV1 { - pub fn new(id: ZwpLinuxDmabufFeedbackV1Id, client: &Rc) -> Self { + pub fn new( + id: ZwpLinuxDmabufFeedbackV1Id, + client: &Rc, + surface: Option<&Rc>, + ) -> Self { Self { id, client: client.clone(), tracker: Default::default(), + last_feedback: Default::default(), + surface: surface.cloned(), } } pub fn send_feedback(&self, feedback: &DrmFeedback) { - self.send_format_table(&feedback.fd, feedback.size); - self.send_main_device(feedback.main_device); - self.send_tranche_target_device(feedback.main_device); - self.send_tranche_formats(&feedback.indices); - self.send_tranche_flags(0); - self.send_tranche_done(); + if self.last_feedback.replace(Some(feedback.id)) == Some(feedback.id) { + return; + } + self.send_format_table(&feedback.shared.fd, feedback.shared.size); + self.send_main_device(feedback.shared.main_device); + for tranch in &feedback.tranches { + self.send_tranche_target_device(tranch.device); + self.send_tranche_formats(&tranch.indices); + self.send_tranche_flags(if tranch.scanout { SCANOUT } else { 0 }); + self.send_tranche_done(); + } self.send_done(); } @@ -96,6 +110,9 @@ impl ZwpLinuxDmabufFeedbackV1 { .state .drm_feedback_consumers .remove(&(self.client.id, self.id)); + if let Some(surface) = &self.surface { + surface.drm_feedback.remove(&self.id); + } } } diff --git a/src/ifs/zwp_linux_dmabuf_v1.rs b/src/ifs/zwp_linux_dmabuf_v1.rs index 5c8370a4..bd53f35c 100644 --- a/src/ifs/zwp_linux_dmabuf_v1.rs +++ b/src/ifs/zwp_linux_dmabuf_v1.rs @@ -3,7 +3,7 @@ use { client::{Client, ClientError}, globals::{Global, GlobalName}, ifs::{ - zwp_linux_buffer_params_v1::ZwpLinuxBufferParamsV1, + wl_surface::WlSurface, zwp_linux_buffer_params_v1::ZwpLinuxBufferParamsV1, zwp_linux_dmabuf_feedback_v1::ZwpLinuxDmabufFeedbackV1, }, leaks::Tracker, @@ -120,8 +120,9 @@ impl ZwpLinuxDmabufV1 { fn get_feedback( self: &Rc, id: ZwpLinuxDmabufFeedbackV1Id, - ) -> Result<(), ZwpLinuxDmabufV1Error> { - let fb = Rc::new(ZwpLinuxDmabufFeedbackV1::new(id, &self.client)); + surface: Option<&Rc>, + ) -> Result, ZwpLinuxDmabufV1Error> { + let fb = Rc::new(ZwpLinuxDmabufFeedbackV1::new(id, &self.client, surface)); track!(self.client, fb); self.client.add_client_obj(&fb)?; self.client @@ -131,7 +132,7 @@ impl ZwpLinuxDmabufV1 { if let Some(feedback) = self.client.state.drm_feedback.get() { fb.send_feedback(&feedback); } - Ok(()) + Ok(fb) } fn get_default_feedback( @@ -139,7 +140,8 @@ impl ZwpLinuxDmabufV1 { parser: MsgParser<'_, '_>, ) -> Result<(), ZwpLinuxDmabufV1Error> { let req: GetDefaultFeedback = self.client.parse(&**self, parser)?; - self.get_feedback(req.id) + self.get_feedback(req.id, None)?; + Ok(()) } fn get_surface_feedback( @@ -147,8 +149,10 @@ impl ZwpLinuxDmabufV1 { parser: MsgParser<'_, '_>, ) -> Result<(), ZwpLinuxDmabufV1Error> { let req: GetSurfaceFeedback = self.client.parse(&**self, parser)?; - let _surface = self.client.lookup(req.surface)?; - self.get_feedback(req.id) + let surface = self.client.lookup(req.surface)?; + let fb = self.get_feedback(req.id, Some(&surface))?; + surface.drm_feedback.set(req.id, fb); + Ok(()) } } diff --git a/src/renderer.rs b/src/renderer.rs index b3972c01..251e9c90 100644 --- a/src/renderer.rs +++ b/src/renderer.rs @@ -35,6 +35,15 @@ pub struct RenderResult { pub presentation_feedbacks: Vec>, } +impl RenderResult { + pub fn dispatch_frame_requests(&mut self) { + for fr in self.frame_requests.drain(..) { + fr.send_done(); + let _ = fr.client.remove_obj(&*fr); + } + } +} + impl Debug for RenderResult { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { f.debug_struct("RenderResult").finish_non_exhaustive() diff --git a/src/state.rs b/src/state.rs index a4fa07c9..c3cca7cf 100644 --- a/src/state.rs +++ b/src/state.rs @@ -12,7 +12,7 @@ use { config::ConfigProxy, cursor::{Cursor, ServerCursors}, dbus::Dbus, - drm_feedback::DrmFeedback, + drm_feedback::{DrmFeedback, DrmFeedbackIds}, fixed::Fixed, forker::ForkerProxy, gfx_api::{GfxContext, GfxError, GfxFramebuffer, GfxTexture}, @@ -143,6 +143,7 @@ pub struct State { pub toplevel_lists: CopyHashMap<(ClientId, ExtForeignToplevelListV1Id), Rc>, pub dma_buf_ids: DmaBufIds, + pub drm_feedback_ids: DrmFeedbackIds, } // impl Drop for State { @@ -348,7 +349,7 @@ impl State { 'handle_new_feedback: { if let Some(ctx) = &ctx { - let feedback = match DrmFeedback::new(&**ctx) { + let feedback = match DrmFeedback::new(&self.drm_feedback_ids, &**ctx) { Ok(fb) => fb, Err(e) => { log::error!("Could not create new DRM feedback: {}", ErrorFmt(e)); @@ -750,11 +751,8 @@ impl State { output.global.preferred_scale.get(), render_hw_cursor, ); - for fr in rr.frame_requests.drain(..) { - fr.send_done(); - let _ = fr.client.remove_obj(&*fr); - } - output.perform_screencopies(&**fb, tex, !render_hw_cursor); + output.perform_screencopies(Some(&**fb), tex, !render_hw_cursor); + rr.dispatch_frame_requests(); } pub fn perform_screencopy( diff --git a/src/tree/output.rs b/src/tree/output.rs index 98667201..c18c2f87 100644 --- a/src/tree/output.rs +++ b/src/tree/output.rs @@ -80,7 +80,7 @@ pub async fn output_render_data(state: Rc) { impl OutputNode { pub fn perform_screencopies( &self, - fb: &dyn GfxFramebuffer, + fb: Option<&dyn GfxFramebuffer>, tex: &Rc, render_hardware_cursor: bool, ) { diff --git a/src/tree/toplevel.rs b/src/tree/toplevel.rs index f0e36fb4..25d4d2f2 100644 --- a/src/tree/toplevel.rs +++ b/src/tree/toplevel.rs @@ -5,6 +5,7 @@ use { ext_foreign_toplevel_handle_v1::ExtForeignToplevelHandleV1, ext_foreign_toplevel_list_v1::ExtForeignToplevelListV1, wl_seat::{collect_kb_foci, collect_kb_foci2, NodeSeatState, SeatId}, + wl_surface::WlSurface, }, rect::Rect, state::State, @@ -161,6 +162,10 @@ pub trait ToplevelNode: Node { fn tl_last_active_child(self: Rc) -> Rc { self.tl_into_dyn() } + + fn tl_scanout_surface(&self) -> Option> { + None + } } pub struct FullscreenedData { @@ -356,8 +361,8 @@ impl ToplevelData { }); drop(data); self.is_fullscreen.set(true); - ws.set_fullscreen_node(&node); node.tl_set_parent(ws.clone()); + ws.set_fullscreen_node(&node); node.clone().tl_set_workspace(ws); node.clone() .tl_change_extents(&ws.output.get().global.pos.get()); diff --git a/src/tree/workspace.rs b/src/tree/workspace.rs index dfc16acb..17f9d29f 100644 --- a/src/tree/workspace.rs +++ b/src/tree/workspace.rs @@ -143,6 +143,11 @@ impl WorkspaceNode { if plane_was_visible { self.plane_set_visible(false); } + if let Some(surface) = node.tl_scanout_surface() { + if let Some(fb) = self.output.get().global.connector.connector.drm_feedback() { + surface.send_feedback(&fb); + } + } } pub fn remove_fullscreen_node(&self) { @@ -151,6 +156,11 @@ impl WorkspaceNode { if self.visible.get() { self.plane_set_visible(true); } + if let Some(surface) = node.tl_scanout_surface() { + if let Some(fb) = surface.client.state.drm_feedback.get() { + surface.send_feedback(&fb); + } + } } } diff --git a/src/video/drm.rs b/src/video/drm.rs index e1e09f68..89fa48c6 100644 --- a/src/video/drm.rs +++ b/src/video/drm.rs @@ -32,6 +32,7 @@ use { use crate::{ backend, + format::Format, io_uring::{IoUring, IoUringError}, utils::{buf::Buf, errorfmt::ErrorFmt, stack::Stack, syncqueue::SyncQueue, vec_ext::VecExt}, video::{ @@ -306,7 +307,11 @@ impl DrmMaster { } } - pub fn add_fb(self: &Rc, dma: &DmaBuf) -> Result { + pub fn add_fb( + self: &Rc, + dma: &DmaBuf, + format: Option<&Format>, + ) -> Result { let mut modifier = 0; let mut flags = 0; if dma.modifier != INVALID_MODIFIER { @@ -330,7 +335,7 @@ impl DrmMaster { self.raw(), dma.width as _, dma.height as _, - dma.format.drm, + format.unwrap_or(dma.format).drm, flags, handles, strides, From 114c29395061a524984a07438d7203cd4af74452 Mon Sep 17 00:00:00 2001 From: Julian Orth Date: Sun, 18 Feb 2024 20:44:56 +0100 Subject: [PATCH 4/5] config: allow disabling direct scanout --- jay-config/src/_private/client.rs | 4 ++++ jay-config/src/_private/ipc.rs | 4 ++++ jay-config/src/video.rs | 14 ++++++++++++++ src/backend.rs | 1 + src/backends/metal/video.rs | 17 ++++++++++++++++- src/backends/x.rs | 4 ++++ src/compositor.rs | 1 + src/config/handler.rs | 18 ++++++++++++++++++ src/state.rs | 1 + 9 files changed, 63 insertions(+), 1 deletion(-) diff --git a/jay-config/src/_private/client.rs b/jay-config/src/_private/client.rs index 62ba9511..a4223e62 100644 --- a/jay-config/src/_private/client.rs +++ b/jay-config/src/_private/client.rs @@ -511,6 +511,10 @@ impl Client { self.send(&ClientMessage::SetGfxApi { device, api }); } + pub fn set_direct_scanout_enabled(&self, device: Option, enabled: bool) { + self.send(&ClientMessage::SetDirectScanoutEnabled { device, enabled }); + } + pub fn connector_connected(&self, connector: Connector) -> bool { let res = self.send_with_response(&ClientMessage::ConnectorConnected { connector }); get_response!(res, false, ConnectorConnected { connected }); diff --git a/jay-config/src/_private/ipc.rs b/jay-config/src/_private/ipc.rs index dfa05d82..fd32e7a7 100644 --- a/jay-config/src/_private/ipc.rs +++ b/jay-config/src/_private/ipc.rs @@ -338,6 +338,10 @@ pub enum ClientMessage<'a> { device: Option, api: GfxApi, }, + SetDirectScanoutEnabled { + device: Option, + enabled: bool, + }, } #[derive(Serialize, Deserialize, Debug)] diff --git a/jay-config/src/video.rs b/jay-config/src/video.rs index 0936a836..9152cd5d 100644 --- a/jay-config/src/video.rs +++ b/jay-config/src/video.rs @@ -369,6 +369,11 @@ impl DrmDevice { pub fn set_gfx_api(self, gfx_api: GfxApi) { get!().set_gfx_api(Some(self), gfx_api); } + + /// Enables or disables direct scanout of client surfaces for this device. + pub fn set_direct_scanout_enabled(self, enabled: bool) { + get!().set_direct_scanout_enabled(Some(self), enabled); + } } /// A graphics API. @@ -389,3 +394,12 @@ pub enum GfxApi { pub fn set_gfx_api(gfx_api: GfxApi) { get!().set_gfx_api(None, gfx_api); } + +/// Enables or disables direct scanout of client surfaces. +/// +/// The default is `true`. +/// +/// This setting can be overwritten per-device with [DrmDevice::set_direct_scanout_enabled]. +pub fn set_direct_scanout_enabled(enabled: bool) { + get!().set_direct_scanout_enabled(None, enabled); +} diff --git a/src/backend.rs b/src/backend.rs index 53cf4b0e..cdeed98e 100644 --- a/src/backend.rs +++ b/src/backend.rs @@ -230,4 +230,5 @@ pub trait BackendDrmDevice { fn set_gfx_api(&self, api: GfxApi); fn gtx_api(&self) -> GfxApi; fn version(&self) -> Result; + fn set_direct_scanout_enabled(&self, enabled: bool); } diff --git a/src/backends/metal/video.rs b/src/backends/metal/video.rs index b0ff64be..71f938dc 100644 --- a/src/backends/metal/video.rs +++ b/src/backends/metal/video.rs @@ -81,6 +81,7 @@ pub struct MetalDrmDevice { pub handle_events: HandleEvents, pub ctx: CloneCell>, pub on_change: OnChange, + pub direct_scanout_enabled: Cell>, } impl BackendDrmDevice for MetalDrmDevice { @@ -115,6 +116,10 @@ impl BackendDrmDevice for MetalDrmDevice { fn version(&self) -> Result { self.gbm.drm.version() } + + fn set_direct_scanout_enabled(&self, enabled: bool) { + self.direct_scanout_enabled.set(Some(enabled)); + } } pub struct HandleEvents { @@ -466,6 +471,13 @@ impl MetalConnector { data } + fn direct_scanout_enabled(&self) -> bool { + self.dev + .direct_scanout_enabled + .get() + .unwrap_or(self.state.direct_scanout_enabled.get()) + } + fn prepare_present_fb( &self, rr: &mut RenderResult, @@ -485,7 +497,9 @@ impl MetalConnector { output.global.preferred_scale.get(), render_hw_cursor, ); - let try_direct_scanout = try_direct_scanout && !output.global.have_shm_screencopies(); + let try_direct_scanout = try_direct_scanout + && !output.global.have_shm_screencopies() + && self.direct_scanout_enabled(); let mut direct_scanout_data = None; if try_direct_scanout { if let Some(dsd) = self.prepare_direct_scanout(&pass, plane) { @@ -1394,6 +1408,7 @@ impl MetalBackend { }, ctx: CloneCell::new(ctx), on_change: Default::default(), + direct_scanout_enabled: Default::default(), }); let (connectors, futures) = get_connectors(self, &dev, &resources.connectors)?; diff --git a/src/backends/x.rs b/src/backends/x.rs index f6131a79..e44d4d6d 100644 --- a/src/backends/x.rs +++ b/src/backends/x.rs @@ -989,6 +989,10 @@ impl BackendDrmDevice for XDrmDevice { fn version(&self) -> Result { self.backend.gbm.drm.version() } + + fn set_direct_scanout_enabled(&self, enabled: bool) { + let _ = enabled; + } } struct XOutput { diff --git a/src/compositor.rs b/src/compositor.rs index 291b64d8..65249213 100644 --- a/src/compositor.rs +++ b/src/compositor.rs @@ -203,6 +203,7 @@ fn start_compositor2( toplevel_lists: Default::default(), dma_buf_ids: Default::default(), drm_feedback_ids: Default::default(), + direct_scanout_enabled: Cell::new(true), }); state.tracker.register(ClientId::from_raw(0)); create_dummy_output(&state); diff --git a/src/config/handler.rs b/src/config/handler.rs index e9445e09..9ba8708a 100644 --- a/src/config/handler.rs +++ b/src/config/handler.rs @@ -590,6 +590,21 @@ impl ConfigProxyHandler { Ok(()) } + fn handle_set_direct_scanout_enabled( + &self, + device: Option, + enabled: bool, + ) -> Result<(), CphError> { + match device { + Some(dev) => self + .get_drm_device(dev)? + .dev + .set_direct_scanout_enabled(enabled), + _ => self.state.direct_scanout_enabled.set(enabled), + } + Ok(()) + } + fn handle_get_default_workspace_capture(&self) { self.respond(Response::GetDefaultWorkspaceCapture { capture: self.state.default_workspace_capture.get(), @@ -1320,6 +1335,9 @@ impl ConfigProxyHandler { ClientMessage::SetGfxApi { device, api } => { self.handle_set_gfx_api(device, api).wrn("set_gfx_api")? } + ClientMessage::SetDirectScanoutEnabled { device, enabled } => self + .handle_set_direct_scanout_enabled(device, enabled) + .wrn("set_direct_scanout_enabled")?, } Ok(()) } diff --git a/src/state.rs b/src/state.rs index c3cca7cf..f7bf0faa 100644 --- a/src/state.rs +++ b/src/state.rs @@ -144,6 +144,7 @@ pub struct State { CopyHashMap<(ClientId, ExtForeignToplevelListV1Id), Rc>, pub dma_buf_ids: DmaBufIds, pub drm_feedback_ids: DrmFeedbackIds, + pub direct_scanout_enabled: Cell, } // impl Drop for State { From 47e469b252698b6f7151968c1c24caa24465657b Mon Sep 17 00:00:00 2001 From: Julian Orth Date: Mon, 19 Feb 2024 11:20:53 +0100 Subject: [PATCH 5/5] metal: only use direct scanout on the render device --- src/backends/metal/video.rs | 28 ++++++++++++++++++++-------- 1 file changed, 20 insertions(+), 8 deletions(-) diff --git a/src/backends/metal/video.rs b/src/backends/metal/video.rs index 71f938dc..1f81b723 100644 --- a/src/backends/metal/video.rs +++ b/src/backends/metal/video.rs @@ -84,6 +84,15 @@ pub struct MetalDrmDevice { pub direct_scanout_enabled: Cell>, } +impl MetalDrmDevice { + pub fn is_render_device(&self) -> bool { + if let Some(ctx) = self.backend.ctx.get() { + return ctx.dev_id == self.id; + } + false + } +} + impl BackendDrmDevice for MetalDrmDevice { fn id(&self) -> DrmDeviceId { self.id @@ -499,7 +508,13 @@ impl MetalConnector { ); let try_direct_scanout = try_direct_scanout && !output.global.have_shm_screencopies() - && self.direct_scanout_enabled(); + && self.direct_scanout_enabled() + // at least on AMD, using a FB on a different device for rendering will fail + // and destroy the render context. it's possible to work around this by waiting + // until the FB is no longer being scanned out, but if a notification pops up + // then we must be able to disable direct scanout immediately. + // https://gitlab.freedesktop.org/drm/amd/-/issues/3186 + && self.dev.is_render_device(); let mut direct_scanout_data = None; if try_direct_scanout { if let Some(dsd) = self.prepare_direct_scanout(&pass, plane) { @@ -657,6 +672,9 @@ impl MetalConnector { } fn compute_drm_feedback(&self) -> Option> { + if !self.dev.is_render_device() { + return None; + } let default = self.backend.default_feedback.get()?; let plane = self.primary_plane.get()?; let mut formats = vec![]; @@ -1751,13 +1769,7 @@ impl MetalBackend { dev_id: dev.id, gfx, })); - let mut is_render_ctx = false; - if let Some(render_ctx) = self.ctx.get() { - if render_ctx.dev_id == dev.id { - is_render_ctx = true; - } - } - if is_render_ctx { + if dev.is_render_device() { self.make_render_device(dev, true); } else { if let Some(dev) = self.device_holder.drm_devices.get(&dev.devnum) {