From 8e01c37f243c808ea28782b79e2e4450691e7eff Mon Sep 17 00:00:00 2001 From: Julian Orth Date: Thu, 3 Oct 2024 08:54:15 +0200 Subject: [PATCH 1/2] metal: emulate vblank events on the nvidia driver --- release-notes.md | 2 ++ src/backends/metal/video.rs | 23 +++++++++++++++++++---- src/compositor.rs | 1 + src/state.rs | 1 + src/tasks/connector.rs | 1 + src/tree/output.rs | 10 ++++++++++ src/utils/event_listener.rs | 8 ++++++++ 7 files changed, 42 insertions(+), 4 deletions(-) diff --git a/release-notes.md b/release-notes.md index 89b3875c..68353054 100644 --- a/release-notes.md +++ b/release-notes.md @@ -1,5 +1,7 @@ # Unreleased +- Emulate vblank events on the nvidia driver. + # 1.6.0 (2024-09-25) - Various bugfixes. diff --git a/src/backends/metal/video.rs b/src/backends/metal/video.rs index 12d9d147..eb17e8ea 100644 --- a/src/backends/metal/video.rs +++ b/src/backends/metal/video.rs @@ -740,8 +740,19 @@ impl MetalConnector { fn queue_sequence(&self) { if let Some(crtc) = self.crtc.get() { + if crtc.needs_vblank_emulation.get() { + return; + } if let Err(e) = self.master.queue_sequence(crtc.id) { - log::error!("Could not queue a CRTC sequence: {}", ErrorFmt(e)); + log::error!("Could not queue a CRTC sequence: {}", ErrorFmt(&e)); + if let DrmError::QueueSequence(OsError(c::EOPNOTSUPP)) = e { + if let Some(node) = self.state.root.outputs.get(&self.connector_id) { + log::warn!("{}: Switching to vblank emulation", self.kernel_id()); + crtc.needs_vblank_emulation.set(true); + node.global.connector.needs_vblank_emulation.set(true); + node.vblank(); + } + } } else { crtc.have_queued_sequence.set(true); } @@ -944,6 +955,7 @@ pub struct MetalCrtc { pub mode_blob: CloneCell>>, pub have_queued_sequence: Cell, + pub needs_vblank_emulation: Cell, } impl Debug for MetalCrtc { @@ -1291,6 +1303,7 @@ fn create_crtc( vrr_enabled: props.get("VRR_ENABLED")?.map(|v| v == 1), mode_blob: Default::default(), have_queued_sequence: Cell::new(false), + needs_vblank_emulation: Cell::new(false), }) } @@ -1955,6 +1968,10 @@ impl MetalBackend { connector.queue_sequence(); } self.update_u32_sequence(&connector, sequence); + let time_ns = tv_sec as u64 * 1_000_000_000 + tv_usec as u64 * 1000; + if crtc.needs_vblank_emulation.get() { + self.handle_drm_sequence_event(dev, crtc_id, time_ns as _, connector.sequence.get()); + } connector.can_present.set(true); if let Some(fb) = connector.next_framebuffer.take() { *connector.active_framebuffer.borrow_mut() = Some(fb); @@ -1976,9 +1993,7 @@ impl MetalBackend { { connector.schedule_present(); } - connector - .next_flip_nsec - .set(tv_sec as u64 * 1_000_000_000 + tv_usec as u64 * 1000 + dd.refresh as u64); + connector.next_flip_nsec.set(time_ns + dd.refresh as u64); { let mut flags = KIND_HW_COMPLETION; if connector.presentation_is_sync.get() { diff --git a/src/compositor.rs b/src/compositor.rs index c3bc5fc4..d9973c3a 100644 --- a/src/compositor.rs +++ b/src/compositor.rs @@ -507,6 +507,7 @@ fn create_dummy_output(state: &Rc) { drm_dev: None, async_event: Default::default(), damaged: Cell::new(false), + needs_vblank_emulation: Cell::new(false), }); let schedule = Rc::new(OutputSchedule::new( &state.ring, diff --git a/src/state.rs b/src/state.rs index 45c30708..39a9b63c 100644 --- a/src/state.rs +++ b/src/state.rs @@ -301,6 +301,7 @@ pub struct ConnectorData { pub drm_dev: Option>, pub async_event: Rc, pub damaged: Cell, + pub needs_vblank_emulation: Cell, } pub struct OutputData { diff --git a/src/tasks/connector.rs b/src/tasks/connector.rs index 48e74020..37d9c53b 100644 --- a/src/tasks/connector.rs +++ b/src/tasks/connector.rs @@ -32,6 +32,7 @@ pub fn handle(state: &Rc, connector: &Rc) { drm_dev: drm_dev.clone(), async_event: Rc::new(AsyncEvent::default()), damaged: Cell::new(false), + needs_vblank_emulation: Cell::new(false), }); if let Some(dev) = drm_dev { dev.connectors.set(id, data.clone()); diff --git a/src/tree/output.rs b/src/tree/output.rs index 93b43aef..490407ca 100644 --- a/src/tree/output.rs +++ b/src/tree/output.rs @@ -136,6 +136,16 @@ impl OutputNode { for listener in self.vblank_event.iter() { listener.after_vblank(); } + if self.global.connector.needs_vblank_emulation.get() { + if self.vblank_event.has_listeners() { + self.global.connector.damage(); + } else { + let connector = self.global.connector.clone(); + self.vblank_event.on_attach(Box::new(move || { + connector.damage(); + })); + } + } } pub fn presented(&self, tv_sec: u64, tv_nsec: u32, refresh: u32, seq: u64, flags: u32) { diff --git a/src/utils/event_listener.rs b/src/utils/event_listener.rs index aef88ff2..784ed72f 100644 --- a/src/utils/event_listener.rs +++ b/src/utils/event_listener.rs @@ -30,6 +30,14 @@ impl EventSource { iter: self.listeners.iter(), } } + + pub fn has_listeners(&self) -> bool { + self.listeners.is_not_empty() + } + + pub fn on_attach(&self, f: Box) { + self.on_attach.set(Some(f)); + } } pub struct EventSourceIter { From 9e66e45abee1950c73e34bcc2ac623c48c27d49d Mon Sep 17 00:00:00 2001 From: Julian Orth Date: Sat, 28 Sep 2024 19:49:55 +0200 Subject: [PATCH 2/2] cpu_worker: fix blocking wait for completions io_uring reads of eventfds must not be mixed with blocking reads since io_uring might be reading in a thread, stealing events from the blocking read. --- src/cpu_worker.rs | 43 +++++++++++++++++++++++++++++++------------ 1 file changed, 31 insertions(+), 12 deletions(-) diff --git a/src/cpu_worker.rs b/src/cpu_worker.rs index a17888b7..d693f534 100644 --- a/src/cpu_worker.rs +++ b/src/cpu_worker.rs @@ -11,7 +11,7 @@ use { ptr_ext::MutPtrExt, queue::AsyncQueue, stack::Stack, }, }, - parking_lot::Mutex, + parking_lot::{Condvar, Mutex}, std::{ any::Any, cell::{Cell, RefCell}, @@ -113,18 +113,25 @@ enum Job { unsafe impl Send for Job {} +#[derive(Default)] +struct CompletedJobsExchange { + queue: VecDeque, + condvar: Option>, +} + struct CpuWorkerData { next: CpuJobIds, jobs_to_enqueue: AsyncQueue, new_jobs: Arc>>, have_new_jobs: Rc, - completed_jobs_remote: Arc>>, + completed_jobs_remote: Arc>, completed_jobs_local: RefCell>, have_completed_jobs: Rc, pending_jobs: CopyHashMap>, ring: Rc, _stop: OwnedFd, pending_job_data_cache: Stack>, + sync_wake_condvar: Arc, } linear_ids!(CpuJobIds, CpuJobId, u64); @@ -172,12 +179,16 @@ impl Drop for PendingJob { self.job_data.state.set(PendingJobState::Abandoned); data.jobs_to_enqueue.push(Job::Cancel { id }); data.do_equeue_jobs(); - let mut buf = 0u64; - while data.pending_jobs.contains(&id) { - if let Err(e) = uapi::read(data.have_completed_jobs.raw(), &mut buf) { - panic!("Could not wait for job completions: {}", ErrorFmt(e)); - } + loop { data.dispatch_completions(); + if !data.pending_jobs.contains(&id) { + break; + } + let mut remote = data.completed_jobs_remote.lock(); + while remote.queue.is_empty() { + remote.condvar = Some(data.sync_wake_condvar.clone()); + data.sync_wake_condvar.wait(&mut remote); + } } } PendingJobState::Abandoned => {} @@ -204,7 +215,7 @@ impl CpuWorkerData { fn dispatch_completions(&self) { let completions = &mut *self.completed_jobs_local.borrow_mut(); - mem::swap(completions, &mut *self.completed_jobs_remote.lock()); + mem::swap(completions, &mut self.completed_jobs_remote.lock().queue); while let Some(id) = completions.pop_front() { let job_data = self.pending_jobs.remove(&id).unwrap(); let job = job_data.job.take().unwrap(); @@ -242,7 +253,7 @@ impl CpuWorkerData { impl CpuWorker { pub fn new(ring: &Rc, eng: &Rc) -> Result { let new_jobs: Arc>> = Default::default(); - let completed_jobs: Arc>> = Default::default(); + let completed_jobs: Arc> = Default::default(); let (stop_read, stop_write) = uapi::pipe2(c::O_CLOEXEC).map_err(|e| CpuWorkerError::Pipe(e.into()))?; let have_new_jobs = @@ -281,6 +292,7 @@ impl CpuWorker { ring: ring.clone(), _stop: stop_read, pending_job_data_cache: Default::default(), + sync_wake_condvar: Arc::new(Condvar::new()), }); Ok(Self { _completions_listener: eng.spawn( @@ -313,7 +325,7 @@ impl CpuWorker { fn work( new_jobs: Arc>>, - completed_jobs: Arc>>, + completed_jobs: Arc>, stop: OwnedFd, have_new_jobs: OwnedFd, have_completed_jobs: OwnedFd, @@ -343,7 +355,7 @@ fn work( struct Worker { eng: Rc, ring: Rc, - completed_jobs: Arc>>, + completed_jobs: Arc>, have_completed_jobs: OwnedFd, async_jobs: CopyHashMap, stopped: Cell, @@ -428,7 +440,14 @@ impl Worker { } fn send_completion(&self, id: CpuJobId) { - self.completed_jobs.lock().push_back(id); + let cv = { + let mut exchange = self.completed_jobs.lock(); + exchange.queue.push_back(id); + exchange.condvar.take() + }; + if let Some(cv) = cv { + cv.notify_all(); + } if let Err(e) = uapi::eventfd_write(self.have_completed_jobs.raw(), 1) { panic!("Could not signal job completion: {}", ErrorFmt(e)); }