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:
parent
43ab00a337
commit
1dab5fb328
7 changed files with 37 additions and 7 deletions
|
@ -71,8 +71,8 @@ void DrmAtomicCommit::addBuffer(DrmPlane *plane, const std::shared_ptr<DrmFrameb
|
||||||
addProperty(plane->fbId, buffer ? buffer->framebufferId() : 0);
|
addProperty(plane->fbId, buffer ? buffer->framebufferId() : 0);
|
||||||
m_buffers[plane] = buffer;
|
m_buffers[plane] = buffer;
|
||||||
m_frames[plane] = frame;
|
m_frames[plane] = frame;
|
||||||
// atomic commits with IN_FENCE_FD fail with NVidia
|
// atomic commits with IN_FENCE_FD fail with NVidia and (as of kernel 6.9) with tearing
|
||||||
if (plane->inFenceFd.isValid() && !plane->gpu()->isNVidia()) {
|
if (plane->inFenceFd.isValid() && !plane->gpu()->isNVidia() && !isTearing()) {
|
||||||
addProperty(plane->inFenceFd, buffer ? buffer->syncFd().get() : -1);
|
addProperty(plane->inFenceFd, buffer ? buffer->syncFd().get() : -1);
|
||||||
}
|
}
|
||||||
m_planes.emplace(plane);
|
m_planes.emplace(plane);
|
||||||
|
@ -98,7 +98,11 @@ void DrmAtomicCommit::setPresentationMode(PresentationMode mode)
|
||||||
|
|
||||||
bool DrmAtomicCommit::test()
|
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()
|
bool DrmAtomicCommit::testAllowModeset()
|
||||||
|
@ -108,7 +112,11 @@ bool DrmAtomicCommit::testAllowModeset()
|
||||||
|
|
||||||
bool DrmAtomicCommit::commit()
|
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()
|
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();
|
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)
|
DrmLegacyCommit::DrmLegacyCommit(DrmPipeline *pipeline, const std::shared_ptr<DrmFramebuffer> &buffer, const std::shared_ptr<OutputFrame> &frame)
|
||||||
: DrmCommit(pipeline->gpu())
|
: DrmCommit(pipeline->gpu())
|
||||||
, m_pipeline(pipeline)
|
, m_pipeline(pipeline)
|
||||||
|
|
|
@ -85,6 +85,7 @@ public:
|
||||||
|
|
||||||
std::optional<std::chrono::steady_clock::time_point> targetPageflipTime() const;
|
std::optional<std::chrono::steady_clock::time_point> targetPageflipTime() const;
|
||||||
bool isReadyFor(std::chrono::steady_clock::time_point pageflipTarget) const;
|
bool isReadyFor(std::chrono::steady_clock::time_point pageflipTarget) const;
|
||||||
|
bool isTearing() const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
bool doCommit(uint32_t flags);
|
bool doCommit(uint32_t flags);
|
||||||
|
|
|
@ -63,7 +63,7 @@ DrmCommitThread::DrmCommitThread(DrmGpu *gpu, const QString &name)
|
||||||
optimizeCommits(m_targetPageflipTime);
|
optimizeCommits(m_targetPageflipTime);
|
||||||
if (!m_commits.front()->isReadyFor(m_targetPageflipTime)) {
|
if (!m_commits.front()->isReadyFor(m_targetPageflipTime)) {
|
||||||
// no commit is ready yet, reschedule
|
// no commit is ready yet, reschedule
|
||||||
if (m_vrr) {
|
if (m_vrr || m_tearing) {
|
||||||
m_targetPageflipTime += 50us;
|
m_targetPageflipTime += 50us;
|
||||||
} else {
|
} else {
|
||||||
m_targetPageflipTime += m_minVblankInterval;
|
m_targetPageflipTime += m_minVblankInterval;
|
||||||
|
@ -108,6 +108,7 @@ void DrmCommitThread::submit()
|
||||||
const bool success = commit->commit();
|
const bool success = commit->commit();
|
||||||
if (success) {
|
if (success) {
|
||||||
m_vrr = vrr.value_or(m_vrr);
|
m_vrr = vrr.value_or(m_vrr);
|
||||||
|
m_tearing = commit->isTearing();
|
||||||
m_committed = std::move(commit);
|
m_committed = std::move(commit);
|
||||||
m_commits.erase(m_commits.begin());
|
m_commits.erase(m_commits.begin());
|
||||||
} else {
|
} else {
|
||||||
|
@ -267,7 +268,9 @@ void DrmCommitThread::addCommit(std::unique_ptr<DrmAtomicCommit> &&commit)
|
||||||
std::unique_lock lock(m_mutex);
|
std::unique_lock lock(m_mutex);
|
||||||
m_commits.push_back(std::move(commit));
|
m_commits.push_back(std::move(commit));
|
||||||
const auto now = std::chrono::steady_clock::now();
|
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;
|
m_targetPageflipTime = now;
|
||||||
} else {
|
} else {
|
||||||
m_targetPageflipTime = estimateNextVblank(now);
|
m_targetPageflipTime = estimateNextVblank(now);
|
||||||
|
|
|
@ -59,6 +59,7 @@ private:
|
||||||
std::chrono::nanoseconds m_minVblankInterval;
|
std::chrono::nanoseconds m_minVblankInterval;
|
||||||
std::vector<std::unique_ptr<DrmAtomicCommit>> m_commitsToDelete;
|
std::vector<std::unique_ptr<DrmAtomicCommit>> m_commitsToDelete;
|
||||||
bool m_vrr = false;
|
bool m_vrr = false;
|
||||||
|
bool m_tearing = false;
|
||||||
std::chrono::nanoseconds m_safetyMargin{0};
|
std::chrono::nanoseconds m_safetyMargin{0};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -40,6 +40,9 @@
|
||||||
#ifndef DRM_CLIENT_CAP_CURSOR_PLANE_HOTSPOT
|
#ifndef DRM_CLIENT_CAP_CURSOR_PLANE_HOTSPOT
|
||||||
#define DRM_CLIENT_CAP_CURSOR_PLANE_HOTSPOT 6
|
#define DRM_CLIENT_CAP_CURSOR_PLANE_HOTSPOT 6
|
||||||
#endif
|
#endif
|
||||||
|
#ifndef DRM_CAP_ATOMIC_ASYNC_PAGE_FLIP
|
||||||
|
#define DRM_CAP_ATOMIC_ASYNC_PAGE_FLIP 0x15
|
||||||
|
#endif
|
||||||
|
|
||||||
namespace KWin
|
namespace KWin
|
||||||
{
|
{
|
||||||
|
@ -89,8 +92,9 @@ DrmGpu::DrmGpu(DrmBackend *backend, int fd, std::unique_ptr<DrmDevice> &&device)
|
||||||
initDrmResources();
|
initDrmResources();
|
||||||
|
|
||||||
if (m_atomicModeSetting == false) {
|
if (m_atomicModeSetting == false) {
|
||||||
// only supported with legacy
|
|
||||||
m_asyncPageflipSupported = drmGetCap(fd, DRM_CAP_ASYNC_PAGE_FLIP, &capability) == 0 && capability == 1;
|
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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -117,6 +117,12 @@ DrmLease *DrmOutput::lease() const
|
||||||
|
|
||||||
bool DrmOutput::updateCursorLayer()
|
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();
|
return m_pipeline->updateCursor();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -287,6 +293,7 @@ void DrmOutput::updateDpmsMode(DpmsMode dpmsMode)
|
||||||
|
|
||||||
bool DrmOutput::present(const std::shared_ptr<OutputFrame> &frame)
|
bool DrmOutput::present(const std::shared_ptr<OutputFrame> &frame)
|
||||||
{
|
{
|
||||||
|
m_desiredPresentationMode = frame->presentationMode();
|
||||||
const bool needsModeset = m_gpu->needsModeset();
|
const bool needsModeset = m_gpu->needsModeset();
|
||||||
bool success;
|
bool success;
|
||||||
if (needsModeset) {
|
if (needsModeset) {
|
||||||
|
|
|
@ -94,6 +94,7 @@ private:
|
||||||
QVector3D m_channelFactors = {1, 1, 1};
|
QVector3D m_channelFactors = {1, 1, 1};
|
||||||
bool m_channelFactorsNeedShaderFallback = false;
|
bool m_channelFactorsNeedShaderFallback = false;
|
||||||
ColorDescription m_scanoutColorDescription = ColorDescription::sRGB;
|
ColorDescription m_scanoutColorDescription = ColorDescription::sRGB;
|
||||||
|
PresentationMode m_desiredPresentationMode = PresentationMode::VSync;
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue