From 1dab5fb328f74bbc5b122b067ea2830d76d7abc5 Mon Sep 17 00:00:00 2001 From: Xaver Hugl Date: Sat, 6 Jul 2024 14:50:17 +0200 Subject: [PATCH] backends/drm: support tearing with atomic modesetting Whenever tearing is desired, this does an atomic test to figure out if the current configuration can do tearing - if not, the backend just transparently falls back to synchronous commits. As the kernel (as of Linux 6.9) rejects all commits that are both async and modify more than the primary plane FB_ID property, this disables the cursor plane and IN_FENCE_FD usage, to make it more likely for the atomic commit to succeed. Once these restrictions are loosened, these checks can be removed as well. --- src/backends/drm/drm_commit.cpp | 21 +++++++++++++++++---- src/backends/drm/drm_commit.h | 1 + src/backends/drm/drm_commit_thread.cpp | 7 +++++-- src/backends/drm/drm_commit_thread.h | 1 + src/backends/drm/drm_gpu.cpp | 6 +++++- src/backends/drm/drm_output.cpp | 7 +++++++ src/backends/drm/drm_output.h | 1 + 7 files changed, 37 insertions(+), 7 deletions(-) diff --git a/src/backends/drm/drm_commit.cpp b/src/backends/drm/drm_commit.cpp index 7a0f260c41..ea34d49c54 100644 --- a/src/backends/drm/drm_commit.cpp +++ b/src/backends/drm/drm_commit.cpp @@ -71,8 +71,8 @@ void DrmAtomicCommit::addBuffer(DrmPlane *plane, const std::shared_ptrfbId, buffer ? buffer->framebufferId() : 0); m_buffers[plane] = buffer; m_frames[plane] = frame; - // atomic commits with IN_FENCE_FD fail with NVidia - if (plane->inFenceFd.isValid() && !plane->gpu()->isNVidia()) { + // atomic commits with IN_FENCE_FD fail with NVidia and (as of kernel 6.9) with tearing + if (plane->inFenceFd.isValid() && !plane->gpu()->isNVidia() && !isTearing()) { addProperty(plane->inFenceFd, buffer ? buffer->syncFd().get() : -1); } m_planes.emplace(plane); @@ -98,7 +98,11 @@ void DrmAtomicCommit::setPresentationMode(PresentationMode mode) bool DrmAtomicCommit::test() { - return doCommit(DRM_MODE_ATOMIC_TEST_ONLY | DRM_MODE_ATOMIC_NONBLOCK); + uint32_t flags = DRM_MODE_ATOMIC_TEST_ONLY | DRM_MODE_ATOMIC_NONBLOCK; + if (isTearing()) { + flags |= DRM_MODE_PAGE_FLIP_ASYNC; + } + return doCommit(flags); } bool DrmAtomicCommit::testAllowModeset() @@ -108,7 +112,11 @@ bool DrmAtomicCommit::testAllowModeset() bool DrmAtomicCommit::commit() { - return doCommit(DRM_MODE_ATOMIC_NONBLOCK | DRM_MODE_PAGE_FLIP_EVENT); + uint32_t flags = DRM_MODE_ATOMIC_NONBLOCK | DRM_MODE_PAGE_FLIP_EVENT; + if (isTearing()) { + flags |= DRM_MODE_PAGE_FLIP_ASYNC; + } + return doCommit(flags); } bool DrmAtomicCommit::commitModeset() @@ -244,6 +252,11 @@ bool DrmAtomicCommit::isReadyFor(std::chrono::steady_clock::time_point pageflipT return (!m_targetPageflipTime || pageflipTarget + s_pageflipSlop >= *m_targetPageflipTime) && areBuffersReadable(); } +bool DrmAtomicCommit::isTearing() const +{ + return m_mode == PresentationMode::Async || m_mode == PresentationMode::AdaptiveAsync; +} + DrmLegacyCommit::DrmLegacyCommit(DrmPipeline *pipeline, const std::shared_ptr &buffer, const std::shared_ptr &frame) : DrmCommit(pipeline->gpu()) , m_pipeline(pipeline) diff --git a/src/backends/drm/drm_commit.h b/src/backends/drm/drm_commit.h index ed9a3399e6..8877b27782 100644 --- a/src/backends/drm/drm_commit.h +++ b/src/backends/drm/drm_commit.h @@ -85,6 +85,7 @@ public: std::optional targetPageflipTime() const; bool isReadyFor(std::chrono::steady_clock::time_point pageflipTarget) const; + bool isTearing() const; private: bool doCommit(uint32_t flags); diff --git a/src/backends/drm/drm_commit_thread.cpp b/src/backends/drm/drm_commit_thread.cpp index b765dd7284..9d30433c23 100644 --- a/src/backends/drm/drm_commit_thread.cpp +++ b/src/backends/drm/drm_commit_thread.cpp @@ -63,7 +63,7 @@ DrmCommitThread::DrmCommitThread(DrmGpu *gpu, const QString &name) optimizeCommits(m_targetPageflipTime); if (!m_commits.front()->isReadyFor(m_targetPageflipTime)) { // no commit is ready yet, reschedule - if (m_vrr) { + if (m_vrr || m_tearing) { m_targetPageflipTime += 50us; } else { m_targetPageflipTime += m_minVblankInterval; @@ -108,6 +108,7 @@ void DrmCommitThread::submit() const bool success = commit->commit(); if (success) { m_vrr = vrr.value_or(m_vrr); + m_tearing = commit->isTearing(); m_committed = std::move(commit); m_commits.erase(m_commits.begin()); } else { @@ -267,7 +268,9 @@ void DrmCommitThread::addCommit(std::unique_ptr &&commit) std::unique_lock lock(m_mutex); m_commits.push_back(std::move(commit)); const auto now = std::chrono::steady_clock::now(); - if (m_vrr && now >= m_lastPageflip + m_minVblankInterval) { + if (m_tearing) { + m_targetPageflipTime = now; + } else if (m_vrr && now >= m_lastPageflip + m_minVblankInterval) { m_targetPageflipTime = now; } else { m_targetPageflipTime = estimateNextVblank(now); diff --git a/src/backends/drm/drm_commit_thread.h b/src/backends/drm/drm_commit_thread.h index 159628f068..7ada259d63 100644 --- a/src/backends/drm/drm_commit_thread.h +++ b/src/backends/drm/drm_commit_thread.h @@ -59,6 +59,7 @@ private: std::chrono::nanoseconds m_minVblankInterval; std::vector> m_commitsToDelete; bool m_vrr = false; + bool m_tearing = false; std::chrono::nanoseconds m_safetyMargin{0}; }; diff --git a/src/backends/drm/drm_gpu.cpp b/src/backends/drm/drm_gpu.cpp index 46d49e0e31..c45c63a9e5 100644 --- a/src/backends/drm/drm_gpu.cpp +++ b/src/backends/drm/drm_gpu.cpp @@ -40,6 +40,9 @@ #ifndef DRM_CLIENT_CAP_CURSOR_PLANE_HOTSPOT #define DRM_CLIENT_CAP_CURSOR_PLANE_HOTSPOT 6 #endif +#ifndef DRM_CAP_ATOMIC_ASYNC_PAGE_FLIP +#define DRM_CAP_ATOMIC_ASYNC_PAGE_FLIP 0x15 +#endif namespace KWin { @@ -89,8 +92,9 @@ DrmGpu::DrmGpu(DrmBackend *backend, int fd, std::unique_ptr &&device) initDrmResources(); if (m_atomicModeSetting == false) { - // only supported with legacy m_asyncPageflipSupported = drmGetCap(fd, DRM_CAP_ASYNC_PAGE_FLIP, &capability) == 0 && capability == 1; + } else { + m_asyncPageflipSupported = drmGetCap(fd, DRM_CAP_ATOMIC_ASYNC_PAGE_FLIP, &capability) == 0 && capability == 1; } } diff --git a/src/backends/drm/drm_output.cpp b/src/backends/drm/drm_output.cpp index 6fcfee79ae..80b971cf90 100644 --- a/src/backends/drm/drm_output.cpp +++ b/src/backends/drm/drm_output.cpp @@ -117,6 +117,12 @@ DrmLease *DrmOutput::lease() const bool DrmOutput::updateCursorLayer() { + const bool tearingDesired = m_desiredPresentationMode == PresentationMode::Async || m_desiredPresentationMode == PresentationMode::AdaptiveAsync; + if (m_pipeline->gpu()->atomicModeSetting() && tearingDesired && m_pipeline->cursorLayer() && m_pipeline->cursorLayer()->isEnabled()) { + // The kernel rejects async commits that change anything but the primary plane FB_ID + // This disables the hardware cursor, so it doesn't interfere with that + return false; + } return m_pipeline->updateCursor(); } @@ -287,6 +293,7 @@ void DrmOutput::updateDpmsMode(DpmsMode dpmsMode) bool DrmOutput::present(const std::shared_ptr &frame) { + m_desiredPresentationMode = frame->presentationMode(); const bool needsModeset = m_gpu->needsModeset(); bool success; if (needsModeset) { diff --git a/src/backends/drm/drm_output.h b/src/backends/drm/drm_output.h index ff4837d896..8c010d4606 100644 --- a/src/backends/drm/drm_output.h +++ b/src/backends/drm/drm_output.h @@ -94,6 +94,7 @@ private: QVector3D m_channelFactors = {1, 1, 1}; bool m_channelFactorsNeedShaderFallback = false; ColorDescription m_scanoutColorDescription = ColorDescription::sRGB; + PresentationMode m_desiredPresentationMode = PresentationMode::VSync; }; }