From 5d5843df9a4010b4d4b19fa5a0dd8448e3d1ddd1 Mon Sep 17 00:00:00 2001 From: Julian Orth Date: Mon, 9 Sep 2024 20:52:00 +0200 Subject: [PATCH 01/10] gfx: remove GfxFramebuffer::take_render_ops --- src/backends/metal/video.rs | 2 +- src/gfx_api.rs | 27 +++++++++++-------------- src/gfx_apis/gl/renderer/context.rs | 6 ++---- src/gfx_apis/gl/renderer/framebuffer.rs | 18 +++++------------ src/gfx_apis/vulkan/image.rs | 12 +++-------- src/gfx_apis/vulkan/shm_image.rs | 1 - src/it/test_gfx_api.rs | 6 +----- src/state.rs | 4 ++-- 8 files changed, 26 insertions(+), 50 deletions(-) diff --git a/src/backends/metal/video.rs b/src/backends/metal/video.rs index 51c36192..3f4a55c7 100644 --- a/src/backends/metal/video.rs +++ b/src/backends/metal/video.rs @@ -923,7 +923,7 @@ impl MetalConnector { match &direct_scanout_data { None => { let sf = buffer_fb - .perform_render_pass(pass) + .perform_render_pass(&pass) .map_err(MetalError::RenderFrame)?; sync_file = buffer.copy_to_dev(sf)?; fb = buffer.drm.clone(); diff --git a/src/gfx_api.rs b/src/gfx_api.rs index 9e7882ec..c31c3849 100644 --- a/src/gfx_api.rs +++ b/src/gfx_api.rs @@ -256,13 +256,11 @@ pub enum ResetStatus { } pub trait GfxFramebuffer: Debug { - fn take_render_ops(&self) -> Vec; - fn physical_size(&self) -> (i32, i32); fn render( &self, - ops: Vec, + ops: &[GfxApiOpt], clear: Option<&Color>, ) -> Result, GfxError>; @@ -286,8 +284,7 @@ impl dyn GfxFramebuffer { } pub fn clear_with(&self, r: f32, g: f32, b: f32, a: f32) -> Result, GfxError> { - let ops = self.take_render_ops(); - self.render(ops, Some(&Color { r, g, b, a })) + self.render(&[], Some(&Color { r, g, b, a })) } pub fn logical_size(&self, transform: Transform) -> (i32, i32) { @@ -320,7 +317,7 @@ impl dyn GfxFramebuffer { x: i32, y: i32, ) -> Result, GfxError> { - let mut ops = self.take_render_ops(); + let mut ops = vec![]; let scale = Scale::from_int(1); let mut renderer = self.renderer_base(&mut ops, scale, Transform::None); renderer.render_texture( @@ -337,7 +334,7 @@ impl dyn GfxFramebuffer { release_sync, ); let clear = self.format().has_alpha.then_some(&Color::TRANSPARENT); - self.render(ops, clear) + self.render(&ops, clear) } pub fn render_custom( @@ -346,10 +343,10 @@ impl dyn GfxFramebuffer { clear: Option<&Color>, f: &mut dyn FnMut(&mut RendererBase), ) -> Result, GfxError> { - let mut ops = self.take_render_ops(); + let mut ops = vec![]; let mut renderer = self.renderer_base(&mut ops, scale, Transform::None); f(&mut renderer); - self.render(ops, clear) + self.render(&ops, clear) } pub fn create_render_pass( @@ -365,7 +362,7 @@ impl dyn GfxFramebuffer { transform: Transform, visualizer: Option<&DamageVisualizer>, ) -> GfxRenderPass { - let mut ops = self.take_render_ops(); + let mut ops = vec![]; let mut renderer = Renderer { base: self.renderer_base(&mut ops, scale, transform), state, @@ -430,8 +427,8 @@ impl dyn GfxFramebuffer { } } - pub fn perform_render_pass(&self, pass: GfxRenderPass) -> Result, GfxError> { - self.render(pass.ops, pass.clear.as_ref()) + pub fn perform_render_pass(&self, pass: &GfxRenderPass) -> Result, GfxError> { + self.render(&pass.ops, pass.clear.as_ref()) } pub fn render_output( @@ -480,7 +477,7 @@ impl dyn GfxFramebuffer { transform, None, ); - self.perform_render_pass(pass) + self.perform_render_pass(&pass) } pub fn render_hardware_cursor( @@ -490,7 +487,7 @@ impl dyn GfxFramebuffer { scale: Scale, transform: Transform, ) -> Result, GfxError> { - let mut ops = self.take_render_ops(); + let mut ops = vec![]; let mut renderer = Renderer { base: self.renderer_base(&mut ops, scale, transform), state, @@ -502,7 +499,7 @@ impl dyn GfxFramebuffer { }, }; cursor.render_hardware_cursor(&mut renderer); - self.render(ops, Some(&Color::TRANSPARENT)) + self.render(&ops, Some(&Color::TRANSPARENT)) } } diff --git a/src/gfx_apis/gl/renderer/context.rs b/src/gfx_apis/gl/renderer/context.rs index f242800c..e0ab21bb 100644 --- a/src/gfx_apis/gl/renderer/context.rs +++ b/src/gfx_apis/gl/renderer/context.rs @@ -4,8 +4,8 @@ use { cpu_worker::CpuWorker, format::{Format, XRGB8888}, gfx_api::{ - AsyncShmGfxTexture, BufferResvUser, GfxApiOpt, GfxContext, GfxError, GfxFormat, - GfxFramebuffer, GfxImage, ResetStatus, ShmGfxTexture, + AsyncShmGfxTexture, BufferResvUser, GfxContext, GfxError, GfxFormat, GfxFramebuffer, + GfxImage, ResetStatus, ShmGfxTexture, }, gfx_apis::gl::{ egl::{context::EglContext, display::EglDisplay, image::EglImage}, @@ -84,7 +84,6 @@ pub(in crate::gfx_apis::gl) struct GlRenderContext { pub(crate) fill_prog_pos: GLint, pub(crate) fill_prog_color: GLint, - pub(crate) gfx_ops: RefCell>, pub(in crate::gfx_apis::gl) gl_state: RefCell, pub(in crate::gfx_apis::gl) buffer_resv_user: BufferResvUser, @@ -169,7 +168,6 @@ impl GlRenderContext { fill_prog_color: fill_prog.get_uniform_location(c"color"), fill_prog, - gfx_ops: Default::default(), gl_state: Default::default(), buffer_resv_user: Default::default(), diff --git a/src/gfx_apis/gl/renderer/framebuffer.rs b/src/gfx_apis/gl/renderer/framebuffer.rs index 96d42e08..0f0c07cf 100644 --- a/src/gfx_apis/gl/renderer/framebuffer.rs +++ b/src/gfx_apis/gl/renderer/framebuffer.rs @@ -17,7 +17,6 @@ use { std::{ cell::Cell, fmt::{Debug, Formatter}, - mem, rc::Rc, }, }; @@ -70,11 +69,11 @@ impl Framebuffer { pub fn render( &self, - mut ops: Vec, + ops: &[GfxApiOpt], clear: Option<&Color>, ) -> Result, RenderError> { let gles = self.ctx.ctx.dpy.gles; - let res = self.ctx.ctx.with_current(|| { + self.ctx.ctx.with_current(|| { unsafe { (gles.glBindFramebuffer)(GL_FRAMEBUFFER, self.gl.fbo); (gles.glViewport)(0, 0, self.gl.width, self.gl.height); @@ -84,32 +83,25 @@ impl Framebuffer { } (gles.glBlendFunc)(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); } - let fd = run_ops(self, &ops); + let fd = run_ops(self, ops); if fd.is_none() { unsafe { (gles.glFlush)(); } } Ok(fd) - }); - ops.clear(); - *self.ctx.gfx_ops.borrow_mut() = ops; - res + }) } } impl GfxFramebuffer for Framebuffer { - fn take_render_ops(&self) -> Vec { - mem::take(&mut *self.ctx.gfx_ops.borrow_mut()) - } - fn physical_size(&self) -> (i32, i32) { (self.gl.width, self.gl.height) } fn render( &self, - ops: Vec, + ops: &[GfxApiOpt], clear: Option<&Color>, ) -> Result, GfxError> { self.render(ops, clear).map_err(|e| e.into()) diff --git a/src/gfx_apis/vulkan/image.rs b/src/gfx_apis/vulkan/image.rs index 53c26af4..a29a16f0 100644 --- a/src/gfx_apis/vulkan/image.rs +++ b/src/gfx_apis/vulkan/image.rs @@ -13,7 +13,7 @@ use { }, rect::Region, theme::Color, - utils::{clonecell::CloneCell, on_drop::OnDrop}, + utils::on_drop::OnDrop, video::dmabuf::{DmaBuf, PlaneVec}, }, ash::vk::{ @@ -59,7 +59,6 @@ pub struct VulkanImage { pub(super) is_undefined: Cell, pub(super) contents_are_undefined: Cell, pub(super) ty: VulkanImageMemory, - pub(super) render_ops: CloneCell>, pub(super) bridge: Option, } @@ -378,7 +377,6 @@ impl VulkanDmaBufImageTemplate { width: self.width, height: self.height, stride: 0, - render_ops: Default::default(), ty: VulkanImageMemory::DmaBuf(VulkanDmaBufImage { template: self.clone(), mems: device_memories, @@ -461,21 +459,17 @@ impl Debug for VulkanImage { } impl GfxFramebuffer for VulkanImage { - fn take_render_ops(&self) -> Vec { - self.render_ops.take() - } - fn physical_size(&self) -> (i32, i32) { (self.width as _, self.height as _) } fn render( &self, - ops: Vec, + ops: &[GfxApiOpt], clear: Option<&Color>, ) -> Result, GfxError> { self.renderer - .execute(self, &ops, clear) + .execute(self, ops, clear) .map_err(|e| e.into()) } diff --git a/src/gfx_apis/vulkan/shm_image.rs b/src/gfx_apis/vulkan/shm_image.rs index 24ef74e5..af050af3 100644 --- a/src/gfx_apis/vulkan/shm_image.rs +++ b/src/gfx_apis/vulkan/shm_image.rs @@ -730,7 +730,6 @@ impl VulkanRenderer { is_undefined: Cell::new(true), contents_are_undefined: Cell::new(true), ty: VulkanImageMemory::Internal(shm), - render_ops: Default::default(), bridge: None, }); let shm = match &img.ty { diff --git a/src/it/test_gfx_api.rs b/src/it/test_gfx_api.rs index 8ef52878..784b39c8 100644 --- a/src/it/test_gfx_api.rs +++ b/src/it/test_gfx_api.rs @@ -404,10 +404,6 @@ impl GfxImage for TestGfxImage { } impl GfxFramebuffer for TestGfxFb { - fn take_render_ops(&self) -> Vec { - vec![] - } - fn physical_size(&self) -> (i32, i32) { match &*self.img { TestGfxImage::Shm(v) => (v.width, v.height), @@ -417,7 +413,7 @@ impl GfxFramebuffer for TestGfxFb { fn render( &self, - ops: Vec, + ops: &[GfxApiOpt], clear: Option<&Color>, ) -> Result, GfxError> { let fb_points = |width: i32, height: i32, rect: &FramebufferRect| { diff --git a/src/state.rs b/src/state.rs index f3f2f5b0..d06889be 100644 --- a/src/state.rs +++ b/src/state.rs @@ -905,7 +905,7 @@ impl State { size: Option<(i32, i32)>, transform: Transform, ) -> Result, GfxError> { - let mut ops = target.take_render_ops(); + let mut ops = vec![]; let mut renderer = Renderer { base: target.renderer_base(&mut ops, Scale::from_int(1), Transform::None), state: self, @@ -943,7 +943,7 @@ impl State { } } } - target.render(ops, Some(&Color::SOLID_BLACK)) + target.render(&ops, Some(&Color::SOLID_BLACK)) } fn have_hardware_cursor(&self) -> bool { From c22af6efb772786f342f3f3000993d5484fd7d49 Mon Sep 17 00:00:00 2001 From: Julian Orth Date: Tue, 10 Sep 2024 11:24:41 +0200 Subject: [PATCH 02/10] commit_timeline: simplify some code --- src/ifs/wl_surface/commit_timeline.rs | 36 ++++++++++++--------------- 1 file changed, 16 insertions(+), 20 deletions(-) diff --git a/src/ifs/wl_surface/commit_timeline.rs b/src/ifs/wl_surface/commit_timeline.rs index 8ec7a1b0..b71a59cf 100644 --- a/src/ifs/wl_surface/commit_timeline.rs +++ b/src/ifs/wl_surface/commit_timeline.rs @@ -152,10 +152,10 @@ impl CommitTimeline { pending: &mut Box, ) -> Result<(), CommitTimelineError> { let mut points = SmallVec::new(); - consume_acquire_points(pending, &mut points); let mut pending_uploads = 0; - count_shm_uploads(pending, &mut pending_uploads); - if points.is_empty() && pending_uploads == 0 && self.own_timeline.entries.is_empty() { + collect_commit_data(pending, &mut points, &mut pending_uploads); + let has_dependencies = points.is_not_empty() || pending_uploads > 0; + if !has_dependencies && self.own_timeline.entries.is_empty() { return surface .apply_state(pending) .map_err(CommitTimelineError::ImmediateCommit); @@ -177,7 +177,7 @@ impl CommitTimeline { }), ); let mut needs_flush = false; - if points.is_not_empty() || pending_uploads > 0 { + if has_dependencies { let noderef = Rc::new(noderef.clone()); let EntryKind::Commit(commit) = &noderef.kind else { unreachable!(); @@ -413,13 +413,22 @@ fn schedule_async_upload( type Point = (Rc, SyncObjPoint); -fn consume_acquire_points(pending: &mut PendingState, points: &mut SmallVec<[Point; 1]>) { +fn collect_commit_data( + pending: &mut PendingState, + acquire_points: &mut SmallVec<[Point; 1]>, + shm_uploads: &mut usize, +) { + if let Some(Some(buffer)) = &pending.buffer { + if buffer.is_shm() { + *shm_uploads += 1; + } + } if let Some(point) = pending.acquire_point.take() { - points.push(point); + acquire_points.push(point); } for ss in pending.subsurfaces.values_mut() { if let Some(state) = &mut ss.pending.state { - consume_acquire_points(state, points); + collect_commit_data(state, acquire_points, shm_uploads); } } } @@ -446,16 +455,3 @@ fn set_effective_timeline( } } } - -fn count_shm_uploads(pending: &PendingState, count: &mut usize) { - if let Some(Some(buffer)) = &pending.buffer { - if buffer.is_shm() { - *count += 1; - } - } - for ss in pending.subsurfaces.values() { - if let Some(state) = &ss.pending.state { - count_shm_uploads(state, count); - } - } -} From 04343c96d6c45fa40f215f2c4e52cd5db98bdb95 Mon Sep 17 00:00:00 2001 From: Julian Orth Date: Tue, 10 Sep 2024 11:52:10 +0200 Subject: [PATCH 03/10] commit_timeline: wait for implicit sync dmabufs --- src/client.rs | 5 +- src/ifs/wl_surface.rs | 12 ++- src/ifs/wl_surface/commit_timeline.rs | 69 ++++++++++++++- src/io_uring.rs | 9 +- src/io_uring/ops.rs | 1 + src/io_uring/ops/poll_external.rs | 116 ++++++++++++++++++++++++++ src/renderer.rs | 2 +- 7 files changed, 199 insertions(+), 15 deletions(-) create mode 100644 src/io_uring/ops/poll_external.rs diff --git a/src/client.rs b/src/client.rs index f13fff45..1e798af0 100644 --- a/src/client.rs +++ b/src/client.rs @@ -168,7 +168,10 @@ impl Clients { last_xwayland_serial: Cell::new(0), surfaces_by_xwayland_serial: Default::default(), activation_tokens: Default::default(), - commit_timelines: Rc::new(CommitTimelines::new(&global.wait_for_sync_obj)), + commit_timelines: Rc::new(CommitTimelines::new( + &global.wait_for_sync_obj, + &global.ring, + )), }); track!(data, data); let display = Rc::new(WlDisplay::new(&data)); diff --git a/src/ifs/wl_surface.rs b/src/ifs/wl_surface.rs index 63b9101b..80c396d4 100644 --- a/src/ifs/wl_surface.rs +++ b/src/ifs/wl_surface.rs @@ -23,8 +23,8 @@ use { drm_feedback::DrmFeedback, fixed::Fixed, gfx_api::{ - AcquireSync, AsyncShmGfxTexture, BufferResv, BufferResvUser, GfxError, ReleaseSync, - SampleRect, SyncFile, + AsyncShmGfxTexture, BufferResv, BufferResvUser, GfxError, ReleaseSync, SampleRect, + SyncFile, }, ifs::{ wl_buffer::WlBuffer, @@ -190,7 +190,6 @@ struct SurfaceBufferExplicitRelease { pub struct SurfaceBuffer { pub buffer: Rc, sync_files: SmallMap, - pub sync: AcquireSync, pub release_sync: ReleaseSync, release: Option, } @@ -1093,9 +1092,9 @@ impl WlSurface { self.reset_shm_textures(); } buffer.update_texture_or_log(self, false); - let (sync, release_sync) = match pending.explicit_sync { - false => (AcquireSync::Implicit, ReleaseSync::Implicit), - true => (AcquireSync::Unnecessary, ReleaseSync::Explicit), + let release_sync = match pending.explicit_sync { + false => ReleaseSync::Implicit, + true => ReleaseSync::Explicit, }; let release = pending .release_point @@ -1104,7 +1103,6 @@ impl WlSurface { let surface_buffer = SurfaceBuffer { buffer, sync_files: Default::default(), - sync, release_sync, release, }; diff --git a/src/ifs/wl_surface/commit_timeline.rs b/src/ifs/wl_surface/commit_timeline.rs index b71a59cf..da4a2c46 100644 --- a/src/ifs/wl_surface/commit_timeline.rs +++ b/src/ifs/wl_surface/commit_timeline.rs @@ -5,12 +5,14 @@ use { wl_buffer::WlBufferStorage, wl_surface::{PendingState, WlSurface, WlSurfaceError}, }, + io_uring::{IoUring, IoUringError, PendingPoll, PollCallback}, utils::{ clonecell::CloneCell, copyhashmap::CopyHashMap, hash_map_ext::HashMapExt, linkedlist::{LinkedList, LinkedNode, NodeRef}, numcell::NumCell, + oserror::OsError, }, video::drm::{ sync_obj::{SyncObj, SyncObjPoint}, @@ -28,6 +30,7 @@ use { slice, }, thiserror::Error, + uapi::{c::c_short, OwnedFd}, }; const MAX_TIMELINE_DEPTH: usize = 256; @@ -37,6 +40,7 @@ linear_ids!(CommitTimelineIds, CommitTimelineId, u64); pub struct CommitTimelines { next_id: CommitTimelineIds, wfs: Rc, + ring: Rc, depth: NumCell, gc: CopyHashMap>, } @@ -83,15 +87,20 @@ pub enum CommitTimelineError { Depth, #[error("Could not upload a shm texture")] ShmUpload(#[source] GfxError), + #[error("Could not register an implicit-sync wait")] + RegisterImplicitPoll(#[source] IoUringError), + #[error("Could not wait for a dmabuf to become idle")] + PollDmabuf(#[source] OsError), } impl CommitTimelines { - pub fn new(wfs: &Rc) -> Self { + pub fn new(wfs: &Rc, ring: &Rc) -> Self { Self { next_id: Default::default(), depth: NumCell::new(0), wfs: wfs.clone(), gc: Default::default(), + ring: ring.clone(), } } @@ -127,6 +136,7 @@ fn break_loops(list: &LinkedList) { if let EntryKind::Commit(c) = &entry.kind { c.wait_handles.take(); *c.shm_upload.borrow_mut() = ShmUploadState::None; + c.pending_polls.take(); } } } @@ -153,8 +163,15 @@ impl CommitTimeline { ) -> Result<(), CommitTimelineError> { let mut points = SmallVec::new(); let mut pending_uploads = 0; - collect_commit_data(pending, &mut points, &mut pending_uploads); - let has_dependencies = points.is_not_empty() || pending_uploads > 0; + let mut implicit_dmabufs = SmallVec::new(); + collect_commit_data( + pending, + &mut points, + &mut pending_uploads, + &mut implicit_dmabufs, + ); + let has_dependencies = + points.is_not_empty() || pending_uploads > 0 || implicit_dmabufs.is_not_empty(); if !has_dependencies && self.own_timeline.entries.is_empty() { return surface .apply_state(pending) @@ -174,6 +191,8 @@ impl CommitTimeline { wait_handles: Cell::new(Default::default()), pending_uploads: NumCell::new(pending_uploads), shm_upload: RefCell::new(ShmUploadState::None), + num_pending_polls: NumCell::new(implicit_dmabufs.len()), + pending_polls: Cell::new(Default::default()), }), ); let mut needs_flush = false; @@ -198,6 +217,18 @@ impl CommitTimeline { *commit.shm_upload.borrow_mut() = ShmUploadState::Todo(noderef.clone()); needs_flush = true; } + if implicit_dmabufs.is_not_empty() { + let mut pending_polls = SmallVec::new(); + for fd in implicit_dmabufs { + let handle = self + .shared + .ring + .readable_external(&fd, noderef.clone()) + .map_err(CommitTimelineError::RegisterImplicitPoll)?; + pending_polls.push(handle); + } + commit.pending_polls.set(pending_polls); + } } if needs_flush && noderef.prev().is_none() { flush_from(noderef.clone()).map_err(CommitTimelineError::DelayedCommit)?; @@ -246,6 +277,23 @@ impl AsyncShmGfxTextureCallback for NodeRef { } } +impl PollCallback for NodeRef { + fn completed(self: Rc, res: Result) { + let EntryKind::Commit(commit) = &self.kind else { + unreachable!(); + }; + if let Err(e) = res { + commit + .surface + .client + .error(CommitTimelineError::PollDmabuf(e)); + return; + } + commit.num_pending_polls.fetch_sub(1); + flush_commit(&self, commit); + } +} + struct Entry { link: Cell>>, shared: Rc, @@ -272,6 +320,8 @@ struct Commit { wait_handles: Cell>, pending_uploads: NumCell, shm_upload: RefCell, + num_pending_polls: NumCell, + pending_polls: Cell>, } fn flush_from(mut point: NodeRef) -> Result<(), WlSurfaceError> { @@ -304,6 +354,9 @@ impl NodeRef { has_unmet_dependencies = true; } } + if c.num_pending_polls.get() > 0 { + has_unmet_dependencies = true; + } if has_unmet_dependencies { return Ok(false); } @@ -417,18 +470,26 @@ fn collect_commit_data( pending: &mut PendingState, acquire_points: &mut SmallVec<[Point; 1]>, shm_uploads: &mut usize, + implicit_dmabufs: &mut SmallVec<[Rc; 1]>, ) { if let Some(Some(buffer)) = &pending.buffer { if buffer.is_shm() { *shm_uploads += 1; } + if !pending.explicit_sync { + if let Some(dmabuf) = &buffer.dmabuf { + for plane in &dmabuf.planes { + implicit_dmabufs.push(plane.fd.clone()); + } + } + } } if let Some(point) = pending.acquire_point.take() { acquire_points.push(point); } for ss in pending.subsurfaces.values_mut() { if let Some(state) = &mut ss.pending.state { - collect_commit_data(state, acquire_points, shm_uploads); + collect_commit_data(state, acquire_points, shm_uploads, implicit_dmabufs); } } } diff --git a/src/io_uring.rs b/src/io_uring.rs index 60c5291a..7a912ba1 100644 --- a/src/io_uring.rs +++ b/src/io_uring.rs @@ -1,11 +1,14 @@ -pub use ops::TaskResultExt; +pub use ops::{ + poll_external::{PendingPoll, PollCallback}, + TaskResultExt, +}; use { crate::{ async_engine::AsyncEngine, io_uring::{ ops::{ accept::AcceptTask, async_cancel::AsyncCancelTask, connect::ConnectTask, - poll::PollTask, read_write::ReadWriteTask, + poll::PollTask, poll_external::PollExternalTask, read_write::ReadWriteTask, read_write_no_cancel::ReadWriteNoCancelTask, recvmsg::RecvmsgTask, sendmsg::SendmsgTask, timeout::TimeoutTask, timeout_link::TimeoutLinkTask, }, @@ -209,6 +212,7 @@ impl IoUring { cached_read_writes_no_cancel: Default::default(), cached_cancels: Default::default(), cached_polls: Default::default(), + cached_polls_external: Default::default(), cached_sendmsg: Default::default(), cached_recvmsg: Default::default(), cached_timeouts: Default::default(), @@ -270,6 +274,7 @@ struct IoUringData { cached_read_writes_no_cancel: Stack>, cached_cancels: Stack>, cached_polls: Stack>, + cached_polls_external: Stack>, cached_sendmsg: Stack>, cached_recvmsg: Stack>, cached_timeouts: Stack>, diff --git a/src/io_uring/ops.rs b/src/io_uring/ops.rs index 15fc1235..e5f4bf0a 100644 --- a/src/io_uring/ops.rs +++ b/src/io_uring/ops.rs @@ -4,6 +4,7 @@ pub mod accept; pub mod async_cancel; pub mod connect; pub mod poll; +pub mod poll_external; pub mod read_write; pub mod read_write_no_cancel; pub mod recvmsg; diff --git a/src/io_uring/ops/poll_external.rs b/src/io_uring/ops/poll_external.rs new file mode 100644 index 00000000..d17440a0 --- /dev/null +++ b/src/io_uring/ops/poll_external.rs @@ -0,0 +1,116 @@ +use { + crate::{ + io_uring::{ + sys::{io_uring_sqe, IORING_OP_POLL_ADD}, + IoUring, IoUringData, IoUringError, IoUringTaskId, Task, + }, + utils::oserror::OsError, + }, + std::{cell::Cell, rc::Rc}, + uapi::{c, OwnedFd}, +}; + +pub trait PollCallback { + fn completed(self: Rc, res: Result); +} + +pub struct PendingPoll { + data: Rc, + shared: Rc, + id: IoUringTaskId, +} + +impl Drop for PendingPoll { + fn drop(&mut self) { + if self.shared.id.get() != self.id { + return; + } + self.shared.callback.take(); + self.data.cancel_task(self.id); + } +} + +impl IoUring { + pub fn poll_external( + &self, + fd: &Rc, + events: c::c_short, + callback: Rc, + ) -> Result { + self.ring.check_destroyed()?; + let mut pw = self.ring.cached_polls_external.pop().unwrap_or_default(); + pw.shared.id.set(self.ring.id_raw()); + pw.shared.callback.set(Some(callback)); + pw.fd = fd.raw() as _; + pw.events = events as _; + pw.data = Some(Data { _fd: fd.clone() }); + let pending = PendingPoll { + data: self.ring.clone(), + shared: pw.shared.clone(), + id: pw.shared.id.get(), + }; + self.ring.schedule(pw); + Ok(pending) + } + + pub fn readable_external( + &self, + fd: &Rc, + callback: Rc, + ) -> Result { + self.poll_external(fd, c::POLLIN, callback) + } + + #[expect(dead_code)] + pub fn writable_external( + &self, + fd: &Rc, + callback: Rc, + ) -> Result { + self.poll_external(fd, c::POLLOUT, callback) + } +} + +struct Data { + _fd: Rc, +} + +#[derive(Default)] +struct PollExternalTaskShared { + id: Cell, + callback: Cell>>, +} + +#[derive(Default)] +pub struct PollExternalTask { + shared: Rc, + events: u16, + fd: i32, + data: Option, +} + +unsafe impl Task for PollExternalTask { + fn id(&self) -> IoUringTaskId { + self.shared.id.get() + } + + fn complete(mut self: Box, ring: &IoUringData, res: i32) { + self.data.take(); + self.shared.id.set(Default::default()); + if let Some(cb) = self.shared.callback.take() { + let res = if res < 0 { + Err(OsError::from(-res as c::c_int)) + } else { + Ok(res as _) + }; + cb.completed(res) + } + ring.cached_polls_external.push(self); + } + + fn encode(&self, sqe: &mut io_uring_sqe) { + sqe.opcode = IORING_OP_POLL_ADD; + sqe.fd = self.fd; + sqe.u3.poll_events = self.events; + } +} diff --git a/src/renderer.rs b/src/renderer.rs index e1445d03..03c2b87b 100644 --- a/src/renderer.rs +++ b/src/renderer.rs @@ -472,7 +472,7 @@ impl Renderer<'_> { self.base.scale, bounds, Some(buffer.clone()), - buffer.sync.clone(), + AcquireSync::Unnecessary, buffer.release_sync, ); } else if let Some(color) = &buffer.buffer.color { From 80c7a1f47c070ce974751a5445f3033eee40da93 Mon Sep 17 00:00:00 2001 From: Julian Orth Date: Mon, 9 Sep 2024 21:19:30 +0200 Subject: [PATCH 04/10] util: add GeometricDecay util --- src/utils.rs | 1 + src/utils/geometric_decay.rs | 41 ++++++++++++++++++++++++++++++++++++ 2 files changed, 42 insertions(+) create mode 100644 src/utils/geometric_decay.rs diff --git a/src/utils.rs b/src/utils.rs index a5c94ee7..fa1fd936 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -17,6 +17,7 @@ pub mod double_click_state; pub mod errorfmt; pub mod event_listener; pub mod fdcloser; +pub mod geometric_decay; pub mod gfx_api_ext; pub mod hash_map_ext; pub mod hex; diff --git a/src/utils/geometric_decay.rs b/src/utils/geometric_decay.rs new file mode 100644 index 00000000..83466724 --- /dev/null +++ b/src/utils/geometric_decay.rs @@ -0,0 +1,41 @@ +use std::cell::Cell; + +pub struct GeometricDecay { + p1: f64, + p2: f64, + v: Cell, +} + +impl GeometricDecay { + #[expect(dead_code)] + pub fn new(mut p1: f64, reset: u64) -> Self { + if p1.is_nan() || p1 < 0.01 { + p1 = 0.01; + } + if p1 > 0.99 { + p1 = 0.99; + } + let p2 = 1.0 - p1; + Self { + p1, + p2, + v: Cell::new(reset as f64 / p1), + } + } + + #[expect(dead_code)] + pub fn reset(&self, v: u64) { + self.v.set(v as f64 / self.p1); + } + + #[expect(dead_code)] + pub fn get(&self) -> u64 { + (self.p1 * self.v.get()) as u64 + } + + #[expect(dead_code)] + pub fn add(&self, n: u64) { + let v = n as f64 + self.p2 * self.v.get(); + self.v.set(v); + } +} From 12c9b36ded7d233057268533f11c281c7bc0ec43 Mon Sep 17 00:00:00 2001 From: Julian Orth Date: Tue, 10 Sep 2024 14:27:45 +0200 Subject: [PATCH 05/10] gfx: wait for idle if rendering doesn't support explicit sync --- src/gfx_apis/gl/egl/display.rs | 4 ++++ src/gfx_apis/gl/gl/sys.rs | 2 +- src/gfx_apis/gl/renderer/framebuffer.rs | 2 +- src/gfx_apis/vulkan/renderer.rs | 1 + 4 files changed, 7 insertions(+), 2 deletions(-) diff --git a/src/gfx_apis/gl/egl/display.rs b/src/gfx_apis/gl/egl/display.rs index 7d139c41..fa7d5d52 100644 --- a/src/gfx_apis/gl/egl/display.rs +++ b/src/gfx_apis/gl/egl/display.rs @@ -129,6 +129,10 @@ impl EglDisplay { .exts .contains(KHR_FENCE_SYNC | KHR_WAIT_SYNC | ANDROID_NATIVE_FENCE_SYNC); + if !dpy.explicit_sync { + log::error!("Driver does not support explicit sync. Rendering will block.") + } + Ok(Rc::new(dpy)) } } diff --git a/src/gfx_apis/gl/gl/sys.rs b/src/gfx_apis/gl/gl/sys.rs index f36650e4..84b81b86 100644 --- a/src/gfx_apis/gl/gl/sys.rs +++ b/src/gfx_apis/gl/gl/sys.rs @@ -66,7 +66,7 @@ dynload! { glClear: unsafe fn(mask: GLbitfield), glBlendFunc: unsafe fn(sfactor: GLenum, dfactor: GLenum), glClearColor: unsafe fn(red: GLfloat, green: GLfloat, blue: GLfloat, alpha: GLfloat), - glFlush: unsafe fn(), + glFinish: unsafe fn(), glReadnPixels: unsafe fn( x: GLint, diff --git a/src/gfx_apis/gl/renderer/framebuffer.rs b/src/gfx_apis/gl/renderer/framebuffer.rs index 0f0c07cf..67fe9b63 100644 --- a/src/gfx_apis/gl/renderer/framebuffer.rs +++ b/src/gfx_apis/gl/renderer/framebuffer.rs @@ -86,7 +86,7 @@ impl Framebuffer { let fd = run_ops(self, ops); if fd.is_none() { unsafe { - (gles.glFlush)(); + (gles.glFinish)(); } } Ok(fd) diff --git a/src/gfx_apis/vulkan/renderer.rs b/src/gfx_apis/vulkan/renderer.rs index 0018700b..7021744c 100644 --- a/src/gfx_apis/vulkan/renderer.rs +++ b/src/gfx_apis/vulkan/renderer.rs @@ -747,6 +747,7 @@ impl VulkanRenderer { Ok(s) => Some(s), Err(e) => { log::error!("Could not export sync file from fence: {}", ErrorFmt(e)); + self.block(); None } }; From 93bfb9c0b4e06badf3a7a70de68fd682d1224fd1 Mon Sep 17 00:00:00 2001 From: Julian Orth Date: Mon, 9 Sep 2024 17:02:09 +0200 Subject: [PATCH 06/10] metal: latch hardware cursors in the backend --- src/backend.rs | 16 ++-- src/backends/metal/video.rs | 181 +++++++++++++++++------------------ src/cursor.rs | 1 + src/cursor_user.rs | 156 ++++++++++++++++-------------- src/output_schedule.rs | 7 +- src/state.rs | 26 +++-- src/tasks/hardware_cursor.rs | 3 +- 7 files changed, 204 insertions(+), 186 deletions(-) diff --git a/src/backend.rs b/src/backend.rs index 81c69213..772db6ff 100644 --- a/src/backend.rs +++ b/src/backend.rs @@ -135,17 +135,19 @@ pub enum ConnectorEvent { FormatsChanged(Rc>, &'static Format), } -pub trait HardwareCursor: Debug { - fn set_enabled(&self, enabled: bool); +pub trait HardwareCursorUpdate { + fn set_enabled(&mut self, enabled: bool); fn get_buffer(&self) -> Rc; - fn set_position(&self, x: i32, y: i32); - fn swap_buffer(&self); - fn set_sync_file(&self, sync_file: Option); - fn commit(&self, schedule_present: bool); - fn schedule_present(&self) -> bool; + fn set_position(&mut self, x: i32, y: i32); + fn swap_buffer(&mut self); + fn set_sync_file(&mut self, sync_file: Option); fn size(&self) -> (i32, i32); } +pub trait HardwareCursor: Debug { + fn damage(&self); +} + pub type TransformMatrix = [[f64; 2]; 2]; linear_ids!(InputDeviceGroupIds, InputDeviceGroupId, usize); diff --git a/src/backends/metal/video.rs b/src/backends/metal/video.rs index 3f4a55c7..992c8f3b 100644 --- a/src/backends/metal/video.rs +++ b/src/backends/metal/video.rs @@ -4,8 +4,8 @@ use { async_engine::{Phase, SpawnedFuture}, backend::{ BackendDrmDevice, BackendDrmLease, BackendDrmLessee, BackendEvent, Connector, - ConnectorEvent, ConnectorId, ConnectorKernelId, DrmDeviceId, HardwareCursor, Mode, - MonitorInfo, + ConnectorEvent, ConnectorId, ConnectorKernelId, DrmDeviceId, HardwareCursor, + HardwareCursorUpdate, Mode, MonitorInfo, }, backends::metal::{MetalBackend, MetalError}, drm_feedback::DrmFeedback, @@ -428,7 +428,7 @@ pub struct MetalConnector { pub can_present: Cell, pub has_damage: Cell, pub cursor_changed: Cell, - pub cursor_scheduled: Cell, + pub cursor_damage: Cell, pub next_flip_nsec: Cell, pub display: RefCell, @@ -446,11 +446,10 @@ pub struct MetalConnector { pub render_result: RefCell, - pub cursor_generation: NumCell, pub cursor_x: Cell, pub cursor_y: Cell, pub cursor_enabled: Cell, - pub cursor_buffers: CloneCell>>, + pub cursor_buffers: CloneCell>>, pub cursor_front_buffer: NumCell, pub cursor_swap_buffer: Cell, pub cursor_sync_file: CloneCell>, @@ -472,15 +471,17 @@ impl Debug for MetalConnector { } pub struct MetalHardwareCursor { - pub generation: u64, pub connector: Rc, - pub cursor_swap_buffer: Cell, - pub cursor_enabled_pending: Cell, - pub cursor_x_pending: Cell, - pub cursor_y_pending: Cell, - pub cursor_buffers: Rc<[RenderBuffer; 3]>, - pub sync_file: CloneCell>, - pub have_changes: Cell, +} + +pub struct MetalHardwareCursorChange<'a> { + pub cursor_swap_buffer: bool, + pub cursor_enabled: bool, + pub cursor_x: i32, + pub cursor_y: i32, + pub cursor_buffer: &'a RenderBuffer, + pub sync_file: Option, + pub cursor_size: (i32, i32), } impl Debug for MetalHardwareCursor { @@ -491,72 +492,38 @@ impl Debug for MetalHardwareCursor { } impl HardwareCursor for MetalHardwareCursor { - fn set_enabled(&self, enabled: bool) { - if self.cursor_enabled_pending.replace(enabled) != enabled { - self.have_changes.set(true); + fn damage(&self) { + self.connector.cursor_damage.set(true); + if self.connector.can_present.get() { + self.connector.schedule_present(); } } +} + +impl HardwareCursorUpdate for MetalHardwareCursorChange<'_> { + fn set_enabled(&mut self, enabled: bool) { + self.cursor_enabled = enabled; + } fn get_buffer(&self) -> Rc { - let buffer = (self.connector.cursor_front_buffer.get() + 1) % self.cursor_buffers.len(); - self.cursor_buffers[buffer].render_fb() + self.cursor_buffer.render_fb() } - fn set_position(&self, x: i32, y: i32) { - self.cursor_x_pending.set(x); - self.cursor_y_pending.set(y); - self.have_changes.set(true); + fn set_position(&mut self, x: i32, y: i32) { + self.cursor_x = x; + self.cursor_y = y; } - fn swap_buffer(&self) { - self.cursor_swap_buffer.set(true); - self.have_changes.set(true); + fn swap_buffer(&mut self) { + self.cursor_swap_buffer = true; } - fn set_sync_file(&self, sync_file: Option) { - self.sync_file.set(sync_file); - self.have_changes.set(true); - } - - fn commit(&self, schedule_present: bool) { - if self.generation != self.connector.cursor_generation.get() { - return; - } - if !self.have_changes.take() { - return; - } - self.connector - .cursor_enabled - .set(self.cursor_enabled_pending.get()); - self.connector.cursor_x.set(self.cursor_x_pending.get()); - self.connector.cursor_y.set(self.cursor_y_pending.get()); - if self.cursor_swap_buffer.take() { - self.connector.cursor_swap_buffer.set(true); - } - self.connector.cursor_sync_file.set(self.sync_file.take()); - self.connector.cursor_changed.set(true); - if schedule_present { - self.schedule_present(); - } - } - - fn schedule_present(&self) -> bool { - if self.connector.cursor_changed.get() { - self.connector.cursor_scheduled.set(true); - if self.connector.can_present.get() { - self.connector.schedule_present(); - } - true - } else { - false - } + fn set_sync_file(&mut self, sync_file: Option) { + self.sync_file = sync_file; } fn size(&self) -> (i32, i32) { - ( - self.connector.dev.cursor_width as _, - self.connector.dev.cursor_height as _, - ) + self.cursor_size } } @@ -622,7 +589,12 @@ impl MetalConnector { self.state.ring.timeout(next_present).await.unwrap(); } } - match self.present(true) { + let Some(node) = self.state.root.outputs.get(&self.connector_id) else { + return; + }; + self.latch_cursor(&node); + node.schedule.latched(); + match self.present(&node, true) { Ok(_) => self.state.set_backend_idle(false), Err(e) => { log::error!("Could not present: {}", ErrorFmt(e)); @@ -671,21 +643,11 @@ impl MetalConnector { | FrontState::Connected { non_desktop: true } => return, FrontState::Connected { non_desktop: false } => {} } - let generation = self.cursor_generation.fetch_add(1) + 1; - let hc = match self.cursor_buffers.get() { - Some(cp) => Some(Rc::new(MetalHardwareCursor { - generation, + let hc = self.cursor_buffers.is_some().then(|| { + Rc::new(MetalHardwareCursor { connector: self.clone(), - cursor_swap_buffer: Cell::new(false), - cursor_enabled_pending: Cell::new(self.cursor_enabled.get()), - cursor_x_pending: Cell::new(self.cursor_x.get()), - cursor_y_pending: Cell::new(self.cursor_y.get()), - cursor_buffers: cp.clone(), - sync_file: Default::default(), - have_changes: Cell::new(false), - }) as _), - _ => None, - }; + }) as _ + }); self.on_change .send_event(ConnectorEvent::HardwareCursor(hc)); } @@ -948,12 +910,48 @@ impl MetalConnector { }) } - pub fn present(&self, try_direct_scanout: bool) -> Result<(), MetalError> { + fn latch_cursor(&self, node: &Rc) { + if !self.cursor_damage.take() { + return; + } + if self.cursor_plane.is_none() { + return; + } + let buffers = self.cursor_buffers.get().unwrap(); + let mut c = MetalHardwareCursorChange { + cursor_enabled: self.cursor_enabled.get(), + cursor_swap_buffer: false, + cursor_x: self.cursor_x.get(), + cursor_y: self.cursor_y.get(), + cursor_buffer: &buffers[(self.cursor_front_buffer.get() + 1) % buffers.len()], + sync_file: None, + cursor_size: (self.dev.cursor_width as _, self.dev.cursor_height as _), + }; + self.state.present_hardware_cursor(node, &mut c); + self.cursor_swap_buffer.set(c.cursor_swap_buffer); + if c.sync_file.is_some() { + self.cursor_sync_file.set(c.sync_file); + } + let mut cursor_changed = false; + cursor_changed |= self.cursor_enabled.replace(c.cursor_enabled) != c.cursor_enabled; + cursor_changed |= c.cursor_swap_buffer; + cursor_changed |= self.cursor_x.replace(c.cursor_x) != c.cursor_x; + cursor_changed |= self.cursor_y.replace(c.cursor_y) != c.cursor_y; + if cursor_changed { + self.cursor_changed.set(true); + } + } + + pub fn present( + &self, + node: &Rc, + try_direct_scanout: bool, + ) -> Result<(), MetalError> { let crtc = match self.crtc.get() { Some(crtc) => crtc, _ => return Ok(()), }; - if (!self.has_damage.get() && !self.cursor_scheduled.get()) || !self.can_present.get() { + if (!self.has_damage.get() && !self.cursor_changed.get()) || !self.can_present.get() { return Ok(()); } if !crtc.active.value.get() { @@ -967,9 +965,6 @@ impl MetalConnector { Some(b) => b, _ => return Ok(()), }; - let Some(node) = self.state.root.outputs.get(&self.connector_id) else { - return Ok(()); - }; let cursor = self.cursor_plane.get(); let mut new_fb = None; let mut changes = self.master.change(); @@ -1097,7 +1092,7 @@ impl MetalConnector { } if let Some(fb) = &new_fb { if let Some(dsd) = &fb.direct_scanout_data { - if self.present(false).is_ok() { + if self.present(node, false).is_ok() { let mut cache = self.scanout_buffers.borrow_mut(); if let Some(buffer) = cache.remove(&dsd.dma_buf_id) { cache.insert( @@ -1130,7 +1125,6 @@ impl MetalConnector { apply_change!(plane.crtc_y); apply_change!(plane.crtc_w); apply_change!(plane.crtc_h); - node.schedule.presented(); self.perform_screencopies(&new_fb, &node); if let Some(fb) = new_fb { if fb.direct_scanout_data.is_none() { @@ -1146,7 +1140,6 @@ impl MetalConnector { self.can_present.set(false); self.has_damage.set(false); self.cursor_changed.set(false); - self.cursor_scheduled.set(false); Ok(()) } } @@ -1609,7 +1602,6 @@ fn create_connector( on_change: Default::default(), present_trigger: Default::default(), render_result: RefCell::new(Default::default()), - cursor_generation: Default::default(), cursor_x: Cell::new(0), cursor_y: Cell::new(0), cursor_enabled: Cell::new(false), @@ -1617,7 +1609,7 @@ fn create_connector( display: RefCell::new(display), frontend_state: Cell::new(FrontState::Disconnected), cursor_changed: Cell::new(false), - cursor_scheduled: Cell::new(false), + cursor_damage: Cell::new(false), cursor_front_buffer: Default::default(), cursor_swap_buffer: Cell::new(false), cursor_sync_file: Default::default(), @@ -2378,7 +2370,6 @@ impl MetalBackend { connector.can_present.set(true); connector.has_damage.set(true); connector.cursor_changed.set(true); - connector.cursor_scheduled.set(true); } if dev.unprocessed_change.get() { return self.handle_drm_change_(dev, false); @@ -2439,7 +2430,10 @@ impl MetalBackend { if let Some(fb) = connector.next_framebuffer.take() { *connector.active_framebuffer.borrow_mut() = Some(fb); } - if connector.has_damage.get() || connector.cursor_scheduled.get() { + if connector.has_damage.get() + || connector.cursor_damage.get() + || connector.cursor_changed.get() + { connector.schedule_present(); } let dd = connector.display.borrow_mut(); @@ -3185,7 +3179,6 @@ impl MetalBackend { } connector.has_damage.set(true); connector.cursor_changed.set(true); - connector.cursor_scheduled.set(true); connector.schedule_present(); } } diff --git a/src/cursor.rs b/src/cursor.rs index 8f8d1775..63d14ed5 100644 --- a/src/cursor.rs +++ b/src/cursor.rs @@ -166,6 +166,7 @@ impl ServerCursors { let load = |names: &[&str]| ServerCursorTemplate::load(names, theme, &scales, &sizes, &paths, ctx); Ok(Some(Self { + // default: load(&["wait", "watch"])?, default: load(&["default", "left_ptr"])?, context_menu: load(&["context-menu"])?, help: load(&["help"])?, diff --git a/src/cursor_user.rs b/src/cursor_user.rs index 46e407b8..44313422 100644 --- a/src/cursor_user.rs +++ b/src/cursor_user.rs @@ -1,5 +1,6 @@ use { crate::{ + backend::HardwareCursorUpdate, cursor::{Cursor, KnownCursor, DEFAULT_CURSOR_SIZE}, fixed::Fixed, rect::Rect, @@ -103,7 +104,7 @@ impl CursorUserGroup { fn remove_hardware_cursor(&self) { self.state.hardware_tick_cursor.push(None); - self.state.disable_hardware_cursors(); + self.state.damage_hardware_cursors(false); self.state.cursor_user_group_hardware_cursor.take(); } @@ -234,6 +235,18 @@ impl CursorUserGroup { } } } + + pub fn present_hardware_cursor( + &self, + output: &Rc, + hc: &mut dyn HardwareCursorUpdate, + ) { + let Some(active) = self.active.get() else { + hc.set_enabled(false); + return; + }; + active.present_hardware_cursor(output, hc); + } } impl CursorUser { @@ -427,86 +440,81 @@ impl CursorUser { return; } let cursor = self.cursor.get(); - self.group.state.hardware_tick_cursor.push(cursor.clone()); - let cursor = match cursor { - Some(c) => c, - _ => { - self.group.state.disable_hardware_cursors(); - return; + self.group.state.hardware_tick_cursor.push(cursor); + for output in self.group.state.root.outputs.lock().values() { + if let Some(hc) = output.hardware_cursor.get() { + if render { + output.hardware_cursor_needs_render.set(true); + } + let defer = output.schedule.defer_cursor_updates(); + if defer { + output.schedule.hardware_cursor_changed(); + } else { + hc.damage(); + } } + } + } + + fn present_hardware_cursor(&self, output: &Rc, hc: &mut dyn HardwareCursorUpdate) { + let Some(cursor) = self.cursor.get() else { + hc.set_enabled(false); + return; }; + let (x, y) = self.pos.get(); + let transform = output.global.persistent.transform.get(); + let render = output.hardware_cursor_needs_render.take(); + let scale = output.global.persistent.scale.get(); if render { cursor.tick(); } - let (x, y) = self.pos.get(); - for output in self.group.state.root.outputs.lock().values() { - if let Some(hc) = output.hardware_cursor.get() { - let commit = || { - let defer = output.schedule.defer_cursor_updates(); - hc.commit(!defer); - if defer { - output.schedule.hardware_cursor_changed(); - } - }; - let transform = output.global.persistent.transform.get(); - let render = render | output.hardware_cursor_needs_render.take(); - let scale = output.global.persistent.scale.get(); - let extents = cursor.extents_at_scale(scale); - let (hc_width, hc_height) = hc.size(); - if render { - let (max_width, max_height) = transform.maybe_swap((hc_width, hc_height)); - if extents.width() > max_width || extents.height() > max_height { - hc.set_enabled(false); - commit(); - continue; - } - } - let opos = output.global.pos.get(); - let (x_rel, y_rel); - if scale == 1 { - x_rel = x.round_down() - opos.x1(); - y_rel = y.round_down() - opos.y1(); - } else { - let scalef = scale.to_f64(); - x_rel = ((x - Fixed::from_int(opos.x1())).to_f64() * scalef).round() as i32; - y_rel = ((y - Fixed::from_int(opos.y1())).to_f64() * scalef).round() as i32; - } - let (width, height) = output.global.pixel_size(); - if extents.intersects(&Rect::new_sized(-x_rel, -y_rel, width, height).unwrap()) { - if render { - let buffer = hc.get_buffer(); - let res = buffer.render_hardware_cursor( - cursor.deref(), - &self.group.state, - scale, - transform, - ); - match res { - Ok(sync_file) => { - hc.set_sync_file(sync_file); - hc.swap_buffer(); - } - Err(e) => { - log::error!("Could not render hardware cursor: {}", ErrorFmt(e)); - } - } - } - hc.set_enabled(true); - let mode = output.global.mode.get(); - let (x_rel, y_rel) = - transform.apply_point(mode.width, mode.height, (x_rel, y_rel)); - let (hot_x, hot_y) = - transform.apply_point(hc_width, hc_height, (-extents.x1(), -extents.y1())); - hc.set_position(x_rel - hot_x, y_rel - hot_y); - } else { - if render { - output.hardware_cursor_needs_render.set(true); - } - hc.set_enabled(false); - } - commit(); + let extents = cursor.extents_at_scale(scale); + let (hc_width, hc_height) = hc.size(); + if render { + let (max_width, max_height) = transform.maybe_swap((hc_width, hc_height)); + if extents.width() > max_width || extents.height() > max_height { + hc.set_enabled(false); + return; } } + let opos = output.global.pos.get(); + let (x_rel, y_rel); + if scale == 1 { + x_rel = x.round_down() - opos.x1(); + y_rel = y.round_down() - opos.y1(); + } else { + let scalef = scale.to_f64(); + x_rel = ((x - Fixed::from_int(opos.x1())).to_f64() * scalef).round() as i32; + y_rel = ((y - Fixed::from_int(opos.y1())).to_f64() * scalef).round() as i32; + } + let (width, height) = output.global.pixel_size(); + if !extents.intersects(&Rect::new_sized(-x_rel, -y_rel, width, height).unwrap()) { + if render { + output.hardware_cursor_needs_render.set(true); + } + hc.set_enabled(false); + return; + } + if render { + let buffer = hc.get_buffer(); + let res = + buffer.render_hardware_cursor(cursor.deref(), &self.group.state, scale, transform); + match res { + Ok(sync_file) => { + hc.set_sync_file(sync_file); + hc.swap_buffer(); + } + Err(e) => { + log::error!("Could not render hardware cursor: {}", ErrorFmt(e)); + } + } + } + hc.set_enabled(true); + let mode = output.global.mode.get(); + let (x_rel, y_rel) = transform.apply_point(mode.width, mode.height, (x_rel, y_rel)); + let (hot_x, hot_y) = + transform.apply_point(hc_width, hc_height, (-extents.x1(), -extents.y1())); + hc.set_position(x_rel - hot_x, y_rel - hot_y); } fn reload_known_cursor(&self) { diff --git a/src/output_schedule.rs b/src/output_schedule.rs index 794d9ccd..bb6e9da4 100644 --- a/src/output_schedule.rs +++ b/src/output_schedule.rs @@ -87,7 +87,7 @@ impl OutputSchedule { } } - pub fn presented(&self) { + pub fn latched(&self) { self.last_present_nsec.set(self.eng.now().nsec()); self.present_scheduled.set(false); self.iteration.fetch_add(1); @@ -166,9 +166,8 @@ impl OutputSchedule { } if self.needs_hardware_cursor_commit.take() { if let Some(hc) = self.hardware_cursor.get() { - if hc.schedule_present() { - self.present_scheduled.set(true); - } + hc.damage(); + self.present_scheduled.set(true); } } if self.needs_software_cursor_damage.take() { diff --git a/src/state.rs b/src/state.rs index d06889be..e5669e04 100644 --- a/src/state.rs +++ b/src/state.rs @@ -4,8 +4,8 @@ use { async_engine::{AsyncEngine, SpawnedFuture}, backend::{ Backend, BackendDrmDevice, BackendEvent, Connector, ConnectorId, ConnectorIds, - DrmDeviceId, DrmDeviceIds, InputDevice, InputDeviceGroupIds, InputDeviceId, - InputDeviceIds, MonitorInfo, + DrmDeviceId, DrmDeviceIds, HardwareCursorUpdate, InputDevice, InputDeviceGroupIds, + InputDeviceId, InputDeviceIds, MonitorInfo, }, backends::dummy::DummyBackend, cli::RunArgs, @@ -847,11 +847,13 @@ impl State { self.slow_ei_clients.clear(); } - pub fn disable_hardware_cursors(&self) { + pub fn damage_hardware_cursors(&self, render: bool) { for output in self.root.outputs.lock().values() { if let Some(hc) = output.hardware_cursor.get() { - hc.set_enabled(false); - hc.commit(true); + if render { + output.hardware_cursor_needs_render.set(true); + } + hc.damage(); } } } @@ -863,7 +865,19 @@ impl State { return; } } - self.disable_hardware_cursors() + self.damage_hardware_cursors(false) + } + + pub fn present_hardware_cursor( + &self, + output: &Rc, + hc: &mut dyn HardwareCursorUpdate, + ) { + let Some(g) = self.cursor_user_group_hardware_cursor.get() else { + hc.set_enabled(false); + return; + }; + g.present_hardware_cursor(output, hc); } pub fn for_each_seat_tester(&self, f: F) { diff --git a/src/tasks/hardware_cursor.rs b/src/tasks/hardware_cursor.rs index 0f6dba81..89b748df 100644 --- a/src/tasks/hardware_cursor.rs +++ b/src/tasks/hardware_cursor.rs @@ -29,7 +29,8 @@ pub async fn handle_hardware_cursor_tick(state: Rc) { break; } } - state.refresh_hardware_cursors(); + cursor.tick(); + state.damage_hardware_cursors(true); } } } From 0dc5d9adb8e2b1b17d9030d3dcee89a231f914d3 Mon Sep 17 00:00:00 2001 From: Julian Orth Date: Mon, 9 Sep 2024 20:49:23 +0200 Subject: [PATCH 07/10] metal: wait for rendering to complete before committing buffers --- src/backends/metal.rs | 3 + src/backends/metal/present.rs | 715 ++++++++++++++++++++++++++++++++++ src/backends/metal/video.rs | 643 ++---------------------------- src/gfx_api.rs | 185 +++++---- src/utils/numcell.rs | 16 + 5 files changed, 881 insertions(+), 681 deletions(-) create mode 100644 src/backends/metal/present.rs diff --git a/src/backends/metal.rs b/src/backends/metal.rs index b1fd8d2c..0d2765e2 100644 --- a/src/backends/metal.rs +++ b/src/backends/metal.rs @@ -1,5 +1,6 @@ mod input; mod monitor; +mod present; mod video; use { @@ -133,6 +134,8 @@ pub enum MetalError { Commit(#[source] DrmError), #[error("Could not clear framebuffer")] Clear(#[source] GfxError), + #[error("The present configuration is out of date")] + OutOfDate, } pub struct MetalBackend { diff --git a/src/backends/metal/present.rs b/src/backends/metal/present.rs new file mode 100644 index 00000000..aa26e838 --- /dev/null +++ b/src/backends/metal/present.rs @@ -0,0 +1,715 @@ +use { + crate::{ + backend::Connector, + backends::metal::{ + video::{ + MetalConnector, MetalCrtc, MetalHardwareCursorChange, MetalPlane, RenderBuffer, + }, + MetalError, + }, + gfx_api::{ + create_render_pass, AcquireSync, BufferResv, GfxApiOpt, GfxRenderPass, GfxTexture, + SyncFile, + }, + theme::Color, + tree::OutputNode, + utils::{errorfmt::ErrorFmt, oserror::OsError, transform_ext::TransformExt}, + video::{ + dmabuf::DmaBufId, + drm::{ + DrmError, DrmFramebuffer, DRM_MODE_ATOMIC_NONBLOCK, DRM_MODE_PAGE_FLIP_ASYNC, + DRM_MODE_PAGE_FLIP_EVENT, + }, + }, + }, + std::rc::{Rc, Weak}, + uapi::c, +}; + +struct Latched { + pass: GfxRenderPass, + damage: u64, +} + +#[derive(Debug)] +pub struct DirectScanoutCache { + tex: Weak, + fb: Option>, +} + +#[derive(Debug)] +pub struct DirectScanoutData { + tex: Rc, + acquire_sync: AcquireSync, + _resv: Option>, + fb: Rc, + dma_buf_id: DmaBufId, + position: DirectScanoutPosition, +} + +#[derive(Debug)] +pub struct DirectScanoutPosition { + pub src_width: i32, + pub src_height: i32, + pub crtc_x: i32, + pub crtc_y: i32, + pub crtc_width: i32, + pub crtc_height: i32, +} + +pub struct PresentFb { + fb: Rc, + tex: Rc, + direct_scanout_data: Option, + sync_file: Option, +} + +enum CursorProgramming { + Enable { + plane: Rc, + fb: Rc, + x: i32, + y: i32, + width: i32, + height: i32, + swap: bool, + }, + Disable { + plane: Rc, + }, +} + +impl MetalConnector { + pub fn schedule_present(&self) { + self.present_trigger.trigger(); + } + + pub async fn present_loop(self: Rc) { + loop { + self.present_trigger.triggered().await; + if let Err(e) = self.present_once().await { + log::error!("Could not present: {}", ErrorFmt(e)); + continue; + } + self.state.set_backend_idle(false); + } + } + + async fn present_once(&self) -> Result<(), MetalError> { + let version = self.version.get(); + if !self.can_present.get() { + return Ok(()); + } + if !self.backend.check_render_context(&self.dev) { + return Ok(()); + } + let Some(node) = self.state.root.outputs.get(&self.connector_id) else { + return Ok(()); + }; + let crtc = match self.crtc.get() { + Some(crtc) => crtc, + _ => return Ok(()), + }; + if !crtc.active.value.get() { + return Ok(()); + } + let plane = match self.primary_plane.get() { + Some(p) => p, + _ => return Ok(()), + }; + let buffers = match self.buffers.get() { + Some(b) => b, + _ => return Ok(()), + }; + + self.latch_cursor(&node)?; + let cursor_programming = self.compute_cursor_programming(); + let latched = self.latch(&node); + node.schedule.latched(); + + if cursor_programming.is_none() && latched.is_none() { + return Ok(()); + } + + let buffer = &buffers[self.next_buffer.get() % buffers.len()]; + let mut present_fb = None; + let mut direct_scanout_id = None; + if let Some(latched) = &latched { + let fb = self.prepare_present_fb(buffer, &plane, &latched.pass, true)?; + direct_scanout_id = fb.direct_scanout_data.as_ref().map(|d| d.dma_buf_id); + present_fb = Some(fb); + } + self.perform_screencopies(&present_fb, &node); + if let Some(sync_file) = self.cursor_sync_file.take() { + if let Err(e) = self.state.ring.readable(&sync_file).await { + log::error!( + "Could not wait for cursor sync file to complete: {}", + ErrorFmt(e) + ); + } + } + self.await_present_fb(present_fb.as_mut()).await; + let mut res = self.program_connector( + version, + &crtc, + &plane, + cursor_programming.as_ref(), + present_fb.as_ref(), + ); + if res.is_err() { + if let Some(dsd_id) = direct_scanout_id { + let fb = self.prepare_present_fb( + buffer, + &plane, + &latched.as_ref().unwrap().pass, + false, + )?; + present_fb = Some(fb); + self.await_present_fb(present_fb.as_mut()).await; + res = self.program_connector( + version, + &crtc, + &plane, + cursor_programming.as_ref(), + present_fb.as_ref(), + ); + if res.is_ok() { + let mut cache = self.scanout_buffers.borrow_mut(); + if let Some(buffer) = cache.remove(&dsd_id) { + cache.insert( + dsd_id, + DirectScanoutCache { + tex: buffer.tex, + fb: None, + }, + ); + } + } + } + } + if let Err(e) = res { + self.render_result + .borrow_mut() + .discard_presentation_feedback(); + if let MetalError::Commit(DrmError::Atomic(OsError(c::EACCES))) = e { + log::debug!("Could not perform atomic commit, likely because we're no longer the DRM master"); + return Ok(()); + } + Err(e) + } else { + macro_rules! apply_change { + ($prop:expr) => { + if let Some(v) = $prop.pending_value.take() { + $prop.value.set(v); + } + }; + } + apply_change!(plane.src_w); + apply_change!(plane.src_h); + apply_change!(plane.crtc_x); + apply_change!(plane.crtc_y); + apply_change!(plane.crtc_w); + apply_change!(plane.crtc_h); + if let Some(fb) = present_fb { + if fb.direct_scanout_data.is_none() { + self.next_buffer.fetch_add(1); + } + self.next_framebuffer.set(Some(fb)); + } + if let Some(CursorProgramming::Enable { swap: true, .. }) = cursor_programming { + self.cursor_swap_buffer.set(false); + self.cursor_front_buffer.fetch_add(1); + } + self.can_present.set(false); + if let Some(latched) = latched { + self.has_damage.fetch_sub(latched.damage); + } + self.cursor_changed.set(false); + Ok(()) + } + } + + async fn await_present_fb(&self, new_fb: Option<&mut PresentFb>) { + let Some(fb) = new_fb else { + return; + }; + let Some(sync_file) = fb.sync_file.take() else { + return; + }; + if let Err(e) = self.state.ring.readable(&sync_file).await { + log::error!( + "Could not wait for primary sync file to complete: {}", + ErrorFmt(e) + ); + } + } + + fn program_connector( + &self, + version: u64, + crtc: &Rc, + plane: &Rc, + cursor: Option<&CursorProgramming>, + new_fb: Option<&PresentFb>, + ) -> Result<(), MetalError> { + let mut changes = self.master.change(); + let mut try_async_flip = self.tearing_requested.get() && self.dev.supports_async_commit; + macro_rules! change { + ($c:expr, $prop:expr, $new:expr) => {{ + if $prop.value.get() != $new { + $c.change($prop.id, $new as u64); + try_async_flip = false; + $prop.pending_value.set(Some($new)); + } + }}; + } + if let Some(fb) = new_fb { + let (crtc_x, crtc_y, crtc_w, crtc_h, src_width, src_height) = + match &fb.direct_scanout_data { + None => { + let plane_w = plane.mode_w.get(); + let plane_h = plane.mode_h.get(); + (0, 0, plane_w, plane_h, plane_w, plane_h) + } + Some(dsd) => { + let p = &dsd.position; + ( + p.crtc_x, + p.crtc_y, + p.crtc_width, + p.crtc_height, + p.src_width, + p.src_height, + ) + } + }; + changes.change_object(plane.id, |c| { + c.change(plane.fb_id, fb.fb.id().0 as _); + change!(c, plane.src_w, (src_width as u32) << 16); + change!(c, plane.src_h, (src_height as u32) << 16); + change!(c, plane.crtc_x, crtc_x); + change!(c, plane.crtc_y, crtc_y); + change!(c, plane.crtc_w, crtc_w); + change!(c, plane.crtc_h, crtc_h); + if !try_async_flip && !self.dev.is_nvidia { + c.change(plane.in_fence_fd, -1i32 as u64); + } + }); + } else { + if self.dev.is_amd && crtc.vrr_enabled.value.get() { + // Work around https://gitlab.freedesktop.org/drm/amd/-/issues/2186 + if let Some(fb) = &*self.active_framebuffer.borrow() { + changes.change_object(plane.id, |c| { + c.change(plane.fb_id, fb.fb.id().0 as _); + }); + } + } + } + if let Some(cursor) = cursor { + try_async_flip = false; + match cursor { + CursorProgramming::Enable { + plane, + fb, + x, + y, + width, + height, + .. + } => { + changes.change_object(plane.id, |c| { + c.change(plane.fb_id, fb.id().0 as _); + c.change(plane.crtc_id.id, crtc.id.0 as _); + c.change(plane.crtc_x.id, *x as _); + c.change(plane.crtc_y.id, *y as _); + c.change(plane.crtc_w.id, *width as _); + c.change(plane.crtc_h.id, *height as _); + c.change(plane.src_x.id, 0); + c.change(plane.src_y.id, 0); + c.change(plane.src_w.id, (*width as u64) << 16); + c.change(plane.src_h.id, (*height as u64) << 16); + if !self.dev.is_nvidia { + c.change(plane.in_fence_fd, -1i32 as u64); + } + }); + } + CursorProgramming::Disable { plane } => { + changes.change_object(plane.id, |c| { + c.change(plane.fb_id, 0); + c.change(plane.crtc_id.id, 0); + }); + } + } + } + if version != self.version.get() { + return Err(MetalError::OutOfDate); + } + let mut res; + 'commit: { + const FLAGS: u32 = DRM_MODE_ATOMIC_NONBLOCK | DRM_MODE_PAGE_FLIP_EVENT; + if try_async_flip { + res = changes.commit(FLAGS | DRM_MODE_PAGE_FLIP_ASYNC, 0); + if res.is_ok() { + break 'commit; + } + } + res = changes.commit(FLAGS, 0); + } + res.map_err(MetalError::Commit) + } + + fn latch_cursor(&self, node: &Rc) -> Result<(), MetalError> { + if !self.cursor_damage.take() { + return Ok(()); + } + if self.cursor_plane.is_none() { + return Ok(()); + } + let buffers = self.cursor_buffers.get().unwrap(); + let mut c = MetalHardwareCursorChange { + cursor_enabled: self.cursor_enabled.get(), + cursor_swap_buffer: false, + cursor_x: self.cursor_x.get(), + cursor_y: self.cursor_y.get(), + cursor_buffer: &buffers[(self.cursor_front_buffer.get() + 1) % buffers.len()], + sync_file: None, + cursor_size: (self.dev.cursor_width as _, self.dev.cursor_height as _), + }; + self.state.present_hardware_cursor(node, &mut c); + if c.cursor_swap_buffer { + c.sync_file = c.cursor_buffer.copy_to_dev(c.sync_file)?; + } + self.cursor_swap_buffer.set(c.cursor_swap_buffer); + if c.sync_file.is_some() { + self.cursor_sync_file.set(c.sync_file); + } + let mut cursor_changed = false; + cursor_changed |= self.cursor_enabled.replace(c.cursor_enabled) != c.cursor_enabled; + cursor_changed |= c.cursor_swap_buffer; + cursor_changed |= self.cursor_x.replace(c.cursor_x) != c.cursor_x; + cursor_changed |= self.cursor_y.replace(c.cursor_y) != c.cursor_y; + if cursor_changed { + self.cursor_changed.set(true); + } + Ok(()) + } + + fn compute_cursor_programming(&self) -> Option { + if !self.cursor_changed.get() { + return None; + } + let plane = self.cursor_plane.get()?; + let programming = if self.cursor_enabled.get() { + let swap = self.cursor_swap_buffer.get(); + let mut front_buffer = self.cursor_front_buffer.get(); + if swap { + front_buffer = front_buffer.wrapping_add(1); + } + let buffers = self.cursor_buffers.get().unwrap(); + let buffer = &buffers[front_buffer % buffers.len()]; + let (width, height) = buffer.dev_fb.physical_size(); + CursorProgramming::Enable { + plane, + fb: buffer.drm.clone(), + x: self.cursor_x.get(), + y: self.cursor_y.get(), + width, + height, + swap, + } + } else { + CursorProgramming::Disable { plane } + }; + Some(programming) + } + + fn latch(&self, node: &Rc) -> Option { + let damage = self.has_damage.get(); + if damage == 0 { + return None; + } + let mut rr = self.render_result.borrow_mut(); + rr.output_id = node.id; + let render_hw_cursor = !self.cursor_enabled.get(); + let mode = node.global.mode.get(); + let pass = create_render_pass( + (mode.width, mode.height), + &**node, + &self.state, + Some(node.global.pos.get()), + Some(&mut rr), + node.global.persistent.scale.get(), + true, + render_hw_cursor, + node.has_fullscreen(), + node.global.persistent.transform.get(), + Some(&self.state.damage_visualizer), + ); + rr.dispatch_frame_requests(self.state.now_msec()); + Some(Latched { pass, damage }) + } + + 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 { + let ct = 'ct: { + let mut ops = pass.ops.iter().rev(); + let ct = 'ct2: { + for opt in &mut ops { + match opt { + GfxApiOpt::Sync => {} + GfxApiOpt::FillRect(_) => { + // Top-most layer must be a texture. + return None; + } + GfxApiOpt::CopyTexture(ct) => break 'ct2 ct, + } + } + return None; + }; + if ct.alpha.is_some() { + // Direct scanout with alpha factor is not supported. + return None; + } + if !ct.tex.format().has_alpha && ct.target.is_covering() { + // Texture covers the entire screen and is opaque. + break 'ct ct; + } + for opt in ops { + match opt { + GfxApiOpt::Sync => {} + GfxApiOpt::FillRect(fr) => { + if fr.color == Color::SOLID_BLACK { + // Black fills can be ignored because this is the CRTC background color. + if fr.rect.is_covering() { + // If fill covers the entire screen, we don't have to look further. + break 'ct ct; + } + } else { + // Fill could be visible. + return None; + } + } + GfxApiOpt::CopyTexture(_) => { + // Texture could be visible. + return None; + } + } + } + if let Some(clear) = pass.clear { + if clear != Color::SOLID_BLACK { + // Background could be visible. + return None; + } + } + ct + }; + if let AcquireSync::None = ct.acquire_sync { + // Cannot perform scanout without sync. + return None; + } + if ct.source.buffer_transform != ct.target.output_transform { + // Rotations and mirroring are not supported. + return None; + } + if !ct.source.is_covering() { + // Viewports are not supported. + return None; + } + if ct.target.x1 < -1.0 || ct.target.y1 < -1.0 || ct.target.x2 > 1.0 || ct.target.y2 > 1.0 { + // Rendering outside the screen is not supported. + return None; + } + let (tex_w, tex_h) = ct.tex.size(); + let (x1, x2, y1, y2) = { + let plane_w = plane.mode_w.get() as f32; + let plane_h = plane.mode_h.get() as f32; + let ((x1, x2), (y1, y2)) = ct + .target + .output_transform + .maybe_swap(((ct.target.x1, ct.target.x2), (ct.target.y1, ct.target.y2))); + ( + (x1 + 1.0) * plane_w / 2.0, + (x2 + 1.0) * plane_w / 2.0, + (y1 + 1.0) * plane_h / 2.0, + (y2 + 1.0) * plane_h / 2.0, + ) + }; + let (crtc_w, crtc_h) = (x2 - x1, y2 - y1); + if crtc_w < 0.0 || crtc_h < 0.0 { + // Flipping x or y axis is not supported. + return None; + } + if self.cursor_enabled.get() && (tex_w as f32, tex_h as f32) != (crtc_w, crtc_h) { + // If hardware cursors are used, we cannot scale the texture. + return None; + } + let Some(dmabuf) = ct.tex.dmabuf() else { + // Shm buffers cannot be scanned out. + return None; + }; + let position = DirectScanoutPosition { + src_width: tex_w, + src_height: tex_h, + crtc_x: x1 as _, + crtc_y: y1 as _, + crtc_width: crtc_w as _, + crtc_height: crtc_h as _, + }; + 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(), + acquire_sync: ct.acquire_sync.clone(), + _resv: ct.buffer_resv.clone(), + fb: fb.clone(), + dma_buf_id: dmabuf.id, + position, + }); + } + let format = 'format: { + if let Some(f) = plane.formats.get(&dmabuf.format.drm) { + break 'format f; + } + // Try opaque format if possible. + 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(), + acquire_sync: ct.acquire_sync.clone(), + _resv: ct.buffer_resv.clone(), + fb: Rc::new(fb), + dma_buf_id: dmabuf.id, + position, + }), + 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 direct_scanout_enabled(&self) -> bool { + self.dev + .direct_scanout_enabled + .get() + .unwrap_or(self.state.direct_scanout_enabled.get()) + } + + fn prepare_present_fb( + &self, + buffer: &RenderBuffer, + plane: &Rc, + pass: &GfxRenderPass, + try_direct_scanout: bool, + ) -> Result { + self.trim_scanout_cache(); + let try_direct_scanout = try_direct_scanout + && 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 { + direct_scanout_data = self.prepare_direct_scanout(&pass, plane); + } + 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 sync_file; + let fb; + let tex; + match &direct_scanout_data { + None => { + let sf = buffer + .render_fb() + .perform_render_pass(pass) + .map_err(MetalError::RenderFrame)?; + sync_file = buffer.copy_to_dev(sf)?; + fb = buffer.drm.clone(); + tex = buffer.render_tex.clone(); + } + Some(dsd) => { + sync_file = match &dsd.acquire_sync { + AcquireSync::None => None, + AcquireSync::Implicit => None, + AcquireSync::SyncFile { sync_file } => Some(sync_file.clone()), + AcquireSync::Unnecessary => None, + }; + fb = dsd.fb.clone(); + tex = dsd.tex.clone(); + } + }; + Ok(PresentFb { + fb, + tex, + direct_scanout_data, + sync_file, + }) + } + + fn perform_screencopies(&self, new_fb: &Option, output: &OutputNode) { + let active_fb; + let fb = match &new_fb { + Some(f) => f, + None => { + active_fb = self.active_framebuffer.borrow(); + match &*active_fb { + None => return, + Some(f) => f, + } + } + }; + let render_hardware_cursor = self.cursor_enabled.get(); + match &fb.direct_scanout_data { + None => { + output.perform_screencopies(&fb.tex, render_hardware_cursor, 0, 0, None); + } + Some(dsd) => { + output.perform_screencopies( + &dsd.tex, + render_hardware_cursor, + dsd.position.crtc_x, + dsd.position.crtc_y, + Some((dsd.position.crtc_width, dsd.position.crtc_height)), + ); + } + } + } +} diff --git a/src/backends/metal/video.rs b/src/backends/metal/video.rs index 992c8f3b..6895a7e9 100644 --- a/src/backends/metal/video.rs +++ b/src/backends/metal/video.rs @@ -7,13 +7,16 @@ use { ConnectorEvent, ConnectorId, ConnectorKernelId, DrmDeviceId, HardwareCursor, HardwareCursorUpdate, Mode, MonitorInfo, }, - backends::metal::{MetalBackend, MetalError}, + backends::metal::{ + present::{DirectScanoutCache, PresentFb}, + MetalBackend, MetalError, + }, drm_feedback::DrmFeedback, edid::Descriptor, format::{Format, ARGB8888, XRGB8888}, gfx_api::{ - needs_render_usage, AcquireSync, BufferResv, GfxApiOpt, GfxContext, GfxFramebuffer, - GfxRenderPass, GfxTexture, ReleaseSync, SyncFile, + needs_render_usage, AcquireSync, GfxContext, GfxFramebuffer, GfxTexture, ReleaseSync, + SyncFile, }, ifs::{ wl_output::OutputId, @@ -21,13 +24,11 @@ use { }, renderer::RenderResult, state::State, - theme::Color, - tree::OutputNode, udev::UdevDevice, utils::{ asyncevent::AsyncEvent, bitflags::BitflagsExt, cell_ext::CellExt, clonecell::CloneCell, copyhashmap::CopyHashMap, errorfmt::ErrorFmt, numcell::NumCell, on_change::OnChange, - opaque_cell::OpaqueCell, oserror::OsError, transform_ext::TransformExt, + opaque_cell::OpaqueCell, oserror::OsError, }, video::{ dmabuf::DmaBufId, @@ -36,8 +37,7 @@ use { DrmCrtc, DrmEncoder, DrmError, DrmEvent, DrmFramebuffer, DrmLease, DrmMaster, DrmModeInfo, DrmObject, DrmPlane, DrmProperty, DrmPropertyDefinition, DrmPropertyType, DrmVersion, PropBlob, DRM_CLIENT_CAP_ATOMIC, - DRM_MODE_ATOMIC_ALLOW_MODESET, DRM_MODE_ATOMIC_NONBLOCK, DRM_MODE_PAGE_FLIP_ASYNC, - DRM_MODE_PAGE_FLIP_EVENT, + DRM_MODE_ATOMIC_ALLOW_MODESET, }, gbm::{GbmBo, GbmDevice, GBM_BO_USE_LINEAR, GBM_BO_USE_RENDERING, GBM_BO_USE_SCANOUT}, Modifier, INVALID_MODIFIER, @@ -49,7 +49,6 @@ use { indexmap::{indexset, IndexMap, IndexSet}, isnt::std_1::collections::IsntHashMap2Ext, jay_config::video::GfxApi, - once_cell::sync::Lazy, std::{ any::Any, cell::{Cell, RefCell}, @@ -58,7 +57,7 @@ use { fmt::{Debug, Formatter}, mem, ops::DerefMut, - rc::{Rc, Weak}, + rc::Rc, }, uapi::{ c::{self, dev_t}, @@ -426,7 +425,7 @@ pub struct MetalConnector { pub lease: Cell>, pub can_present: Cell, - pub has_damage: Cell, + pub has_damage: NumCell, pub cursor_changed: Cell, pub cursor_damage: Cell, pub next_flip_nsec: Cell, @@ -462,6 +461,8 @@ pub struct MetalConnector { pub tearing_requested: Cell, pub try_switch_format: Cell, + + pub version: NumCell, } impl Debug for MetalConnector { @@ -537,72 +538,7 @@ impl Debug for ConnectorFutures { } } -#[derive(Debug)] -pub struct DirectScanoutCache { - tex: Weak, - fb: Option>, -} - -#[derive(Debug)] -pub struct DirectScanoutData { - tex: Rc, - acquire_sync: AcquireSync, - _resv: Option>, - fb: Rc, - dma_buf_id: DmaBufId, - position: DirectScanoutPosition, -} - -#[derive(Debug)] -pub struct DirectScanoutPosition { - pub src_width: i32, - pub src_height: i32, - pub crtc_x: i32, - pub crtc_y: i32, - pub crtc_width: i32, - pub crtc_height: i32, -} - -#[derive(Debug)] -pub struct PresentFb { - fb: Rc, - tex: Rc, - direct_scanout_data: Option, - sync_file: Option, -} - impl MetalConnector { - async fn present_loop(self: Rc) { - loop { - self.present_trigger.triggered().await; - static DELTA: Lazy> = Lazy::new(|| { - if let Ok(max_render_time) = std::env::var("JAY_MAX_RENDER_TIME_NSEC") { - if let Ok(max_render_time) = max_render_time.parse() { - return Some(max_render_time); - } - } - None - }); - if let Some(delta) = *DELTA { - let next_present = self.next_flip_nsec.get().saturating_sub(delta); - if self.state.now_nsec() < next_present { - self.state.ring.timeout(next_present).await.unwrap(); - } - } - let Some(node) = self.state.root.outputs.get(&self.connector_id) else { - return; - }; - self.latch_cursor(&node); - node.schedule.latched(); - match self.present(&node, true) { - Ok(_) => self.state.set_backend_idle(false), - Err(e) => { - log::error!("Could not present: {}", ErrorFmt(e)); - } - } - } - } - fn send_vrr_enabled(&self) { match self.frontend_state.get() { FrontState::Removed @@ -657,522 +593,6 @@ impl MetalConnector { self.enabled.get() && dd.connection == ConnectorStatus::Connected } - pub fn schedule_present(&self) { - self.present_trigger.trigger(); - } - - 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 { - let ct = 'ct: { - let mut ops = pass.ops.iter().rev(); - let ct = 'ct2: { - for opt in &mut ops { - match opt { - GfxApiOpt::Sync => {} - GfxApiOpt::FillRect(_) => { - // Top-most layer must be a texture. - return None; - } - GfxApiOpt::CopyTexture(ct) => break 'ct2 ct, - } - } - return None; - }; - if ct.alpha.is_some() { - // Direct scanout with alpha factor is not supported. - return None; - } - if !ct.tex.format().has_alpha && ct.target.is_covering() { - // Texture covers the entire screen and is opaque. - break 'ct ct; - } - for opt in ops { - match opt { - GfxApiOpt::Sync => {} - GfxApiOpt::FillRect(fr) => { - if fr.color == Color::SOLID_BLACK { - // Black fills can be ignored because this is the CRTC background color. - if fr.rect.is_covering() { - // If fill covers the entire screen, we don't have to look further. - break 'ct ct; - } - } else { - // Fill could be visible. - return None; - } - } - GfxApiOpt::CopyTexture(_) => { - // Texture could be visible. - return None; - } - } - } - if let Some(clear) = pass.clear { - if clear != Color::SOLID_BLACK { - // Background could be visible. - return None; - } - } - ct - }; - if let AcquireSync::None = ct.acquire_sync { - // Cannot perform scanout without sync. - return None; - } - if ct.source.buffer_transform != ct.target.output_transform { - // Rotations and mirroring are not supported. - return None; - } - if !ct.source.is_covering() { - // Viewports are not supported. - return None; - } - if ct.target.x1 < -1.0 || ct.target.y1 < -1.0 || ct.target.x2 > 1.0 || ct.target.y2 > 1.0 { - // Rendering outside the screen is not supported. - return None; - } - let (tex_w, tex_h) = ct.tex.size(); - let (x1, x2, y1, y2) = { - let plane_w = plane.mode_w.get() as f32; - let plane_h = plane.mode_h.get() as f32; - let ((x1, x2), (y1, y2)) = ct - .target - .output_transform - .maybe_swap(((ct.target.x1, ct.target.x2), (ct.target.y1, ct.target.y2))); - ( - (x1 + 1.0) * plane_w / 2.0, - (x2 + 1.0) * plane_w / 2.0, - (y1 + 1.0) * plane_h / 2.0, - (y2 + 1.0) * plane_h / 2.0, - ) - }; - let (crtc_w, crtc_h) = (x2 - x1, y2 - y1); - if crtc_w < 0.0 || crtc_h < 0.0 { - // Flipping x or y axis is not supported. - return None; - } - if self.cursor_enabled.get() && (tex_w as f32, tex_h as f32) != (crtc_w, crtc_h) { - // If hardware cursors are used, we cannot scale the texture. - return None; - } - let Some(dmabuf) = ct.tex.dmabuf() else { - // Shm buffers cannot be scanned out. - return None; - }; - let position = DirectScanoutPosition { - src_width: tex_w, - src_height: tex_h, - crtc_x: x1 as _, - crtc_y: y1 as _, - crtc_width: crtc_w as _, - crtc_height: crtc_h as _, - }; - 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(), - acquire_sync: ct.acquire_sync.clone(), - _resv: ct.buffer_resv.clone(), - fb: fb.clone(), - dma_buf_id: dmabuf.id, - position, - }); - } - let format = 'format: { - if let Some(f) = plane.formats.get(&dmabuf.format.drm) { - break 'format f; - } - // Try opaque format if possible. - 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(), - acquire_sync: ct.acquire_sync.clone(), - _resv: ct.buffer_resv.clone(), - fb: Rc::new(fb), - dma_buf_id: dmabuf.id, - position, - }), - 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 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, - buffer: &RenderBuffer, - plane: &Rc, - output: &OutputNode, - try_direct_scanout: bool, - ) -> Result { - 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.persistent.scale.get(), - true, - render_hw_cursor, - output.has_fullscreen(), - output.global.persistent.transform.get(), - Some(&self.state.damage_visualizer), - ); - let try_direct_scanout = try_direct_scanout - && 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 { - direct_scanout_data = self.prepare_direct_scanout(&pass, plane); - } - 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 sync_file; - let fb; - let tex; - match &direct_scanout_data { - None => { - let sf = buffer_fb - .perform_render_pass(&pass) - .map_err(MetalError::RenderFrame)?; - sync_file = buffer.copy_to_dev(sf)?; - fb = buffer.drm.clone(); - tex = buffer.render_tex.clone(); - } - Some(dsd) => { - sync_file = match &dsd.acquire_sync { - AcquireSync::None => None, - AcquireSync::Implicit => None, - AcquireSync::SyncFile { sync_file } => Some(sync_file.clone()), - AcquireSync::Unnecessary => None, - }; - fb = dsd.fb.clone(); - tex = dsd.tex.clone(); - } - }; - Ok(PresentFb { - fb, - tex, - direct_scanout_data, - sync_file, - }) - } - - fn latch_cursor(&self, node: &Rc) { - if !self.cursor_damage.take() { - return; - } - if self.cursor_plane.is_none() { - return; - } - let buffers = self.cursor_buffers.get().unwrap(); - let mut c = MetalHardwareCursorChange { - cursor_enabled: self.cursor_enabled.get(), - cursor_swap_buffer: false, - cursor_x: self.cursor_x.get(), - cursor_y: self.cursor_y.get(), - cursor_buffer: &buffers[(self.cursor_front_buffer.get() + 1) % buffers.len()], - sync_file: None, - cursor_size: (self.dev.cursor_width as _, self.dev.cursor_height as _), - }; - self.state.present_hardware_cursor(node, &mut c); - self.cursor_swap_buffer.set(c.cursor_swap_buffer); - if c.sync_file.is_some() { - self.cursor_sync_file.set(c.sync_file); - } - let mut cursor_changed = false; - cursor_changed |= self.cursor_enabled.replace(c.cursor_enabled) != c.cursor_enabled; - cursor_changed |= c.cursor_swap_buffer; - cursor_changed |= self.cursor_x.replace(c.cursor_x) != c.cursor_x; - cursor_changed |= self.cursor_y.replace(c.cursor_y) != c.cursor_y; - if cursor_changed { - self.cursor_changed.set(true); - } - } - - pub fn present( - &self, - node: &Rc, - try_direct_scanout: bool, - ) -> Result<(), MetalError> { - let crtc = match self.crtc.get() { - Some(crtc) => crtc, - _ => return Ok(()), - }; - if (!self.has_damage.get() && !self.cursor_changed.get()) || !self.can_present.get() { - return Ok(()); - } - if !crtc.active.value.get() { - return Ok(()); - } - let plane = match self.primary_plane.get() { - Some(p) => p, - _ => return Ok(()), - }; - let buffers = match self.buffers.get() { - Some(b) => b, - _ => return Ok(()), - }; - let cursor = self.cursor_plane.get(); - let mut new_fb = None; - let mut changes = self.master.change(); - let mut try_async_flip = self.tearing_requested.get() && self.dev.supports_async_commit; - macro_rules! change { - ($c:expr, $prop:expr, $new:expr) => {{ - if $prop.value.get() != $new { - $c.change($prop.id, $new as u64); - try_async_flip = false; - $prop.pending_value.set(Some($new)); - } - }}; - } - if self.has_damage.get() { - if !self.backend.check_render_context(&self.dev) { - return Ok(()); - } - let buffer = &buffers[self.next_buffer.get() % buffers.len()]; - let mut rr = self.render_result.borrow_mut(); - rr.output_id = node.id; - let fb = self.prepare_present_fb(&mut rr, buffer, &plane, &node, try_direct_scanout)?; - rr.dispatch_frame_requests(self.state.now_msec()); - let (crtc_x, crtc_y, crtc_w, crtc_h, src_width, src_height) = - match &fb.direct_scanout_data { - None => { - let plane_w = plane.mode_w.get(); - let plane_h = plane.mode_h.get(); - (0, 0, plane_w, plane_h, plane_w, plane_h) - } - Some(dsd) => { - let p = &dsd.position; - ( - p.crtc_x, - p.crtc_y, - p.crtc_width, - p.crtc_height, - p.src_width, - p.src_height, - ) - } - }; - let in_fence = fb.sync_file.as_ref().map(|s| s.raw()).unwrap_or(-1); - changes.change_object(plane.id, |c| { - c.change(plane.fb_id, fb.fb.id().0 as _); - change!(c, plane.src_w, (src_width as u32) << 16); - change!(c, plane.src_h, (src_height as u32) << 16); - change!(c, plane.crtc_x, crtc_x); - change!(c, plane.crtc_y, crtc_y); - change!(c, plane.crtc_w, crtc_w); - change!(c, plane.crtc_h, crtc_h); - if !try_async_flip && !self.dev.is_nvidia { - c.change(plane.in_fence_fd, in_fence as u64); - } - }); - new_fb = Some(fb); - } else { - if self.dev.is_amd && crtc.vrr_enabled.value.get() { - // Work around https://gitlab.freedesktop.org/drm/amd/-/issues/2186 - if let Some(fb) = &*self.active_framebuffer.borrow() { - changes.change_object(plane.id, |c| { - c.change(plane.fb_id, fb.fb.id().0 as _); - }); - } - } - } - let mut cursor_swap_buffer = false; - let mut cursor_sync_file = None; - if self.cursor_changed.get() && cursor.is_some() { - try_async_flip = false; - let plane = cursor.unwrap(); - if self.cursor_enabled.get() { - cursor_swap_buffer = self.cursor_swap_buffer.get(); - let mut front_buffer = self.cursor_front_buffer.get(); - if cursor_swap_buffer { - front_buffer = front_buffer.wrapping_add(1); - cursor_sync_file = self.cursor_sync_file.get(); - } - let buffers = self.cursor_buffers.get().unwrap(); - let buffer = &buffers[front_buffer % buffers.len()]; - if cursor_swap_buffer { - cursor_sync_file = buffer.copy_to_dev(cursor_sync_file)?; - } - let in_fence = cursor_sync_file.as_ref().map(|s| s.raw()).unwrap_or(-1); - let (width, height) = buffer.dev_fb.physical_size(); - changes.change_object(plane.id, |c| { - c.change(plane.fb_id, buffer.drm.id().0 as _); - c.change(plane.crtc_id.id, crtc.id.0 as _); - c.change(plane.crtc_x.id, self.cursor_x.get() as _); - c.change(plane.crtc_y.id, self.cursor_y.get() as _); - c.change(plane.crtc_w.id, width as _); - c.change(plane.crtc_h.id, height as _); - c.change(plane.src_x.id, 0); - c.change(plane.src_y.id, 0); - c.change(plane.src_w.id, (width as u64) << 16); - c.change(plane.src_h.id, (height as u64) << 16); - if !self.dev.is_nvidia { - c.change(plane.in_fence_fd, in_fence as u64); - } - }); - } else { - changes.change_object(plane.id, |c| { - c.change(plane.fb_id, 0); - c.change(plane.crtc_id.id, 0); - }); - } - } - let mut res; - 'commit: { - const FLAGS: u32 = DRM_MODE_ATOMIC_NONBLOCK | DRM_MODE_PAGE_FLIP_EVENT; - if try_async_flip { - res = changes.commit(FLAGS | DRM_MODE_PAGE_FLIP_ASYNC, 0); - if res.is_ok() { - break 'commit; - } - } - res = changes.commit(FLAGS, 0); - } - if let Err(e) = res { - if let DrmError::Atomic(OsError(c::EACCES)) = e { - log::debug!("Could not perform atomic commit, likely because we're no longer the DRM master"); - self.render_result - .borrow_mut() - .discard_presentation_feedback(); - return Ok(()); - } - if let Some(fb) = &new_fb { - if let Some(dsd) = &fb.direct_scanout_data { - if self.present(node, 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, - }, - ); - } - return Ok(()); - } - } - } - self.render_result - .borrow_mut() - .discard_presentation_feedback(); - Err(MetalError::Commit(e)) - } else { - macro_rules! apply_change { - ($prop:expr) => { - if let Some(v) = $prop.pending_value.take() { - $prop.value.set(v); - } - }; - } - apply_change!(plane.src_w); - apply_change!(plane.src_h); - apply_change!(plane.crtc_x); - apply_change!(plane.crtc_y); - apply_change!(plane.crtc_w); - apply_change!(plane.crtc_h); - self.perform_screencopies(&new_fb, &node); - if let Some(fb) = new_fb { - if fb.direct_scanout_data.is_none() { - self.next_buffer.fetch_add(1); - } - self.next_framebuffer.set(Some(fb)); - } - if cursor_swap_buffer { - self.cursor_swap_buffer.set(false); - self.cursor_front_buffer.fetch_add(1); - self.cursor_sync_file.take(); - } - self.can_present.set(false); - self.has_damage.set(false); - self.cursor_changed.set(false); - Ok(()) - } - } - - fn perform_screencopies(&self, new_fb: &Option, output: &OutputNode) { - let active_fb; - let fb = match &new_fb { - Some(f) => f, - None => { - active_fb = self.active_framebuffer.borrow(); - match &*active_fb { - None => return, - Some(f) => f, - } - } - }; - let render_hardware_cursor = self.cursor_enabled.get(); - match &fb.direct_scanout_data { - None => { - output.perform_screencopies(&fb.tex, render_hardware_cursor, 0, 0, None); - } - Some(dsd) => { - output.perform_screencopies( - &dsd.tex, - render_hardware_cursor, - dsd.position.crtc_x, - dsd.position.crtc_y, - Some((dsd.position.crtc_width, dsd.position.crtc_height)), - ); - } - } - } - pub fn update_drm_feedback(&self) { let fb = self.compute_drm_feedback(); self.drm_feedback.set(fb); @@ -1312,7 +732,7 @@ impl Connector for MetalConnector { } fn damage(&self) { - self.has_damage.set(true); + self.has_damage.fetch_add(1); if self.can_present.get() { self.schedule_present(); } @@ -1511,8 +931,8 @@ pub enum PlaneType { #[derive(Debug)] pub struct PlaneFormat { - format: &'static Format, - modifiers: IndexSet, + pub format: &'static Format, + pub modifiers: IndexSet, } pub struct MetalPlane { @@ -1595,7 +1015,7 @@ fn create_connector( non_desktop_override: Default::default(), lease: Cell::new(None), can_present: Cell::new(true), - has_damage: Cell::new(true), + has_damage: NumCell::new(1), primary_plane: Default::default(), cursor_plane: Default::default(), crtc: Default::default(), @@ -1621,6 +1041,7 @@ fn create_connector( next_flip_nsec: Cell::new(0), tearing_requested: Cell::new(false), try_switch_format: Cell::new(false), + version: Default::default(), }); let futures = ConnectorFutures { _present: backend @@ -1973,7 +1394,7 @@ struct Preserve { } impl MetalBackend { - fn check_render_context(&self, dev: &Rc) -> bool { + pub fn check_render_context(&self, dev: &Rc) -> bool { let ctx = match self.ctx.get() { Some(ctx) => ctx, None => return false, @@ -2368,7 +1789,7 @@ impl MetalBackend { ) -> Result<(), MetalError> { for connector in dev.connectors.lock().values() { connector.can_present.set(true); - connector.has_damage.set(true); + connector.has_damage.fetch_add(1); connector.cursor_changed.set(true); } if dev.unprocessed_change.get() { @@ -2430,7 +1851,7 @@ impl MetalBackend { if let Some(fb) = connector.next_framebuffer.take() { *connector.active_framebuffer.borrow_mut() = Some(fb); } - if connector.has_damage.get() + if connector.has_damage.is_not_zero() || connector.cursor_damage.get() || connector.cursor_changed.get() { @@ -2498,6 +1919,7 @@ impl MetalBackend { connector.cursor_plane.set(None); connector.cursor_enabled.set(false); connector.crtc.set(None); + connector.version.fetch_add(1); let dd = connector.display.borrow_mut(); dd.crtc_id.value.set(DrmCrtc::NONE); changes.change_object(connector.id, |c| { @@ -2748,6 +2170,7 @@ impl MetalBackend { } let crtc = dev.dev.crtcs.get(&crtc_id).unwrap(); connector.crtc.set(Some(crtc.clone())); + connector.version.fetch_add(1); crtc.connector.set(Some(connector.clone())); if !crtc.active.value.get() { log::debug!("Crtc is not active"); @@ -3004,6 +2427,7 @@ impl MetalBackend { c.change(crtc.vrr_enabled.id, dd.should_enable_vrr() as _); }); connector.crtc.set(Some(crtc.clone())); + connector.version.fetch_add(1); dd.crtc_id.value.set(crtc.id); crtc.connector.set(Some(connector.clone())); crtc.active.value.set(true); @@ -3157,6 +2581,7 @@ impl MetalBackend { connector.cursor_enabled.set(false); connector.buffer_format.set(buffer_format); connector.try_switch_format.set(false); + connector.version.fetch_add(1); Ok(()) } @@ -3177,7 +2602,7 @@ impl MetalBackend { dd.mode.as_ref().unwrap(), ); } - connector.has_damage.set(true); + connector.has_damage.fetch_add(1); connector.cursor_changed.set(true); connector.schedule_present(); } @@ -3185,31 +2610,31 @@ impl MetalBackend { #[derive(Debug)] pub struct RenderBuffer { - drm: Rc, - _dev_bo: GbmBo, - _render_bo: Option, + pub drm: Rc, + pub _dev_bo: GbmBo, + pub _render_bo: Option, // ctx = dev // buffer location = dev - dev_fb: Rc, + pub dev_fb: Rc, // ctx = dev // buffer location = render - dev_tex: Option>, + pub dev_tex: Option>, // ctx = render // buffer location = render - render_tex: Rc, + pub render_tex: Rc, // ctx = render // buffer location = render - render_fb: Option>, + pub render_fb: Option>, } impl RenderBuffer { - fn render_fb(&self) -> Rc { + pub fn render_fb(&self) -> Rc { self.render_fb .clone() .unwrap_or_else(|| self.dev_fb.clone()) } - fn copy_to_dev(&self, sync_file: Option) -> Result, MetalError> { + pub fn copy_to_dev(&self, sync_file: Option) -> Result, MetalError> { let Some(tex) = &self.dev_tex else { return Ok(sync_file); }; diff --git a/src/gfx_api.rs b/src/gfx_api.rs index c31c3849..d1a74828 100644 --- a/src/gfx_api.rs +++ b/src/gfx_api.rs @@ -288,7 +288,7 @@ impl dyn GfxFramebuffer { } pub fn logical_size(&self, transform: Transform) -> (i32, i32) { - transform.maybe_swap(self.physical_size()) + logical_size(self.physical_size(), transform) } pub fn renderer_base<'a>( @@ -297,16 +297,7 @@ impl dyn GfxFramebuffer { scale: Scale, transform: Transform, ) -> RendererBase<'a> { - let (width, height) = self.logical_size(transform); - RendererBase { - ops, - scaled: scale != 1, - scale, - scalef: scale.to_f64(), - transform, - fb_width: width as _, - fb_height: height as _, - } + renderer_base(self.physical_size(), ops, scale, transform) } pub fn copy_texture( @@ -362,69 +353,19 @@ impl dyn GfxFramebuffer { transform: Transform, visualizer: Option<&DamageVisualizer>, ) -> GfxRenderPass { - let mut ops = vec![]; - let mut renderer = Renderer { - base: self.renderer_base(&mut ops, scale, transform), + create_render_pass( + self.physical_size(), + node, state, + cursor_rect, result, - logical_extents: node.node_absolute_position().at_point(0, 0), - pixel_extents: { - let (width, height) = self.logical_size(transform); - Rect::new(0, 0, width, height).unwrap() - }, - }; - node.node_render(&mut renderer, 0, 0, None); - if let Some(rect) = cursor_rect { - let seats = state.globals.lock_seats(); - for seat in seats.values() { - let (x, y) = seat.pointer_cursor().position_int(); - if let Some(im) = seat.input_method() { - for (_, popup) in &im.popups { - if popup.surface.node_visible() { - let pos = popup.surface.buffer_abs_pos.get(); - let extents = popup.surface.extents.get().move_(pos.x1(), pos.y1()); - if extents.intersects(&rect) { - let (x, y) = rect.translate(pos.x1(), pos.y1()); - renderer.render_surface(&popup.surface, x, y, None); - } - } - } - } - if let Some(drag) = seat.toplevel_drag() { - drag.render(&mut renderer, &rect, x, y); - } - if let Some(dnd_icon) = seat.dnd_icon() { - dnd_icon.render(&mut renderer, &rect, x, y); - } - if render_cursor { - let cursor_user_group = seat.cursor_group(); - if render_hardware_cursor || !cursor_user_group.hardware_cursor() { - if let Some(cursor_user) = cursor_user_group.active() { - if let Some(cursor) = cursor_user.get() { - cursor.tick(); - let (mut x, mut y) = cursor_user.position(); - x -= Fixed::from_int(rect.x1()); - y -= Fixed::from_int(rect.y1()); - cursor.render(&mut renderer, x, y); - } - } - } - } - } - } - if let Some(visualizer) = visualizer { - if let Some(cursor_rect) = cursor_rect { - visualizer.render(&cursor_rect, &mut renderer.base); - } - } - let c = match black_background { - true => Color::SOLID_BLACK, - false => state.theme.colors.background.get(), - }; - GfxRenderPass { - ops, - clear: Some(c), - } + scale, + render_cursor, + render_hardware_cursor, + black_background, + transform, + visualizer, + ) } pub fn perform_render_pass(&self, pass: &GfxRenderPass) -> Result, GfxError> { @@ -691,3 +632,103 @@ impl Drop for PendingShmUpload { self.cancel.cancel(self.id); } } + +pub fn create_render_pass( + physical_size: (i32, i32), + node: &dyn Node, + state: &State, + cursor_rect: Option, + result: Option<&mut RenderResult>, + scale: Scale, + render_cursor: bool, + render_hardware_cursor: bool, + black_background: bool, + transform: Transform, + visualizer: Option<&DamageVisualizer>, +) -> GfxRenderPass { + let mut ops = vec![]; + let mut renderer = Renderer { + base: renderer_base(physical_size, &mut ops, scale, transform), + state, + result, + logical_extents: node.node_absolute_position().at_point(0, 0), + pixel_extents: { + let (width, height) = logical_size(physical_size, transform); + Rect::new(0, 0, width, height).unwrap() + }, + }; + node.node_render(&mut renderer, 0, 0, None); + if let Some(rect) = cursor_rect { + let seats = state.globals.lock_seats(); + for seat in seats.values() { + let (x, y) = seat.pointer_cursor().position_int(); + if let Some(im) = seat.input_method() { + for (_, popup) in &im.popups { + if popup.surface.node_visible() { + let pos = popup.surface.buffer_abs_pos.get(); + let extents = popup.surface.extents.get().move_(pos.x1(), pos.y1()); + if extents.intersects(&rect) { + let (x, y) = rect.translate(pos.x1(), pos.y1()); + renderer.render_surface(&popup.surface, x, y, None); + } + } + } + } + if let Some(drag) = seat.toplevel_drag() { + drag.render(&mut renderer, &rect, x, y); + } + if let Some(dnd_icon) = seat.dnd_icon() { + dnd_icon.render(&mut renderer, &rect, x, y); + } + if render_cursor { + let cursor_user_group = seat.cursor_group(); + if render_hardware_cursor || !cursor_user_group.hardware_cursor() { + if let Some(cursor_user) = cursor_user_group.active() { + if let Some(cursor) = cursor_user.get() { + cursor.tick(); + let (mut x, mut y) = cursor_user.position(); + x -= Fixed::from_int(rect.x1()); + y -= Fixed::from_int(rect.y1()); + cursor.render(&mut renderer, x, y); + } + } + } + } + } + } + if let Some(visualizer) = visualizer { + if let Some(cursor_rect) = cursor_rect { + visualizer.render(&cursor_rect, &mut renderer.base); + } + } + let c = match black_background { + true => Color::SOLID_BLACK, + false => state.theme.colors.background.get(), + }; + GfxRenderPass { + ops, + clear: Some(c), + } +} + +pub fn renderer_base<'a>( + physical_size: (i32, i32), + ops: &'a mut Vec, + scale: Scale, + transform: Transform, +) -> RendererBase<'a> { + let (width, height) = logical_size(physical_size, transform); + RendererBase { + ops, + scaled: scale != 1, + scale, + scalef: scale.to_f64(), + transform, + fb_width: width as _, + fb_height: height as _, + } +} + +pub fn logical_size(physical_size: (i32, i32), transform: Transform) -> (i32, i32) { + transform.maybe_swap(physical_size) +} diff --git a/src/utils/numcell.rs b/src/utils/numcell.rs index 7ef51ebe..18739943 100644 --- a/src/utils/numcell.rs +++ b/src/utils/numcell.rs @@ -74,6 +74,22 @@ impl NumCell { { self.t.set(self.t.get() & n); } + + #[inline(always)] + pub fn is_zero(&self) -> bool + where + T: Eq + Copy + Default, + { + self.t.get() == T::default() + } + + #[inline(always)] + pub fn is_not_zero(&self) -> bool + where + T: Eq + Copy + Default, + { + !self.is_zero() + } } impl + Copy> BitOr for &'_ NumCell { From a1985b2870f61d5da08df97fce903523cd805679 Mon Sep 17 00:00:00 2001 From: Julian Orth Date: Tue, 10 Sep 2024 14:29:44 +0200 Subject: [PATCH 08/10] metal: disable implicit sync in KMS --- src/backends/metal.rs | 4 +++- src/backends/metal/present.rs | 12 ++++++++---- src/backends/metal/video.rs | 12 ++++++++++++ src/video/drm/sync_obj.rs | 18 +++++++++++++++++- src/video/drm/sys.rs | 4 ++-- 5 files changed, 42 insertions(+), 8 deletions(-) diff --git a/src/backends/metal.rs b/src/backends/metal.rs index 0d2765e2..759e60f5 100644 --- a/src/backends/metal.rs +++ b/src/backends/metal.rs @@ -16,7 +16,7 @@ use { }, dbus::{DbusError, SignalHandler}, drm_feedback::DrmFeedback, - gfx_api::GfxError, + gfx_api::{GfxError, SyncFile}, ifs::{ wl_output::OutputId, wl_seat::tablet::{ @@ -150,6 +150,7 @@ pub struct MetalBackend { pause_handler: Cell>, resume_handler: Cell>, ctx: CloneCell>>, + signaled_sync_file: CloneCell>, default_feedback: CloneCell>>, persistent_display_data: CopyHashMap, Rc>, } @@ -324,6 +325,7 @@ pub async fn create(state: &Rc) -> Result, MetalError> { pause_handler: Default::default(), resume_handler: Default::default(), ctx: Default::default(), + signaled_sync_file: Default::default(), default_feedback: Default::default(), persistent_display_data: Default::default(), }); diff --git a/src/backends/metal/present.rs b/src/backends/metal/present.rs index aa26e838..d7e0bfa6 100644 --- a/src/backends/metal/present.rs +++ b/src/backends/metal/present.rs @@ -292,7 +292,9 @@ impl MetalConnector { change!(c, plane.crtc_w, crtc_w); change!(c, plane.crtc_h, crtc_h); if !try_async_flip && !self.dev.is_nvidia { - c.change(plane.in_fence_fd, -1i32 as u64); + if let Some(sf) = self.backend.signaled_sync_file.get() { + c.change(plane.in_fence_fd, sf.0.raw() as u64); + } } }); } else { @@ -329,7 +331,9 @@ impl MetalConnector { c.change(plane.src_w.id, (*width as u64) << 16); c.change(plane.src_h.id, (*height as u64) << 16); if !self.dev.is_nvidia { - c.change(plane.in_fence_fd, -1i32 as u64); + if let Some(sf) = self.backend.signaled_sync_file.get() { + c.change(plane.in_fence_fd, sf.0.raw() as u64); + } } }); } @@ -512,8 +516,8 @@ impl MetalConnector { } ct }; - if let AcquireSync::None = ct.acquire_sync { - // Cannot perform scanout without sync. + if let AcquireSync::None | AcquireSync::Implicit = ct.acquire_sync { + // Cannot perform scanout without explicit sync. return None; } if ct.source.buffer_transform != ct.target.output_transform { diff --git a/src/backends/metal/video.rs b/src/backends/metal/video.rs index 6895a7e9..79aa6470 100644 --- a/src/backends/metal/video.rs +++ b/src/backends/metal/video.rs @@ -2029,6 +2029,18 @@ impl MetalBackend { } } let ctx = dev.ctx.get(); + if self.signaled_sync_file.is_none() { + if let Some(sync) = ctx.gfx.sync_obj_ctx() { + match sync.create_signaled_sync_file() { + Ok(sf) => { + self.signaled_sync_file.set(Some(sf)); + } + Err(e) => { + log::warn!("Could not create signaled sync file: {}", ErrorFmt(e)); + } + } + } + } 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)), diff --git a/src/video/drm/sync_obj.rs b/src/video/drm/sync_obj.rs index 97de5b7a..39c1b6ec 100644 --- a/src/video/drm/sync_obj.rs +++ b/src/video/drm/sync_obj.rs @@ -13,7 +13,8 @@ use { sys::{ sync_ioc_merge, sync_obj_create, sync_obj_destroy, sync_obj_eventfd, sync_obj_fd_to_handle, sync_obj_handle_to_fd, sync_obj_signal, sync_obj_transfer, - DRM_SYNCOBJ_FD_TO_HANDLE_FLAGS_IMPORT_SYNC_FILE, + DRM_SYNCOBJ_CREATE_SIGNALED, DRM_SYNCOBJ_FD_TO_HANDLE_FLAGS_IMPORT_SYNC_FILE, + DRM_SYNCOBJ_HANDLE_TO_FD_FLAGS_EXPORT_SYNC_FILE, DRM_SYNCOBJ_WAIT_FLAGS_WAIT_AVAILABLE, }, DrmError, @@ -125,6 +126,21 @@ impl SyncObjCtx { Ok(sync_obj) } + pub fn create_signaled_sync_file(&self) -> Result { + let handle = sync_obj_create(self.inner.drm.raw(), DRM_SYNCOBJ_CREATE_SIGNALED) + .map_err(DrmError::CreateSyncObj)?; + let handle = SyncObjHandle(handle); + let fd = sync_obj_handle_to_fd( + self.inner.drm.raw(), + handle.0, + DRM_SYNCOBJ_HANDLE_TO_FD_FLAGS_EXPORT_SYNC_FILE, + ); + destroy(&self.inner.drm, handle); + fd.map_err(DrmError::ExportSyncObj) + .map(Rc::new) + .map(SyncFile) + } + pub fn wait_for_point( &self, eventfd: &OwnedFd, diff --git a/src/video/drm/sys.rs b/src/video/drm/sys.rs index 556a8ef0..225d0693 100644 --- a/src/video/drm/sys.rs +++ b/src/video/drm/sys.rs @@ -1177,7 +1177,7 @@ pub struct drm_format_modifier { unsafe impl Pod for drm_format_modifier {} -// pub const DRM_SYNCOBJ_CREATE_SIGNALED: u32 = 1 << 0; +pub const DRM_SYNCOBJ_CREATE_SIGNALED: u32 = 1 << 0; #[repr(C)] struct drm_syncobj_create { @@ -1212,7 +1212,7 @@ pub fn sync_obj_destroy(drm: c::c_int, handle: u32) -> Result<(), OsError> { } pub const DRM_SYNCOBJ_FD_TO_HANDLE_FLAGS_IMPORT_SYNC_FILE: u32 = 1 << 0; -// pub const DRM_SYNCOBJ_HANDLE_TO_FD_FLAGS_EXPORT_SYNC_FILE: u32 = 1 << 0; +pub const DRM_SYNCOBJ_HANDLE_TO_FD_FLAGS_EXPORT_SYNC_FILE: u32 = 1 << 0; #[repr(C)] struct drm_syncobj_handle { From a37ce1acda9524878f9feb518fd728d7df5c689f Mon Sep 17 00:00:00 2001 From: Julian Orth Date: Tue, 10 Sep 2024 15:38:38 +0200 Subject: [PATCH 09/10] metal: request crtc sequence events --- src/backends/metal/video.rs | 75 ++++++++++++++++++++++++++++++++++++- src/video/drm.rs | 28 ++++++++++++-- src/video/drm/sys.rs | 37 +++++++++++++++++- 3 files changed, 135 insertions(+), 5 deletions(-) diff --git a/src/backends/metal/video.rs b/src/backends/metal/video.rs index 79aa6470..627c497a 100644 --- a/src/backends/metal/video.rs +++ b/src/backends/metal/video.rs @@ -463,6 +463,7 @@ pub struct MetalConnector { pub try_switch_format: Cell, pub version: NumCell, + pub sequence: Cell, } impl Debug for MetalConnector { @@ -712,6 +713,16 @@ impl MetalConnector { }, } } + + fn queue_sequence(&self) { + if let Some(crtc) = self.crtc.get() { + if let Err(e) = self.master.queue_sequence(crtc.id) { + log::error!("Could not queue a CRTC sequence: {}", ErrorFmt(e)); + } else { + crtc.have_queued_sequence.set(true); + } + } + } } impl Connector for MetalConnector { @@ -908,6 +919,7 @@ pub struct MetalCrtc { pub vrr_enabled: MutableProperty, pub mode_blob: CloneCell>>, + pub have_queued_sequence: Cell, } impl Debug for MetalCrtc { @@ -1042,6 +1054,7 @@ fn create_connector( tearing_requested: Cell::new(false), try_switch_format: Cell::new(false), version: Default::default(), + sequence: Default::default(), }); let futures = ConnectorFutures { _present: backend @@ -1244,6 +1257,7 @@ fn create_crtc( out_fence_ptr: props.get("OUT_FENCE_PTR")?.id, vrr_enabled: props.get("VRR_ENABLED")?.map(|v| v == 1), mode_blob: Default::default(), + have_queued_sequence: Cell::new(false), }) } @@ -1828,9 +1842,64 @@ impl MetalBackend { sequence, crtc_id, } => self.handle_drm_flip_event(dev, crtc_id, tv_sec, tv_usec, sequence), + DrmEvent::Sequence { + time_ns, + sequence, + crtc_id, + } => self.handle_drm_sequence_event(dev, crtc_id, time_ns, sequence), } } + fn update_sequence(&self, connector: &Rc, new: u64) { + if connector.sequence.replace(new) == new { + return; + } + // nothing + } + + fn update_u32_sequence(&self, connector: &Rc, sequence: u32) { + let old = connector.sequence.get(); + let mut new = (old & !(u32::MAX as u64)) | (sequence as u64); + if new < old { + new += 1 << u32::BITS; + if new < old { + log::warn!("Ignoring nonsensical sequence {sequence} (old = {old})"); + return; + } + } + if new > old + (1 << (u32::BITS - 1)) { + new = new.saturating_sub(1 << u32::BITS); + if new < old { + return; + } + } + self.update_sequence(connector, new); + } + + fn handle_drm_sequence_event( + self: &Rc, + dev: &Rc, + crtc_id: DrmCrtc, + time_ns: i64, + sequence: u64, + ) { + let crtc = match dev.dev.crtcs.get(&crtc_id) { + Some(c) => c, + _ => return, + }; + crtc.have_queued_sequence.set(false); + let connector = match crtc.connector.get() { + Some(c) => c, + _ => return, + }; + self.update_sequence(&connector, sequence); + connector.queue_sequence(); + let dd = connector.display.borrow(); + connector + .next_flip_nsec + .set(time_ns as u64 + dd.refresh as u64); + } + fn handle_drm_flip_event( self: &Rc, dev: &Rc, @@ -1847,6 +1916,10 @@ impl MetalBackend { Some(c) => c, _ => return, }; + if !crtc.have_queued_sequence.get() { + connector.queue_sequence(); + } + self.update_u32_sequence(&connector, sequence); connector.can_present.set(true); if let Some(fb) = connector.next_framebuffer.take() { *connector.active_framebuffer.borrow_mut() = Some(fb); @@ -1877,7 +1950,7 @@ impl MetalBackend { tv_sec as _, tv_usec * 1000, refresh, - sequence as _, + connector.sequence.get(), KIND_VSYNC | KIND_HW_COMPLETION, ); let _ = fb.client.remove_obj(&*fb); diff --git a/src/video/drm.rs b/src/video/drm.rs index ceca8c77..be0a05e2 100644 --- a/src/video/drm.rs +++ b/src/video/drm.rs @@ -40,9 +40,9 @@ use crate::{ video::{ dmabuf::DmaBuf, drm::sys::{ - auth_magic, drm_format_modifier, drm_format_modifier_blob, drop_master, get_version, - revoke_lease, DRM_CAP_ATOMIC_ASYNC_PAGE_FLIP, DRM_CAP_CURSOR_HEIGHT, - DRM_CAP_CURSOR_WIDTH, FORMAT_BLOB_CURRENT, + auth_magic, drm_event_crtc_sequence, drm_format_modifier, drm_format_modifier_blob, + drop_master, get_version, queue_sequence, revoke_lease, DRM_CAP_ATOMIC_ASYNC_PAGE_FLIP, + DRM_CAP_CURSOR_HEIGHT, DRM_CAP_CURSOR_WIDTH, FORMAT_BLOB_CURRENT, }, Modifier, INVALID_MODIFIER, }, @@ -142,6 +142,8 @@ pub enum DrmError { CreateLease(#[source] OsError), #[error("Could not drop DRM master")] DropMaster(#[source] OsError), + #[error("Could not queue a CRTC sequence")] + QueueSequence(#[source] OsError), } fn render_node_name(fd: c::c_int) -> Result { @@ -223,6 +225,10 @@ impl Drm { pub fn is_master(&self) -> bool { auth_magic(self.fd.raw(), 0) != Err(OsError(c::EACCES)) } + + pub fn queue_sequence(&self, crtc: DrmCrtc) -> Result<(), DrmError> { + queue_sequence(self.fd.raw(), crtc).map_err(DrmError::QueueSequence) + } } pub struct InFormat { @@ -554,6 +560,17 @@ impl DrmMaster { crtc_id: DrmCrtc(event.crtc_id), }); } + sys::DRM_EVENT_CRTC_SEQUENCE => { + let event: drm_event_crtc_sequence = match uapi::pod_read_init(buf) { + Ok(e) => e, + _ => return Err(DrmError::InvalidRead), + }; + self.events.push(DrmEvent::Sequence { + time_ns: event.time_ns, + sequence: event.sequence, + crtc_id: DrmCrtc(event.user_data as _), + }); + } _ => {} } buf = &buf[len..]; @@ -582,6 +599,11 @@ pub enum DrmEvent { sequence: u32, crtc_id: DrmCrtc, }, + Sequence { + time_ns: i64, + sequence: u64, + crtc_id: DrmCrtc, + }, } pub struct DrmFramebuffer { diff --git a/src/video/drm/sys.rs b/src/video/drm/sys.rs index 225d0693..65bb050e 100644 --- a/src/video/drm/sys.rs +++ b/src/video/drm/sys.rs @@ -1048,7 +1048,6 @@ pub fn gem_close(fd: c::c_int, handle: u32) -> Result<(), OsError> { #[expect(dead_code)] pub const DRM_EVENT_VBLANK: u32 = 0x01; pub const DRM_EVENT_FLIP_COMPLETE: u32 = 0x02; -#[expect(dead_code)] pub const DRM_EVENT_CRTC_SEQUENCE: u32 = 0x03; #[repr(C)] @@ -1071,6 +1070,16 @@ pub struct drm_event_vblank { unsafe impl Pod for drm_event_vblank {} +#[repr(C)] +pub struct drm_event_crtc_sequence { + pub base: drm_event, + pub user_data: u64, + pub time_ns: i64, + pub sequence: u64, +} + +unsafe impl Pod for drm_event_crtc_sequence {} + #[repr(C)] struct drm_mode_get_blob { blob_id: u32, @@ -1399,3 +1408,29 @@ pub fn auth_magic(fd: c::c_int, magic: c::c_uint) -> Result<(), OsError> { } Ok(()) } + +const DRM_CRTC_SEQUENCE_RELATIVE: u32 = 0x00000001; +// const DRM_CRTC_SEQUENCE_NEXT_ON_MISS: u32 = 0x00000002; + +#[repr(C)] +struct drm_crtc_queue_sequence { + crtc_id: u32, + flags: u32, + sequence: u64, + user_data: u64, +} + +const DRM_IOCTL_CRTC_QUEUE_SEQUENCE: u64 = drm_iowr::(0x3c); + +pub fn queue_sequence(fd: c::c_int, crtc: DrmCrtc) -> Result<(), OsError> { + let mut res = drm_crtc_queue_sequence { + crtc_id: crtc.0, + flags: DRM_CRTC_SEQUENCE_RELATIVE, + sequence: 1, + user_data: crtc.0 as _, + }; + unsafe { + ioctl(fd, DRM_IOCTL_CRTC_QUEUE_SEQUENCE, &mut res)?; + } + Ok(()) +} From 87d60d267eab1825509b35c29c3bac91b2393a84 Mon Sep 17 00:00:00 2001 From: Julian Orth Date: Tue, 10 Sep 2024 14:28:06 +0200 Subject: [PATCH 10/10] metal: commit 1.5ms before the next page flip --- src/backends/metal/present.rs | 57 +++++++++++++++++++++++++++++++++-- src/backends/metal/video.rs | 48 +++++++++++++++++++++++++++-- src/time.rs | 5 +++ src/utils/geometric_decay.rs | 4 --- 4 files changed, 105 insertions(+), 9 deletions(-) diff --git a/src/backends/metal/present.rs b/src/backends/metal/present.rs index d7e0bfa6..be9f266d 100644 --- a/src/backends/metal/present.rs +++ b/src/backends/metal/present.rs @@ -12,6 +12,7 @@ use { SyncFile, }, theme::Color, + time::Time, tree::OutputNode, utils::{errorfmt::ErrorFmt, oserror::OsError, transform_ext::TransformExt}, video::{ @@ -22,7 +23,11 @@ use { }, }, }, - std::rc::{Rc, Weak}, + std::{ + env, + rc::{Rc, Weak}, + sync::LazyLock, + }, uapi::c, }; @@ -79,19 +84,63 @@ enum CursorProgramming { }, } +pub const DEFAULT_PRE_COMMIT_MARGIN: u64 = 16_000_000; // 16ms +pub const MIN_POST_COMMIT_MARGIN: u64 = 1_500_000; // 1.5ms +pub const MAX_POST_COMMIT_MARGIN: u64 = 16_000_000; // 16ms +pub const DEFAULT_POST_COMMIT_MARGIN: u64 = MIN_POST_COMMIT_MARGIN; +pub const POST_COMMIT_MARGIN_DELTA: u64 = 500_000; // 500us + +static NO_FRAME_SCHEDULING: LazyLock = LazyLock::new(|| { + let res = env::var("JAY_NO_FRAME_SCHEDULING").ok().as_deref() == Some("1"); + if res { + log::warn!("Frame scheduling is disabled."); + } + res +}); + impl MetalConnector { pub fn schedule_present(&self) { self.present_trigger.trigger(); } pub async fn present_loop(self: Rc) { + let mut cur_sec = 0; + let mut max = 0; loop { self.present_trigger.triggered().await; + if !self.can_present.get() { + continue; + } + let mut expected_sequence = self.sequence.get() + 1; + let mut start = Time::now_unchecked(); + let use_frame_scheduling = !self.try_async_flip() && !*NO_FRAME_SCHEDULING; + if use_frame_scheduling { + let margin = self.pre_commit_margin.get() + self.post_commit_margin.get(); + let next_present = self.next_flip_nsec.get().saturating_sub(margin); + if start.nsec() < next_present { + self.state.ring.timeout(next_present).await.unwrap(); + start = Time::now_unchecked(); + } else { + expected_sequence += 1; + } + } if let Err(e) = self.present_once().await { log::error!("Could not present: {}", ErrorFmt(e)); continue; } + if use_frame_scheduling { + self.expected_sequence.set(Some(expected_sequence)); + } self.state.set_backend_idle(false); + let duration = start.elapsed(); + max = max.max(duration.as_nanos() as _); + if start.0.tv_sec != cur_sec { + cur_sec = start.0.tv_sec; + self.pre_commit_margin_decay.add(max); + self.pre_commit_margin + .set(self.pre_commit_margin_decay.get()); + max = 0; + } } } @@ -244,6 +293,10 @@ impl MetalConnector { } } + fn try_async_flip(&self) -> bool { + self.tearing_requested.get() && self.dev.supports_async_commit + } + fn program_connector( &self, version: u64, @@ -253,7 +306,7 @@ impl MetalConnector { new_fb: Option<&PresentFb>, ) -> Result<(), MetalError> { let mut changes = self.master.change(); - let mut try_async_flip = self.tearing_requested.get() && self.dev.supports_async_commit; + let mut try_async_flip = self.try_async_flip(); macro_rules! change { ($c:expr, $prop:expr, $new:expr) => {{ if $prop.value.get() != $new { diff --git a/src/backends/metal/video.rs b/src/backends/metal/video.rs index 627c497a..3c80bf87 100644 --- a/src/backends/metal/video.rs +++ b/src/backends/metal/video.rs @@ -8,7 +8,11 @@ use { HardwareCursorUpdate, Mode, MonitorInfo, }, backends::metal::{ - present::{DirectScanoutCache, PresentFb}, + present::{ + DirectScanoutCache, PresentFb, DEFAULT_POST_COMMIT_MARGIN, + DEFAULT_PRE_COMMIT_MARGIN, MAX_POST_COMMIT_MARGIN, MIN_POST_COMMIT_MARGIN, + POST_COMMIT_MARGIN_DELTA, + }, MetalBackend, MetalError, }, drm_feedback::DrmFeedback, @@ -27,8 +31,8 @@ use { udev::UdevDevice, utils::{ asyncevent::AsyncEvent, bitflags::BitflagsExt, cell_ext::CellExt, clonecell::CloneCell, - copyhashmap::CopyHashMap, errorfmt::ErrorFmt, numcell::NumCell, on_change::OnChange, - opaque_cell::OpaqueCell, oserror::OsError, + copyhashmap::CopyHashMap, errorfmt::ErrorFmt, geometric_decay::GeometricDecay, + numcell::NumCell, on_change::OnChange, opaque_cell::OpaqueCell, oserror::OsError, }, video::{ dmabuf::DmaBufId, @@ -464,6 +468,13 @@ pub struct MetalConnector { pub version: NumCell, pub sequence: Cell, + pub expected_sequence: Cell>, + pub pre_commit_margin: Cell, + pub pre_commit_margin_decay: GeometricDecay, + pub post_commit_margin: Cell, + pub post_commit_margin_decay: GeometricDecay, + pub vblank_miss_sec: Cell, + pub vblank_miss_this_sec: NumCell, } impl Debug for MetalConnector { @@ -1055,6 +1066,13 @@ fn create_connector( try_switch_format: Cell::new(false), version: Default::default(), sequence: Default::default(), + expected_sequence: Default::default(), + pre_commit_margin_decay: GeometricDecay::new(0.5, DEFAULT_PRE_COMMIT_MARGIN), + pre_commit_margin: Cell::new(DEFAULT_PRE_COMMIT_MARGIN), + post_commit_margin_decay: GeometricDecay::new(0.1, DEFAULT_POST_COMMIT_MARGIN), + post_commit_margin: Cell::new(DEFAULT_POST_COMMIT_MARGIN), + vblank_miss_sec: Cell::new(0), + vblank_miss_this_sec: Default::default(), }); let futures = ConnectorFutures { _present: backend @@ -1924,6 +1942,30 @@ impl MetalBackend { if let Some(fb) = connector.next_framebuffer.take() { *connector.active_framebuffer.borrow_mut() = Some(fb); } + if let Some(expected) = connector.expected_sequence.take() { + if connector.vblank_miss_sec.replace(tv_sec) != tv_sec { + let n_missed = connector.vblank_miss_this_sec.replace(0); + if n_missed > 0 { + log::debug!("{}: Missed {n_missed} page flips", connector.kernel_id()); + let new_margin = (connector.post_commit_margin.get() + + POST_COMMIT_MARGIN_DELTA) + .min(MAX_POST_COMMIT_MARGIN); + connector.post_commit_margin_decay.reset(new_margin); + connector.post_commit_margin.set(new_margin); + } else { + connector + .post_commit_margin_decay + .add(MIN_POST_COMMIT_MARGIN); + connector + .post_commit_margin + .set(connector.post_commit_margin_decay.get()); + } + } + let actual = connector.sequence.get(); + if expected < actual { + connector.vblank_miss_this_sec.fetch_add(1); + } + } if connector.has_damage.is_not_zero() || connector.cursor_damage.get() || connector.cursor_changed.get() diff --git a/src/time.rs b/src/time.rs index 909d052d..aaddcf04 100644 --- a/src/time.rs +++ b/src/time.rs @@ -58,6 +58,11 @@ impl Time { let nsec = self.0.tv_nsec as u64 / 1_000_000; sec + nsec } + + pub fn elapsed(self) -> Duration { + let now = Self::now_unchecked(); + now - self + } } impl Eq for Time {} diff --git a/src/utils/geometric_decay.rs b/src/utils/geometric_decay.rs index 83466724..8455cd93 100644 --- a/src/utils/geometric_decay.rs +++ b/src/utils/geometric_decay.rs @@ -7,7 +7,6 @@ pub struct GeometricDecay { } impl GeometricDecay { - #[expect(dead_code)] pub fn new(mut p1: f64, reset: u64) -> Self { if p1.is_nan() || p1 < 0.01 { p1 = 0.01; @@ -23,17 +22,14 @@ impl GeometricDecay { } } - #[expect(dead_code)] pub fn reset(&self, v: u64) { self.v.set(v as f64 / self.p1); } - #[expect(dead_code)] pub fn get(&self) -> u64 { (self.p1 * self.v.get()) as u64 } - #[expect(dead_code)] pub fn add(&self, n: u64) { let v = n as f64 + self.p2 * self.v.get(); self.v.set(v);