/* KWin - the KDE window manager This file is part of the KDE project. SPDX-FileCopyrightText: 2023 Xaver Hugl SPDX-License-Identifier: GPL-2.0-or-later */ #include "drm_commit.h" #include "core/renderbackend.h" #include "drm_blob.h" #include "drm_buffer.h" #include "drm_connector.h" #include "drm_crtc.h" #include "drm_gpu.h" #include "drm_object.h" #include "drm_property.h" #include #include using namespace std::chrono_literals; namespace KWin { DrmCommit::DrmCommit(DrmGpu *gpu) : m_gpu(gpu) { } DrmCommit::~DrmCommit() { Q_ASSERT(QThread::currentThread() == QCoreApplication::instance()->thread()); } DrmGpu *DrmCommit::gpu() const { return m_gpu; } DrmAtomicCommit::DrmAtomicCommit(DrmGpu *gpu) : DrmCommit(gpu) { } DrmAtomicCommit::DrmAtomicCommit(const QList &pipelines) : DrmCommit(pipelines.front()->gpu()) , m_pipelines(pipelines) { } void DrmAtomicCommit::addProperty(const DrmProperty &prop, uint64_t value) { if (Q_UNLIKELY(!prop.isValid())) { qCWarning(KWIN_DRM) << "Trying to add an invalid property" << prop.name(); return; } prop.checkValueInRange(value); m_properties[prop.drmObject()->id()][prop.propId()] = value; } void DrmAtomicCommit::addBlob(const DrmProperty &prop, const std::shared_ptr &blob) { addProperty(prop, blob ? blob->blobId() : 0); m_blobs[&prop] = blob; } void DrmAtomicCommit::addBuffer(DrmPlane *plane, const std::shared_ptr &buffer, const std::shared_ptr &frame) { addProperty(plane->fbId, buffer ? buffer->framebufferId() : 0); m_buffers[plane] = buffer; m_frames[plane] = frame; // 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); if (frame) { if (m_targetPageflipTime) { m_targetPageflipTime = std::min(*m_targetPageflipTime, frame->targetPageflipTime()); } else { m_targetPageflipTime = frame->targetPageflipTime(); } } } void DrmAtomicCommit::setVrr(DrmCrtc *crtc, bool vrr) { addProperty(crtc->vrrEnabled, vrr ? 1 : 0); m_vrr = vrr; } void DrmAtomicCommit::setPresentationMode(PresentationMode mode) { m_mode = mode; } bool DrmAtomicCommit::test() { 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() { return doCommit(DRM_MODE_ATOMIC_TEST_ONLY | DRM_MODE_ATOMIC_ALLOW_MODESET); } bool DrmAtomicCommit::commit() { 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() { m_modeset = true; return doCommit(DRM_MODE_ATOMIC_ALLOW_MODESET); } bool DrmAtomicCommit::doCommit(uint32_t flags) { std::vector objects; std::vector propertyCounts; std::vector propertyIds; std::vector values; objects.reserve(m_properties.size()); propertyCounts.reserve(m_properties.size()); uint64_t totalPropertiesCount = 0; for (const auto &[object, properties] : m_properties) { objects.push_back(object); propertyCounts.push_back(properties.size()); totalPropertiesCount += properties.size(); } propertyIds.reserve(totalPropertiesCount); values.reserve(totalPropertiesCount); for (const auto &[object, properties] : m_properties) { for (const auto &[property, value] : properties) { propertyIds.push_back(property); values.push_back(value); } } drm_mode_atomic commitData{ .flags = flags, .count_objs = uint32_t(objects.size()), .objs_ptr = reinterpret_cast(objects.data()), .count_props_ptr = reinterpret_cast(propertyCounts.data()), .props_ptr = reinterpret_cast(propertyIds.data()), .prop_values_ptr = reinterpret_cast(values.data()), .reserved = 0, .user_data = reinterpret_cast(this), }; return drmIoctl(m_gpu->fd(), DRM_IOCTL_MODE_ATOMIC, &commitData) == 0; } void DrmAtomicCommit::pageFlipped(std::chrono::nanoseconds timestamp) { Q_ASSERT(QThread::currentThread() == QCoreApplication::instance()->thread()); for (const auto &[plane, buffer] : m_buffers) { plane->setCurrentBuffer(buffer); } for (const auto &[plane, frame] : m_frames) { if (frame) { frame->presented(timestamp, m_mode); } } m_frames.clear(); for (const auto pipeline : std::as_const(m_pipelines)) { pipeline->pageFlipped(timestamp); } } bool DrmAtomicCommit::areBuffersReadable() const { return std::ranges::all_of(m_buffers, [](const auto &pair) { const auto &[plane, buffer] = pair; return !buffer || buffer->isReadable(); }); } void DrmAtomicCommit::setDeadline(std::chrono::steady_clock::time_point deadline) { for (const auto &[plane, buffer] : m_buffers) { if (buffer) { buffer->setDeadline(deadline); } } } std::optional DrmAtomicCommit::isVrr() const { return m_vrr; } const std::unordered_set &DrmAtomicCommit::modifiedPlanes() const { return m_planes; } void DrmAtomicCommit::merge(DrmAtomicCommit *onTop) { for (const auto &[obj, properties] : onTop->m_properties) { auto &ownProperties = m_properties[obj]; for (const auto &[prop, value] : properties) { ownProperties[prop] = value; } } for (const auto &[plane, buffer] : onTop->m_buffers) { m_buffers[plane] = buffer; m_frames[plane] = onTop->m_frames[plane]; m_planes.emplace(plane); } for (const auto &[prop, blob] : onTop->m_blobs) { m_blobs[prop] = blob; } if (onTop->m_vrr) { m_vrr = onTop->m_vrr; } m_cursorOnly &= onTop->isCursorOnly(); if (!m_targetPageflipTime) { m_targetPageflipTime = onTop->m_targetPageflipTime; } else if (onTop->m_targetPageflipTime) { *m_targetPageflipTime = std::min(*m_targetPageflipTime, *onTop->m_targetPageflipTime); } } void DrmAtomicCommit::setCursorOnly(bool cursor) { m_cursorOnly = cursor; } bool DrmAtomicCommit::isCursorOnly() const { return m_cursorOnly; } std::optional DrmAtomicCommit::targetPageflipTime() const { return m_targetPageflipTime; } bool DrmAtomicCommit::isReadyFor(std::chrono::steady_clock::time_point pageflipTarget) const { static constexpr auto s_pageflipSlop = 500us; 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) , m_crtc(m_pipeline->crtc()) , m_buffer(buffer) , m_frame(frame) { } bool DrmLegacyCommit::doModeset(DrmConnector *connector, DrmConnectorMode *mode) { uint32_t connectorId = connector->id(); if (drmModeSetCrtc(gpu()->fd(), m_crtc->id(), m_buffer->framebufferId(), 0, 0, &connectorId, 1, mode->nativeMode()) == 0) { m_crtc->setCurrent(m_buffer); return true; } else { return false; } } bool DrmLegacyCommit::doPageflip(PresentationMode mode) { m_mode = mode; uint32_t flags = DRM_MODE_PAGE_FLIP_EVENT; if (mode == PresentationMode::Async || mode == PresentationMode::AdaptiveAsync) { flags |= DRM_MODE_PAGE_FLIP_ASYNC; } return drmModePageFlip(gpu()->fd(), m_crtc->id(), m_buffer->framebufferId(), flags, this) == 0; } void DrmLegacyCommit::pageFlipped(std::chrono::nanoseconds timestamp) { Q_ASSERT(QThread::currentThread() == QCoreApplication::instance()->thread()); m_crtc->setCurrent(m_buffer); if (m_frame) { m_frame->presented(timestamp, m_mode); m_frame.reset(); } m_pipeline->pageFlipped(timestamp); } }