316 lines
8.8 KiB
C++
316 lines
8.8 KiB
C++
/*
|
|
KWin - the KDE window manager
|
|
This file is part of the KDE project.
|
|
|
|
SPDX-FileCopyrightText: 2023 Xaver Hugl <xaver.hugl@gmail.com>
|
|
|
|
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 <QCoreApplication>
|
|
#include <QThread>
|
|
|
|
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;
|
|
}
|
|
|
|
void DrmCommit::setDefunct()
|
|
{
|
|
m_defunct = true;
|
|
}
|
|
|
|
DrmAtomicCommit::DrmAtomicCommit(DrmGpu *gpu)
|
|
: DrmCommit(gpu)
|
|
{
|
|
}
|
|
|
|
DrmAtomicCommit::DrmAtomicCommit(const QList<DrmPipeline *> &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<DrmBlob> &blob)
|
|
{
|
|
addProperty(prop, blob ? blob->blobId() : 0);
|
|
m_blobs[&prop] = blob;
|
|
}
|
|
|
|
void DrmAtomicCommit::addBuffer(DrmPlane *plane, const std::shared_ptr<DrmFramebuffer> &buffer, const std::shared_ptr<OutputFrame> &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<uint32_t> objects;
|
|
std::vector<uint32_t> propertyCounts;
|
|
std::vector<uint32_t> propertyIds;
|
|
std::vector<uint64_t> 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<uint64_t>(objects.data()),
|
|
.count_props_ptr = reinterpret_cast<uint64_t>(propertyCounts.data()),
|
|
.props_ptr = reinterpret_cast<uint64_t>(propertyIds.data()),
|
|
.prop_values_ptr = reinterpret_cast<uint64_t>(values.data()),
|
|
.reserved = 0,
|
|
.user_data = reinterpret_cast<uint64_t>(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);
|
|
}
|
|
if (m_defunct) {
|
|
return;
|
|
}
|
|
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<bool> DrmAtomicCommit::isVrr() const
|
|
{
|
|
return m_vrr;
|
|
}
|
|
|
|
const std::unordered_set<DrmPlane *> &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;
|
|
}
|
|
if (!m_targetPageflipTime) {
|
|
m_targetPageflipTime = onTop->m_targetPageflipTime;
|
|
} else if (onTop->m_targetPageflipTime) {
|
|
*m_targetPageflipTime = std::min(*m_targetPageflipTime, *onTop->m_targetPageflipTime);
|
|
}
|
|
if (m_allowedVrrDelay && onTop->m_allowedVrrDelay) {
|
|
*m_allowedVrrDelay = std::min(*m_allowedVrrDelay, *onTop->m_allowedVrrDelay);
|
|
} else {
|
|
m_allowedVrrDelay.reset();
|
|
}
|
|
}
|
|
|
|
void DrmAtomicCommit::setAllowedVrrDelay(std::optional<std::chrono::nanoseconds> allowedDelay)
|
|
{
|
|
m_allowedVrrDelay = allowedDelay;
|
|
}
|
|
|
|
std::optional<std::chrono::nanoseconds> DrmAtomicCommit::allowedVrrDelay() const
|
|
{
|
|
return m_allowedVrrDelay;
|
|
}
|
|
|
|
std::optional<std::chrono::steady_clock::time_point> 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<DrmFramebuffer> &buffer, const std::shared_ptr<OutputFrame> &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_defunct) {
|
|
return;
|
|
}
|
|
if (m_frame) {
|
|
m_frame->presented(timestamp, m_mode);
|
|
m_frame.reset();
|
|
}
|
|
m_pipeline->pageFlipped(timestamp);
|
|
}
|
|
}
|