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.
This commit is contained in:
Xaver Hugl 2024-07-06 14:50:17 +02:00
parent 43ab00a337
commit 1dab5fb328
7 changed files with 37 additions and 7 deletions

View file

@ -71,8 +71,8 @@ void DrmAtomicCommit::addBuffer(DrmPlane *plane, const std::shared_ptr<DrmFrameb
addProperty(plane->fbId, 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<DrmFramebuffer> &buffer, const std::shared_ptr<OutputFrame> &frame)
: DrmCommit(pipeline->gpu())
, m_pipeline(pipeline)

View file

@ -85,6 +85,7 @@ public:
std::optional<std::chrono::steady_clock::time_point> targetPageflipTime() const;
bool isReadyFor(std::chrono::steady_clock::time_point pageflipTarget) const;
bool isTearing() const;
private:
bool doCommit(uint32_t flags);

View file

@ -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<DrmAtomicCommit> &&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);

View file

@ -59,6 +59,7 @@ private:
std::chrono::nanoseconds m_minVblankInterval;
std::vector<std::unique_ptr<DrmAtomicCommit>> m_commitsToDelete;
bool m_vrr = false;
bool m_tearing = false;
std::chrono::nanoseconds m_safetyMargin{0};
};

View file

@ -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<DrmDevice> &&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;
}
}

View file

@ -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<OutputFrame> &frame)
{
m_desiredPresentationMode = frame->presentationMode();
const bool needsModeset = m_gpu->needsModeset();
bool success;
if (needsModeset) {

View file

@ -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;
};
}