Add kwin full source tree, greeter login, zsh, pcid service, and build system improvements

This commit is contained in:
2026-04-26 22:31:07 +01:00
parent d4a6b356eb
commit 70a84cefee
3416 changed files with 1360518 additions and 10522 deletions
@@ -0,0 +1,6 @@
add_subdirectory(drm)
add_subdirectory(fakeinput)
add_subdirectory(libinput)
add_subdirectory(virtual)
add_subdirectory(wayland)
add_subdirectory(x11)
@@ -0,0 +1,29 @@
target_sources(kwin PRIVATE
drm_abstract_output.cpp
drm_backend.cpp
drm_blob.cpp
drm_buffer.cpp
drm_colorop.cpp
drm_commit.cpp
drm_commit_thread.cpp
drm_connector.cpp
drm_crtc.cpp
drm_egl_backend.cpp
drm_egl_layer.cpp
drm_egl_layer_surface.cpp
drm_gpu.cpp
drm_layer.cpp
drm_logging.cpp
drm_object.cpp
drm_output.cpp
drm_pipeline.cpp
drm_pipeline_legacy.cpp
drm_plane.cpp
drm_property.cpp
drm_qpainter_backend.cpp
drm_qpainter_layer.cpp
drm_virtual_egl_layer.cpp
drm_virtual_output.cpp
)
target_link_libraries(kwin PRIVATE Libdrm::Libdrm gbm::gbm PkgConfig::Libxcvt)
@@ -0,0 +1,36 @@
/*
KWin - the KDE window manager
This file is part of the KDE project.
SPDX-FileCopyrightText: 2021 Xaver Hugl <xaver.hugl@gmail.com>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "drm_abstract_output.h"
#include "core/renderbackend.h"
#include "drm_backend.h"
#include "drm_gpu.h"
#include "drm_layer.h"
namespace KWin
{
DrmAbstractOutput::DrmAbstractOutput()
: m_renderLoop(std::make_unique<RenderLoop>(this))
{
}
RenderLoop *DrmAbstractOutput::renderLoop() const
{
return m_renderLoop.get();
}
void DrmAbstractOutput::updateEnabled(bool enabled)
{
State next = m_state;
next.enabled = enabled;
setState(next);
}
}
#include "moc_drm_abstract_output.cpp"
@@ -0,0 +1,40 @@
/*
KWin - the KDE window manager
This file is part of the KDE project.
SPDX-FileCopyrightText: 2021 Xaver Hugl <xaver.hugl@gmail.com>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#pragma once
#include "core/output.h"
namespace KWin
{
class DrmBackend;
class DrmOutputLayer;
class OutputFrame;
class DrmAbstractOutput : public Output
{
Q_OBJECT
public:
explicit DrmAbstractOutput();
RenderLoop *renderLoop() const override;
virtual bool present(const std::shared_ptr<OutputFrame> &frame) = 0;
virtual DrmOutputLayer *primaryLayer() const = 0;
virtual DrmOutputLayer *cursorLayer() const = 0;
void updateEnabled(bool enabled);
protected:
friend class DrmGpu;
std::unique_ptr<RenderLoop> m_renderLoop;
};
}
@@ -0,0 +1,468 @@
/*
KWin - the KDE window manager
This file is part of the KDE project.
SPDX-FileCopyrightText: 2015 Martin Gräßlin <mgraesslin@kde.org>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "drm_backend.h"
#include "config-kwin.h"
#include "backends/libinput/libinputbackend.h"
#include "core/outputconfiguration.h"
#include "core/session.h"
#include "drm_egl_backend.h"
#include "drm_gpu.h"
#include "drm_layer.h"
#include "drm_logging.h"
#include "drm_output.h"
#include "drm_pipeline.h"
#include "drm_qpainter_backend.h"
#include "drm_render_backend.h"
#include "drm_virtual_output.h"
#include "utils/udev.h"
// KF5
#include <KCoreAddons>
#include <KLocalizedString>
// Qt
#include <QCoreApplication>
#include <QFileInfo>
#include <QSocketNotifier>
#include <QStringBuilder>
// system
#include <algorithm>
#include <cerrno>
#include <ranges>
#include <sys/stat.h>
#include <unistd.h>
// drm
#include <gbm.h>
#include <libdrm/drm_mode.h>
#include <xf86drm.h>
namespace KWin
{
static QStringList splitPathList(const QString &input, const QChar delimiter)
{
QStringList ret;
QString tmp;
for (int i = 0; i < input.size(); i++) {
if (input[i] == delimiter) {
if (i > 0 && input[i - 1] == '\\') {
tmp[tmp.size() - 1] = delimiter;
} else if (!tmp.isEmpty()) {
ret.append(tmp);
tmp = QString();
}
} else {
tmp.append(input[i]);
}
}
if (!tmp.isEmpty()) {
ret.append(tmp);
}
return ret;
}
DrmBackend::DrmBackend(Session *session, QObject *parent)
: OutputBackend(parent)
, m_udev(std::make_unique<Udev>())
, m_udevMonitor(m_udev->monitor())
, m_session(session)
, m_explicitGpus(splitPathList(qEnvironmentVariable("KWIN_DRM_DEVICES"), ':'))
{
}
DrmBackend::~DrmBackend() = default;
Session *DrmBackend::session() const
{
return m_session;
}
Outputs DrmBackend::outputs() const
{
return m_outputs;
}
bool DrmBackend::initialize()
{
connect(m_session, &Session::devicePaused, this, [this](dev_t deviceId) {
if (const auto gpu = findGpu(deviceId)) {
gpu->setActive(false);
}
});
connect(m_session, &Session::deviceResumed, this, [this](dev_t deviceId) {
if (const auto gpu = findGpu(deviceId); gpu && !gpu->isActive()) {
gpu->setActive(true);
// the output list might've changed while the device was inactive
// note that this might delete the gpu!
updateOutputs();
}
});
if (!m_explicitGpus.isEmpty()) {
for (const QString &fileName : m_explicitGpus) {
addGpu(fileName);
}
} else {
const auto devices = m_udev->listGPUs();
for (const auto &device : devices) {
if (device->seat() == m_session->seat()) {
addGpu(device->devNode());
}
}
}
if (m_gpus.empty()) {
qCWarning(KWIN_DRM) << "No suitable DRM devices have been found";
return false;
}
// setup udevMonitor
if (m_udevMonitor) {
m_udevMonitor->filterSubsystemDevType("drm");
const int fd = m_udevMonitor->fd();
if (fd != -1) {
m_socketNotifier = std::make_unique<QSocketNotifier>(fd, QSocketNotifier::Read);
connect(m_socketNotifier.get(), &QSocketNotifier::activated, this, &DrmBackend::handleUdevEvent);
m_udevMonitor->enable();
}
}
updateOutputs();
if (m_explicitGpus.empty() && m_gpus.size() > 1) {
std::ranges::sort(m_gpus, [](const auto &gpu1, const auto &gpu2) {
const size_t internalOutputs1 = std::ranges::count_if(gpu1->drmOutputs(), &Output::isInternal);
const size_t internalOutputs2 = std::ranges::count_if(gpu2->drmOutputs(), &Output::isInternal);
if (internalOutputs1 != internalOutputs2) {
return internalOutputs1 > internalOutputs2;
}
const size_t desktopOutputs1 = std::ranges::count_if(gpu1->drmOutputs(), std::not_fn(&Output::isNonDesktop));
const size_t desktopOutputs2 = std::ranges::count_if(gpu2->drmOutputs(), std::not_fn(&Output::isNonDesktop));
if (desktopOutputs1 != desktopOutputs2) {
return desktopOutputs1 > desktopOutputs2;
}
return gpu1->drmOutputs().size() > gpu2->drmOutputs().size();
});
qCDebug(KWIN_DRM) << "chose" << m_gpus.front()->drmDevice()->path() << "as the primary GPU";
}
return true;
}
void DrmBackend::handleUdevEvent()
{
while (auto device = m_udevMonitor->getDevice()) {
// Ignore the device seat if the KWIN_DRM_DEVICES envvar is set.
if (!m_explicitGpus.isEmpty()) {
const auto canonicalPath = QFileInfo(device->devNode()).canonicalFilePath();
const bool foundMatch = std::ranges::any_of(m_explicitGpus, [&canonicalPath](const QString &explicitPath) {
return QFileInfo(explicitPath).canonicalFilePath() == canonicalPath;
});
if (!foundMatch) {
continue;
}
} else {
if (device->seat() != m_session->seat()) {
continue;
}
}
if (device->action() == QLatin1StringView("add")) {
DrmGpu *gpu = findGpu(device->devNum());
if (gpu) {
qCWarning(KWIN_DRM) << "Received unexpected add udev event for:" << device->devNode();
continue;
}
if (addGpu(device->devNode())) {
updateOutputs();
}
} else if (device->action() == QLatin1StringView("remove")) {
DrmGpu *gpu = findGpu(device->devNum());
if (gpu) {
if (primaryGpu() == gpu) {
qCCritical(KWIN_DRM) << "Primary gpu has been removed! Quitting...";
QCoreApplication::exit(1);
return;
} else {
gpu->setRemoved();
updateOutputs();
}
}
} else if (device->action() == QLatin1StringView("change")) {
DrmGpu *gpu = findGpu(device->devNum());
if (!gpu) {
gpu = addGpu(device->devNode());
}
if (gpu && gpu->isActive()) {
qCDebug(KWIN_DRM) << "Received change event for monitored drm device" << gpu->drmDevice()->path();
updateOutputs();
}
}
}
}
DrmGpu *DrmBackend::addGpu(const QString &fileName)
{
int fd = m_session->openRestricted(fileName);
if (fd < 0) {
qCWarning(KWIN_DRM) << "failed to open drm device at" << fileName;
return nullptr;
}
if (!drmIsKMS(fd)) {
qCDebug(KWIN_DRM) << "Skipping KMS incapable drm device node at" << fileName;
m_session->closeRestricted(fd);
return nullptr;
}
auto drmDevice = DrmDevice::openWithAuthentication(fileName, fd);
if (!drmDevice) {
m_session->closeRestricted(fd);
return nullptr;
}
m_gpus.push_back(std::make_unique<DrmGpu>(this, fd, std::move(drmDevice)));
auto gpu = m_gpus.back().get();
qCDebug(KWIN_DRM, "adding GPU %s", qPrintable(fileName));
connect(gpu, &DrmGpu::outputAdded, this, &DrmBackend::addOutput);
connect(gpu, &DrmGpu::outputRemoved, this, &DrmBackend::removeOutput);
Q_EMIT gpuAdded(gpu);
return gpu;
}
void DrmBackend::addOutput(DrmAbstractOutput *o)
{
const bool allOff = std::ranges::all_of(m_outputs, [](Output *output) {
return output->dpmsMode() != Output::DpmsMode::On;
});
if (allOff && m_recentlyUnpluggedDpmsOffOutputs.contains(o->uuid())) {
if (DrmOutput *drmOutput = qobject_cast<DrmOutput *>(o)) {
// When the system is in dpms power saving mode, KWin turns on all outputs if the user plugs a new output in
// as that's an intentional action and they expect to see the output light up.
// Some outputs however temporarily disconnect in some situations, most often shortly after they go into standby.
// To not turn on outputs in that case, restore the previous dpms state
drmOutput->updateDpmsMode(Output::DpmsMode::Off);
drmOutput->pipeline()->setActive(false);
drmOutput->renderLoop()->inhibit();
m_recentlyUnpluggedDpmsOffOutputs.removeOne(drmOutput->uuid());
}
}
m_outputs.append(o);
Q_EMIT outputAdded(o);
}
static const int s_dpmsTimeout = []() {
bool ok = false;
int ret = qEnvironmentVariableIntValue("KWIN_DPMS_WORKAROUND_TIMEOUT", &ok);
if (ok) {
return ret;
} else {
return 2000;
}
}();
void DrmBackend::removeOutput(DrmAbstractOutput *o)
{
if (o->dpmsMode() == Output::DpmsMode::Off) {
const QUuid id = o->uuid();
m_recentlyUnpluggedDpmsOffOutputs.push_back(id);
QTimer::singleShot(s_dpmsTimeout, this, [this, id]() {
m_recentlyUnpluggedDpmsOffOutputs.removeOne(id);
});
}
o->updateEnabled(false);
m_outputs.removeOne(o);
Q_EMIT outputRemoved(o);
}
void DrmBackend::updateOutputs()
{
for (auto it = m_gpus.begin(); it != m_gpus.end(); ++it) {
if ((*it)->isRemoved()) {
(*it)->removeOutputs();
} else {
(*it)->updateOutputs();
}
}
Q_EMIT outputsQueried();
for (auto it = m_gpus.begin(); it != m_gpus.end();) {
DrmGpu *gpu = it->get();
if (gpu->isRemoved() || (gpu != primaryGpu() && gpu->drmOutputs().isEmpty())) {
qCDebug(KWIN_DRM) << "Removing GPU" << it->get();
const std::unique_ptr<DrmGpu> keepAlive = std::move(*it);
it = m_gpus.erase(it);
Q_EMIT gpuRemoved(keepAlive.get());
} else {
it++;
}
}
}
std::unique_ptr<InputBackend> DrmBackend::createInputBackend()
{
return std::make_unique<LibinputBackend>(m_session);
}
std::unique_ptr<QPainterBackend> DrmBackend::createQPainterBackend()
{
return std::make_unique<DrmQPainterBackend>(this);
}
std::unique_ptr<OpenGLBackend> DrmBackend::createOpenGLBackend()
{
return std::make_unique<EglGbmBackend>(this);
}
QList<CompositingType> DrmBackend::supportedCompositors() const
{
return QList<CompositingType>{OpenGLCompositing, QPainterCompositing};
}
QString DrmBackend::supportInformation() const
{
QString supportInfo;
QDebug s(&supportInfo);
s.nospace();
s << "Name: "
<< "DRM" << Qt::endl;
for (size_t g = 0; g < m_gpus.size(); g++) {
s << "Atomic Mode Setting on GPU " << g << ": " << m_gpus.at(g)->atomicModeSetting() << Qt::endl;
}
return supportInfo;
}
Output *DrmBackend::createVirtualOutput(const QString &name, const QString &description, const QSize &size, double scale)
{
const auto ret = new DrmVirtualOutput(this, name, description, size, scale);
m_virtualOutputs.push_back(ret);
addOutput(ret);
Q_EMIT outputsQueried();
return ret;
}
void DrmBackend::removeVirtualOutput(Output *output)
{
auto virtualOutput = qobject_cast<DrmVirtualOutput *>(output);
Q_ASSERT(virtualOutput);
if (!m_virtualOutputs.removeOne(virtualOutput)) {
return;
}
removeOutput(virtualOutput);
Q_EMIT outputsQueried();
virtualOutput->unref();
}
DrmGpu *DrmBackend::primaryGpu() const
{
return m_gpus.empty() ? nullptr : m_gpus.front().get();
}
DrmGpu *DrmBackend::findGpu(dev_t deviceId) const
{
auto it = std::ranges::find_if(m_gpus, [deviceId](const auto &gpu) {
return gpu->drmDevice()->deviceId() == deviceId;
});
return it == m_gpus.end() ? nullptr : it->get();
}
size_t DrmBackend::gpuCount() const
{
return m_gpus.size();
}
bool DrmBackend::applyOutputChanges(const OutputConfiguration &config)
{
QList<DrmOutput *> toBeEnabled;
QList<DrmOutput *> toBeDisabled;
for (const auto &gpu : m_gpus) {
const auto &outputs = gpu->drmOutputs();
for (const auto &output : outputs) {
if (output->isNonDesktop()) {
continue;
}
if (const auto changeset = config.constChangeSet(output)) {
output->queueChanges(changeset);
if (changeset->enabled.value_or(output->isEnabled())) {
toBeEnabled << output;
} else {
toBeDisabled << output;
}
}
}
if (gpu->testPendingConfiguration() != DrmPipeline::Error::None) {
for (const auto &output : std::as_const(toBeEnabled)) {
output->revertQueuedChanges();
}
for (const auto &output : std::as_const(toBeDisabled)) {
output->revertQueuedChanges();
}
return false;
}
}
// first, apply changes to drm outputs.
// This may remove the placeholder output and thus change m_outputs!
for (const auto &output : std::as_const(toBeEnabled)) {
if (const auto changeset = config.constChangeSet(output)) {
output->applyQueuedChanges(changeset);
}
}
for (const auto &output : std::as_const(toBeDisabled)) {
if (const auto changeset = config.constChangeSet(output)) {
output->applyQueuedChanges(changeset);
}
}
// only then apply changes to the virtual outputs
for (const auto &output : std::as_const(m_virtualOutputs)) {
output->applyChanges(config);
}
return true;
}
void DrmBackend::setRenderBackend(DrmRenderBackend *backend)
{
m_renderBackend = backend;
}
DrmRenderBackend *DrmBackend::renderBackend() const
{
return m_renderBackend;
}
void DrmBackend::createLayers()
{
for (const auto &gpu : m_gpus) {
gpu->recreateSurfaces();
}
for (const auto &virt : std::as_const(m_virtualOutputs)) {
virt->recreateSurface();
}
}
void DrmBackend::releaseBuffers()
{
for (const auto &gpu : m_gpus) {
gpu->releaseBuffers();
}
for (const auto &virt : std::as_const(m_virtualOutputs)) {
virt->primaryLayer()->releaseBuffers();
}
}
const std::vector<std::unique_ptr<DrmGpu>> &DrmBackend::gpus() const
{
return m_gpus;
}
EglDisplay *DrmBackend::sceneEglDisplayObject() const
{
return m_gpus.front()->eglDisplay();
}
}
#include "moc_drm_backend.cpp"
@@ -0,0 +1,98 @@
/*
KWin - the KDE window manager
This file is part of the KDE project.
SPDX-FileCopyrightText: 2015 Martin Gräßlin <mgraesslin@kde.org>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#pragma once
#include "core/outputbackend.h"
#include <QList>
#include <QPointer>
#include <QSize>
#include <QSocketNotifier>
#include <memory>
#include <sys/types.h>
namespace KWin
{
class Session;
class Udev;
class UdevMonitor;
class UdevDevice;
class DrmAbstractOutput;
class Cursor;
class DrmGpu;
class DrmVirtualOutput;
class DrmRenderBackend;
class KWIN_EXPORT DrmBackend : public OutputBackend
{
Q_OBJECT
public:
explicit DrmBackend(Session *session, QObject *parent = nullptr);
~DrmBackend() override;
std::unique_ptr<InputBackend> createInputBackend() override;
std::unique_ptr<QPainterBackend> createQPainterBackend() override;
std::unique_ptr<OpenGLBackend> createOpenGLBackend() override;
EglDisplay *sceneEglDisplayObject() const override;
bool initialize() override;
Outputs outputs() const override;
Session *session() const override;
QList<CompositingType> supportedCompositors() const override;
QString supportInformation() const override;
Output *createVirtualOutput(const QString &name, const QString &description, const QSize &size, double scale) override;
void removeVirtualOutput(Output *output) override;
DrmGpu *primaryGpu() const;
DrmGpu *findGpu(dev_t deviceId) const;
size_t gpuCount() const;
void setRenderBackend(DrmRenderBackend *backend);
DrmRenderBackend *renderBackend() const;
void createLayers();
void releaseBuffers();
void updateOutputs();
const std::vector<std::unique_ptr<DrmGpu>> &gpus() const;
Q_SIGNALS:
void gpuAdded(DrmGpu *gpu);
void gpuRemoved(DrmGpu *gpu);
protected:
bool applyOutputChanges(const OutputConfiguration &config) override;
private:
friend class DrmGpu;
void addOutput(DrmAbstractOutput *output);
void removeOutput(DrmAbstractOutput *output);
void handleUdevEvent();
DrmGpu *addGpu(const QString &fileName);
std::unique_ptr<Udev> m_udev;
std::unique_ptr<UdevMonitor> m_udevMonitor;
std::unique_ptr<QSocketNotifier> m_socketNotifier;
Session *m_session;
QList<DrmAbstractOutput *> m_outputs;
QList<QUuid> m_recentlyUnpluggedDpmsOffOutputs;
const QStringList m_explicitGpus;
std::vector<std::unique_ptr<DrmGpu>> m_gpus;
QList<DrmVirtualOutput *> m_virtualOutputs;
DrmRenderBackend *m_renderBackend = nullptr;
};
}
@@ -0,0 +1,42 @@
/*
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_blob.h"
#include "drm_gpu.h"
namespace KWin
{
DrmBlob::DrmBlob(DrmGpu *gpu, uint32_t blobId)
: m_gpu(gpu)
, m_blobId(blobId)
{
}
DrmBlob::~DrmBlob()
{
if (m_blobId) {
drmModeDestroyPropertyBlob(m_gpu->fd(), m_blobId);
}
}
uint32_t DrmBlob::blobId() const
{
return m_blobId;
}
std::shared_ptr<DrmBlob> DrmBlob::create(DrmGpu *gpu, const void *data, uint32_t dataSize)
{
uint32_t id = 0;
if (drmModeCreatePropertyBlob(gpu->fd(), data, dataSize, &id) == 0) {
return std::make_shared<DrmBlob>(gpu, id);
} else {
return nullptr;
}
}
}
@@ -0,0 +1,33 @@
/*
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
*/
#pragma once
#include <memory>
#include <stdint.h>
namespace KWin
{
class DrmGpu;
class DrmBlob
{
public:
DrmBlob(DrmGpu *gpu, uint32_t blobId);
~DrmBlob();
uint32_t blobId() const;
static std::shared_ptr<DrmBlob> create(DrmGpu *gpu, const void *data, uint32_t dataSize);
protected:
DrmGpu *const m_gpu;
const uint32_t m_blobId;
};
}
@@ -0,0 +1,125 @@
/*
KWin - the KDE window manager
This file is part of the KDE project.
SPDX-FileCopyrightText: 2015 Martin Gräßlin <mgraesslin@kde.org>
SPDX-FileCopyrightText: 2022 Xaver Hugl <xaver.hugl@gmail.com>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "drm_buffer.h"
#include "core/graphicsbuffer.h"
#include "drm_gpu.h"
// system
#include <sys/mman.h>
#if defined(Q_OS_LINUX)
#include <linux/dma-buf.h>
#include <linux/sync_file.h>
#endif
// drm
#include <drm_fourcc.h>
#include <sys/ioctl.h>
#include <unistd.h>
#include <xf86drm.h>
#include <xf86drmMode.h>
#ifdef Q_OS_LINUX
#include <linux/dma-buf.h>
#endif
namespace KWin
{
static bool s_envIsSet = false;
static bool s_disableBufferWait = qEnvironmentVariableIntValue("KWIN_DRM_DISABLE_BUFFER_READABILITY_CHECKS", &s_envIsSet) && s_envIsSet;
DrmFramebuffer::DrmFramebuffer(DrmGpu *gpu, uint32_t fbId, GraphicsBuffer *buffer, FileDescriptor &&readFence)
: m_framebufferId(fbId)
, m_gpu(gpu)
, m_bufferRef(buffer)
{
if (s_disableBufferWait || ((m_gpu->isVmwgfx()) && !s_envIsSet)) {
// buffer readability checks cause frames to be wrongly delayed on Virtual Machines running vmwgfx
m_readable = true;
}
m_syncFd = std::move(readFence);
#ifdef DMA_BUF_IOCTL_EXPORT_SYNC_FILE
if (!m_syncFd.isValid()) {
dma_buf_export_sync_file req{
.flags = DMA_BUF_SYNC_READ,
.fd = -1,
};
if (drmIoctl(buffer->dmabufAttributes()->fd[0].get(), DMA_BUF_IOCTL_EXPORT_SYNC_FILE, &req) == 0) {
m_syncFd = FileDescriptor{req.fd};
}
}
#endif
}
DrmFramebuffer::~DrmFramebuffer()
{
uint32_t nonConstFb = m_framebufferId;
#ifdef DRM_IOCTL_MODE_CLOSEFB
struct drm_mode_closefb closeArgs{
.fb_id = m_framebufferId,
.pad = 0,
};
if (drmIoctl(m_gpu->fd(), DRM_IOCTL_MODE_CLOSEFB, &closeArgs) != 0) {
drmIoctl(m_gpu->fd(), DRM_IOCTL_MODE_RMFB, &nonConstFb);
}
#else
drmIoctl(m_gpu->fd(), DRM_IOCTL_MODE_RMFB, &nonConstFb);
#endif
}
uint32_t DrmFramebuffer::framebufferId() const
{
return m_framebufferId;
}
GraphicsBuffer *DrmFramebuffer::buffer() const
{
return *m_bufferRef;
}
void DrmFramebuffer::releaseBuffer()
{
m_bufferRef = nullptr;
}
const FileDescriptor &DrmFramebuffer::syncFd() const
{
return m_syncFd;
}
bool DrmFramebuffer::isReadable()
{
if (m_readable) {
return true;
} else if (m_syncFd.isValid()) {
return m_readable = m_syncFd.isReadable();
} else {
const auto &fds = m_bufferRef->dmabufAttributes()->fd;
m_readable = std::ranges::all_of(fds, [](const auto &fd) {
return !fd.isValid() || fd.isReadable();
});
return m_readable;
}
}
void DrmFramebuffer::setDeadline(std::chrono::steady_clock::time_point deadline)
{
#ifdef SYNC_IOC_SET_DEADLINE
if (!m_syncFd.isValid()) {
return;
}
sync_set_deadline args{
.deadline_ns = uint64_t(deadline.time_since_epoch().count()),
.pad = 0,
};
drmIoctl(m_syncFd.get(), SYNC_IOC_SET_DEADLINE, &args);
#endif
}
}
@@ -0,0 +1,49 @@
/*
KWin - the KDE window manager
This file is part of the KDE project.
SPDX-FileCopyrightText: 2015 Martin Gräßlin <mgraesslin@kde.org>
SPDX-FileCopyrightText: 2022 Xaver Hugl <xaver.hugl@gmail.com>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#pragma once
#include "core/graphicsbuffer.h"
#include "utils/filedescriptor.h"
#include <chrono>
namespace KWin
{
class DrmGpu;
class DrmFramebuffer;
class DrmFramebuffer
{
public:
DrmFramebuffer(DrmGpu *gpu, uint32_t fbId, GraphicsBuffer *buffer, FileDescriptor &&readFence);
~DrmFramebuffer();
uint32_t framebufferId() const;
/**
* may be nullptr
*/
GraphicsBuffer *buffer() const;
void releaseBuffer();
bool isReadable();
const FileDescriptor &syncFd() const;
void setDeadline(std::chrono::steady_clock::time_point deadline);
protected:
const uint32_t m_framebufferId;
DrmGpu *const m_gpu;
GraphicsBufferRef m_bufferRef;
bool m_readable = false;
FileDescriptor m_syncFd;
};
}
@@ -0,0 +1,244 @@
/*
KWin - the KDE window manager
This file is part of the KDE project.
SPDX-FileCopyrightText: 2024 Xaver Hugl <xaver.hugl@gmail.com>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "drm_colorop.h"
#include "drm_blob.h"
#include "drm_commit.h"
#include "drm_object.h"
#include <ranges>
namespace KWin
{
DrmAbstractColorOp::DrmAbstractColorOp(DrmAbstractColorOp *next)
: m_next(next)
{
}
DrmAbstractColorOp::~DrmAbstractColorOp()
{
}
DrmAbstractColorOp *DrmAbstractColorOp::next() const
{
return m_next;
}
bool DrmAbstractColorOp::matchPipeline(DrmAtomicCommit *commit, const ColorPipeline &pipeline)
{
if (m_cachedPipeline && *m_cachedPipeline == pipeline) {
commit->merge(m_cache.get());
return true;
}
DrmAbstractColorOp *currentOp = this;
const auto needsLimitedRange = [](const ColorOp &op) {
// KMS LUTs have an input and output range of [0, 1]
return std::holds_alternative<ColorTransferFunction>(op.operation)
|| std::holds_alternative<InverseColorTransferFunction>(op.operation);
};
// first, only check if the pipeline can be programmed in the first place
// don't calculate LUTs just yet
std::optional<ColorOp> initialOp;
double valueScaling = 1;
if (!pipeline.ops.empty() && needsLimitedRange(pipeline.ops.front()) && pipeline.ops.front().input.max > 1) {
valueScaling = 1.0 / pipeline.ops.front().input.max;
initialOp = ColorOp{
.input = pipeline.ops.front().input,
.operation = ColorMultiplier{valueScaling},
.output = ValueRange{
.min = pipeline.ops.front().input.min * valueScaling,
.max = 1.0,
},
};
while (currentOp && !currentOp->canBeUsedFor(*initialOp, false)) {
currentOp = currentOp->next();
}
if (!currentOp) {
return false;
}
currentOp = currentOp->next();
}
for (auto it = pipeline.ops.begin(); it != pipeline.ops.end(); it++) {
while (currentOp && !currentOp->canBeUsedFor(*it, true)) {
currentOp = currentOp->next();
}
if (!currentOp) {
return false;
}
}
// now actually program the properties
currentOp = this;
m_cache = std::make_unique<DrmAtomicCommit>(commit->gpu());
if (initialOp) {
while (!currentOp->canBeUsedFor(*initialOp, false)) {
currentOp->bypass(m_cache.get());
currentOp = currentOp->next();
}
currentOp->program(m_cache.get(), std::span(&*initialOp, 1), 1, 1);
currentOp = currentOp->next();
}
for (auto it = pipeline.ops.begin(); it != pipeline.ops.end();) {
while (!currentOp->canBeUsedFor(*it, true)) {
currentOp->bypass(m_cache.get());
currentOp = currentOp->next();
}
auto firstIt = it;
it++;
// combine as many operations into one hardware operation as possible
while (it != pipeline.ops.end() && currentOp->canBeUsedFor(*it, true)) {
it++;
}
std::span operations(firstIt, it);
double outputScaling = 1.0;
if (it != pipeline.ops.end() && (needsLimitedRange(operations.front()) || needsLimitedRange(operations.back()) || needsLimitedRange(*it))) {
// if this or the next operation needs a limited range, or we need limited range for the output, make it happen
outputScaling = 1.0 / operations.back().output.max;
}
currentOp->program(m_cache.get(), operations, valueScaling, outputScaling);
valueScaling = outputScaling;
currentOp = currentOp->next();
}
while (currentOp) {
currentOp->bypass(m_cache.get());
currentOp = currentOp->next();
}
commit->merge(m_cache.get());
m_cachedPipeline = pipeline;
return true;
}
DrmLutColorOp::DrmLutColorOp(DrmAbstractColorOp *next, DrmProperty *prop, uint32_t maxSize)
: DrmAbstractColorOp(next)
, m_prop(prop)
, m_maxSize(maxSize)
, m_components(m_maxSize)
{
}
bool DrmLutColorOp::canBeUsedFor(const ColorOp &op, bool scaling)
{
constexpr double eta = 0.0001;
// if scaling is true, we can assume the input to be bounded to [0; 1] already
if ((!scaling && op.input.max > 1 + eta) || op.input.min < -eta) {
return false;
}
if (std::holds_alternative<ColorTransferFunction>(op.operation) || std::holds_alternative<InverseColorTransferFunction>(op.operation)
|| std::holds_alternative<ColorTonemapper>(op.operation) || std::holds_alternative<std::shared_ptr<ColorTransformation>>(op.operation)) {
// the required resolution depends heavily on the function and on the input and output ranges / multipliers
// but this is good enough for now
return m_maxSize >= 1024;
} else if (std::holds_alternative<ColorMultiplier>(op.operation)) {
return true;
}
return false;
}
void DrmLutColorOp::program(DrmAtomicCommit *commit, std::span<const ColorOp> operations, double inputScale, double outputScale)
{
for (uint32_t i = 0; i < m_maxSize; i++) {
const double input = i / double(m_maxSize - 1);
const double scaledInput = input / inputScale;
QVector3D output(scaledInput, scaledInput, scaledInput);
for (const auto &op : operations) {
if (auto tf = std::get_if<ColorTransferFunction>(&op.operation)) {
output = tf->tf.encodedToNits(output);
} else if (auto tf = std::get_if<InverseColorTransferFunction>(&op.operation)) {
output = tf->tf.nitsToEncoded(output);
} else if (auto mult = std::get_if<ColorMultiplier>(&op.operation)) {
output *= mult->factors;
} else if (auto tonemap = std::get_if<ColorTonemapper>(&op.operation)) {
output.setX(tonemap->map(output.x()));
} else if (auto lut1d = std::get_if<std::shared_ptr<ColorTransformation>>(&op.operation)) {
output = (*lut1d)->transform(output);
} else {
Q_UNREACHABLE();
}
}
m_components[i] = {
.red = uint16_t(std::round(std::clamp(output.x() * outputScale, 0.0, 1.0) * std::numeric_limits<uint16_t>::max())),
.green = uint16_t(std::round(std::clamp(output.y() * outputScale, 0.0, 1.0) * std::numeric_limits<uint16_t>::max())),
.blue = uint16_t(std::round(std::clamp(output.z() * outputScale, 0.0, 1.0) * std::numeric_limits<uint16_t>::max())),
.reserved = 0,
};
}
commit->addBlob(*m_prop, DrmBlob::create(m_prop->drmObject()->gpu(), m_components.data(), sizeof(drm_color_lut) * m_maxSize));
}
void DrmLutColorOp::bypass(DrmAtomicCommit *commit)
{
commit->addBlob(*m_prop, nullptr);
}
LegacyMatrixColorOp::LegacyMatrixColorOp(DrmAbstractColorOp *next, DrmProperty *prop)
: DrmAbstractColorOp(next)
, m_prop(prop)
{
}
bool LegacyMatrixColorOp::canBeUsedFor(const ColorOp &op, bool scaling)
{
// this isn't necessarily true, but let's keep things simple for now
if (auto matrix = std::get_if<ColorMatrix>(&op.operation)) {
return std::abs(matrix->mat(3, 0) - 0) < ColorPipeline::s_maxResolution
&& std::abs(matrix->mat(3, 1) - 0) < ColorPipeline::s_maxResolution
&& std::abs(matrix->mat(3, 2) - 0) < ColorPipeline::s_maxResolution
&& std::abs(matrix->mat(3, 3) - 1) < ColorPipeline::s_maxResolution
&& std::abs(matrix->mat(0, 3) - 0) < ColorPipeline::s_maxResolution
&& std::abs(matrix->mat(1, 3) - 0) < ColorPipeline::s_maxResolution
&& std::abs(matrix->mat(2, 3) - 0) < ColorPipeline::s_maxResolution;
} else if (std::holds_alternative<ColorMultiplier>(op.operation)) {
return true;
}
return false;
}
static uint64_t doubleToFixed(double value)
{
// ctm values are in S31.32 sign-magnitude format
uint64_t ret = std::abs(value) * (1ull << 32);
if (value < 0) {
ret |= 1ull << 63;
}
return ret;
}
void LegacyMatrixColorOp::program(DrmAtomicCommit *commit, std::span<const ColorOp> operations, double inputScale, double outputScale)
{
// NOTE that matrix operations have to be added in reverse order to get the correct result!
QMatrix4x4 result;
result.scale(outputScale);
for (const auto &op : operations | std::views::reverse) {
if (auto matrix = std::get_if<ColorMatrix>(&op.operation)) {
result *= matrix->mat;
} else if (auto mult = std::get_if<ColorMultiplier>(&op.operation)) {
result.scale(mult->factors.x(), mult->factors.y(), mult->factors.z());
} else {
Q_UNREACHABLE();
}
}
result.scale(1.0 / inputScale);
drm_color_ctm data = {
.matrix = {
doubleToFixed(result(0, 0)), doubleToFixed(result(0, 1)), doubleToFixed(result(0, 2)), //
doubleToFixed(result(1, 0)), doubleToFixed(result(1, 1)), doubleToFixed(result(1, 2)), //
doubleToFixed(result(2, 0)), doubleToFixed(result(2, 1)), doubleToFixed(result(2, 2)), //
},
};
commit->addBlob(*m_prop, DrmBlob::create(m_prop->drmObject()->gpu(), &data, sizeof(data)));
}
void LegacyMatrixColorOp::bypass(DrmAtomicCommit *commit)
{
commit->addBlob(*m_prop, nullptr);
}
}
@@ -0,0 +1,71 @@
/*
KWin - the KDE window manager
This file is part of the KDE project.
SPDX-FileCopyrightText: 2024 Xaver Hugl <xaver.hugl@gmail.com>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#pragma once
#include "core/colorpipeline.h"
#include <drm.h>
#include <memory>
#include <span>
namespace KWin
{
class DrmBlob;
class DrmProperty;
class DrmAtomicCommit;
class DrmAbstractColorOp
{
public:
explicit DrmAbstractColorOp(DrmAbstractColorOp *next);
virtual ~DrmAbstractColorOp();
bool matchPipeline(DrmAtomicCommit *commit, const ColorPipeline &pipeline);
virtual bool canBeUsedFor(const ColorOp &op, bool scaling) = 0;
virtual void program(DrmAtomicCommit *commit, std::span<const ColorOp> operations, double inputScale, double outputScale) = 0;
virtual void bypass(DrmAtomicCommit *commit) = 0;
DrmAbstractColorOp *next() const;
protected:
DrmAbstractColorOp *m_next = nullptr;
std::optional<ColorPipeline> m_cachedPipeline;
std::unique_ptr<DrmAtomicCommit> m_cache;
};
class DrmLutColorOp : public DrmAbstractColorOp
{
public:
explicit DrmLutColorOp(DrmAbstractColorOp *next, DrmProperty *prop, uint32_t maxSize);
bool canBeUsedFor(const ColorOp &op, bool scaling) override;
void program(DrmAtomicCommit *commit, std::span<const ColorOp> operations, double inputScale, double outputScale) override;
void bypass(DrmAtomicCommit *commit) override;
private:
DrmProperty *const m_prop;
const uint32_t m_maxSize;
QList<drm_color_lut> m_components;
};
class LegacyMatrixColorOp : public DrmAbstractColorOp
{
public:
explicit LegacyMatrixColorOp(DrmAbstractColorOp *next, DrmProperty *prop);
bool canBeUsedFor(const ColorOp &op, bool scaling) override;
void program(DrmAtomicCommit *commit, std::span<const ColorOp> operations, double inputScale, double outputScale) override;
void bypass(DrmAtomicCommit *commit) override;
private:
DrmProperty *const m_prop;
};
}
@@ -0,0 +1,315 @@
/*
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);
}
}
@@ -0,0 +1,125 @@
/*
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
*/
#pragma once
#include <memory>
#include <xf86drmMode.h>
#include <QHash>
#include <chrono>
#include <unordered_map>
#include <unordered_set>
#include "core/renderloop.h"
#include "drm_pointer.h"
#include "drm_property.h"
namespace KWin
{
class DrmBlob;
class DrmConnector;
class DrmConnectorMode;
class DrmCrtc;
class DrmFramebuffer;
class DrmGpu;
class DrmPlane;
class DrmProperty;
class DrmPipeline;
class OutputFrame;
class DrmCommit
{
public:
virtual ~DrmCommit();
DrmGpu *gpu() const;
virtual void pageFlipped(std::chrono::nanoseconds timestamp) = 0;
void setDefunct();
protected:
DrmCommit(DrmGpu *gpu);
DrmGpu *const m_gpu;
bool m_defunct = false;
};
class DrmAtomicCommit : public DrmCommit
{
public:
explicit DrmAtomicCommit(DrmGpu *gpu);
explicit DrmAtomicCommit(const QList<DrmPipeline *> &pipelines);
explicit DrmAtomicCommit(const DrmAtomicCommit &copy) = default;
void addProperty(const DrmProperty &prop, uint64_t value);
template<typename T>
void addEnum(const DrmEnumProperty<T> &prop, T enumValue)
{
addProperty(prop, prop.valueForEnum(enumValue));
}
void addBlob(const DrmProperty &prop, const std::shared_ptr<DrmBlob> &blob);
void addBuffer(DrmPlane *plane, const std::shared_ptr<DrmFramebuffer> &buffer, const std::shared_ptr<OutputFrame> &frame);
void setVrr(DrmCrtc *crtc, bool vrr);
void setPresentationMode(PresentationMode mode);
bool test();
bool testAllowModeset();
bool commit();
bool commitModeset();
void pageFlipped(std::chrono::nanoseconds timestamp) override;
bool areBuffersReadable() const;
void setDeadline(std::chrono::steady_clock::time_point deadline);
std::optional<bool> isVrr() const;
const std::unordered_set<DrmPlane *> &modifiedPlanes() const;
void merge(DrmAtomicCommit *onTop);
void setAllowedVrrDelay(std::optional<std::chrono::nanoseconds> allowedDelay);
std::optional<std::chrono::nanoseconds> allowedVrrDelay() const;
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);
const QList<DrmPipeline *> m_pipelines;
std::optional<std::chrono::steady_clock::time_point> m_targetPageflipTime;
std::optional<std::chrono::nanoseconds> m_allowedVrrDelay;
std::unordered_map<const DrmProperty *, std::shared_ptr<DrmBlob>> m_blobs;
std::unordered_map<DrmPlane *, std::shared_ptr<DrmFramebuffer>> m_buffers;
std::unordered_map<DrmPlane *, std::shared_ptr<OutputFrame>> m_frames;
std::unordered_set<DrmPlane *> m_planes;
std::optional<bool> m_vrr;
std::unordered_map<uint32_t /* object */, std::unordered_map<uint32_t /* property */, uint64_t /* value */>> m_properties;
bool m_modeset = false;
PresentationMode m_mode = PresentationMode::VSync;
};
class DrmLegacyCommit : public DrmCommit
{
public:
DrmLegacyCommit(DrmPipeline *pipeline, const std::shared_ptr<DrmFramebuffer> &buffer, const std::shared_ptr<OutputFrame> &frame);
bool doModeset(DrmConnector *connector, DrmConnectorMode *mode);
bool doPageflip(PresentationMode mode);
void pageFlipped(std::chrono::nanoseconds timestamp) override;
private:
DrmPipeline *const m_pipeline;
DrmCrtc *const m_crtc;
const std::shared_ptr<DrmFramebuffer> m_buffer;
std::shared_ptr<OutputFrame> m_frame;
PresentationMode m_mode = PresentationMode::VSync;
};
}
@@ -0,0 +1,415 @@
/*
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_thread.h"
#include "drm_commit.h"
#include "drm_gpu.h"
#include "drm_logging.h"
#include "utils/realtime.h"
#include <ranges>
#include <span>
#include <thread>
using namespace std::chrono_literals;
namespace KWin
{
DrmCommitThread::DrmCommitThread(DrmGpu *gpu, const QString &name)
: m_gpu(gpu)
{
if (!gpu->atomicModeSetting()) {
return;
}
m_thread.reset(QThread::create([this]() {
const auto thread = QThread::currentThread();
gainRealTime();
while (true) {
if (thread->isInterruptionRequested()) {
return;
}
std::unique_lock lock(m_mutex);
bool timeout = false;
if (m_committed) {
timeout = m_commitPending.wait_for(lock, DrmGpu::s_pageflipTimeout) == std::cv_status::timeout;
} else if (m_commits.empty()) {
m_commitPending.wait(lock);
}
if (m_committed) {
if (timeout) {
// if the main thread just hung for a while, the pageflip will be processed after the wait
// but not if it's a real pageflip timeout
m_ping = false;
QMetaObject::invokeMethod(this, &DrmCommitThread::handlePing, Qt::ConnectionType::QueuedConnection);
while (!m_ping) {
m_pong.wait(lock);
}
if (m_committed) {
qCCritical(KWIN_DRM, "Pageflip timed out! This is a bug in the %s kernel driver", qPrintable(m_gpu->driverName()));
if (m_gpu->isAmdgpu()) {
qCCritical(KWIN_DRM, "Please report this at https://gitlab.freedesktop.org/drm/amd/-/issues");
} else if (m_gpu->isNVidia()) {
qCCritical(KWIN_DRM, "Please report this at https://forums.developer.nvidia.com/c/gpu-graphics/linux");
} else if (m_gpu->isI915()) {
qCCritical(KWIN_DRM, "Please report this at https://gitlab.freedesktop.org/drm/i915/kernel/-/issues");
}
qCCritical(KWIN_DRM, "With the output of 'sudo dmesg' and 'journalctl --user-unit plasma-kwin_wayland --boot 0'");
m_pageflipTimeoutDetected = true;
} else {
qCWarning(KWIN_DRM, "The main thread was hanging temporarily!");
}
} else {
// the commit would fail with EBUSY, wait until the pageflip is done
}
continue;
}
if (m_commits.empty()) {
continue;
}
const auto now = std::chrono::steady_clock::now();
if (m_targetPageflipTime > now + m_safetyMargin) {
lock.unlock();
std::this_thread::sleep_until(m_targetPageflipTime - m_safetyMargin);
lock.lock();
// the main thread might've modified the list
if (m_commits.empty()) {
continue;
}
}
optimizeCommits(m_targetPageflipTime);
if (!m_commits.front()->isReadyFor(m_targetPageflipTime)) {
// no commit is ready yet, reschedule
if (m_vrr || m_tearing) {
m_targetPageflipTime += 50us;
} else {
m_targetPageflipTime += m_minVblankInterval;
}
continue;
}
if (m_commits.front()->allowedVrrDelay() && m_vrr) {
// wait for a higher priority commit to be in, or the timeout to be hit
const bool allDelay = std::ranges::all_of(m_commits, [](const auto &commit) {
return commit->allowedVrrDelay().has_value();
});
auto delays = m_commits | std::views::filter([](const auto &commit) {
return commit->allowedVrrDelay().has_value();
}) | std::views::transform([](const auto &commit) {
return *commit->allowedVrrDelay();
});
const std::chrono::nanoseconds lowestDelay = *std::ranges::min_element(delays);
const auto delayedTarget = m_lastPageflip + lowestDelay;
if (allDelay) {
// all commits should be delayed, just wait for the timeout
if (m_commitPending.wait_until(lock, delayedTarget) == std::cv_status::no_timeout) {
continue;
}
} else {
// TODO replace this with polling for the buffers to be ready instead
bool timeout = true;
while (std::chrono::steady_clock::now() < delayedTarget && timeout && m_commits.front()->allowedVrrDelay().has_value()) {
timeout = m_commitPending.wait_for(lock, 50us) == std::cv_status::timeout;
if (m_commits.empty()) {
break;
}
optimizeCommits(delayedTarget);
}
if (!timeout) {
// some new commit was added, process that
continue;
}
}
if (m_commits.empty()) {
continue;
}
}
submit();
}
}));
m_thread->setObjectName(name);
m_thread->start();
}
void DrmCommitThread::submit()
{
DrmAtomicCommit *commit = m_commits.front().get();
const auto vrr = commit->isVrr();
const bool success = commit->commit();
if (success) {
m_lastCommitTime = std::chrono::steady_clock::now();
m_vrr = vrr.value_or(m_vrr);
m_tearing = commit->isTearing();
m_committed = std::move(m_commits.front());
m_commits.erase(m_commits.begin());
} else {
if (m_commits.size() > 1) {
// the failure may have been because of the reordering of commits
// -> collapse all commits into one and try again with an already tested state
while (m_commits.size() > 1) {
auto toMerge = std::move(m_commits[1]);
m_commits.erase(m_commits.begin() + 1);
commit->merge(toMerge.get());
m_commitsToDelete.push_back(std::move(toMerge));
}
if (commit->test()) {
// presentation didn't fail after all, try again
submit();
return;
}
}
for (auto &commit : m_commits) {
m_commitsToDelete.push_back(std::move(commit));
}
m_commits.clear();
qCWarning(KWIN_DRM) << "atomic commit failed:" << strerror(errno);
}
QMetaObject::invokeMethod(this, &DrmCommitThread::clearDroppedCommits, Qt::ConnectionType::QueuedConnection);
}
static std::unique_ptr<DrmAtomicCommit> mergeCommits(std::span<const std::unique_ptr<DrmAtomicCommit>> commits)
{
auto ret = std::make_unique<DrmAtomicCommit>(*commits.front());
for (const auto &onTop : commits.subspan(1)) {
ret->merge(onTop.get());
}
return ret;
}
void DrmCommitThread::optimizeCommits(TimePoint pageflipTarget)
{
if (m_commits.size() <= 1) {
return;
}
// merge commits in the front that are already ready (regardless of which planes they modify)
if (m_commits.front()->areBuffersReadable()) {
const auto firstNotReady = std::find_if(m_commits.begin() + 1, m_commits.end(), [pageflipTarget](const auto &commit) {
return !commit->isReadyFor(pageflipTarget);
});
if (firstNotReady != m_commits.begin() + 1) {
auto merged = mergeCommits(std::span(m_commits.begin(), firstNotReady));
std::move(m_commits.begin(), firstNotReady, std::back_inserter(m_commitsToDelete));
m_commits.erase(m_commits.begin() + 1, firstNotReady);
m_commits.front() = std::move(merged);
}
}
// merge commits that are ready and modify the same drm planes
for (auto it = m_commits.begin(); it != m_commits.end();) {
const auto startIt = it;
auto &startCommit = *startIt;
const auto firstNotSamePlaneNotReady = std::find_if(startIt + 1, m_commits.end(), [&startCommit, pageflipTarget](const auto &commit) {
return startCommit->modifiedPlanes() != commit->modifiedPlanes() || !commit->isReadyFor(pageflipTarget);
});
if (firstNotSamePlaneNotReady == startIt + 1) {
it++;
continue;
}
auto merged = mergeCommits(std::span(startIt, firstNotSamePlaneNotReady));
std::move(startIt, firstNotSamePlaneNotReady, std::back_inserter(m_commitsToDelete));
startCommit = std::move(merged);
it = m_commits.erase(startIt + 1, firstNotSamePlaneNotReady);
}
if (m_commits.size() == 1) {
// already done
return;
}
std::unique_ptr<DrmAtomicCommit> front;
if (m_commits.front()->isReadyFor(pageflipTarget)) {
// can't just move the commit, or merging might drop the last reference
// to an OutputFrame, which should only happen in the main thread
front = std::make_unique<DrmAtomicCommit>(*m_commits.front());
m_commitsToDelete.push_back(std::move(m_commits.front()));
m_commits.erase(m_commits.begin());
}
// try to move commits that are ready to the front
for (auto it = m_commits.begin() + 1; it != m_commits.end();) {
auto &commit = *it;
if (!commit->isReadyFor(pageflipTarget)) {
it++;
continue;
}
// commits that target the same plane(s) need to stay in the same order
const auto &planes = commit->modifiedPlanes();
const bool skipping = std::any_of(m_commits.begin(), it, [&planes](const auto &other) {
return std::ranges::any_of(planes, [&other](DrmPlane *plane) {
return other->modifiedPlanes().contains(plane);
});
});
if (skipping) {
it++;
continue;
}
// find out if the modified commit order will actually work
std::unique_ptr<DrmAtomicCommit> duplicate;
if (front) {
duplicate = std::make_unique<DrmAtomicCommit>(*front);
duplicate->merge(commit.get());
if (!duplicate->test()) {
m_commitsToDelete.push_back(std::move(duplicate));
it++;
continue;
}
} else {
if (!commit->test()) {
it++;
continue;
}
duplicate = std::make_unique<DrmAtomicCommit>(*commit);
}
bool success = true;
for (const auto &otherCommit : m_commits) {
if (otherCommit != commit) {
duplicate->merge(otherCommit.get());
if (!duplicate->test()) {
success = false;
break;
}
}
}
m_commitsToDelete.push_back(std::move(duplicate));
if (success) {
if (front) {
front->merge(commit.get());
m_commitsToDelete.push_back(std::move(commit));
} else {
front = std::make_unique<DrmAtomicCommit>(*commit);
m_commitsToDelete.push_back(std::move(commit));
}
it = m_commits.erase(it);
} else {
it++;
}
}
if (front) {
m_commits.insert(m_commits.begin(), std::move(front));
}
}
DrmCommitThread::~DrmCommitThread()
{
if (m_thread) {
{
std::unique_lock lock(m_mutex);
m_thread->requestInterruption();
m_commitPending.notify_all();
m_ping = true;
m_pong.notify_all();
}
m_thread->wait();
if (m_committed) {
m_committed->setDefunct();
m_gpu->addDefunctCommit(std::move(m_committed));
}
}
}
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_tearing) {
m_targetPageflipTime = now;
} else if (m_vrr && now >= m_lastPageflip + m_minVblankInterval) {
m_targetPageflipTime = now;
} else {
m_targetPageflipTime = estimateNextVblank(now);
}
m_commits.back()->setDeadline(m_targetPageflipTime - m_safetyMargin);
m_commitPending.notify_all();
}
void DrmCommitThread::setPendingCommit(std::unique_ptr<DrmLegacyCommit> &&commit)
{
m_committed = std::move(commit);
}
void DrmCommitThread::clearDroppedCommits()
{
std::unique_lock lock(m_mutex);
m_commitsToDelete.clear();
}
static const std::chrono::microseconds s_safetyMarginMinimum = []() {
bool ok = false;
int value = qEnvironmentVariableIntValue("KWIN_DRM_OVERRIDE_SAFETY_MARGIN", &ok);
if (ok) {
return std::chrono::microseconds(value);
} else {
return 1500us;
}
}();
void DrmCommitThread::setModeInfo(uint32_t maximum, std::chrono::nanoseconds vblankTime)
{
std::unique_lock lock(m_mutex);
m_minVblankInterval = std::chrono::nanoseconds(1'000'000'000'000ull / maximum);
// the kernel rejects commits that happen during vblank
// the 1.5ms on top of that was chosen experimentally, for the time it takes to commit + scheduling inaccuracies
m_safetyMargin = vblankTime + s_safetyMarginMinimum;
}
void DrmCommitThread::pageFlipped(std::chrono::nanoseconds timestamp)
{
std::unique_lock lock(m_mutex);
if (m_pageflipTimeoutDetected) {
qCCritical(KWIN_DRM, "Pageflip arrived after all, %lums after the commit", std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::steady_clock::now() - m_lastCommitTime).count());
m_pageflipTimeoutDetected = false;
}
m_lastPageflip = TimePoint(timestamp);
m_committed.reset();
if (!m_commits.empty()) {
m_targetPageflipTime = estimateNextVblank(std::chrono::steady_clock::now());
m_commitPending.notify_all();
}
}
bool DrmCommitThread::pageflipsPending()
{
std::unique_lock lock(m_mutex);
return !m_commits.empty() || m_committed;
}
TimePoint DrmCommitThread::estimateNextVblank(TimePoint now) const
{
// the pageflip timestamp may be in the future
const uint64_t pageflipsSince = now >= m_lastPageflip ? (now - m_lastPageflip) / m_minVblankInterval : 0;
return m_lastPageflip + m_minVblankInterval * (pageflipsSince + 1);
}
std::chrono::nanoseconds DrmCommitThread::safetyMargin() const
{
return m_safetyMargin;
}
bool DrmCommitThread::drain()
{
std::unique_lock lock(m_mutex);
if (m_committed) {
return true;
}
if (m_commits.empty()) {
return false;
}
if (m_commits.size() > 1) {
m_commits.front() = mergeCommits(m_commits);
m_commits.erase(m_commits.begin() + 1, m_commits.end());
}
submit();
return m_committed != nullptr;
}
void DrmCommitThread::handlePing()
{
// this will process the pageflip and call pageFlipped if there is one
m_gpu->dispatchEvents();
std::unique_lock lock(m_mutex);
m_ping = true;
m_pong.notify_one();
}
}
@@ -0,0 +1,80 @@
/*
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
*/
#pragma once
#include <QObject>
#include <QThread>
#include <condition_variable>
#include <mutex>
#include <vector>
namespace KWin
{
class DrmGpu;
class DrmCommit;
class DrmAtomicCommit;
class DrmLegacyCommit;
using TimePoint = std::chrono::steady_clock::time_point;
class DrmCommitThread : public QObject
{
Q_OBJECT
public:
explicit DrmCommitThread(DrmGpu *gpu, const QString &name);
~DrmCommitThread();
void addCommit(std::unique_ptr<DrmAtomicCommit> &&commit);
void setPendingCommit(std::unique_ptr<DrmLegacyCommit> &&commit);
void setModeInfo(uint32_t maximum, std::chrono::nanoseconds vblankTime);
void pageFlipped(std::chrono::nanoseconds timestamp);
bool pageflipsPending();
/**
* @return how long before the desired presentation timestamp the commit has to be added
* in order to get presented at that timestamp
*/
std::chrono::nanoseconds safetyMargin() const;
/**
* attempts to submit the currently scheduled commits as a single one,
* if there isn't already a commit pending
*
* @returns if there's a pending commit after this method returns
*/
bool drain();
private:
void clearDroppedCommits();
TimePoint estimateNextVblank(TimePoint now) const;
void optimizeCommits(TimePoint pageflipTarget);
void submit();
void handlePing();
DrmGpu *const m_gpu;
std::unique_ptr<DrmCommit> m_committed;
std::vector<std::unique_ptr<DrmAtomicCommit>> m_commits;
std::unique_ptr<QThread> m_thread;
std::mutex m_mutex;
std::condition_variable m_commitPending;
std::condition_variable m_pong;
TimePoint m_lastPageflip;
TimePoint m_targetPageflipTime;
TimePoint m_lastCommitTime;
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};
bool m_ping = false;
bool m_pageflipTimeoutDetected = false;
};
}
@@ -0,0 +1,505 @@
/*
KWin - the KDE window manager
This file is part of the KDE project.
SPDX-FileCopyrightText: 2016 Roman Gilg <subdiff@gmail.com>
SPDX-FileCopyrightText: 2021 Xaver Hugl <xaver.hugl@gmail.com>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "drm_connector.h"
#include "drm_commit.h"
#include "drm_crtc.h"
#include "drm_gpu.h"
#include "drm_logging.h"
#include "drm_output.h"
#include "drm_pipeline.h"
#include "drm_pointer.h"
#include <cerrno>
#include <cstring>
#include <libxcvt/libxcvt.h>
namespace KWin
{
static QSize resolutionForMode(const drmModeModeInfo *info)
{
return QSize(info->hdisplay, info->vdisplay);
}
static quint64 refreshRateForMode(_drmModeModeInfo *m)
{
// Calculate higher precision (mHz) refresh rate
// logic based on Weston, see compositor-drm.c
quint64 refreshRate = (m->clock * 1000000LL / m->htotal + m->vtotal / 2) / m->vtotal;
if (m->flags & DRM_MODE_FLAG_INTERLACE) {
refreshRate *= 2;
}
if (m->flags & DRM_MODE_FLAG_DBLSCAN) {
refreshRate /= 2;
}
if (m->vscan > 1) {
refreshRate /= m->vscan;
}
return refreshRate;
}
static OutputMode::Flags flagsForMode(const drmModeModeInfo *info, OutputMode::Flags additionalFlags)
{
OutputMode::Flags flags = additionalFlags;
if (info->type & DRM_MODE_TYPE_PREFERRED) {
flags |= OutputMode::Flag::Preferred;
}
return flags;
}
DrmConnectorMode::DrmConnectorMode(DrmConnector *connector, drmModeModeInfo nativeMode, Flags additionalFlags)
: OutputMode(resolutionForMode(&nativeMode), refreshRateForMode(&nativeMode), flagsForMode(&nativeMode, additionalFlags))
, m_connector(connector)
, m_nativeMode(nativeMode)
{
}
std::shared_ptr<DrmBlob> DrmConnectorMode::blob()
{
if (!m_blob) {
m_blob = DrmBlob::create(m_connector->gpu(), &m_nativeMode, sizeof(m_nativeMode));
}
return m_blob;
}
std::chrono::nanoseconds DrmConnectorMode::vblankTime() const
{
return std::chrono::nanoseconds(((m_nativeMode.vtotal - m_nativeMode.vdisplay) * m_nativeMode.htotal * 1'000'000ULL) / m_nativeMode.clock);
}
drmModeModeInfo *DrmConnectorMode::nativeMode()
{
return &m_nativeMode;
}
static inline bool checkIfEqual(const drmModeModeInfo *one, const drmModeModeInfo *two)
{
return std::memcmp(one, two, sizeof(drmModeModeInfo)) == 0;
}
bool DrmConnectorMode::operator==(const DrmConnectorMode &otherMode)
{
return checkIfEqual(&m_nativeMode, &otherMode.m_nativeMode);
}
bool DrmConnectorMode::operator==(const drmModeModeInfo &otherMode)
{
return checkIfEqual(&m_nativeMode, &otherMode);
}
DrmConnector::DrmConnector(DrmGpu *gpu, uint32_t connectorId)
: DrmObject(gpu, connectorId, DRM_MODE_OBJECT_CONNECTOR)
, crtcId(this, QByteArrayLiteral("CRTC_ID"))
, nonDesktop(this, QByteArrayLiteral("non-desktop"))
, dpms(this, QByteArrayLiteral("DPMS"))
, edidProp(this, QByteArrayLiteral("EDID"))
, overscan(this, QByteArrayLiteral("overscan"))
, vrrCapable(this, QByteArrayLiteral("vrr_capable"))
, underscan(this, QByteArrayLiteral("underscan"), {
QByteArrayLiteral("off"),
QByteArrayLiteral("on"),
QByteArrayLiteral("auto"),
})
, underscanVBorder(this, QByteArrayLiteral("underscan vborder"))
, underscanHBorder(this, QByteArrayLiteral("underscan hborder"))
, broadcastRGB(this, QByteArrayLiteral("Broadcast RGB"), {
QByteArrayLiteral("Automatic"),
QByteArrayLiteral("Full"),
QByteArrayLiteral("Limited 16:235"),
})
, maxBpc(this, QByteArrayLiteral("max bpc"))
, linkStatus(this, QByteArrayLiteral("link-status"), {
QByteArrayLiteral("Good"),
QByteArrayLiteral("Bad"),
})
, contentType(this, QByteArrayLiteral("content type"), {
QByteArrayLiteral("No Data"),
QByteArrayLiteral("Graphics"),
QByteArrayLiteral("Photo"),
QByteArrayLiteral("Cinema"),
QByteArrayLiteral("Game"),
})
, panelOrientation(this, QByteArrayLiteral("panel orientation"), {
QByteArrayLiteral("Normal"),
QByteArrayLiteral("Upside Down"),
QByteArrayLiteral("Left Side Up"),
QByteArrayLiteral("Right Side Up"),
})
, hdrMetadata(this, QByteArrayLiteral("HDR_OUTPUT_METADATA"))
, scalingMode(this, QByteArrayLiteral("scaling mode"), {
QByteArrayLiteral("None"),
QByteArrayLiteral("Full"),
QByteArrayLiteral("Center"),
QByteArrayLiteral("Full aspect"),
})
, colorspace(this, QByteArrayLiteral("Colorspace"), {
QByteArrayLiteral("Default"),
QByteArrayLiteral("BT709_YCC"),
QByteArrayLiteral("opRGB"),
QByteArrayLiteral("BT2020_RGB"),
QByteArrayLiteral("BT2020_YCC"),
})
, path(this, QByteArrayLiteral("PATH"))
{
}
bool DrmConnector::init()
{
if (!updateProperties()) {
return false;
}
m_possibleCrtcs = drmModeConnectorGetPossibleCrtcs(gpu()->fd(), m_conn.get());
return true;
}
bool DrmConnector::isConnected() const
{
return !m_driverModes.empty() && m_conn && m_conn->connection == DRM_MODE_CONNECTED;
}
QString DrmConnector::connectorName() const
{
const char *connectorName = drmModeGetConnectorTypeName(m_conn->connector_type);
if (!connectorName) {
connectorName = "Unknown";
}
return QStringLiteral("%1-%2").arg(connectorName).arg(m_conn->connector_type_id);
}
QString DrmConnector::modelName() const
{
if (m_edid.serialNumber().isEmpty()) {
return connectorName() + QLatin1Char('-') + m_edid.nameString();
} else {
return m_edid.nameString();
}
}
bool DrmConnector::isInternal() const
{
return m_conn->connector_type == DRM_MODE_CONNECTOR_LVDS || m_conn->connector_type == DRM_MODE_CONNECTOR_eDP
|| m_conn->connector_type == DRM_MODE_CONNECTOR_DSI;
}
QSize DrmConnector::physicalSize() const
{
return m_physicalSize;
}
QByteArray DrmConnector::mstPath() const
{
return m_mstPath;
}
QList<std::shared_ptr<DrmConnectorMode>> DrmConnector::modes() const
{
return m_modes;
}
std::shared_ptr<DrmConnectorMode> DrmConnector::findMode(const drmModeModeInfo &modeInfo) const
{
const auto it = std::ranges::find_if(m_modes, [&modeInfo](const auto &mode) {
return checkIfEqual(mode->nativeMode(), &modeInfo);
});
return it == m_modes.constEnd() ? nullptr : *it;
}
Output::SubPixel DrmConnector::subpixel() const
{
switch (m_conn->subpixel) {
case DRM_MODE_SUBPIXEL_UNKNOWN:
return Output::SubPixel::Unknown;
case DRM_MODE_SUBPIXEL_HORIZONTAL_RGB:
return Output::SubPixel::Horizontal_RGB;
case DRM_MODE_SUBPIXEL_HORIZONTAL_BGR:
return Output::SubPixel::Horizontal_BGR;
case DRM_MODE_SUBPIXEL_VERTICAL_RGB:
return Output::SubPixel::Vertical_RGB;
case DRM_MODE_SUBPIXEL_VERTICAL_BGR:
return Output::SubPixel::Vertical_BGR;
case DRM_MODE_SUBPIXEL_NONE:
return Output::SubPixel::None;
default:
return Output::SubPixel::Unknown;
}
}
bool DrmConnector::updateProperties()
{
if (auto connector = drmModeGetConnector(gpu()->fd(), id())) {
m_conn.reset(connector);
} else {
qCWarning(KWIN_DRM) << "drmModeGetConnector() failed:" << strerror(errno);
}
if (!m_conn) {
return false;
}
DrmPropertyList props = queryProperties();
crtcId.update(props);
nonDesktop.update(props);
dpms.update(props);
edidProp.update(props);
overscan.update(props);
vrrCapable.update(props);
underscan.update(props);
underscanVBorder.update(props);
underscanHBorder.update(props);
broadcastRGB.update(props);
maxBpc.update(props);
linkStatus.update(props);
contentType.update(props);
panelOrientation.update(props);
hdrMetadata.update(props);
scalingMode.update(props);
colorspace.update(props);
path.update(props);
if (gpu()->atomicModeSetting() && !crtcId.isValid()) {
qCWarning(KWIN_DRM) << "Failed to update the basic connector properties (CRTC_ID)";
return false;
}
// parse edid
if (edidProp.immutableBlob()) {
m_edid = Edid(edidProp.immutableBlob()->data, edidProp.immutableBlob()->length);
if (!m_edid.isValid()) {
qCWarning(KWIN_DRM) << "Couldn't parse EDID for connector" << this;
}
} else {
m_edid = Edid{};
if (m_conn->connection == DRM_MODE_CONNECTED) {
qCDebug(KWIN_DRM) << "Could not find edid for connector" << this;
}
}
// check the physical size
if (m_edid.physicalSize().isEmpty()) {
m_physicalSize = QSize(m_conn->mmWidth, m_conn->mmHeight);
} else {
m_physicalSize = m_edid.physicalSize();
}
// update modes
bool equal = m_conn->count_modes == m_driverModes.count();
for (int i = 0; equal && i < m_conn->count_modes; i++) {
equal &= checkIfEqual(m_driverModes[i]->nativeMode(), &m_conn->modes[i]);
}
if (!equal && m_conn->count_modes > 0) {
// reload modes
m_driverModes.clear();
for (int i = 0; i < m_conn->count_modes; i++) {
m_driverModes.append(std::make_shared<DrmConnectorMode>(this, m_conn->modes[i], OutputMode::Flags()));
}
m_modes.clear();
m_modes.append(m_driverModes);
if (scalingMode.isValid() && scalingMode.hasEnum(ScalingMode::Full_Aspect)) {
m_modes.append(generateCommonModes());
}
}
m_mstPath.clear();
if (auto blob = path.immutableBlob()) {
QByteArray value = QByteArray(static_cast<const char *>(blob->data), blob->length);
if (value.startsWith("mst:")) {
// for backwards compatibility reasons the string also contains the drm connector id
// remove that to get a more stable identifier
const ssize_t firstHyphen = value.indexOf('-');
if (firstHyphen > 0) {
m_mstPath = value.mid(firstHyphen);
} else {
qCWarning(KWIN_DRM) << "Unexpected format in path property:" << value;
}
} else {
qCWarning(KWIN_DRM) << "Unknown path type detected:" << value;
}
}
return true;
}
bool DrmConnector::isCrtcSupported(DrmCrtc *crtc) const
{
return (m_possibleCrtcs & (1 << crtc->pipeIndex()));
}
bool DrmConnector::isNonDesktop() const
{
return nonDesktop.isValid() && nonDesktop.value() == 1;
}
const Edid *DrmConnector::edid() const
{
return &m_edid;
}
void DrmConnector::disable(DrmAtomicCommit *commit)
{
commit->addProperty(crtcId, 0);
}
static const QList<QSize> s_commonModes = {
/* 4:3 (1.33) */
QSize(1600, 1200),
QSize(1280, 1024), /* 5:4 (1.25) */
QSize(1024, 768),
/* 16:10 (1.6) */
QSize(2560, 1600),
QSize(1920, 1200),
QSize(1280, 800),
/* 16:9 (1.77) */
QSize(5120, 2880),
QSize(3840, 2160),
QSize(3200, 1800),
QSize(2880, 1620),
QSize(2560, 1440),
QSize(1920, 1080),
QSize(1600, 900),
QSize(1368, 768),
QSize(1280, 720),
};
QList<std::shared_ptr<DrmConnectorMode>> DrmConnector::generateCommonModes()
{
QList<std::shared_ptr<DrmConnectorMode>> ret;
QSize maxSize;
uint32_t maxSizeRefreshRate = 0;
for (const auto &mode : std::as_const(m_driverModes)) {
if (mode->size().width() >= maxSize.width() && mode->size().height() >= maxSize.height() && mode->refreshRate() >= maxSizeRefreshRate) {
maxSize = mode->size();
maxSizeRefreshRate = mode->refreshRate();
}
}
const uint64_t maxBandwidthEstimation = maxSize.width() * maxSize.height() * uint64_t(maxSizeRefreshRate);
for (const auto &size : s_commonModes) {
const uint64_t bandwidthEstimation = size.width() * size.height() * 60000ull;
if (size.width() > maxSize.width() || size.height() > maxSize.height() || bandwidthEstimation > maxBandwidthEstimation) {
continue;
}
const auto generatedMode = generateMode(size, 60);
const bool alreadyExists = std::ranges::any_of(m_driverModes, [generatedMode](const auto &mode) {
return mode->size() == generatedMode->size() && mode->refreshRate() == generatedMode->refreshRate();
});
if (alreadyExists) {
continue;
}
ret << generatedMode;
}
return ret;
}
std::shared_ptr<DrmConnectorMode> DrmConnector::generateMode(const QSize &size, float refreshRate)
{
auto modeInfo = libxcvt_gen_mode_info(size.width(), size.height(), refreshRate, false, false);
drmModeModeInfo mode{
.clock = uint32_t(modeInfo->dot_clock),
.hdisplay = uint16_t(modeInfo->hdisplay),
.hsync_start = modeInfo->hsync_start,
.hsync_end = modeInfo->hsync_end,
.htotal = modeInfo->htotal,
.vdisplay = uint16_t(modeInfo->vdisplay),
.vsync_start = modeInfo->vsync_start,
.vsync_end = modeInfo->vsync_end,
.vtotal = modeInfo->vtotal,
.vscan = 1,
.vrefresh = uint32_t(modeInfo->vrefresh),
.flags = modeInfo->mode_flags,
.type = DRM_MODE_TYPE_USERDEF,
};
sprintf(mode.name, "%dx%d@%d", size.width(), size.height(), mode.vrefresh);
free(modeInfo);
return std::make_shared<DrmConnectorMode>(this, mode, OutputMode::Flag::Generated);
}
QDebug &operator<<(QDebug &s, const KWin::DrmConnector *obj)
{
QDebugStateSaver saver(s);
if (obj) {
QString connState = QStringLiteral("Disconnected");
if (!obj->m_conn || obj->m_conn->connection == DRM_MODE_UNKNOWNCONNECTION) {
connState = QStringLiteral("Unknown Connection");
} else if (obj->m_conn->connection == DRM_MODE_CONNECTED) {
connState = QStringLiteral("Connected");
}
s.nospace() << "DrmConnector(id=" << obj->id() << ", gpu=" << obj->gpu() << ", name=" << obj->connectorName() << ", connection=" << connState << ", countMode=" << (obj->m_conn ? obj->m_conn->count_modes : 0)
<< ')';
} else {
s << "DrmConnector(0x0)";
}
return s;
}
DrmConnector::DrmContentType DrmConnector::kwinToDrmContentType(ContentType type)
{
switch (type) {
case ContentType::None:
return DrmContentType::Graphics;
case ContentType::Photo:
return DrmContentType::Photo;
case ContentType::Video:
return DrmContentType::Cinema;
case ContentType::Game:
return DrmContentType::Game;
default:
Q_UNREACHABLE();
}
}
OutputTransform DrmConnector::toKWinTransform(PanelOrientation orientation)
{
switch (orientation) {
case PanelOrientation::Normal:
return KWin::OutputTransform::Normal;
case PanelOrientation::RightUp:
return KWin::OutputTransform::Rotate270;
case PanelOrientation::LeftUp:
return KWin::OutputTransform::Rotate90;
case PanelOrientation::UpsideDown:
return KWin::OutputTransform::Rotate180;
default:
Q_UNREACHABLE();
}
}
DrmConnector::BroadcastRgbOptions DrmConnector::rgbRangeToBroadcastRgb(Output::RgbRange rgbRange)
{
switch (rgbRange) {
case Output::RgbRange::Automatic:
return BroadcastRgbOptions::Automatic;
case Output::RgbRange::Full:
return BroadcastRgbOptions::Full;
case Output::RgbRange::Limited:
return BroadcastRgbOptions::Limited;
default:
Q_UNREACHABLE();
}
}
Output::RgbRange DrmConnector::broadcastRgbToRgbRange(BroadcastRgbOptions rgbRange)
{
switch (rgbRange) {
case BroadcastRgbOptions::Automatic:
return Output::RgbRange::Automatic;
case BroadcastRgbOptions::Full:
return Output::RgbRange::Full;
case BroadcastRgbOptions::Limited:
return Output::RgbRange::Limited;
default:
Q_UNREACHABLE();
}
}
}
@@ -0,0 +1,161 @@
/*
KWin - the KDE window manager
This file is part of the KDE project.
SPDX-FileCopyrightText: 2016 Roman Gilg <subdiff@gmail.com>
SPDX-FileCopyrightText: 2021 Xaver Hugl <xaver.hugl@gmail.com>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#pragma once
#include <QPoint>
#include <QSize>
#include <QSize>
#include "core/output.h"
#include "drm_blob.h"
#include "drm_object.h"
#include "drm_pointer.h"
#include "utils/edid.h"
namespace KWin
{
class DrmConnector;
class DrmCrtc;
/**
* The DrmConnectorMode class represents a native mode and the associated blob.
*/
class DrmConnectorMode : public OutputMode
{
public:
DrmConnectorMode(DrmConnector *connector, drmModeModeInfo nativeMode, Flags additionalFlags);
drmModeModeInfo *nativeMode();
std::shared_ptr<DrmBlob> blob();
std::chrono::nanoseconds vblankTime() const;
bool operator==(const DrmConnectorMode &otherMode);
bool operator==(const drmModeModeInfo &otherMode);
private:
DrmConnector *m_connector;
drmModeModeInfo m_nativeMode;
std::shared_ptr<DrmBlob> m_blob;
};
class DrmConnector : public DrmObject
{
public:
DrmConnector(DrmGpu *gpu, uint32_t connectorId);
bool init();
bool updateProperties() override;
void disable(DrmAtomicCommit *commit) override;
bool isCrtcSupported(DrmCrtc *crtc) const;
bool isConnected() const;
bool isNonDesktop() const;
bool isInternal() const;
const Edid *edid() const;
QString connectorName() const;
QString modelName() const;
QSize physicalSize() const;
/**
* @returns the mst path of the connector. Is empty if invalid
*/
QByteArray mstPath() const;
QList<std::shared_ptr<DrmConnectorMode>> modes() const;
std::shared_ptr<DrmConnectorMode> findMode(const drmModeModeInfo &modeInfo) const;
Output::SubPixel subpixel() const;
enum class UnderscanOptions : uint64_t {
Off = 0,
On = 1,
Auto = 2,
};
enum class BroadcastRgbOptions : uint64_t {
Automatic = 0,
Full = 1,
Limited = 2
};
enum class LinkStatus : uint64_t {
Good = 0,
Bad = 1
};
enum class DrmContentType : uint64_t {
None = 0,
Graphics = 1,
Photo = 2,
Cinema = 3,
Game = 4
};
enum class PanelOrientation : uint64_t {
Normal = 0,
UpsideDown = 1,
LeftUp = 2,
RightUp = 3
};
enum class ScalingMode : uint64_t {
None = 0,
Full = 1,
Center = 2,
Full_Aspect = 3
};
enum class Colorspace : uint64_t {
Default,
BT709_YCC,
opRGB,
BT2020_RGB,
BT2020_YCC,
};
DrmProperty crtcId;
DrmProperty nonDesktop;
DrmProperty dpms;
DrmProperty edidProp;
DrmProperty overscan;
DrmProperty vrrCapable;
DrmEnumProperty<UnderscanOptions> underscan;
DrmProperty underscanVBorder;
DrmProperty underscanHBorder;
DrmEnumProperty<BroadcastRgbOptions> broadcastRGB;
DrmProperty maxBpc;
DrmEnumProperty<LinkStatus> linkStatus;
DrmEnumProperty<DrmContentType> contentType;
DrmEnumProperty<PanelOrientation> panelOrientation;
DrmProperty hdrMetadata;
DrmEnumProperty<ScalingMode> scalingMode;
DrmEnumProperty<Colorspace> colorspace;
DrmProperty path;
static DrmContentType kwinToDrmContentType(ContentType type);
static OutputTransform toKWinTransform(PanelOrientation orientation);
static BroadcastRgbOptions rgbRangeToBroadcastRgb(Output::RgbRange rgbRange);
static Output::RgbRange broadcastRgbToRgbRange(BroadcastRgbOptions rgbRange);
private:
QList<std::shared_ptr<DrmConnectorMode>> generateCommonModes();
std::shared_ptr<DrmConnectorMode> generateMode(const QSize &size, float refreshRate);
DrmUniquePtr<drmModeConnector> m_conn;
Edid m_edid;
QSize m_physicalSize = QSize(-1, -1);
QList<std::shared_ptr<DrmConnectorMode>> m_driverModes;
QList<std::shared_ptr<DrmConnectorMode>> m_modes;
uint32_t m_possibleCrtcs = 0;
QByteArray m_mstPath;
friend QDebug &operator<<(QDebug &s, const KWin::DrmConnector *obj);
};
QDebug &operator<<(QDebug &s, const KWin::DrmConnector *obj);
}
@@ -0,0 +1,139 @@
/*
KWin - the KDE window manager
This file is part of the KDE project.
SPDX-FileCopyrightText: 2016 Roman Gilg <subdiff@gmail.com>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "drm_crtc.h"
#include "drm_backend.h"
#include "drm_buffer.h"
#include "drm_commit.h"
#include "drm_gpu.h"
#include "drm_output.h"
#include "drm_pointer.h"
namespace KWin
{
DrmCrtc::DrmCrtc(DrmGpu *gpu, uint32_t crtcId, int pipeIndex, DrmPlane *primaryPlane, DrmPlane *cursorPlane)
: DrmObject(gpu, crtcId, DRM_MODE_OBJECT_CRTC)
, modeId(this, QByteArrayLiteral("MODE_ID"))
, active(this, QByteArrayLiteral("ACTIVE"))
, vrrEnabled(this, QByteArrayLiteral("VRR_ENABLED"))
, gammaLut(this, QByteArrayLiteral("GAMMA_LUT"))
, gammaLutSize(this, QByteArrayLiteral("GAMMA_LUT_SIZE"))
, ctm(this, QByteArrayLiteral("CTM"))
, degammaLut(this, QByteArrayLiteral("DEGAMMA_LUT"))
, degammaLutSize(this, QByteArrayLiteral("DEGAMMA_LUT_SIZE"))
, m_crtc(drmModeGetCrtc(gpu->fd(), crtcId))
, m_pipeIndex(pipeIndex)
, m_primaryPlane(primaryPlane)
, m_cursorPlane(cursorPlane)
{
}
bool DrmCrtc::init()
{
return updateProperties();
}
bool DrmCrtc::updateProperties()
{
if (!m_crtc) {
return false;
}
DrmPropertyList props = queryProperties();
modeId.update(props);
active.update(props);
vrrEnabled.update(props);
gammaLut.update(props);
gammaLutSize.update(props);
ctm.update(props);
degammaLut.update(props);
degammaLutSize.update(props);
if (!postBlendingPipeline) {
DrmAbstractColorOp *next = nullptr;
if (gammaLut.isValid() && gammaLutSize.isValid() && gammaLutSize.value() > 0) {
m_postBlendingColorOps.push_back(std::make_unique<DrmLutColorOp>(next, &gammaLut, gammaLutSize.value()));
next = m_postBlendingColorOps.back().get();
}
if (!gpu()->isNVidia() && ctm.isValid()) {
m_postBlendingColorOps.push_back(std::make_unique<LegacyMatrixColorOp>(next, &ctm));
next = m_postBlendingColorOps.back().get();
}
if (!gpu()->isNVidia() && !gpu()->isI915() && degammaLut.isValid() && degammaLutSize.isValid() && degammaLutSize.value() > 0) {
m_postBlendingColorOps.push_back(std::make_unique<DrmLutColorOp>(next, &degammaLut, degammaLutSize.value()));
next = m_postBlendingColorOps.back().get();
}
postBlendingPipeline = next;
}
const bool ret = !gpu()->atomicModeSetting() || (modeId.isValid() && active.isValid());
if (!ret) {
qCWarning(KWIN_DRM) << "Failed to update the basic crtc properties. modeId:" << modeId.isValid() << "active:" << active.isValid();
}
return ret;
}
drmModeModeInfo DrmCrtc::queryCurrentMode()
{
DrmUniquePtr<drmModeCrtc> crtc(drmModeGetCrtc(gpu()->fd(), id()));
if (crtc) {
return crtc->mode;
} else {
return m_crtc->mode;
}
}
int DrmCrtc::pipeIndex() const
{
return m_pipeIndex;
}
std::shared_ptr<DrmFramebuffer> DrmCrtc::current() const
{
return m_currentBuffer;
}
void DrmCrtc::setCurrent(const std::shared_ptr<DrmFramebuffer> &buffer)
{
m_currentBuffer = buffer;
}
int DrmCrtc::gammaRampSize() const
{
if (gpu()->atomicModeSetting()) {
// limit atomic gamma ramp to 4096 to work around https://gitlab.freedesktop.org/drm/intel/-/issues/3916
if (gammaLutSize.isValid() && gammaLutSize.value() <= 4096) {
return gammaLutSize.value();
}
}
return m_crtc->gamma_size;
}
DrmPlane *DrmCrtc::primaryPlane() const
{
return m_primaryPlane;
}
DrmPlane *DrmCrtc::cursorPlane() const
{
return m_cursorPlane;
}
void DrmCrtc::disable(DrmAtomicCommit *commit)
{
commit->addProperty(active, 0);
commit->addProperty(modeId, 0);
}
void DrmCrtc::releaseCurrentBuffer()
{
if (m_currentBuffer) {
m_currentBuffer->releaseBuffer();
}
}
}
@@ -0,0 +1,67 @@
/*
KWin - the KDE window manager
This file is part of the KDE project.
SPDX-FileCopyrightText: 2016 Roman Gilg <subdiff@gmail.com>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#pragma once
#include "drm_colorop.h"
#include "drm_object.h"
#include <QPoint>
#include <memory>
namespace KWin
{
class DrmBackend;
class DrmFramebuffer;
class GammaRamp;
class DrmGpu;
class DrmPlane;
class DrmCrtc : public DrmObject
{
public:
explicit DrmCrtc(DrmGpu *gpu, uint32_t crtcId, int pipeIndex, DrmPlane *primaryPlane, DrmPlane *cursorPlane);
bool init();
void disable(DrmAtomicCommit *commit) override;
bool updateProperties() override;
int pipeIndex() const;
int gammaRampSize() const;
DrmPlane *primaryPlane() const;
DrmPlane *cursorPlane() const;
drmModeModeInfo queryCurrentMode();
std::shared_ptr<DrmFramebuffer> current() const;
void setCurrent(const std::shared_ptr<DrmFramebuffer> &buffer);
void releaseCurrentBuffer();
DrmProperty modeId;
DrmProperty active;
DrmProperty vrrEnabled;
DrmProperty gammaLut;
DrmProperty gammaLutSize;
DrmProperty ctm;
DrmProperty degammaLut;
DrmProperty degammaLutSize;
DrmAbstractColorOp *postBlendingPipeline = nullptr;
private:
DrmUniquePtr<drmModeCrtc> m_crtc;
std::shared_ptr<DrmFramebuffer> m_currentBuffer;
int m_pipeIndex;
DrmPlane *m_primaryPlane;
DrmPlane *m_cursorPlane;
std::vector<std::unique_ptr<DrmAbstractColorOp>> m_postBlendingColorOps;
};
}
@@ -0,0 +1,194 @@
/*
KWin - the KDE window manager
This file is part of the KDE project.
SPDX-FileCopyrightText: 2015 Martin Gräßlin <mgraesslin@kde.org>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "drm_egl_backend.h"
#include "platformsupport/scenes/opengl/basiceglsurfacetexture_wayland.h"
// kwin
#include "core/syncobjtimeline.h"
#include "drm_abstract_output.h"
#include "drm_backend.h"
#include "drm_egl_layer.h"
#include "drm_gpu.h"
#include "drm_logging.h"
#include "drm_output.h"
#include "drm_pipeline.h"
#include "drm_virtual_egl_layer.h"
// system
#include <drm_fourcc.h>
#include <gbm.h>
#include <unistd.h>
namespace KWin
{
EglGbmBackend::EglGbmBackend(DrmBackend *drmBackend)
: m_backend(drmBackend)
{
drmBackend->setRenderBackend(this);
connect(m_backend, &DrmBackend::gpuRemoved, this, [this](DrmGpu *gpu) {
m_contexts.erase(gpu->eglDisplay());
});
}
EglGbmBackend::~EglGbmBackend()
{
m_backend->releaseBuffers();
const auto outputs = m_backend->outputs();
for (const auto output : outputs) {
if (auto drmOutput = dynamic_cast<DrmOutput *>(output)) {
drmOutput->pipeline()->setLayers(nullptr, nullptr);
}
}
m_contexts.clear();
cleanup();
m_backend->setRenderBackend(nullptr);
}
bool EglGbmBackend::initializeEgl()
{
initClientExtensions();
auto display = m_backend->primaryGpu()->eglDisplay();
// Use eglGetPlatformDisplayEXT() to get the display pointer
// if the implementation supports it.
if (!display) {
display = createEglDisplay(m_backend->primaryGpu());
if (!display) {
return false;
}
}
setEglDisplay(display);
return true;
}
EglDisplay *EglGbmBackend::createEglDisplay(DrmGpu *gpu) const
{
for (const QByteArray &extension : {QByteArrayLiteral("EGL_EXT_platform_base"), QByteArrayLiteral("EGL_KHR_platform_gbm")}) {
if (!hasClientExtension(extension)) {
qCWarning(KWIN_DRM) << extension << "client extension is not supported by the platform";
return nullptr;
}
}
gpu->setEglDisplay(EglDisplay::create(eglGetPlatformDisplayEXT(EGL_PLATFORM_GBM_KHR, gpu->drmDevice()->gbmDevice(), nullptr)));
return gpu->eglDisplay();
}
void EglGbmBackend::init()
{
if (!initializeEgl()) {
setFailed("Could not initialize egl");
return;
}
if (!initRenderingContext()) {
setFailed("Could not initialize rendering context");
return;
}
initWayland();
m_backend->createLayers();
}
bool EglGbmBackend::initRenderingContext()
{
return createContext(EGL_NO_CONFIG_KHR) && makeCurrent();
}
EglDisplay *EglGbmBackend::displayForGpu(DrmGpu *gpu)
{
if (gpu == m_backend->primaryGpu()) {
return eglDisplayObject();
}
auto display = gpu->eglDisplay();
if (!display) {
display = createEglDisplay(gpu);
}
return display;
}
std::shared_ptr<EglContext> EglGbmBackend::contextForGpu(DrmGpu *gpu)
{
if (gpu == m_backend->primaryGpu()) {
return m_context;
}
auto display = gpu->eglDisplay();
if (!display) {
display = createEglDisplay(gpu);
if (!display) {
return nullptr;
}
}
auto &context = m_contexts[display];
if (const auto c = context.lock()) {
return c;
}
const auto ret = std::shared_ptr<EglContext>(EglContext::create(display, EGL_NO_CONFIG_KHR, EGL_NO_CONTEXT));
context = ret;
return ret;
}
std::unique_ptr<SurfaceTexture> EglGbmBackend::createSurfaceTextureWayland(SurfacePixmap *pixmap)
{
return std::make_unique<BasicEGLSurfaceTextureWayland>(this, pixmap);
}
DrmDevice *EglGbmBackend::drmDevice() const
{
return gpu()->drmDevice();
}
bool EglGbmBackend::present(Output *output, const std::shared_ptr<OutputFrame> &frame)
{
return static_cast<DrmAbstractOutput *>(output)->present(frame);
}
void EglGbmBackend::repairPresentation(Output *output)
{
// read back drm properties, most likely our info is out of date somehow
// or we need a modeset
QTimer::singleShot(0, m_backend, &DrmBackend::updateOutputs);
}
OutputLayer *EglGbmBackend::primaryLayer(Output *output)
{
return static_cast<DrmAbstractOutput *>(output)->primaryLayer();
}
OutputLayer *EglGbmBackend::cursorLayer(Output *output)
{
return static_cast<DrmAbstractOutput *>(output)->cursorLayer();
}
std::pair<std::shared_ptr<KWin::GLTexture>, ColorDescription> EglGbmBackend::textureForOutput(Output *output) const
{
const auto drmOutput = static_cast<DrmAbstractOutput *>(output);
if (const auto virtualLayer = dynamic_cast<VirtualEglGbmLayer *>(drmOutput->primaryLayer())) {
return std::make_pair(virtualLayer->texture(), virtualLayer->colorDescription());
}
const auto layer = static_cast<EglGbmLayer *>(drmOutput->primaryLayer());
return std::make_pair(layer->texture(), layer->colorDescription());
}
std::shared_ptr<DrmPipelineLayer> EglGbmBackend::createDrmPlaneLayer(DrmPipeline *pipeline, DrmPlane::TypeIndex type)
{
return std::make_shared<EglGbmLayer>(this, pipeline, type);
}
std::shared_ptr<DrmOutputLayer> EglGbmBackend::createLayer(DrmVirtualOutput *output)
{
return std::make_shared<VirtualEglGbmLayer>(this, output);
}
DrmGpu *EglGbmBackend::gpu() const
{
return m_backend->primaryGpu();
}
} // namespace KWin
#include "moc_drm_egl_backend.cpp"
@@ -0,0 +1,76 @@
/*
KWin - the KDE window manager
This file is part of the KDE project.
SPDX-FileCopyrightText: 2015 Martin Gräßlin <mgraesslin@kde.org>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#pragma once
#include "drm_plane.h"
#include "drm_render_backend.h"
#include "opengl/glutils.h"
#include "platformsupport/scenes/opengl/abstract_egl_backend.h"
#include <QHash>
#include <QPointer>
#include <optional>
namespace KWin
{
struct DmaBufAttributes;
class Output;
class DrmAbstractOutput;
class DrmOutput;
class DumbSwapchain;
class DrmBackend;
class DrmGpu;
class EglGbmLayer;
class DrmOutputLayer;
class DrmPipeline;
class EglContext;
class EglDisplay;
/**
* @brief OpenGL Backend using Egl on a GBM surface.
*/
class EglGbmBackend : public AbstractEglBackend, public DrmRenderBackend
{
Q_OBJECT
public:
EglGbmBackend(DrmBackend *drmBackend);
~EglGbmBackend() override;
std::unique_ptr<SurfaceTexture> createSurfaceTextureWayland(SurfacePixmap *pixmap) override;
DrmDevice *drmDevice() const override;
bool present(Output *output, const std::shared_ptr<OutputFrame> &frame) override;
void repairPresentation(Output *output) override;
OutputLayer *primaryLayer(Output *output) override;
OutputLayer *cursorLayer(Output *output) override;
void init() override;
std::shared_ptr<DrmPipelineLayer> createDrmPlaneLayer(DrmPipeline *pipeline, DrmPlane::TypeIndex type) override;
std::shared_ptr<DrmOutputLayer> createLayer(DrmVirtualOutput *output) override;
std::pair<std::shared_ptr<KWin::GLTexture>, ColorDescription> textureForOutput(Output *requestedOutput) const override;
DrmGpu *gpu() const;
EglDisplay *displayForGpu(DrmGpu *gpu);
std::shared_ptr<EglContext> contextForGpu(DrmGpu *gpu);
private:
bool initializeEgl();
bool initRenderingContext();
EglDisplay *createEglDisplay(DrmGpu *gpu) const;
DrmBackend *m_backend;
std::map<EglDisplay *, std::weak_ptr<EglContext>> m_contexts;
friend class EglGbmTexture;
};
} // namespace
@@ -0,0 +1,169 @@
/*
KWin - the KDE window manager
This file is part of the KDE project.
SPDX-FileCopyrightText: 2022 Xaver Hugl <xaver.hugl@gmail.com>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "drm_egl_layer.h"
#include "core/colorpipeline.h"
#include "core/iccprofile.h"
#include "drm_backend.h"
#include "drm_buffer.h"
#include "drm_crtc.h"
#include "drm_egl_backend.h"
#include "drm_gpu.h"
#include "drm_output.h"
#include "drm_pipeline.h"
#include "scene/surfaceitem_wayland.h"
#include "wayland/surface.h"
#include <QRegion>
#include <drm_fourcc.h>
#include <errno.h>
#include <gbm.h>
#include <unistd.h>
namespace KWin
{
static EglGbmLayerSurface::BufferTarget targetFor(DrmPipeline *pipeline, DrmPlane::TypeIndex planeType)
{
if (planeType != DrmPlane::TypeIndex::Cursor) {
return EglGbmLayerSurface::BufferTarget::Normal;
}
if (pipeline->gpu()->atomicModeSetting() && !pipeline->gpu()->isVirtualMachine()) {
return EglGbmLayerSurface::BufferTarget::Linear;
}
return EglGbmLayerSurface::BufferTarget::Dumb;
}
EglGbmLayer::EglGbmLayer(EglGbmBackend *eglBackend, DrmPipeline *pipeline, DrmPlane::TypeIndex type)
: DrmPipelineLayer(pipeline, type)
, m_surface(pipeline->gpu(), eglBackend, targetFor(pipeline, type), type == DrmPlane::TypeIndex::Primary ? EglGbmLayerSurface::FormatOption::PreferAlpha : EglGbmLayerSurface::FormatOption::RequireAlpha)
{
}
std::optional<OutputLayerBeginFrameInfo> EglGbmLayer::doBeginFrame()
{
if (m_type == DrmPlane::TypeIndex::Cursor && m_pipeline->output()->shouldDisableCursorPlane()) {
return std::nullopt;
}
// note that this allows blending to happen in sRGB or PQ encoding with the cursor plane.
// That's technically incorrect, but it looks okay and is intentionally allowed
// as the hardware cursor is more important than an incorrectly blended cursor edge
m_scanoutBuffer.reset();
m_colorPipeline = ColorPipeline{};
return m_surface.startRendering(targetRect().size(),
m_pipeline->output()->transform().combine(OutputTransform::FlipY),
m_pipeline->formats(m_type),
m_pipeline->output()->scanoutColorDescription(),
m_pipeline->output()->needsShadowBuffer() ? m_pipeline->output()->adaptedChannelFactors() : QVector3D(1, 1, 1),
m_pipeline->output()->needsShadowBuffer() ? m_pipeline->iccProfile() : nullptr,
m_pipeline->output()->scale(),
m_pipeline->output()->colorPowerTradeoff());
}
bool EglGbmLayer::doEndFrame(const QRegion &renderedRegion, const QRegion &damagedRegion, OutputFrame *frame)
{
return m_surface.endRendering(damagedRegion, frame);
}
bool EglGbmLayer::checkTestBuffer()
{
return m_surface.renderTestBuffer(targetRect().size(), m_pipeline->formats(m_type), m_pipeline->output()->colorPowerTradeoff()) != nullptr;
}
std::shared_ptr<GLTexture> EglGbmLayer::texture() const
{
if (m_scanoutBuffer) {
const auto ret = m_surface.eglBackend()->importDmaBufAsTexture(*m_scanoutBuffer->buffer()->dmabufAttributes());
ret->setContentTransform(offloadTransform().combine(OutputTransform::FlipY));
return ret;
} else {
return m_surface.texture();
}
}
ColorDescription EglGbmLayer::colorDescription() const
{
return m_surface.colorDescription();
}
bool EglGbmLayer::doImportScanoutBuffer(GraphicsBuffer *buffer, const ColorDescription &color, RenderingIntent intent, const std::shared_ptr<OutputFrame> &frame)
{
static bool valid;
static const bool directScanoutDisabled = qEnvironmentVariableIntValue("KWIN_DRM_NO_DIRECT_SCANOUT", &valid) == 1 && valid;
if (directScanoutDisabled) {
return false;
}
if (m_pipeline->gpu()->needsModeset()) {
// don't do direct scanout with modeset, it might lead to locking
// the hardware to some buffer format we can't switch away from
return false;
}
if (m_pipeline->output()->colorProfileSource() == Output::ColorProfileSource::ICC && !m_pipeline->output()->highDynamicRange() && m_pipeline->iccProfile()) {
// TODO make the icc profile output a color pipeline too?
return false;
}
ColorPipeline pipeline = ColorPipeline::create(color, m_pipeline->output()->scanoutColorDescription(), intent);
if (m_pipeline->output()->needsShadowBuffer()) {
pipeline.addTransferFunction(m_pipeline->output()->scanoutColorDescription().transferFunction());
pipeline.addMultiplier(m_pipeline->output()->adaptedChannelFactors());
pipeline.addInverseTransferFunction(m_pipeline->output()->scanoutColorDescription().transferFunction());
}
m_colorPipeline = pipeline;
// kernel documentation says that
// "Devices that dont support subpixel plane coordinates can ignore the fractional part."
// so we need to make sure that doesn't cause a difference vs the composited result
if (sourceRect() != sourceRect().toRect()) {
return false;
}
const auto plane = m_type == DrmPlane::TypeIndex::Primary ? m_pipeline->crtc()->primaryPlane() : m_pipeline->crtc()->cursorPlane();
if (offloadTransform() != OutputTransform::Kind::Normal && (!plane || !plane->supportsTransformation(offloadTransform()))) {
return false;
}
// importing a buffer from another GPU without an explicit modifier can mess up the buffer format
if (buffer->dmabufAttributes()->modifier == DRM_FORMAT_MOD_INVALID && m_pipeline->gpu()->platform()->gpuCount() > 1) {
return false;
}
m_scanoutBuffer = m_pipeline->gpu()->importBuffer(buffer, FileDescriptor{});
if (m_scanoutBuffer) {
m_surface.forgetDamage(); // TODO: Use absolute frame sequence numbers for indexing the DamageJournal. It's more flexible and less error-prone
}
return m_scanoutBuffer != nullptr;
}
std::shared_ptr<DrmFramebuffer> EglGbmLayer::currentBuffer() const
{
return m_scanoutBuffer ? m_scanoutBuffer : m_surface.currentBuffer();
}
void EglGbmLayer::releaseBuffers()
{
m_scanoutBuffer.reset();
m_surface.destroyResources();
}
DrmDevice *EglGbmLayer::scanoutDevice() const
{
return m_pipeline->gpu()->drmDevice();
}
QHash<uint32_t, QList<uint64_t>> EglGbmLayer::supportedDrmFormats() const
{
return m_pipeline->formats(m_type);
}
QList<QSize> EglGbmLayer::recommendedSizes() const
{
return m_pipeline->recommendedSizes(m_type);
}
const ColorPipeline &EglGbmLayer::colorPipeline() const
{
return m_colorPipeline;
}
}
@@ -0,0 +1,51 @@
/*
KWin - the KDE window manager
This file is part of the KDE project.
SPDX-FileCopyrightText: 2022 Xaver Hugl <xaver.hugl@gmail.com>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#pragma once
#include "drm_layer.h"
#include "drm_egl_layer_surface.h"
#include <QMap>
#include <QPointer>
#include <QRegion>
#include <epoxy/egl.h>
#include <optional>
namespace KWin
{
class EglGbmBackend;
class EglGbmLayer : public DrmPipelineLayer
{
public:
explicit EglGbmLayer(EglGbmBackend *eglBackend, DrmPipeline *pipeline, DrmPlane::TypeIndex type);
std::optional<OutputLayerBeginFrameInfo> doBeginFrame() override;
bool doEndFrame(const QRegion &renderedRegion, const QRegion &damagedRegion, OutputFrame *frame) override;
bool checkTestBuffer() override;
std::shared_ptr<DrmFramebuffer> currentBuffer() const override;
std::shared_ptr<GLTexture> texture() const override;
ColorDescription colorDescription() const;
void releaseBuffers() override;
DrmDevice *scanoutDevice() const override;
QHash<uint32_t, QList<uint64_t>> supportedDrmFormats() const override;
QList<QSize> recommendedSizes() const override;
const ColorPipeline &colorPipeline() const override;
private:
bool doImportScanoutBuffer(GraphicsBuffer *buffer, const ColorDescription &color, RenderingIntent intent, const std::shared_ptr<OutputFrame> &frame) override;
std::shared_ptr<DrmFramebuffer> m_scanoutBuffer;
ColorPipeline m_colorPipeline;
EglGbmLayerSurface m_surface;
};
}
@@ -0,0 +1,749 @@
/*
KWin - the KDE window manager
This file is part of the KDE project.
SPDX-FileCopyrightText: 2022 Xaver Hugl <xaver.hugl@gmail.com>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "drm_egl_layer_surface.h"
#include "config-kwin.h"
#include "core/colortransformation.h"
#include "core/graphicsbufferview.h"
#include "core/iccprofile.h"
#include "drm_egl_backend.h"
#include "drm_gpu.h"
#include "drm_logging.h"
#include "opengl/eglnativefence.h"
#include "opengl/eglswapchain.h"
#include "opengl/gllut.h"
#include "opengl/glrendertimequery.h"
#include "opengl/icc_shader.h"
#include "platformsupport/scenes/qpainter/qpainterswapchain.h"
#include "utils/drm_format_helper.h"
#include <drm_fourcc.h>
#include <errno.h>
#include <gbm.h>
#include <unistd.h>
namespace KWin
{
static const QList<uint64_t> linearModifier = {DRM_FORMAT_MOD_LINEAR};
static const QList<uint64_t> implicitModifier = {DRM_FORMAT_MOD_INVALID};
static const QList<uint32_t> cpuCopyFormats = {DRM_FORMAT_ARGB8888, DRM_FORMAT_XRGB8888};
static const bool bufferAgeEnabled = qEnvironmentVariable("KWIN_USE_BUFFER_AGE") != QStringLiteral("0");
static gbm_format_name_desc formatName(uint32_t format)
{
gbm_format_name_desc ret;
gbm_format_get_name(format, &ret);
return ret;
}
EglGbmLayerSurface::EglGbmLayerSurface(DrmGpu *gpu, EglGbmBackend *eglBackend, BufferTarget target, FormatOption formatOption)
: m_gpu(gpu)
, m_eglBackend(eglBackend)
, m_requestedBufferTarget(target)
, m_formatOption(formatOption)
{
}
EglGbmLayerSurface::~EglGbmLayerSurface() = default;
EglGbmLayerSurface::Surface::~Surface()
{
if (importContext) {
importContext->makeCurrent();
importGbmSwapchain.reset();
importedTextureCache.clear();
importContext.reset();
}
if (context) {
context->makeCurrent();
}
}
void EglGbmLayerSurface::destroyResources()
{
m_surface = {};
m_oldSurface = {};
}
static QList<FormatInfo> filterAndSortFormats(const QHash<uint32_t, QList<uint64_t>> &formats, EglGbmLayerSurface::FormatOption option, Output::ColorPowerTradeoff tradeoff)
{
QList<FormatInfo> ret;
for (auto it = formats.begin(); it != formats.end(); it++) {
const auto info = FormatInfo::get(it.key());
if (!info) {
continue;
}
if (option == EglGbmLayerSurface::FormatOption::RequireAlpha && !info->alphaBits) {
continue;
}
if (info->bitsPerColor < 8) {
continue;
}
ret.push_back(*info);
}
std::ranges::sort(ret, [tradeoff](const FormatInfo &before, const FormatInfo &after) {
if (tradeoff == Output::ColorPowerTradeoff::PreferAccuracy && before.bitsPerColor != after.bitsPerColor) {
return before.bitsPerColor > after.bitsPerColor;
}
if (before.floatingPoint != after.floatingPoint) {
return !before.floatingPoint;
}
const bool beforeHasAlpha = before.alphaBits != 0;
const bool afterHasAlpha = after.alphaBits != 0;
if (beforeHasAlpha != afterHasAlpha) {
return beforeHasAlpha;
}
if (before.bitsPerPixel != after.bitsPerPixel) {
return before.bitsPerPixel < after.bitsPerPixel;
}
return before.bitsPerColor > after.bitsPerColor;
});
return ret;
}
std::optional<OutputLayerBeginFrameInfo> EglGbmLayerSurface::startRendering(const QSize &bufferSize, OutputTransform transformation, const QHash<uint32_t, QList<uint64_t>> &formats, const ColorDescription &colorDescription, const QVector3D &channelFactors, const std::shared_ptr<IccProfile> &iccProfile, double scale, Output::ColorPowerTradeoff tradeoff)
{
if (!checkSurface(bufferSize, formats, tradeoff)) {
return std::nullopt;
}
if (!m_eglBackend->openglContext()->makeCurrent()) {
return std::nullopt;
}
auto slot = m_surface->gbmSwapchain->acquire();
if (!slot) {
return std::nullopt;
}
if (slot->framebuffer()->colorAttachment()->contentTransform() != transformation) {
m_surface->damageJournal.clear();
}
slot->framebuffer()->colorAttachment()->setContentTransform(transformation);
m_surface->currentSlot = slot;
m_surface->scale = scale;
if (m_surface->targetColorDescription != colorDescription || m_surface->channelFactors != channelFactors || m_surface->iccProfile != iccProfile) {
m_surface->damageJournal.clear();
m_surface->shadowDamageJournal.clear();
m_surface->needsShadowBuffer = channelFactors != QVector3D(1, 1, 1) || iccProfile || colorDescription.transferFunction().type != TransferFunction::gamma22;
m_surface->targetColorDescription = colorDescription;
m_surface->channelFactors = channelFactors;
m_surface->iccProfile = iccProfile;
if (iccProfile) {
if (!m_surface->iccShader) {
m_surface->iccShader = std::make_unique<IccShader>();
}
} else {
m_surface->iccShader.reset();
}
if (m_surface->needsShadowBuffer) {
const double maxLuminance = colorDescription.maxHdrLuminance().value_or(colorDescription.referenceLuminance());
m_surface->intermediaryColorDescription = ColorDescription(colorDescription.containerColorimetry(), TransferFunction(TransferFunction::gamma22, 0, maxLuminance),
colorDescription.referenceLuminance(), colorDescription.minLuminance(),
colorDescription.maxAverageLuminance(), colorDescription.maxHdrLuminance(),
colorDescription.containerColorimetry(), colorDescription.sdrColorimetry());
} else {
m_surface->intermediaryColorDescription = colorDescription;
}
}
m_surface->compositingTimeQuery = std::make_unique<GLRenderTimeQuery>(m_surface->context);
m_surface->compositingTimeQuery->begin();
if (m_surface->needsShadowBuffer) {
if (!m_surface->shadowSwapchain || m_surface->shadowSwapchain->size() != m_surface->gbmSwapchain->size()) {
const auto formats = m_eglBackend->eglDisplayObject()->nonExternalOnlySupportedDrmFormats();
const QList<FormatInfo> sortedFormats = filterAndSortFormats(formats, m_formatOption, tradeoff);
for (const auto format : sortedFormats) {
auto modifiers = formats[format.drmFormat];
if (format.floatingPoint && m_eglBackend->gpu()->isAmdgpu() && qEnvironmentVariableIntValue("KWIN_DRM_NO_DCC_WORKAROUND") == 0) {
// using modifiers with DCC here causes glitches on amdgpu: https://gitlab.freedesktop.org/mesa/mesa/-/issues/10875
if (!modifiers.contains(DRM_FORMAT_MOD_LINEAR)) {
continue;
}
modifiers = {DRM_FORMAT_MOD_LINEAR};
}
m_surface->shadowSwapchain = EglSwapchain::create(m_eglBackend->drmDevice()->allocator(), m_eglBackend->openglContext(), m_surface->gbmSwapchain->size(), format.drmFormat, modifiers);
if (m_surface->shadowSwapchain) {
break;
}
}
}
if (!m_surface->shadowSwapchain) {
qCCritical(KWIN_DRM) << "Failed to create shadow swapchain!";
return std::nullopt;
}
m_surface->currentShadowSlot = m_surface->shadowSwapchain->acquire();
if (!m_surface->currentShadowSlot) {
return std::nullopt;
}
m_surface->currentShadowSlot->texture()->setContentTransform(m_surface->currentSlot->framebuffer()->colorAttachment()->contentTransform());
return OutputLayerBeginFrameInfo{
.renderTarget = RenderTarget(m_surface->currentShadowSlot->framebuffer(), m_surface->intermediaryColorDescription),
.repaint = bufferAgeEnabled ? m_surface->shadowDamageJournal.accumulate(m_surface->currentShadowSlot->age(), infiniteRegion()) : infiniteRegion(),
};
} else {
m_surface->shadowSwapchain.reset();
m_surface->currentShadowSlot.reset();
return OutputLayerBeginFrameInfo{
.renderTarget = RenderTarget(m_surface->currentSlot->framebuffer(), m_surface->intermediaryColorDescription),
.repaint = bufferAgeEnabled ? m_surface->damageJournal.accumulate(slot->age(), infiniteRegion()) : infiniteRegion(),
};
}
}
static GLVertexBuffer *uploadGeometry(const QRegion &devicePixels, const QSize &fboSize)
{
GLVertexBuffer *vbo = GLVertexBuffer::streamingBuffer();
vbo->reset();
vbo->setAttribLayout(std::span(GLVertexBuffer::GLVertex2DLayout), sizeof(GLVertex2D));
const auto optMap = vbo->map<GLVertex2D>(devicePixels.rectCount() * 6);
if (!optMap) {
return nullptr;
}
const auto map = *optMap;
size_t vboIndex = 0;
for (QRectF rect : devicePixels) {
const float x0 = rect.left();
const float y0 = rect.top();
const float x1 = rect.right();
const float y1 = rect.bottom();
const float u0 = x0 / fboSize.width();
const float v0 = y0 / fboSize.height();
const float u1 = x1 / fboSize.width();
const float v1 = y1 / fboSize.height();
// first triangle
map[vboIndex++] = GLVertex2D{
.position = QVector2D(x0, y0),
.texcoord = QVector2D(u0, v0),
};
map[vboIndex++] = GLVertex2D{
.position = QVector2D(x1, y1),
.texcoord = QVector2D(u1, v1),
};
map[vboIndex++] = GLVertex2D{
.position = QVector2D(x0, y1),
.texcoord = QVector2D(u0, v1),
};
// second triangle
map[vboIndex++] = GLVertex2D{
.position = QVector2D(x0, y0),
.texcoord = QVector2D(u0, v0),
};
map[vboIndex++] = GLVertex2D{
.position = QVector2D(x1, y0),
.texcoord = QVector2D(u1, v0),
};
map[vboIndex++] = GLVertex2D{
.position = QVector2D(x1, y1),
.texcoord = QVector2D(u1, v1),
};
}
vbo->unmap();
vbo->setVertexCount(vboIndex);
return vbo;
}
bool EglGbmLayerSurface::endRendering(const QRegion &damagedRegion, OutputFrame *frame)
{
if (m_surface->needsShadowBuffer) {
const QRegion logicalRepaint = damagedRegion | m_surface->damageJournal.accumulate(m_surface->currentSlot->age(), infiniteRegion());
m_surface->damageJournal.add(damagedRegion);
m_surface->shadowDamageJournal.add(damagedRegion);
QRegion repaint;
if (logicalRepaint == infiniteRegion()) {
repaint = QRect(QPoint(), m_surface->gbmSwapchain->size());
} else {
const auto mapping = m_surface->currentShadowSlot->framebuffer()->colorAttachment()->contentTransform().combine(OutputTransform::FlipY);
const QSize rotatedSize = mapping.map(m_surface->gbmSwapchain->size());
for (const QRect rect : logicalRepaint) {
repaint |= mapping.map(scaledRect(rect, m_surface->scale), rotatedSize).toAlignedRect() & QRect(QPoint(), m_surface->gbmSwapchain->size());
}
}
GLFramebuffer *fbo = m_surface->currentSlot->framebuffer();
GLFramebuffer::pushFramebuffer(fbo);
ShaderBinder binder = m_surface->iccShader ? ShaderBinder(m_surface->iccShader->shader()) : ShaderBinder(ShaderTrait::MapTexture | ShaderTrait::TransformColorspace);
if (m_surface->iccShader) {
// absolute colorimetric, because whitepoint adjustment is done in compositing already
m_surface->iccShader->setUniforms(m_surface->iccProfile, m_surface->intermediaryColorDescription, RenderingIntent::AbsoluteColorimetric);
} else {
binder.shader()->setColorspaceUniforms(m_surface->intermediaryColorDescription, m_surface->targetColorDescription, RenderingIntent::RelativeColorimetric);
QMatrix4x4 ctm;
ctm(0, 0) = m_surface->channelFactors.x();
ctm(1, 1) = m_surface->channelFactors.y();
ctm(2, 2) = m_surface->channelFactors.z();
binder.shader()->setUniform(GLShader::Mat4Uniform::ColorimetryTransformation, ctm);
}
QMatrix4x4 mat;
mat.scale(1, -1);
mat.ortho(QRectF(QPointF(), fbo->size()));
binder.shader()->setUniform(GLShader::Mat4Uniform::ModelViewProjectionMatrix, mat);
glDisable(GL_BLEND);
if (const auto vbo = uploadGeometry(repaint, m_surface->gbmSwapchain->size())) {
m_surface->currentShadowSlot->texture()->bind();
vbo->render(GL_TRIANGLES);
m_surface->currentShadowSlot->texture()->unbind();
}
EGLNativeFence fence(m_surface->context->displayObject());
m_surface->shadowSwapchain->release(m_surface->currentShadowSlot, fence.takeFileDescriptor());
GLFramebuffer::popFramebuffer();
} else {
m_surface->damageJournal.add(damagedRegion);
}
m_surface->compositingTimeQuery->end();
if (frame) {
frame->addRenderTimeQuery(std::move(m_surface->compositingTimeQuery));
}
glFlush();
EGLNativeFence sourceFence(m_eglBackend->eglDisplayObject());
if (!sourceFence.isValid()) {
// llvmpipe doesn't do synchronization properly: https://gitlab.freedesktop.org/mesa/mesa/-/issues/9375
// and NVidia doesn't support implicit sync
glFinish();
}
m_surface->gbmSwapchain->release(m_surface->currentSlot, sourceFence.fileDescriptor().duplicate());
const auto buffer = importBuffer(m_surface.get(), m_surface->currentSlot.get(), sourceFence.takeFileDescriptor(), frame, damagedRegion);
if (buffer) {
m_surface->currentFramebuffer = buffer;
return true;
} else {
return false;
}
}
EglGbmBackend *EglGbmLayerSurface::eglBackend() const
{
return m_eglBackend;
}
std::shared_ptr<DrmFramebuffer> EglGbmLayerSurface::currentBuffer() const
{
return m_surface ? m_surface->currentFramebuffer : nullptr;
}
const ColorDescription &EglGbmLayerSurface::colorDescription() const
{
if (m_surface) {
return m_surface->currentShadowSlot ? m_surface->intermediaryColorDescription : m_surface->targetColorDescription;
} else {
return ColorDescription::sRGB;
}
}
std::shared_ptr<GLTexture> EglGbmLayerSurface::texture() const
{
if (m_surface) {
return m_surface->currentShadowSlot ? m_surface->currentShadowSlot->texture() : m_surface->currentSlot->texture();
} else {
return nullptr;
}
}
std::shared_ptr<DrmFramebuffer> EglGbmLayerSurface::renderTestBuffer(const QSize &bufferSize, const QHash<uint32_t, QList<uint64_t>> &formats, Output::ColorPowerTradeoff tradeoff)
{
EglContext *context = m_eglBackend->openglContext();
if (!context->makeCurrent()) {
qCWarning(KWIN_DRM) << "EglGbmLayerSurface::renderTestBuffer: failed to make opengl context current";
return nullptr;
}
if (checkSurface(bufferSize, formats, tradeoff)) {
return m_surface->currentFramebuffer;
} else {
return nullptr;
}
}
void EglGbmLayerSurface::forgetDamage()
{
if (m_surface) {
m_surface->damageJournal.clear();
m_surface->importDamageJournal.clear();
m_surface->shadowDamageJournal.clear();
}
}
bool EglGbmLayerSurface::checkSurface(const QSize &size, const QHash<uint32_t, QList<uint64_t>> &formats, Output::ColorPowerTradeoff tradeoff)
{
if (doesSurfaceFit(m_surface.get(), size, formats, tradeoff)) {
return true;
}
if (doesSurfaceFit(m_oldSurface.get(), size, formats, tradeoff)) {
m_surface = std::move(m_oldSurface);
return true;
}
if (auto newSurface = createSurface(size, formats, tradeoff)) {
m_oldSurface = std::move(m_surface);
if (m_oldSurface) {
// FIXME: Use absolute frame sequence numbers for indexing the DamageJournal
m_oldSurface->damageJournal.clear();
m_oldSurface->shadowDamageJournal.clear();
m_oldSurface->gbmSwapchain->resetBufferAge();
if (m_oldSurface->shadowSwapchain) {
m_oldSurface->shadowSwapchain->resetBufferAge();
}
if (m_oldSurface->importGbmSwapchain) {
m_oldSurface->importGbmSwapchain->resetBufferAge();
m_oldSurface->importDamageJournal.clear();
}
}
m_surface = std::move(newSurface);
return true;
}
return false;
}
bool EglGbmLayerSurface::doesSurfaceFit(Surface *surface, const QSize &size, const QHash<uint32_t, QList<uint64_t>> &formats, Output::ColorPowerTradeoff tradeoff) const
{
if (!surface || !surface->gbmSwapchain || surface->gbmSwapchain->size() != size) {
return false;
}
if (surface->tradeoff != tradeoff) {
return false;
}
if (surface->bufferTarget == BufferTarget::Dumb) {
return formats.contains(surface->importDumbSwapchain->format());
}
switch (surface->importMode) {
case MultiGpuImportMode::None:
case MultiGpuImportMode::Dmabuf:
case MultiGpuImportMode::LinearDmabuf: {
const auto format = surface->gbmSwapchain->format();
return formats.contains(format) && (surface->gbmSwapchain->modifier() == DRM_FORMAT_MOD_INVALID || formats[format].contains(surface->gbmSwapchain->modifier()));
}
case MultiGpuImportMode::DumbBuffer:
return formats.contains(surface->importDumbSwapchain->format());
case MultiGpuImportMode::Egl: {
const auto format = surface->importGbmSwapchain->format();
const auto it = formats.find(format);
return it != formats.end() && (surface->importGbmSwapchain->modifier() == DRM_FORMAT_MOD_INVALID || it->contains(surface->importGbmSwapchain->modifier()));
}
}
Q_UNREACHABLE();
}
std::unique_ptr<EglGbmLayerSurface::Surface> EglGbmLayerSurface::createSurface(const QSize &size, const QHash<uint32_t, QList<uint64_t>> &formats, Output::ColorPowerTradeoff tradeoff) const
{
const QList<FormatInfo> sortedFormats = filterAndSortFormats(formats, m_formatOption, tradeoff);
// special case: the cursor plane needs linear, but not all GPUs (NVidia) can render to linear
auto bufferTarget = m_requestedBufferTarget;
if (m_gpu == m_eglBackend->gpu()) {
const bool needsLinear = std::ranges::all_of(sortedFormats, [&formats](const FormatInfo &fmt) {
const auto &mods = formats[fmt.drmFormat];
return std::ranges::all_of(mods, [](const auto &mod) {
return mod == DRM_FORMAT_MOD_LINEAR;
});
});
if (needsLinear) {
const auto renderFormats = m_eglBackend->eglDisplayObject()->allSupportedDrmFormats();
const bool noLinearSupport = std::ranges::none_of(sortedFormats, [&renderFormats](const auto &formatInfo) {
const auto it = renderFormats.constFind(formatInfo.drmFormat);
return it != renderFormats.cend() && it->nonExternalOnlyModifiers.contains(DRM_FORMAT_MOD_LINEAR);
});
if (noLinearSupport) {
bufferTarget = BufferTarget::Dumb;
}
}
}
const auto doTestFormats = [this, &size, &formats, bufferTarget, tradeoff](const QList<FormatInfo> &gbmFormats, MultiGpuImportMode importMode) -> std::unique_ptr<Surface> {
for (const auto &format : gbmFormats) {
auto surface = createSurface(size, format.drmFormat, formats[format.drmFormat], importMode, bufferTarget, tradeoff);
if (surface) {
return surface;
}
}
return nullptr;
};
if (m_gpu == m_eglBackend->gpu()) {
return doTestFormats(sortedFormats, MultiGpuImportMode::None);
}
// special case, we're using different display devices but the same render device
const auto display = m_eglBackend->displayForGpu(m_gpu);
if (display && !display->renderNode().isEmpty() && display->renderNode() == m_eglBackend->eglDisplayObject()->renderNode()) {
if (auto surface = doTestFormats(sortedFormats, MultiGpuImportMode::None)) {
return surface;
}
}
if (auto surface = doTestFormats(sortedFormats, MultiGpuImportMode::Egl)) {
qCDebug(KWIN_DRM) << "chose egl import with format" << formatName(surface->gbmSwapchain->format()).name << "and modifier" << surface->gbmSwapchain->modifier();
return surface;
}
if (auto surface = doTestFormats(sortedFormats, MultiGpuImportMode::Dmabuf)) {
qCDebug(KWIN_DRM) << "chose dmabuf import with format" << formatName(surface->gbmSwapchain->format()).name << "and modifier" << surface->gbmSwapchain->modifier();
return surface;
}
if (auto surface = doTestFormats(sortedFormats, MultiGpuImportMode::LinearDmabuf)) {
qCDebug(KWIN_DRM) << "chose linear dmabuf import with format" << formatName(surface->gbmSwapchain->format()).name << "and modifier" << surface->gbmSwapchain->modifier();
return surface;
}
if (auto surface = doTestFormats(sortedFormats, MultiGpuImportMode::DumbBuffer)) {
qCDebug(KWIN_DRM) << "chose cpu import with format" << formatName(surface->gbmSwapchain->format()).name << "and modifier" << surface->gbmSwapchain->modifier();
return surface;
}
return nullptr;
}
static QList<uint64_t> filterModifiers(const QList<uint64_t> &one, const QList<uint64_t> &two)
{
QList<uint64_t> ret = one;
ret.erase(std::remove_if(ret.begin(), ret.end(), [&two](uint64_t mod) {
return !two.contains(mod);
}),
ret.end());
return ret;
}
std::unique_ptr<EglGbmLayerSurface::Surface> EglGbmLayerSurface::createSurface(const QSize &size, uint32_t format, const QList<uint64_t> &modifiers, MultiGpuImportMode importMode, BufferTarget bufferTarget, Output::ColorPowerTradeoff tradeoff) const
{
const bool cpuCopy = importMode == MultiGpuImportMode::DumbBuffer || bufferTarget == BufferTarget::Dumb;
QList<uint64_t> renderModifiers;
auto ret = std::make_unique<Surface>();
const auto drmFormat = m_eglBackend->eglDisplayObject()->allSupportedDrmFormats()[format];
if (importMode == MultiGpuImportMode::Egl) {
ret->importContext = m_eglBackend->contextForGpu(m_gpu);
if (!ret->importContext || ret->importContext->isSoftwareRenderer()) {
return nullptr;
}
const auto importDrmFormat = ret->importContext->displayObject()->allSupportedDrmFormats()[format];
renderModifiers = filterModifiers(importDrmFormat.allModifiers,
drmFormat.nonExternalOnlyModifiers);
// transferring non-linear buffers with implicit modifiers between GPUs is likely to yield wrong results
renderModifiers.removeAll(DRM_FORMAT_MOD_INVALID);
} else if (cpuCopy) {
if (!cpuCopyFormats.contains(format)) {
return nullptr;
}
renderModifiers = drmFormat.nonExternalOnlyModifiers;
} else {
renderModifiers = filterModifiers(modifiers, drmFormat.nonExternalOnlyModifiers);
}
if (renderModifiers.empty()) {
return nullptr;
}
ret->context = m_eglBackend->contextForGpu(m_eglBackend->gpu());
ret->bufferTarget = bufferTarget;
ret->importMode = importMode;
ret->gbmSwapchain = createGbmSwapchain(m_eglBackend->gpu(), m_eglBackend->openglContext(), size, format, renderModifiers, importMode, bufferTarget);
ret->tradeoff = tradeoff;
if (!ret->gbmSwapchain) {
return nullptr;
}
if (cpuCopy) {
ret->importDumbSwapchain = std::make_unique<QPainterSwapchain>(m_gpu->drmDevice()->allocator(), size, format);
} else if (importMode == MultiGpuImportMode::Egl) {
ret->importGbmSwapchain = createGbmSwapchain(m_gpu, ret->importContext.get(), size, format, modifiers, MultiGpuImportMode::None, BufferTarget::Normal);
if (!ret->importGbmSwapchain) {
return nullptr;
}
}
if (!doRenderTestBuffer(ret.get())) {
return nullptr;
}
return ret;
}
std::shared_ptr<EglSwapchain> EglGbmLayerSurface::createGbmSwapchain(DrmGpu *gpu, EglContext *context, const QSize &size, uint32_t format, const QList<uint64_t> &modifiers, MultiGpuImportMode importMode, BufferTarget bufferTarget) const
{
static bool modifiersEnvSet = false;
static const bool modifiersEnv = qEnvironmentVariableIntValue("KWIN_DRM_USE_MODIFIERS", &modifiersEnvSet) != 0;
bool allowModifiers = (m_gpu->addFB2ModifiersSupported() || importMode == MultiGpuImportMode::Egl || importMode == MultiGpuImportMode::DumbBuffer) && (!modifiersEnvSet || (modifiersEnvSet && modifiersEnv)) && modifiers != implicitModifier;
#if !HAVE_GBM_BO_GET_FD_FOR_PLANE
allowModifiers &= m_gpu == gpu;
#endif
const bool linearSupported = modifiers.contains(DRM_FORMAT_MOD_LINEAR);
const bool preferLinear = importMode == MultiGpuImportMode::DumbBuffer || bufferTarget == BufferTarget::Linear;
const bool forceLinear = importMode == MultiGpuImportMode::LinearDmabuf || (importMode != MultiGpuImportMode::None && importMode != MultiGpuImportMode::DumbBuffer && !allowModifiers);
if (forceLinear && !linearSupported) {
return nullptr;
}
if (linearSupported && (preferLinear || forceLinear)) {
if (const auto swapchain = EglSwapchain::create(gpu->drmDevice()->allocator(), context, size, format, linearModifier)) {
return swapchain;
} else if (forceLinear) {
return nullptr;
}
}
if (allowModifiers) {
if (auto swapchain = EglSwapchain::create(gpu->drmDevice()->allocator(), context, size, format, modifiers)) {
return swapchain;
}
}
return EglSwapchain::create(gpu->drmDevice()->allocator(), context, size, format, implicitModifier);
}
std::shared_ptr<DrmFramebuffer> EglGbmLayerSurface::doRenderTestBuffer(Surface *surface) const
{
auto slot = surface->gbmSwapchain->acquire();
if (!slot) {
return nullptr;
}
if (const auto ret = importBuffer(surface, slot.get(), FileDescriptor{}, nullptr, infiniteRegion())) {
surface->currentSlot = slot;
surface->currentFramebuffer = ret;
return ret;
} else {
return nullptr;
}
}
std::shared_ptr<DrmFramebuffer> EglGbmLayerSurface::importBuffer(Surface *surface, EglSwapchainSlot *slot, FileDescriptor &&readFence, OutputFrame *frame, const QRegion &damagedRegion) const
{
if (surface->bufferTarget == BufferTarget::Dumb || surface->importMode == MultiGpuImportMode::DumbBuffer) {
return importWithCpu(surface, slot, frame);
} else if (surface->importMode == MultiGpuImportMode::Egl) {
return importWithEgl(surface, slot->buffer(), std::move(readFence), frame, damagedRegion);
} else {
const auto ret = m_gpu->importBuffer(slot->buffer(), std::move(readFence));
if (!ret) {
qCWarning(KWIN_DRM, "Failed to create framebuffer: %s", strerror(errno));
}
return ret;
}
}
std::shared_ptr<DrmFramebuffer> EglGbmLayerSurface::importWithEgl(Surface *surface, GraphicsBuffer *sourceBuffer, FileDescriptor &&readFence, OutputFrame *frame, const QRegion &damagedRegion) const
{
Q_ASSERT(surface->importGbmSwapchain);
const auto display = m_eglBackend->displayForGpu(m_gpu);
// older versions of the NVidia proprietary driver support neither implicit sync nor EGL_ANDROID_native_fence_sync
if (!readFence.isValid() || !display->supportsNativeFence()) {
glFinish();
}
if (!surface->importContext->makeCurrent()) {
return nullptr;
}
std::unique_ptr<GLRenderTimeQuery> renderTime;
if (frame) {
renderTime = std::make_unique<GLRenderTimeQuery>(surface->importContext);
renderTime->begin();
}
if (readFence.isValid()) {
const auto destinationFence = EGLNativeFence::importFence(surface->importContext->displayObject(), std::move(readFence));
destinationFence.waitSync();
}
auto &sourceTexture = surface->importedTextureCache[sourceBuffer];
if (!sourceTexture) {
sourceTexture = surface->importContext->importDmaBufAsTexture(*sourceBuffer->dmabufAttributes());
}
if (!sourceTexture) {
qCWarning(KWIN_DRM, "failed to import the source texture!");
return nullptr;
}
auto slot = surface->importGbmSwapchain->acquire();
if (!slot) {
qCWarning(KWIN_DRM, "failed to import the local texture!");
return nullptr;
}
QRegion deviceDamage;
if (damagedRegion == infiniteRegion()) {
deviceDamage = QRect(QPoint(), surface->gbmSwapchain->size());
} else {
const auto mapping = surface->currentSlot->framebuffer()->colorAttachment()->contentTransform().combine(OutputTransform::FlipY);
const QSize rotatedSize = mapping.map(surface->gbmSwapchain->size());
for (const QRect rect : damagedRegion) {
deviceDamage |= mapping.map(scaledRect(rect, surface->scale), rotatedSize).toAlignedRect() & QRect(QPoint(), surface->gbmSwapchain->size());
}
}
const QRegion repaint = (deviceDamage | surface->importDamageJournal.accumulate(slot->age(), infiniteRegion())) & QRect(QPoint(), surface->gbmSwapchain->size());
surface->importDamageJournal.add(deviceDamage);
GLFramebuffer *fbo = slot->framebuffer();
surface->importContext->pushFramebuffer(fbo);
const auto shader = surface->importContext->shaderManager()->pushShader(sourceTexture->target() == GL_TEXTURE_EXTERNAL_OES ? ShaderTrait::MapExternalTexture : ShaderTrait::MapTexture);
QMatrix4x4 mat;
mat.scale(1, -1);
mat.ortho(QRect(QPoint(), fbo->size()));
shader->setUniform(GLShader::Mat4Uniform::ModelViewProjectionMatrix, mat);
if (const auto vbo = uploadGeometry(repaint, fbo->size())) {
sourceTexture->bind();
vbo->render(GL_TRIANGLES);
sourceTexture->unbind();
}
surface->importContext->popFramebuffer();
surface->importContext->shaderManager()->popShader();
glFlush();
EGLNativeFence endFence(display);
if (!endFence.isValid()) {
glFinish();
}
surface->importGbmSwapchain->release(slot, endFence.fileDescriptor().duplicate());
if (frame) {
renderTime->end();
frame->addRenderTimeQuery(std::move(renderTime));
}
// restore the old context
m_eglBackend->makeCurrent();
return m_gpu->importBuffer(slot->buffer(), endFence.takeFileDescriptor());
}
std::shared_ptr<DrmFramebuffer> EglGbmLayerSurface::importWithCpu(Surface *surface, EglSwapchainSlot *source, OutputFrame *frame) const
{
std::unique_ptr<CpuRenderTimeQuery> copyTime;
if (frame) {
copyTime = std::make_unique<CpuRenderTimeQuery>();
}
Q_ASSERT(surface->importDumbSwapchain);
const auto slot = surface->importDumbSwapchain->acquire();
if (!slot) {
qCWarning(KWIN_DRM) << "EglGbmLayerSurface::importWithCpu: failed to get a target dumb buffer";
return nullptr;
}
const auto size = source->buffer()->size();
const qsizetype srcStride = 4 * size.width();
EglContext *context = m_eglBackend->openglContext();
GLFramebuffer::pushFramebuffer(source->framebuffer());
QImage *const dst = slot->view()->image();
if (dst->bytesPerLine() == srcStride) {
context->glReadnPixels(0, 0, dst->width(), dst->height(), GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, dst->sizeInBytes(), dst->bits());
} else {
// there's padding, need to copy line by line
if (surface->cpuCopyCache.size() != dst->size()) {
surface->cpuCopyCache = QImage(dst->size(), QImage::Format_RGBA8888);
}
context->glReadnPixels(0, 0, dst->width(), dst->height(), GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, surface->cpuCopyCache.sizeInBytes(), surface->cpuCopyCache.bits());
for (int i = 0; i < dst->height(); i++) {
std::memcpy(dst->scanLine(i), surface->cpuCopyCache.scanLine(i), srcStride);
}
}
GLFramebuffer::popFramebuffer();
const auto ret = m_gpu->importBuffer(slot->buffer(), FileDescriptor{});
if (!ret) {
qCWarning(KWIN_DRM, "Failed to create a framebuffer: %s", strerror(errno));
}
surface->importDumbSwapchain->release(slot);
if (frame) {
copyTime->end();
frame->addRenderTimeQuery(std::move(copyTime));
}
return ret;
}
}
#include "moc_drm_egl_layer_surface.cpp"
@@ -0,0 +1,133 @@
/*
KWin - the KDE window manager
This file is part of the KDE project.
SPDX-FileCopyrightText: 2022 Xaver Hugl <xaver.hugl@gmail.com>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#pragma once
#include <QHash>
#include <QMap>
#include <QPointer>
#include <QRegion>
#include <chrono>
#include <optional>
#include "core/outputlayer.h"
#include "drm_plane.h"
#include "opengl/gltexture.h"
#include "utils/damagejournal.h"
#include "utils/filedescriptor.h"
namespace KWin
{
class DrmFramebuffer;
class EglSwapchain;
class EglSwapchainSlot;
class QPainterSwapchain;
class ShadowBuffer;
class EglContext;
class EglGbmBackend;
class GraphicsBuffer;
class SurfaceItem;
class GLTexture;
class GLRenderTimeQuery;
class ColorTransformation;
class GlLookUpTable;
class IccProfile;
class IccShader;
class EglGbmLayerSurface : public QObject
{
Q_OBJECT
public:
enum class BufferTarget {
Normal,
Linear,
Dumb
};
enum class FormatOption {
PreferAlpha,
RequireAlpha
};
EglGbmLayerSurface(DrmGpu *gpu, EglGbmBackend *eglBackend, BufferTarget target = BufferTarget::Normal, FormatOption formatOption = FormatOption::PreferAlpha);
~EglGbmLayerSurface();
std::optional<OutputLayerBeginFrameInfo> startRendering(const QSize &bufferSize, OutputTransform transformation, const QHash<uint32_t, QList<uint64_t>> &formats, const ColorDescription &colorDescription, const QVector3D &channelFactors, const std::shared_ptr<IccProfile> &iccProfile, double scale, Output::ColorPowerTradeoff tradeoff);
bool endRendering(const QRegion &damagedRegion, OutputFrame *frame);
std::shared_ptr<GLTexture> texture() const;
void destroyResources();
EglGbmBackend *eglBackend() const;
std::shared_ptr<DrmFramebuffer> renderTestBuffer(const QSize &bufferSize, const QHash<uint32_t, QList<uint64_t>> &formats, Output::ColorPowerTradeoff tradeoff);
void forgetDamage();
std::shared_ptr<DrmFramebuffer> currentBuffer() const;
const ColorDescription &colorDescription() const;
private:
enum class MultiGpuImportMode {
None,
Dmabuf,
LinearDmabuf,
Egl,
DumbBuffer
};
struct Surface
{
~Surface();
std::shared_ptr<EglContext> context;
std::shared_ptr<EglSwapchain> gbmSwapchain;
std::shared_ptr<EglSwapchainSlot> currentSlot;
DamageJournal damageJournal;
std::unique_ptr<QPainterSwapchain> importDumbSwapchain;
std::shared_ptr<EglContext> importContext;
std::shared_ptr<EglSwapchain> importGbmSwapchain;
QHash<GraphicsBuffer *, std::shared_ptr<GLTexture>> importedTextureCache;
QImage cpuCopyCache;
MultiGpuImportMode importMode;
DamageJournal importDamageJournal;
std::shared_ptr<DrmFramebuffer> currentFramebuffer;
BufferTarget bufferTarget;
double scale = 1;
// for color management
bool needsShadowBuffer = false;
std::shared_ptr<EglSwapchain> shadowSwapchain;
std::shared_ptr<EglSwapchainSlot> currentShadowSlot;
ColorDescription targetColorDescription = ColorDescription::sRGB;
ColorDescription intermediaryColorDescription = ColorDescription::sRGB;
QVector3D channelFactors = {1, 1, 1};
double brightness = 1.0;
std::unique_ptr<IccShader> iccShader;
std::shared_ptr<IccProfile> iccProfile;
DamageJournal shadowDamageJournal;
Output::ColorPowerTradeoff tradeoff = Output::ColorPowerTradeoff::PreferEfficiency;
std::unique_ptr<GLRenderTimeQuery> compositingTimeQuery;
};
bool checkSurface(const QSize &size, const QHash<uint32_t, QList<uint64_t>> &formats, Output::ColorPowerTradeoff tradeoff);
bool doesSurfaceFit(Surface *surface, const QSize &size, const QHash<uint32_t, QList<uint64_t>> &formats, Output::ColorPowerTradeoff tradeoff) const;
std::unique_ptr<Surface> createSurface(const QSize &size, const QHash<uint32_t, QList<uint64_t>> &formats, Output::ColorPowerTradeoff tradeoff) const;
std::unique_ptr<Surface> createSurface(const QSize &size, uint32_t format, const QList<uint64_t> &modifiers, MultiGpuImportMode importMode, BufferTarget bufferTarget, Output::ColorPowerTradeoff tradeoff) const;
std::shared_ptr<EglSwapchain> createGbmSwapchain(DrmGpu *gpu, EglContext *context, const QSize &size, uint32_t format, const QList<uint64_t> &modifiers, MultiGpuImportMode importMode, BufferTarget bufferTarget) const;
std::shared_ptr<DrmFramebuffer> doRenderTestBuffer(Surface *surface) const;
std::shared_ptr<DrmFramebuffer> importBuffer(Surface *surface, EglSwapchainSlot *source, FileDescriptor &&readFence, OutputFrame *frame, const QRegion &damagedRegion) const;
std::shared_ptr<DrmFramebuffer> importWithEgl(Surface *surface, GraphicsBuffer *sourceBuffer, FileDescriptor &&readFence, OutputFrame *frame, const QRegion &damagedRegion) const;
std::shared_ptr<DrmFramebuffer> importWithCpu(Surface *surface, EglSwapchainSlot *source, OutputFrame *frame) const;
std::unique_ptr<Surface> m_surface;
std::unique_ptr<Surface> m_oldSurface;
DrmGpu *const m_gpu;
EglGbmBackend *const m_eglBackend;
const BufferTarget m_requestedBufferTarget;
const FormatOption m_formatOption;
};
}
@@ -0,0 +1,955 @@
/*
KWin - the KDE window manager
This file is part of the KDE project.
SPDX-FileCopyrightText: 2020 Xaver Hugl <xaver.hugl@gmail.com>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "drm_gpu.h"
#include "config-kwin.h"
#include "core/gbmgraphicsbufferallocator.h"
#include "core/session.h"
#include "drm_backend.h"
#include "drm_buffer.h"
#include "drm_commit.h"
#include "drm_commit_thread.h"
#include "drm_connector.h"
#include "drm_crtc.h"
#include "drm_egl_backend.h"
#include "drm_layer.h"
#include "drm_logging.h"
#include "drm_output.h"
#include "drm_pipeline.h"
#include "drm_plane.h"
#include <QFile>
#include <algorithm>
#include <drm_fourcc.h>
#include <errno.h>
#include <fcntl.h>
#include <gbm.h>
#include <libdrm/drm_mode.h>
#include <poll.h>
#include <ranges>
#include <unistd.h>
#include <xf86drm.h>
#include <xf86drmMode.h>
#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
{
DrmGpu::DrmGpu(DrmBackend *backend, int fd, std::unique_ptr<DrmDevice> &&device)
: m_fd(fd)
, m_drmDevice(std::move(device))
, m_atomicModeSetting(false)
, m_platform(backend)
{
uint64_t capability = 0;
if (drmGetCap(fd, DRM_CAP_CURSOR_WIDTH, &capability) == 0) {
m_cursorSize.setWidth(capability);
} else {
m_cursorSize.setWidth(64);
}
if (drmGetCap(fd, DRM_CAP_CURSOR_HEIGHT, &capability) == 0) {
m_cursorSize.setHeight(capability);
} else {
m_cursorSize.setHeight(64);
}
int ret = drmGetCap(fd, DRM_CAP_TIMESTAMP_MONOTONIC, &capability);
if (ret == 0 && capability == 1) {
m_presentationClock = CLOCK_MONOTONIC;
} else {
m_presentationClock = CLOCK_REALTIME;
}
m_addFB2ModifiersSupported = drmGetCap(fd, DRM_CAP_ADDFB2_MODIFIERS, &capability) == 0 && capability == 1;
qCDebug(KWIN_DRM) << "drmModeAddFB2WithModifiers is" << (m_addFB2ModifiersSupported ? "supported" : "not supported") << "on GPU" << this;
// find out what driver this kms device is using
DrmUniquePtr<drmVersion> version(drmGetVersion(fd));
m_isI915 = strstr(version->name, "i915");
m_isNVidia = strstr(version->name, "nvidia-drm");
m_isAmdgpu = strstr(version->name, "amdgpu");
m_isVmwgfx = strstr(version->name, "vmwgfx");
m_isVirtualMachine = strstr(version->name, "virtio") || strstr(version->name, "qxl")
|| strstr(version->name, "vmwgfx") || strstr(version->name, "vboxvideo");
if (m_isNVidia) {
QFile moduleVersion("/sys/module/nvidia_drm/version");
if (moduleVersion.open(QIODeviceBase::OpenModeFlag::ReadOnly)) {
m_nvidiaDriverVersion = Version::parseString(moduleVersion.readLine(100));
}
}
m_driverName = version->name;
m_socketNotifier = std::make_unique<QSocketNotifier>(fd, QSocketNotifier::Read);
connect(m_socketNotifier.get(), &QSocketNotifier::activated, this, &DrmGpu::dispatchEvents);
initDrmResources();
if (m_atomicModeSetting == false) {
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;
}
}
DrmGpu::~DrmGpu()
{
removeOutputs();
m_eglDisplay.reset();
m_crtcs.clear();
m_connectors.clear();
m_planes.clear();
m_socketNotifier.reset();
m_platform->session()->closeRestricted(m_fd);
}
FileDescriptor DrmGpu::createNonMasterFd() const
{
char *path = drmGetDeviceNameFromFd2(m_fd);
FileDescriptor fd{open(path, O_RDWR | O_CLOEXEC)};
if (!fd.isValid()) {
qCWarning(KWIN_DRM) << "Could not open DRM fd for leasing!" << strerror(errno);
} else {
if (drmIsMaster(fd.get())) {
if (drmDropMaster(fd.get()) != 0) {
qCWarning(KWIN_DRM) << "Could not create a non-master DRM fd for leasing!" << strerror(errno);
return FileDescriptor{};
}
}
}
return fd;
}
clockid_t DrmGpu::presentationClock() const
{
return m_presentationClock;
}
void DrmGpu::initDrmResources()
{
// try atomic mode setting
bool isEnvVarSet = false;
bool noAMS = qEnvironmentVariableIntValue("KWIN_DRM_NO_AMS", &isEnvVarSet) != 0 && isEnvVarSet;
if (noAMS) {
qCWarning(KWIN_DRM) << "Atomic Mode Setting requested off via environment variable. Using legacy mode on GPU" << this;
} else if (drmSetClientCap(m_fd, DRM_CLIENT_CAP_ATOMIC, 1) == 0) {
if (m_isVirtualMachine) {
// ATOMIC must be set before attemping CURSOR_PLANE_HOTSPOT
if (drmSetClientCap(m_fd, DRM_CLIENT_CAP_CURSOR_PLANE_HOTSPOT, 1) != 0) {
qCWarning(KWIN_DRM, "Atomic Mode Setting disabled on GPU %s because of cursor offset issues in virtual machines", qPrintable(m_drmDevice->path()));
drmSetClientCap(m_fd, DRM_CLIENT_CAP_ATOMIC, 0);
noAMS = true;
}
}
DrmUniquePtr<drmModePlaneRes> planeResources(drmModeGetPlaneResources(m_fd));
if (planeResources && !noAMS) {
qCDebug(KWIN_DRM) << "Using Atomic Mode Setting on gpu" << this;
qCDebug(KWIN_DRM) << "Number of planes on GPU" << this << ":" << planeResources->count_planes;
// create the plane objects
for (unsigned int i = 0; i < planeResources->count_planes; ++i) {
auto plane = std::make_unique<DrmPlane>(this, planeResources->planes[i]);
if (plane->init()) {
m_allObjects << plane.get();
m_planes.push_back(std::move(plane));
}
}
if (m_planes.empty()) {
qCWarning(KWIN_DRM) << "Failed to create any plane. Falling back to legacy mode on GPU " << this;
}
} else {
qCWarning(KWIN_DRM) << "Failed to get plane resources. Falling back to legacy mode on GPU " << this;
}
} else {
qCWarning(KWIN_DRM) << "drmSetClientCap for Atomic Mode Setting failed. Using legacy mode on GPU" << this;
}
m_atomicModeSetting = !m_planes.empty();
DrmUniquePtr<drmModeRes> resources(drmModeGetResources(m_fd));
if (!resources) {
qCCritical(KWIN_DRM) << "drmModeGetResources for getting CRTCs failed on GPU" << this;
return;
}
QList<DrmPlane *> assignedPlanes;
for (int i = 0; i < resources->count_crtcs; ++i) {
uint32_t crtcId = resources->crtcs[i];
QList<DrmPlane *> primaryCandidates;
QList<DrmPlane *> cursorCandidates;
QList<DrmPlane *> overlayCandidates;
for (const auto &plane : m_planes) {
if (plane->isCrtcSupported(i) && !assignedPlanes.contains(plane.get())) {
if (plane->type.enumValue() == DrmPlane::TypeIndex::Primary) {
primaryCandidates.push_back(plane.get());
} else if (plane->type.enumValue() == DrmPlane::TypeIndex::Cursor) {
cursorCandidates.push_back(plane.get());
} else if (plane->type.enumValue() == DrmPlane::TypeIndex::Overlay) {
overlayCandidates.push_back(plane.get());
}
}
}
if (m_atomicModeSetting && primaryCandidates.empty()) {
qCWarning(KWIN_DRM) << "Could not find a suitable primary plane for crtc" << resources->crtcs[i];
continue;
}
const auto findBestPlane = [crtcId](const QList<DrmPlane *> &list) {
// if the plane is already used with this crtc, prefer it
const auto connected = std::ranges::find_if(list, [crtcId](DrmPlane *plane) {
return plane->crtcId.value() == crtcId;
});
if (connected != list.end()) {
return *connected;
}
// don't take away planes from other crtcs. The kernel currently rejects such commits
const auto notconnected = std::ranges::find_if(list, [](DrmPlane *plane) {
return plane->crtcId.value() == 0;
});
if (notconnected != list.end()) {
return *notconnected;
}
return list.empty() ? nullptr : list.front();
};
DrmPlane *primary = findBestPlane(primaryCandidates);
assignedPlanes.push_back(primary);
DrmPlane *cursor = findBestPlane(cursorCandidates);
if (!cursor) {
cursor = findBestPlane(overlayCandidates);
}
if (cursor) {
assignedPlanes.push_back(cursor);
}
auto crtc = std::make_unique<DrmCrtc>(this, crtcId, i, primary, cursor);
if (!crtc->init()) {
continue;
}
m_allObjects << crtc.get();
m_crtcs.push_back(std::move(crtc));
}
}
bool DrmGpu::updateOutputs()
{
if (!m_isActive) {
return false;
}
DrmUniquePtr<drmModeRes> resources(drmModeGetResources(m_fd));
if (!resources) {
qCWarning(KWIN_DRM) << "drmModeGetResources failed:" << strerror(errno);
return false;
}
// In principle these things are supposed to be detected through the wayland protocol.
// In practice SteamVR doesn't always behave correctly
if (DrmUniquePtr<drmModeLesseeListRes> lessees{drmModeListLessees(m_fd)}) {
for (const auto &output : std::as_const(m_drmOutputs)) {
if (output->lease()) {
const bool leaseActive = std::ranges::any_of(std::span(lessees->lessees, lessees->count), [output](uint32_t id) {
return output->lease()->lesseeId() == id;
});
if (!leaseActive) {
Q_EMIT output->lease()->revokeRequested();
}
}
}
} else {
qCWarning(KWIN_DRM) << "drmModeListLessees() failed:" << strerror(errno);
}
// update crtc properties
for (const auto &crtc : std::as_const(m_crtcs)) {
crtc->updateProperties();
}
// update plane properties
for (const auto &plane : std::as_const(m_planes)) {
plane->updateProperties();
}
// check for added and removed connectors
QList<DrmConnector *> existing;
QList<DrmOutput *> addedOutputs;
for (int i = 0; i < resources->count_connectors; ++i) {
const uint32_t currentConnector = resources->connectors[i];
const auto it = std::ranges::find_if(m_connectors, [currentConnector](const auto &connector) {
return connector->id() == currentConnector;
});
if (it == m_connectors.end()) {
auto conn = std::make_shared<DrmConnector>(this, currentConnector);
if (!conn->init()) {
continue;
}
existing.push_back(conn.get());
m_allObjects.push_back(conn.get());
m_connectors.push_back(std::move(conn));
} else {
(*it)->updateProperties();
existing.push_back(it->get());
}
}
for (auto it = m_connectors.begin(); it != m_connectors.end();) {
DrmConnector *conn = it->get();
const auto output = findOutput(conn->id());
const bool stillExists = existing.contains(conn);
if (!stillExists || !conn->isConnected()) {
if (output) {
removeOutput(output);
}
} else if (!output) {
qCDebug(KWIN_DRM, "New %soutput on GPU %s: %s", conn->isNonDesktop() ? "non-desktop " : "", qPrintable(m_drmDevice->path()), qPrintable(conn->modelName()));
auto &pipeline = m_pipelineMap[conn];
pipeline = std::make_unique<DrmPipeline>(conn);
m_pipelines.push_back(pipeline.get());
auto output = new DrmOutput(*it, pipeline.get());
m_drmOutputs << output;
addedOutputs << output;
Q_EMIT outputAdded(output);
pipeline->setActive(true);
pipeline->setEnable(false);
pipeline->setMode(conn->modes().front());
pipeline->applyPendingChanges();
} else {
output->updateConnectorProperties();
}
if (stillExists) {
if (conn->linkStatus.isValid() && conn->linkStatus.enumValue() == DrmConnector::LinkStatus::Bad) {
qCWarning(KWIN_DRM, "Bad link status detected on connector %s", qPrintable(conn->connectorName()));
}
it++;
} else {
m_allObjects.removeOne(it->get());
it = m_connectors.erase(it);
}
}
return true;
}
void DrmGpu::removeOutputs()
{
const auto outputs = m_drmOutputs;
for (const auto &output : outputs) {
removeOutput(output);
}
}
DrmPipeline::Error DrmGpu::checkCrtcAssignment(QList<DrmConnector *> connectors, const QList<DrmCrtc *> &crtcs)
{
if (connectors.isEmpty()) {
const auto result = testPipelines();
qCDebug(KWIN_DRM) << "Testing CRTC assignment..." << (result == DrmPipeline::Error::None ? "passed" : "failed");
return result;
}
qCDebug(KWIN_DRM) << "Attempting to match" << connectors << "with" << crtcs;
auto connector = connectors.takeFirst();
auto pipelineIt = m_pipelineMap.find(connector);
if (pipelineIt == m_pipelineMap.end()) {
// this connector doesn't even have a connected output
return checkCrtcAssignment(connectors, crtcs);
}
auto pipeline = pipelineIt->second.get();
if (!pipeline->enabled() || !connector->isConnected()) {
// disabled pipelines don't need CRTCs
pipeline->setCrtc(nullptr);
qCDebug(KWIN_DRM) << "Unassigning CRTC from connector" << connector->id();
return checkCrtcAssignment(connectors, crtcs);
}
if (crtcs.isEmpty()) {
// we have no crtc left to drive this connector
qCDebug(KWIN_DRM) << "Ran out of CRTCs";
return DrmPipeline::Error::InvalidArguments;
}
DrmCrtc *currentCrtc = nullptr;
if (m_atomicModeSetting) {
// try the crtc that this connector is already connected to first
const uint32_t id = connector->crtcId.value();
auto it = std::ranges::find_if(crtcs, [id](const auto &crtc) {
return id == crtc->id();
});
if (it != crtcs.end()) {
currentCrtc = *it;
auto crtcsLeft = crtcs;
crtcsLeft.removeOne(currentCrtc);
pipeline->setCrtc(currentCrtc);
qCDebug(KWIN_DRM) << "Assigning CRTC" << currentCrtc->id() << "to connector" << connector->id();
do {
DrmPipeline::Error err = checkCrtcAssignment(connectors, crtcsLeft);
if (err == DrmPipeline::Error::None || err == DrmPipeline::Error::NoPermission || err == DrmPipeline::Error::FramePending) {
return err;
}
} while (pipeline->pruneModifier());
}
}
for (const auto &crtc : std::as_const(crtcs)) {
if (connector->isCrtcSupported(crtc) && crtc != currentCrtc) {
auto crtcsLeft = crtcs;
crtcsLeft.removeOne(crtc);
pipeline->setCrtc(crtc);
qCDebug(KWIN_DRM) << "Assigning CRTC" << crtc->id() << "to connector" << connector->id();
do {
DrmPipeline::Error err = checkCrtcAssignment(connectors, crtcsLeft);
if (err == DrmPipeline::Error::None || err == DrmPipeline::Error::NoPermission || err == DrmPipeline::Error::FramePending) {
return err;
}
} while (pipeline->pruneModifier());
}
}
return DrmPipeline::Error::InvalidArguments;
}
DrmPipeline::Error DrmGpu::testPendingConfiguration()
{
QList<DrmConnector *> connectors;
QList<DrmCrtc *> crtcs;
// only change resources that aren't currently leased away
for (const auto &conn : m_connectors) {
const bool isLeased = std::ranges::any_of(m_drmOutputs, [&conn](const auto output) {
return output->lease() && output->pipeline()->connector() == conn.get();
});
if (!isLeased) {
connectors.push_back(conn.get());
}
}
for (const auto &crtc : m_crtcs) {
const bool isLeased = std::ranges::any_of(m_drmOutputs, [&crtc](const auto output) {
return output->lease() && output->pipeline()->crtc() == crtc.get();
});
if (!isLeased) {
crtcs.push_back(crtc.get());
}
}
if (m_atomicModeSetting) {
// sort outputs by being already connected (to any CRTC) so that already working outputs get preferred
std::sort(connectors.begin(), connectors.end(), [](auto c1, auto c2) {
return c1->crtcId.value() > c2->crtcId.value();
});
}
for (DrmPipeline *pipeline : m_pipelines) {
if (!pipeline->primaryLayer()) {
pipeline->setLayers(m_platform->renderBackend()->createDrmPlaneLayer(pipeline, DrmPlane::TypeIndex::Primary), m_platform->renderBackend()->createDrmPlaneLayer(pipeline, DrmPlane::TypeIndex::Cursor));
}
if (!pipeline->output()->lease()) {
// reset all outputs to their most basic configuration (primary plane without scaling)
// for the test, and set the target rects appropriately
const auto primary = pipeline->output()->primaryLayer();
primary->setTargetRect(QRect(QPoint(0, 0), pipeline->mode()->size()));
primary->setSourceRect(QRect(QPoint(0, 0), pipeline->mode()->size()));
primary->setEnabled(true);
pipeline->output()->cursorLayer()->setEnabled(false);
}
}
return checkCrtcAssignment(connectors, crtcs);
}
DrmPipeline::Error DrmGpu::testPipelines()
{
if (m_pipelines.empty()) {
// nothing to do
return DrmPipeline::Error::None;
}
QList<DrmPipeline *> inactivePipelines;
std::ranges::copy_if(m_pipelines, std::back_inserter(inactivePipelines), [](const auto pipeline) {
return pipeline->enabled() && !pipeline->active();
});
DrmPipeline::Error test = DrmPipeline::commitPipelines(m_pipelines, DrmPipeline::CommitMode::TestAllowModeset, unusedObjects());
if (!inactivePipelines.isEmpty() && test == DrmPipeline::Error::None) {
// ensure that pipelines that are set as enabled but currently inactive
// still work when they need to be set active again
for (const auto pipeline : std::as_const(inactivePipelines)) {
pipeline->setActive(true);
}
test = DrmPipeline::commitPipelines(m_pipelines, DrmPipeline::CommitMode::TestAllowModeset, unusedObjects());
for (const auto pipeline : std::as_const(inactivePipelines)) {
pipeline->setActive(false);
}
}
return test;
}
DrmOutput *DrmGpu::findOutput(quint32 connector)
{
auto it = std::ranges::find_if(m_drmOutputs, [connector](DrmOutput *o) {
return o->connector()->id() == connector;
});
if (it != m_drmOutputs.constEnd()) {
return *it;
}
return nullptr;
}
bool DrmGpu::isIdle() const
{
return std::ranges::none_of(m_pipelines, [](DrmPipeline *pipeline) {
return pipeline->commitThread()->pageflipsPending();
});
}
static std::chrono::nanoseconds convertTimestamp(const timespec &timestamp)
{
return std::chrono::seconds(timestamp.tv_sec) + std::chrono::nanoseconds(timestamp.tv_nsec);
}
static std::chrono::nanoseconds convertTimestamp(clockid_t sourceClock, clockid_t targetClock,
const timespec &timestamp)
{
if (sourceClock == targetClock) {
return convertTimestamp(timestamp);
}
timespec sourceCurrentTime = {};
timespec targetCurrentTime = {};
clock_gettime(sourceClock, &sourceCurrentTime);
clock_gettime(targetClock, &targetCurrentTime);
const auto delta = convertTimestamp(sourceCurrentTime) - convertTimestamp(timestamp);
return convertTimestamp(targetCurrentTime) - delta;
}
void DrmGpu::pageFlipHandler(int fd, unsigned int sequence, unsigned int sec, unsigned int usec, unsigned int crtc_id, void *user_data)
{
const auto commit = static_cast<DrmCommit *>(user_data);
const auto gpu = commit->gpu();
const bool defunct = std::erase_if(gpu->m_defunctCommits, [commit](const auto &defunct) {
return defunct.get() == commit;
}) != 0;
if (defunct) {
return;
}
// The static_cast<> here are for a 32-bit environment where
// sizeof(time_t) == sizeof(unsigned int) == 4 . Putting @p sec
// into a time_t cuts off the most-significant bit (after the
// year 2038), similarly long can't hold all the bits of an
// unsigned multiplication.
std::chrono::nanoseconds timestamp = convertTimestamp(gpu->presentationClock(), CLOCK_MONOTONIC,
{static_cast<time_t>(sec), static_cast<long>(usec * 1000)});
if (timestamp == std::chrono::nanoseconds::zero()) {
qCDebug(KWIN_DRM, "Got invalid timestamp (sec: %u, usec: %u) on gpu %s",
sec, usec, qPrintable(gpu->drmDevice()->path()));
timestamp = std::chrono::steady_clock::now().time_since_epoch();
}
commit->pageFlipped(timestamp);
}
void DrmGpu::dispatchEvents()
{
drmEventContext context = {};
context.version = 3;
context.page_flip_handler2 = pageFlipHandler;
drmHandleEvent(m_fd, &context);
}
void DrmGpu::addDefunctCommit(std::unique_ptr<DrmCommit> &&commit)
{
m_defunctCommits.push_back(std::move(commit));
}
void DrmGpu::removeOutput(DrmOutput *output)
{
qCDebug(KWIN_DRM) << "Removing output" << output;
m_drmOutputs.removeOne(output);
Q_EMIT outputRemoved(output);
m_pipelines.removeOne(output->pipeline());
m_pipelineMap.erase(output->connector());
output->removePipeline();
output->unref();
// force a modeset to make sure unused objects are cleaned up
m_forceModeset = true;
}
DrmBackend *DrmGpu::platform() const
{
return m_platform;
}
const QList<DrmPipeline *> DrmGpu::pipelines() const
{
return m_pipelines;
}
std::unique_ptr<DrmLease> DrmGpu::leaseOutputs(const QList<DrmOutput *> &outputs)
{
const bool alreadyLeased = std::ranges::any_of(outputs, [](DrmOutput *output) {
return output->lease();
});
if (alreadyLeased) {
return nullptr;
}
// allocate crtcs for the outputss
for (DrmOutput *output : outputs) {
output->pipeline()->setEnable(true);
output->pipeline()->setActive(false);
}
if (testPendingConfiguration() != DrmPipeline::Error::None) {
return nullptr;
}
QList<uint32_t> objects;
for (DrmOutput *output : outputs) {
if (!output->addLeaseObjects(objects)) {
return nullptr;
}
}
uint32_t lesseeId;
FileDescriptor fd{drmModeCreateLease(m_fd, objects.constData(), objects.count(), 0, &lesseeId)};
if (!fd.isValid()) {
qCWarning(KWIN_DRM) << "Could not create DRM lease!" << strerror(errno);
qCWarning(KWIN_DRM) << "Tried to lease the following" << objects.count() << "resources:";
for (const auto &res : std::as_const(objects)) {
qCWarning(KWIN_DRM) << res;
}
return nullptr;
} else {
qCDebug(KWIN_DRM) << "Created lease for" << objects.count() << "resources:";
for (const auto &res : std::as_const(objects)) {
qCDebug(KWIN_DRM) << res;
}
return std::make_unique<DrmLease>(this, std::move(fd), lesseeId, outputs);
}
}
QList<DrmOutput *> DrmGpu::drmOutputs() const
{
return m_drmOutputs;
}
int DrmGpu::fd() const
{
return m_fd;
}
DrmDevice *DrmGpu::drmDevice() const
{
return m_drmDevice.get();
}
bool DrmGpu::atomicModeSetting() const
{
return m_atomicModeSetting;
}
EglDisplay *DrmGpu::eglDisplay() const
{
return m_eglDisplay.get();
}
void DrmGpu::setEglDisplay(std::unique_ptr<EglDisplay> &&display)
{
m_eglDisplay = std::move(display);
}
bool DrmGpu::addFB2ModifiersSupported() const
{
return m_addFB2ModifiersSupported;
}
bool DrmGpu::asyncPageflipSupported() const
{
return m_asyncPageflipSupported;
}
bool DrmGpu::isI915() const
{
return m_isI915;
}
bool DrmGpu::isNVidia() const
{
return m_isNVidia;
}
bool DrmGpu::isAmdgpu() const
{
return m_isAmdgpu;
}
bool DrmGpu::isVmwgfx() const
{
return m_isVmwgfx;
}
bool DrmGpu::isVirtualMachine() const
{
return m_isVirtualMachine;
}
std::optional<Version> DrmGpu::nvidiaDriverVersion() const
{
return m_nvidiaDriverVersion;
}
bool DrmGpu::isRemoved() const
{
return m_isRemoved;
}
void DrmGpu::setRemoved()
{
m_isRemoved = true;
}
void DrmGpu::setActive(bool active)
{
if (m_isActive != active) {
m_isActive = active;
if (active) {
for (const auto &output : std::as_const(m_drmOutputs)) {
output->renderLoop()->uninhibit();
}
for (const auto &output : std::as_const(m_drmOutputs)) {
// force a modeset with legacy, we can't reliably know if one is needed
if (!atomicModeSetting()) {
output->pipeline()->forceLegacyModeset();
}
}
} else {
for (const auto &output : std::as_const(m_drmOutputs)) {
output->renderLoop()->inhibit();
}
}
Q_EMIT activeChanged(active);
}
}
bool DrmGpu::isActive() const
{
return m_isActive;
}
bool DrmGpu::needsModeset() const
{
return m_forceModeset
|| !m_pendingModesetFrames.empty()
|| std::ranges::any_of(m_pipelines, [](DrmPipeline *pipeline) {
return !pipeline->output()->lease() && pipeline->needsModeset();
});
}
void DrmGpu::maybeModeset(DrmPipeline *pipeline, const std::shared_ptr<OutputFrame> &frame)
{
if (pipeline && frame) {
m_pendingModesetFrames.emplace(pipeline, frame);
}
auto pipelines = m_pipelines;
for (const auto &output : std::as_const(m_drmOutputs)) {
if (output->lease()) {
pipelines.removeOne(output->pipeline());
}
}
const bool presentPendingForAll = std::ranges::all_of(pipelines, [](const auto &pipeline) {
return pipeline->modesetPresentPending() || !pipeline->activePending();
});
if (!presentPendingForAll) {
// commit only once all pipelines are ready for presentation
return;
}
if (!isIdle()) {
// doing a modeset with pending pageflips would crash
return;
}
// if the commit succeeds, it'll call DrmAtomicCommit::pageFlipped, which calls this method again...
// this is ugly, but at least simple and prevents the recursion
if (m_inModeset) {
return;
}
m_inModeset = true;
const DrmPipeline::Error err = DrmPipeline::commitPipelines(pipelines, DrmPipeline::CommitMode::CommitModeset, unusedObjects());
m_inModeset = false;
for (DrmPipeline *pipeline : std::as_const(pipelines)) {
if (pipeline->modesetPresentPending()) {
pipeline->resetModesetPresentPending();
}
}
m_forceModeset = false;
if (err == DrmPipeline::Error::None) {
for (const auto &[pipeline, frame] : m_pendingModesetFrames) {
frame->presented(std::chrono::steady_clock::now().time_since_epoch(), PresentationMode::VSync);
}
} else {
if (err != DrmPipeline::Error::FramePending) {
QTimer::singleShot(0, m_platform, &DrmBackend::updateOutputs);
}
}
m_pendingModesetFrames.clear();
}
QList<DrmObject *> DrmGpu::unusedObjects() const
{
QList<DrmObject *> ret = m_allObjects;
for (const auto &pipeline : m_pipelines) {
ret.removeOne(pipeline->connector());
if (pipeline->crtc()) {
ret.removeOne(pipeline->crtc());
ret.removeOne(pipeline->crtc()->primaryPlane());
ret.removeOne(pipeline->crtc()->cursorPlane());
}
}
return ret;
}
QSize DrmGpu::cursorSize() const
{
return m_cursorSize;
}
void DrmGpu::releaseBuffers()
{
for (const auto &plane : std::as_const(m_planes)) {
plane->releaseCurrentBuffer();
}
for (const auto &crtc : std::as_const(m_crtcs)) {
crtc->releaseCurrentBuffer();
}
for (const auto &pipeline : std::as_const(m_pipelines)) {
if (DrmPipelineLayer *layer = pipeline->primaryLayer()) {
layer->releaseBuffers();
}
if (DrmPipelineLayer *layer = pipeline->cursorLayer()) {
layer->releaseBuffers();
}
}
}
void DrmGpu::recreateSurfaces()
{
for (const auto &pipeline : std::as_const(m_pipelines)) {
pipeline->setLayers(m_platform->renderBackend()->createDrmPlaneLayer(pipeline, DrmPlane::TypeIndex::Primary), m_platform->renderBackend()->createDrmPlaneLayer(pipeline, DrmPlane::TypeIndex::Cursor));
pipeline->applyPendingChanges();
}
}
std::shared_ptr<DrmFramebuffer> DrmGpu::importBuffer(GraphicsBuffer *buffer, FileDescriptor &&readFence)
{
const DmaBufAttributes *attributes = buffer->dmabufAttributes();
if (Q_UNLIKELY(!attributes)) {
return nullptr;
}
uint32_t handles[] = {0, 0, 0, 0};
auto cleanup = qScopeGuard([this, &handles]() {
for (int i = 0; i < 4; ++i) {
if (handles[i] == 0) {
continue;
}
bool closed = false;
for (int j = 0; j < i; ++j) {
if (handles[i] == handles[j]) {
closed = true;
break;
}
}
if (closed) {
continue;
}
drmCloseBufferHandle(m_fd, handles[i]);
}
});
for (int i = 0; i < attributes->planeCount; ++i) {
if (drmPrimeFDToHandle(m_fd, attributes->fd[i].get(), &handles[i]) != 0) {
qCWarning(KWIN_DRM) << "drmPrimeFDToHandle() failed";
return nullptr;
}
}
uint32_t framebufferId = 0;
int ret;
if (addFB2ModifiersSupported() && attributes->modifier != DRM_FORMAT_MOD_INVALID) {
uint64_t modifier[4] = {0, 0, 0, 0};
for (int i = 0; i < attributes->planeCount; ++i) {
modifier[i] = attributes->modifier;
}
ret = drmModeAddFB2WithModifiers(m_fd,
attributes->width,
attributes->height,
attributes->format,
handles,
attributes->pitch.data(),
attributes->offset.data(),
modifier,
&framebufferId,
DRM_MODE_FB_MODIFIERS);
} else {
ret = drmModeAddFB2(m_fd,
attributes->width,
attributes->height,
attributes->format,
handles,
attributes->pitch.data(),
attributes->offset.data(),
&framebufferId,
0);
if (ret == EOPNOTSUPP && attributes->planeCount == 1) {
ret = drmModeAddFB(m_fd,
attributes->width,
attributes->height,
24, 32,
attributes->pitch[0],
handles[0],
&framebufferId);
}
}
if (ret != 0) {
return nullptr;
}
return std::make_shared<DrmFramebuffer>(this, framebufferId, buffer, std::move(readFence));
}
QString DrmGpu::driverName() const
{
return m_driverName;
}
DrmLease::DrmLease(DrmGpu *gpu, FileDescriptor &&fd, uint32_t lesseeId, const QList<DrmOutput *> &outputs)
: m_gpu(gpu)
, m_fd(std::move(fd))
, m_lesseeId(lesseeId)
, m_outputs(outputs)
{
for (const auto output : m_outputs) {
output->leased(this);
}
}
DrmLease::~DrmLease()
{
qCDebug(KWIN_DRM, "Revoking lease with leaseID %d", m_lesseeId);
drmModeRevokeLease(m_gpu->fd(), m_lesseeId);
for (const auto &output : m_outputs) {
output->leaseEnded();
output->pipeline()->setEnable(false);
}
}
FileDescriptor &DrmLease::fd()
{
return m_fd;
}
uint32_t DrmLease::lesseeId() const
{
return m_lesseeId;
}
}
QDebug &operator<<(QDebug &s, const KWin::DrmGpu *gpu)
{
s << gpu->drmDevice()->path();
return s;
}
#include "moc_drm_gpu.cpp"
@@ -0,0 +1,180 @@
/*
KWin - the KDE window manager
This file is part of the KDE project.
SPDX-FileCopyrightText: 2020 Xaver Hugl <xaver.hugl@gmail.com>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#pragma once
#include "core/drmdevice.h"
#include "drm_pipeline.h"
#include "utils/filedescriptor.h"
#include "utils/version.h"
#include <QList>
#include <QPointer>
#include <QSize>
#include <QSocketNotifier>
#include <qobject.h>
#include <chrono>
#include <epoxy/egl.h>
#include <sys/types.h>
namespace KWin
{
class DrmOutput;
class DrmObject;
class DrmCrtc;
class DrmConnector;
class DrmPlane;
class DrmBackend;
class EglGbmBackend;
class DrmAbstractOutput;
class DrmRenderBackend;
class DrmVirtualOutput;
class EglDisplay;
class GraphicsBuffer;
class GraphicsBufferAllocator;
class OutputFrame;
class DrmCommit;
class DrmLease : public QObject
{
Q_OBJECT
public:
DrmLease(DrmGpu *gpu, FileDescriptor &&fd, uint32_t lesseeId, const QList<DrmOutput *> &outputs);
~DrmLease();
FileDescriptor &fd();
uint32_t lesseeId() const;
Q_SIGNALS:
void revokeRequested();
private:
DrmGpu *const m_gpu;
FileDescriptor m_fd;
const uint32_t m_lesseeId;
const QList<DrmOutput *> m_outputs;
};
class DrmGpu : public QObject
{
Q_OBJECT
public:
/**
* This should always be longer than any real pageflip can take, even with PSR and modesets
*/
static constexpr std::chrono::milliseconds s_pageflipTimeout = std::chrono::seconds(1);
DrmGpu(DrmBackend *backend, int fd, std::unique_ptr<DrmDevice> &&device);
~DrmGpu();
int fd() const;
DrmDevice *drmDevice() const;
bool isRemoved() const;
void setRemoved();
void setActive(bool active);
bool isActive() const;
bool atomicModeSetting() const;
bool addFB2ModifiersSupported() const;
bool asyncPageflipSupported() const;
bool isI915() const;
bool isNVidia() const;
bool isAmdgpu() const;
bool isVmwgfx() const;
bool isVirtualMachine() const;
std::optional<Version> nvidiaDriverVersion() const;
QString driverName() const;
EglDisplay *eglDisplay() const;
DrmBackend *platform() const;
/**
* Returns the clock from which presentation timestamps are sourced. The returned value
* can be either CLOCK_MONOTONIC or CLOCK_REALTIME.
*/
clockid_t presentationClock() const;
QSize cursorSize() const;
QList<DrmOutput *> drmOutputs() const;
const QList<DrmPipeline *> pipelines() const;
void setEglDisplay(std::unique_ptr<EglDisplay> &&display);
bool updateOutputs();
void removeOutputs();
DrmPipeline::Error testPendingConfiguration();
bool needsModeset() const;
void maybeModeset(DrmPipeline *pipeline, const std::shared_ptr<OutputFrame> &frame);
std::shared_ptr<DrmFramebuffer> importBuffer(GraphicsBuffer *buffer, FileDescriptor &&explicitFence);
void releaseBuffers();
void recreateSurfaces();
FileDescriptor createNonMasterFd() const;
std::unique_ptr<DrmLease> leaseOutputs(const QList<DrmOutput *> &outputs);
bool isIdle() const;
void dispatchEvents();
void addDefunctCommit(std::unique_ptr<DrmCommit> &&commit);
Q_SIGNALS:
void activeChanged(bool active);
void outputAdded(DrmAbstractOutput *output);
void outputRemoved(DrmAbstractOutput *output);
private:
DrmOutput *findOutput(quint32 connector);
void removeOutput(DrmOutput *output);
void initDrmResources();
DrmPipeline::Error checkCrtcAssignment(QList<DrmConnector *> connectors, const QList<DrmCrtc *> &crtcs);
DrmPipeline::Error testPipelines();
QList<DrmObject *> unusedObjects() const;
static void pageFlipHandler(int fd, unsigned int sequence, unsigned int sec, unsigned int usec, unsigned int crtc_id, void *user_data);
const int m_fd;
const std::unique_ptr<DrmDevice> m_drmDevice;
bool m_atomicModeSetting;
bool m_addFB2ModifiersSupported = false;
bool m_isNVidia;
bool m_isI915;
bool m_isAmdgpu;
bool m_isVmwgfx;
bool m_isVirtualMachine;
QString m_driverName;
bool m_supportsCursorPlaneHotspot = false;
bool m_asyncPageflipSupported = false;
bool m_isRemoved = false;
bool m_isActive = true;
bool m_forceModeset = false;
clockid_t m_presentationClock;
std::unique_ptr<EglDisplay> m_eglDisplay;
DrmBackend *const m_platform;
std::optional<Version> m_nvidiaDriverVersion;
std::vector<std::unique_ptr<DrmPlane>> m_planes;
std::vector<std::unique_ptr<DrmCrtc>> m_crtcs;
std::vector<std::shared_ptr<DrmConnector>> m_connectors;
std::unordered_map<DrmConnector *, std::unique_ptr<DrmPipeline>> m_pipelineMap;
QList<DrmObject *> m_allObjects;
QList<DrmPipeline *> m_pipelines;
QList<DrmOutput *> m_drmOutputs;
std::unique_ptr<QSocketNotifier> m_socketNotifier;
QSize m_cursorSize;
std::unordered_map<DrmPipeline *, std::shared_ptr<OutputFrame>> m_pendingModesetFrames;
bool m_inModeset = false;
std::vector<std::unique_ptr<DrmCommit>> m_defunctCommits;
};
}
QDebug &operator<<(QDebug &s, const KWin::DrmGpu *gpu);
@@ -0,0 +1,44 @@
/*
KWin - the KDE window manager
This file is part of the KDE project.
SPDX-FileCopyrightText: 2022 Xaver Hugl <xaver.hugl@gmail.com>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "drm_layer.h"
#include "core/graphicsbuffer.h"
#include "drm_buffer.h"
#include "drm_output.h"
#include "drm_pipeline.h"
#include <QMatrix4x4>
#include <drm_fourcc.h>
namespace KWin
{
DrmOutputLayer::DrmOutputLayer(Output *output)
: OutputLayer(output)
{
}
DrmOutputLayer::~DrmOutputLayer() = default;
std::shared_ptr<GLTexture> DrmOutputLayer::texture() const
{
return nullptr;
}
DrmPipelineLayer::DrmPipelineLayer(DrmPipeline *pipeline, DrmPlane::TypeIndex type)
: DrmOutputLayer(pipeline->output())
, m_pipeline(pipeline)
, m_type(type)
{
}
const ColorPipeline &DrmPipelineLayer::colorPipeline() const
{
return m_colorPipeline;
}
}
@@ -0,0 +1,50 @@
/*
KWin - the KDE window manager
This file is part of the KDE project.
SPDX-FileCopyrightText: 2022 Xaver Hugl <xaver.hugl@gmail.com>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#pragma once
#include "core/colorpipeline.h"
#include "core/outputlayer.h"
#include "drm_plane.h"
#include <QRegion>
#include <memory>
#include <optional>
namespace KWin
{
class SurfaceItem;
class DrmFramebuffer;
class GLTexture;
class DrmPipeline;
class DrmOutputLayer : public OutputLayer
{
public:
explicit DrmOutputLayer(Output *output);
virtual ~DrmOutputLayer();
virtual std::shared_ptr<GLTexture> texture() const;
virtual void releaseBuffers() = 0;
};
class DrmPipelineLayer : public DrmOutputLayer
{
public:
explicit DrmPipelineLayer(DrmPipeline *pipeline, DrmPlane::TypeIndex type);
virtual bool checkTestBuffer() = 0;
virtual std::shared_ptr<DrmFramebuffer> currentBuffer() const = 0;
virtual const ColorPipeline &colorPipeline() const;
protected:
DrmPipeline *const m_pipeline;
const DrmPlane::TypeIndex m_type;
ColorPipeline m_colorPipeline;
};
}
@@ -0,0 +1,10 @@
/*
KWin - the KDE window manager
This file is part of the KDE project.
SPDX-FileCopyrightText: 2015 Martin Gräßlin <mgraesslin@kde.org>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "drm_logging.h"
Q_LOGGING_CATEGORY(KWIN_DRM, "kwin_wayland_drm", QtWarningMsg)
@@ -0,0 +1,13 @@
/*
KWin - the KDE window manager
This file is part of the KDE project.
SPDX-FileCopyrightText: 2015 Martin Gräßlin <mgraesslin@kde.org>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#pragma once
#include <QDebug>
#include <QLoggingCategory>
Q_DECLARE_LOGGING_CATEGORY(KWIN_DRM)
@@ -0,0 +1,105 @@
/*
KWin - the KDE window manager
This file is part of the KDE project.
SPDX-FileCopyrightText: 2016 Roman Gilg <subdiff@gmail.com>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "drm_object.h"
#include <errno.h>
#include "drm_commit.h"
#include "drm_gpu.h"
#include "drm_logging.h"
#include "drm_pointer.h"
namespace KWin
{
DrmObject::DrmObject(DrmGpu *gpu, uint32_t objectId, uint32_t objectType)
: m_gpu(gpu)
, m_id(objectId)
, m_objectType(objectType)
{
}
DrmPropertyList DrmObject::queryProperties() const
{
DrmUniquePtr<drmModeObjectProperties> properties(drmModeObjectGetProperties(m_gpu->fd(), m_id, m_objectType));
if (!properties) {
qCWarning(KWIN_DRM) << "Failed to get properties for object" << m_id;
return {};
}
DrmPropertyList ret;
for (uint32_t i = 0; i < properties->count_props; i++) {
DrmUniquePtr<drmModePropertyRes> prop(drmModeGetProperty(m_gpu->fd(), properties->props[i]));
if (!prop) {
qCWarning(KWIN_DRM, "Getting property %d of object %d failed!", properties->props[i], m_id);
continue;
}
ret.addProperty(std::move(prop), properties->prop_values[i]);
}
return ret;
}
uint32_t DrmObject::id() const
{
return m_id;
}
DrmGpu *DrmObject::gpu() const
{
return m_gpu;
}
uint32_t DrmObject::type() const
{
return m_objectType;
}
QString DrmObject::typeName() const
{
switch (m_objectType) {
case DRM_MODE_OBJECT_CONNECTOR:
return QStringLiteral("connector");
case DRM_MODE_OBJECT_CRTC:
return QStringLiteral("crtc");
case DRM_MODE_OBJECT_PLANE:
return QStringLiteral("plane");
default:
return QStringLiteral("unknown?");
}
}
void DrmPropertyList::addProperty(DrmUniquePtr<drmModePropertyRes> &&prop, uint64_t value)
{
m_properties.push_back(std::make_pair(std::move(prop), value));
}
std::optional<std::pair<DrmUniquePtr<drmModePropertyRes>, uint64_t>> DrmPropertyList::takeProperty(const QByteArray &name)
{
const auto it = std::ranges::find_if(m_properties, [&name](const auto &pair) {
return pair.first->name == name;
});
if (it != m_properties.end()) {
auto ret = std::move(*it);
m_properties.erase(it);
return ret;
} else {
return std::nullopt;
}
}
}
QDebug operator<<(QDebug s, const KWin::DrmObject *obj)
{
QDebugStateSaver saver(s);
if (obj) {
s.nospace() << "DrmObject(id=" << obj->id() << ", gpu=" << obj->gpu() << ')';
} else {
s << "DrmObject(0x0)";
}
return s;
}
@@ -0,0 +1,72 @@
/*
KWin - the KDE window manager
This file is part of the KDE project.
SPDX-FileCopyrightText: 2016 Roman Gilg <subdiff@gmail.com>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#pragma once
#include <QByteArray>
#include <QList>
#include <QMap>
#include <vector>
// drm
#include <xf86drmMode.h>
#include "drm_pointer.h"
#include "drm_property.h"
namespace KWin
{
class DrmBackend;
class DrmGpu;
class DrmOutput;
class DrmAtomicCommit;
class DrmPropertyList
{
public:
void addProperty(DrmUniquePtr<drmModePropertyRes> &&prop, uint64_t value);
std::optional<std::pair<DrmUniquePtr<drmModePropertyRes>, uint64_t>> takeProperty(const QByteArray &name);
private:
std::vector<std::pair<DrmUniquePtr<drmModePropertyRes>, uint64_t>> m_properties;
};
class DrmObject
{
public:
virtual ~DrmObject() = default;
DrmObject(const DrmObject &) = delete;
/**
* Set the properties in such a way that this resource won't be used anymore
*/
virtual void disable(DrmAtomicCommit *commit) = 0;
virtual bool updateProperties() = 0;
uint32_t id() const;
DrmGpu *gpu() const;
uint32_t type() const;
QString typeName() const;
protected:
DrmObject(DrmGpu *gpu, uint32_t objectId, uint32_t objectType);
DrmPropertyList queryProperties() const;
private:
DrmGpu *m_gpu;
const uint32_t m_id;
const uint32_t m_objectType;
};
}
QDebug operator<<(QDebug stream, const KWin::DrmObject *);
@@ -0,0 +1,668 @@
/*
KWin - the KDE window manager
This file is part of the KDE project.
SPDX-FileCopyrightText: 2015 Martin Gräßlin <mgraesslin@kde.org>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "drm_output.h"
#include "drm_backend.h"
#include "drm_connector.h"
#include "drm_crtc.h"
#include "drm_gpu.h"
#include "drm_pipeline.h"
#include "core/brightnessdevice.h"
#include "core/colortransformation.h"
#include "core/iccprofile.h"
#include "core/outputconfiguration.h"
#include "core/renderbackend.h"
#include "core/renderloop.h"
#include "core/renderloop_p.h"
#include "drm_layer.h"
#include "drm_logging.h"
#include "utils/kernel.h"
// Qt
#include <QCryptographicHash>
#include <QMatrix4x4>
#include <QPainter>
// c++
#include <cerrno>
// drm
#include <drm_fourcc.h>
#include <libdrm/drm_mode.h>
#include <xf86drm.h>
namespace KWin
{
static bool s_disableTripleBufferingSet = false;
static const bool s_disableTripleBuffering = qEnvironmentVariableIntValue("KWIN_DRM_DISABLE_TRIPLE_BUFFERING", &s_disableTripleBufferingSet) == 1;
DrmOutput::DrmOutput(const std::shared_ptr<DrmConnector> &conn, DrmPipeline *pipeline)
: m_gpu(conn->gpu())
, m_pipeline(pipeline)
, m_connector(conn)
{
m_pipeline->setOutput(this);
if (m_gpu->atomicModeSetting() && ((!s_disableTripleBufferingSet && !m_gpu->isNVidia()) || (s_disableTripleBufferingSet && !s_disableTripleBuffering))) {
m_renderLoop->setMaxPendingFrameCount(2);
}
const Edid *edid = m_connector->edid();
setInformation(Information{
.name = m_connector->connectorName(),
.manufacturer = edid->manufacturerString(),
.model = m_connector->modelName(),
.serialNumber = edid->serialNumber(),
.eisaId = edid->eisaId(),
.physicalSize = m_connector->physicalSize(),
.edid = *edid,
.subPixel = m_connector->subpixel(),
.capabilities = computeCapabilities(),
.panelOrientation = m_connector->panelOrientation.isValid() ? DrmConnector::toKWinTransform(m_connector->panelOrientation.enumValue()) : OutputTransform::Normal,
.internal = m_connector->isInternal(),
.nonDesktop = m_connector->isNonDesktop(),
.mstPath = m_connector->mstPath(),
.maxPeakBrightness = edid->desiredMaxLuminance(),
.maxAverageBrightness = edid->desiredMaxFrameAverageLuminance(),
.minBrightness = edid->desiredMinLuminance(),
});
updateConnectorProperties();
m_turnOffTimer.setSingleShot(true);
m_turnOffTimer.setInterval(dimAnimationTime());
connect(&m_turnOffTimer, &QTimer::timeout, this, [this] {
if (!setDrmDpmsMode(DpmsMode::Off)) {
// in case of failure, undo aboutToTurnOff() from setDpmsMode()
Q_EMIT wakeUp();
}
});
}
bool DrmOutput::addLeaseObjects(QList<uint32_t> &objectList)
{
if (!m_pipeline->crtc()) {
qCWarning(KWIN_DRM) << "Can't lease connector: No suitable crtc available";
return false;
}
qCDebug(KWIN_DRM) << "adding connector" << m_pipeline->connector()->id() << "to lease";
objectList << m_pipeline->connector()->id();
objectList << m_pipeline->crtc()->id();
if (m_pipeline->crtc()->primaryPlane()) {
objectList << m_pipeline->crtc()->primaryPlane()->id();
}
return true;
}
void DrmOutput::leased(DrmLease *lease)
{
m_lease = lease;
}
void DrmOutput::leaseEnded()
{
qCDebug(KWIN_DRM) << "ended lease for connector" << m_pipeline->connector()->id();
m_lease = nullptr;
}
DrmLease *DrmOutput::lease() const
{
return m_lease;
}
bool DrmOutput::shouldDisableCursorPlane() const
{
// 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 m_desiredPresentationMode == PresentationMode::Async || m_desiredPresentationMode == PresentationMode::AdaptiveAsync
|| m_pipeline->amdgpuVrrWorkaroundActive();
}
bool DrmOutput::updateCursorLayer(std::optional<std::chrono::nanoseconds> allowedVrrDelay)
{
if (m_pipeline->gpu()->atomicModeSetting() && shouldDisableCursorPlane() && m_pipeline->cursorLayer() && m_pipeline->cursorLayer()->isEnabled()) {
return false;
}
return m_pipeline->updateCursor(allowedVrrDelay);
}
QList<std::shared_ptr<OutputMode>> DrmOutput::getModes() const
{
const auto drmModes = m_pipeline->connector()->modes();
QList<std::shared_ptr<OutputMode>> ret;
ret.reserve(drmModes.count());
for (const auto &drmMode : drmModes) {
ret.append(drmMode);
}
return ret;
}
void DrmOutput::setDpmsMode(DpmsMode mode)
{
if (mode == dpmsMode()) {
return;
}
if (mode == DpmsMode::Off) {
if (!m_turnOffTimer.isActive()) {
updateDpmsMode(DpmsMode::AboutToTurnOff);
Q_EMIT aboutToTurnOff(std::chrono::milliseconds(m_turnOffTimer.interval()));
m_turnOffTimer.start();
}
} else {
if (m_turnOffTimer.isActive()) {
updateDpmsMode(mode);
m_turnOffTimer.stop();
Q_EMIT wakeUp();
} else if (setDrmDpmsMode(mode)) {
Q_EMIT wakeUp();
}
}
}
bool DrmOutput::setDrmDpmsMode(DpmsMode mode)
{
if (!isEnabled()) {
return false;
}
bool active = mode == DpmsMode::On || mode == DpmsMode::AboutToTurnOff;
bool isActive = dpmsMode() == DpmsMode::On || dpmsMode() == DpmsMode::AboutToTurnOff;
if (active == isActive) {
updateDpmsMode(mode);
return true;
}
m_pipeline->setActive(active);
if (DrmPipeline::commitPipelines({m_pipeline}, DrmPipeline::CommitMode::TestAllowModeset) == DrmPipeline::Error::None) {
m_pipeline->applyPendingChanges();
updateDpmsMode(mode);
if (active) {
m_renderLoop->uninhibit();
m_renderLoop->scheduleRepaint();
// re-set KMS color pipeline stuff
tryKmsColorOffloading();
} else {
m_renderLoop->inhibit();
// with the renderloop inhibited, there won't be a new frame
// to trigger this automatically
m_gpu->maybeModeset(m_pipeline, nullptr);
}
return true;
} else {
qCWarning(KWIN_DRM) << "Setting dpms mode failed!";
m_pipeline->revertPendingChanges();
return false;
}
}
DrmPlane::Transformations outputToPlaneTransform(OutputTransform transform)
{
using PlaneTrans = DrmPlane::Transformation;
switch (transform.kind()) {
case OutputTransform::Normal:
return PlaneTrans::Rotate0;
case OutputTransform::FlipX:
return PlaneTrans::ReflectX | PlaneTrans::Rotate0;
case OutputTransform::Rotate90:
return PlaneTrans::Rotate90;
case OutputTransform::FlipX90:
return PlaneTrans::ReflectX | PlaneTrans::Rotate90;
case OutputTransform::Rotate180:
return PlaneTrans::Rotate180;
case OutputTransform::FlipX180:
return PlaneTrans::ReflectX | PlaneTrans::Rotate180;
case OutputTransform::Rotate270:
return PlaneTrans::Rotate270;
case OutputTransform::FlipX270:
return PlaneTrans::ReflectX | PlaneTrans::Rotate270;
default:
Q_UNREACHABLE();
}
}
void DrmOutput::updateConnectorProperties()
{
updateInformation();
State next = m_state;
next.modes = getModes();
if (!next.currentMode) {
// some mode needs to be set
next.currentMode = next.modes.constFirst();
}
if (!next.modes.contains(next.currentMode)) {
next.currentMode->setRemoved();
next.modes.push_front(next.currentMode);
}
setState(next);
}
static const bool s_allowColorspaceIntel = qEnvironmentVariableIntValue("KWIN_DRM_ALLOW_INTEL_COLORSPACE") == 1;
static const bool s_allowColorspaceNVidia = qEnvironmentVariableIntValue("KWIN_DRM_ALLOW_NVIDIA_COLORSPACE") == 1;
Output::Capabilities DrmOutput::computeCapabilities() const
{
Capabilities capabilities = Capability::Dpms | Capability::IccProfile;
if (m_connector->overscan.isValid() || m_connector->underscan.isValid()) {
capabilities |= Capability::Overscan;
}
if (m_connector->vrrCapable.isValid() && m_connector->vrrCapable.value()) {
capabilities |= Capability::Vrr;
}
if (m_gpu->asyncPageflipSupported()) {
capabilities |= Capability::Tearing;
}
if (m_connector->broadcastRGB.isValid()) {
capabilities |= Capability::RgbRange;
}
if (m_connector->colorspace.isValid() && (m_connector->colorspace.hasEnum(DrmConnector::Colorspace::BT2020_RGB) || m_connector->colorspace.hasEnum(DrmConnector::Colorspace::BT2020_YCC)) && m_connector->edid()->supportsBT2020()) {
bool allowColorspace = true;
if (m_gpu->isI915()) {
allowColorspace &= s_allowColorspaceIntel || linuxKernelVersion() >= Version(6, 11);
} else if (m_gpu->isNVidia()) {
allowColorspace &= s_allowColorspaceNVidia || m_gpu->nvidiaDriverVersion() >= Version(565, 57, 1);
}
if (allowColorspace) {
capabilities |= Capability::WideColorGamut;
}
}
if (m_connector->hdrMetadata.isValid() && m_connector->edid()->supportsPQ() && (capabilities & Capability::WideColorGamut)) {
capabilities |= Capability::HighDynamicRange;
}
if (m_connector->isInternal()) {
// TODO only set this if an orientation sensor is available?
capabilities |= Capability::AutoRotation;
}
if (m_state.highDynamicRange || m_brightnessDevice || m_state.allowSdrSoftwareBrightness) {
capabilities |= Capability::BrightnessControl;
}
return capabilities;
}
void DrmOutput::updateInformation()
{
// not all changes are currently handled by the rest of KWin
// so limit the changes to what's verified to work
const Edid *edid = m_connector->edid();
Information nextInfo = m_information;
nextInfo.capabilities = computeCapabilities();
nextInfo.maxPeakBrightness = edid->desiredMaxLuminance();
nextInfo.maxAverageBrightness = edid->desiredMaxFrameAverageLuminance();
nextInfo.minBrightness = edid->desiredMinLuminance();
setInformation(nextInfo);
}
void DrmOutput::updateDpmsMode(DpmsMode dpmsMode)
{
State next = m_state;
next.dpmsMode = dpmsMode;
setState(next);
}
bool DrmOutput::present(const std::shared_ptr<OutputFrame> &frame)
{
m_desiredPresentationMode = frame->presentationMode();
const bool needsModeset = m_gpu->needsModeset();
bool success;
if (needsModeset) {
m_pipeline->setPresentationMode(PresentationMode::VSync);
m_pipeline->setContentType(DrmConnector::DrmContentType::Graphics);
m_pipeline->maybeModeset(frame);
success = true;
} else {
m_pipeline->setPresentationMode(frame->presentationMode());
if (m_pipeline->cursorLayer()->isEnabled()) {
// the cursor plane needs to be disabled before we enable tearing; see DrmOutput::updateCursorLayer
if (frame->presentationMode() == PresentationMode::AdaptiveAsync) {
m_pipeline->setPresentationMode(PresentationMode::AdaptiveSync);
} else if (frame->presentationMode() == PresentationMode::Async) {
m_pipeline->setPresentationMode(PresentationMode::VSync);
}
}
DrmPipeline::Error err = m_pipeline->present(frame);
if (err != DrmPipeline::Error::None && frame->presentationMode() == PresentationMode::AdaptiveAsync) {
// tearing can fail in various circumstances, but vrr shouldn't
m_pipeline->setPresentationMode(PresentationMode::AdaptiveSync);
err = m_pipeline->present(frame);
}
if (err != DrmPipeline::Error::None && frame->presentationMode() != PresentationMode::VSync) {
// retry with the most basic presentation mode
m_pipeline->setPresentationMode(PresentationMode::VSync);
err = m_pipeline->present(frame);
}
success = err == DrmPipeline::Error::None;
}
m_renderLoop->setPresentationMode(m_pipeline->presentationMode());
if (!success) {
return false;
}
Q_EMIT outputChange(frame->damage());
if (frame->brightness() != m_state.currentBrightness || (frame->artificialHdrHeadroom() && frame->artificialHdrHeadroom() != m_state.artificialHdrHeadroom)) {
updateBrightness(frame->brightness().value_or(m_state.currentBrightness.value_or(m_state.brightnessSetting)), frame->artificialHdrHeadroom().value_or(m_state.artificialHdrHeadroom));
}
return true;
}
DrmConnector *DrmOutput::connector() const
{
return m_connector.get();
}
DrmPipeline *DrmOutput::pipeline() const
{
return m_pipeline;
}
bool DrmOutput::queueChanges(const std::shared_ptr<OutputChangeSet> &props)
{
const auto mode = props->mode.value_or(currentMode()).lock();
if (!mode) {
return false;
}
const bool bt2020 = props->wideColorGamut.value_or(m_state.wideColorGamut) && (capabilities() & Capability::WideColorGamut);
const bool hdr = props->highDynamicRange.value_or(m_state.highDynamicRange) && (capabilities() & Capability::HighDynamicRange);
m_pipeline->setMode(std::static_pointer_cast<DrmConnectorMode>(mode));
m_pipeline->setOverscan(props->overscan.value_or(m_pipeline->overscan()));
m_pipeline->setRgbRange(props->rgbRange.value_or(m_pipeline->rgbRange()));
m_pipeline->setEnable(props->enabled.value_or(m_pipeline->enabled()));
m_pipeline->setHighDynamicRange(hdr);
m_pipeline->setWideColorGamut(bt2020);
// TODO migrate this env var to a proper setting
static bool preferreedColorDepthIsSet = false;
static const int preferred = qEnvironmentVariableIntValue("KWIN_DRM_PREFER_COLOR_DEPTH", &preferreedColorDepthIsSet);
if (preferreedColorDepthIsSet) {
m_pipeline->setMaxBpc(preferred / 3);
} else if (props->colorPowerTradeoff.value_or(m_state.colorPowerTradeoff) == ColorPowerTradeoff::PreferAccuracy) {
m_pipeline->setMaxBpc(m_connector->mstPath().isEmpty() ? 16 : 8);
} else {
m_pipeline->setMaxBpc(m_connector->mstPath().isEmpty() ? 10 : 8);
}
if (bt2020 || hdr || props->colorProfileSource.value_or(m_state.colorProfileSource) != ColorProfileSource::ICC) {
// ICC profiles don't support HDR (yet)
m_pipeline->setIccProfile(nullptr);
} else {
m_pipeline->setIccProfile(props->iccProfile.value_or(m_state.iccProfile));
}
// remove the color pipeline for the atomic test
// otherwise it could potentially fail
if (m_gpu->atomicModeSetting()) {
m_pipeline->setCrtcColorPipeline(ColorPipeline{});
}
return true;
}
static QVector3D adaptChannelFactors(const ColorDescription &originalColor, const QVector3D &sRGBchannelFactors)
{
QVector3D adaptedChannelFactors = ColorDescription::sRGB.containerColorimetry().relativeColorimetricTo(originalColor.containerColorimetry()) * sRGBchannelFactors;
// ensure none of the values reach zero, otherwise the white point might end up on or outside
// the edges of the gamut, which leads to terrible glitches
adaptedChannelFactors.setX(std::max(adaptedChannelFactors.x(), 0.01f));
adaptedChannelFactors.setY(std::max(adaptedChannelFactors.y(), 0.01f));
adaptedChannelFactors.setZ(std::max(adaptedChannelFactors.z(), 0.01f));
return adaptedChannelFactors;
}
static std::pair<ColorDescription, QVector3D> applyNightLight(const ColorDescription &originalColor, const QVector3D &sRGBchannelFactors)
{
const QVector3D adapted = adaptChannelFactors(originalColor, sRGBchannelFactors);
// the new white point can have channels above one, compensate for that
const double brightness = 1.0 / std::max({adapted.x(), adapted.y(), adapted.z(), 1.0f});
const xyY newWhite = XYZ::fromVector(originalColor.containerColorimetry().toXYZ() * adapted).toxyY();
return std::make_pair(originalColor.withWhitepoint(newWhite).dimmed(brightness), adapted);
}
std::pair<ColorDescription, QVector3D> DrmOutput::createColorDescription(const std::shared_ptr<OutputChangeSet> &props, double brightness) const
{
const auto colorSource = props->colorProfileSource.value_or(colorProfileSource());
const bool effectiveHdr = props->highDynamicRange.value_or(m_state.highDynamicRange) && (capabilities() & Capability::HighDynamicRange);
const bool effectiveWcg = props->wideColorGamut.value_or(m_state.wideColorGamut) && (capabilities() & Capability::WideColorGamut);
const double sdrGamutWideness = props->sdrGamutWideness.value_or(m_state.sdrGamutWideness);
const auto iccProfile = props->iccProfile.value_or(m_state.iccProfile);
if (colorSource == ColorProfileSource::ICC && !effectiveHdr && !effectiveWcg && iccProfile) {
const double minBrightness = iccProfile->minBrightness().value_or(0);
const double maxBrightness = iccProfile->maxBrightness().value_or(200);
const auto sdrColor = Colorimetry::fromName(NamedColorimetry::BT709).interpolateGamutTo(iccProfile->colorimetry(), sdrGamutWideness);
const bool allowSdrSoftwareBrightness = props->allowSdrSoftwareBrightness.value_or(m_state.allowSdrSoftwareBrightness);
const double brightnessFactor = (!m_brightnessDevice && allowSdrSoftwareBrightness) ? brightness : 1.0;
const double effectiveReferenceLuminance = 5 + (maxBrightness - 5) * brightnessFactor;
return applyNightLight(ColorDescription(iccProfile->colorimetry(), TransferFunction(TransferFunction::gamma22, 0, maxBrightness), effectiveReferenceLuminance, minBrightness, maxBrightness, maxBrightness, iccProfile->colorimetry(), sdrColor), m_channelFactors);
}
const Colorimetry nativeColorimetry = m_information.edid.colorimetry().value_or(Colorimetry::fromName(NamedColorimetry::BT709));
const Colorimetry containerColorimetry = effectiveWcg ? Colorimetry::fromName(NamedColorimetry::BT2020) : (colorSource == ColorProfileSource::EDID ? nativeColorimetry : Colorimetry::fromName(NamedColorimetry::BT709));
const Colorimetry masteringColorimetry = (effectiveWcg || colorSource == ColorProfileSource::EDID) ? nativeColorimetry : Colorimetry::fromName(NamedColorimetry::BT709);
const Colorimetry sdrColorimetry = (effectiveWcg || colorSource == ColorProfileSource::EDID) ? Colorimetry::fromName(NamedColorimetry::BT709).interpolateGamutTo(nativeColorimetry, sdrGamutWideness) : Colorimetry::fromName(NamedColorimetry::BT709);
// TODO the EDID can contain a gamma value, use that when available and colorSource == ColorProfileSource::EDID
const double maxAverageBrightness = effectiveHdr ? props->maxAverageBrightnessOverride.value_or(m_state.maxAverageBrightnessOverride).value_or(m_connector->edid()->desiredMaxFrameAverageLuminance().value_or(m_state.referenceLuminance)) : 200;
const double maxPeakBrightness = effectiveHdr ? props->maxPeakBrightnessOverride.value_or(m_state.maxPeakBrightnessOverride).value_or(m_connector->edid()->desiredMaxLuminance().value_or(800)) : 200 * m_state.artificialHdrHeadroom;
const double referenceLuminance = effectiveHdr ? props->referenceLuminance.value_or(m_state.referenceLuminance) : 200;
const auto transferFunction = TransferFunction{effectiveHdr ? TransferFunction::PerceptualQuantizer : TransferFunction::gamma22}.relativeScaledTo(referenceLuminance * m_state.artificialHdrHeadroom);
// HDR screens are weird, sending them the min. luminance from the EDID does *not* make all of them present the darkest luminance the display can show
// to work around that, (unless overridden by the user), assume the min. luminance of the transfer function instead
const double minBrightness = effectiveHdr ? props->minBrightnessOverride.value_or(m_state.minBrightnessOverride).value_or(TransferFunction::defaultMinLuminanceFor(TransferFunction::PerceptualQuantizer)) : transferFunction.minLuminance;
const bool allowSdrSoftwareBrightness = props->allowSdrSoftwareBrightness.value_or(m_state.allowSdrSoftwareBrightness);
const double brightnessFactor = (!m_brightnessDevice && allowSdrSoftwareBrightness) || effectiveHdr ? brightness : 1.0;
const double effectiveReferenceLuminance = 5 + (referenceLuminance - 5) * brightnessFactor;
return applyNightLight(ColorDescription(containerColorimetry, transferFunction, effectiveReferenceLuminance, minBrightness, maxAverageBrightness, maxPeakBrightness, masteringColorimetry, sdrColorimetry), m_channelFactors);
}
void DrmOutput::applyQueuedChanges(const std::shared_ptr<OutputChangeSet> &props)
{
if (!m_connector->isConnected()) {
return;
}
Q_EMIT aboutToChange(props.get());
m_pipeline->applyPendingChanges();
State next = m_state;
next.enabled = props->enabled.value_or(m_state.enabled) && m_pipeline->crtc();
next.position = props->pos.value_or(m_state.position);
next.scale = props->scale.value_or(m_state.scale);
next.transform = props->transform.value_or(m_state.transform);
next.manualTransform = props->manualTransform.value_or(m_state.manualTransform);
next.currentMode = m_pipeline->mode();
next.overscan = m_pipeline->overscan();
next.rgbRange = m_pipeline->rgbRange();
next.highDynamicRange = props->highDynamicRange.value_or(m_state.highDynamicRange);
next.referenceLuminance = props->referenceLuminance.value_or(m_state.referenceLuminance);
next.wideColorGamut = props->wideColorGamut.value_or(m_state.wideColorGamut);
next.autoRotatePolicy = props->autoRotationPolicy.value_or(m_state.autoRotatePolicy);
next.maxPeakBrightnessOverride = props->maxPeakBrightnessOverride.value_or(m_state.maxPeakBrightnessOverride);
next.maxAverageBrightnessOverride = props->maxAverageBrightnessOverride.value_or(m_state.maxAverageBrightnessOverride);
next.minBrightnessOverride = props->minBrightnessOverride.value_or(m_state.minBrightnessOverride);
next.sdrGamutWideness = props->sdrGamutWideness.value_or(m_state.sdrGamutWideness);
next.iccProfilePath = props->iccProfilePath.value_or(m_state.iccProfilePath);
next.iccProfile = props->iccProfile.value_or(m_state.iccProfile);
std::tie(next.colorDescription, m_adaptedChannelFactors) = createColorDescription(props, m_state.currentBrightness.value_or(m_state.brightnessSetting));
next.vrrPolicy = props->vrrPolicy.value_or(m_state.vrrPolicy);
next.colorProfileSource = props->colorProfileSource.value_or(m_state.colorProfileSource);
next.brightnessSetting = props->brightness.value_or(m_state.brightnessSetting);
next.desiredModeSize = props->desiredModeSize.value_or(m_state.desiredModeSize);
next.desiredModeRefreshRate = props->desiredModeRefreshRate.value_or(m_state.desiredModeRefreshRate);
next.allowSdrSoftwareBrightness = props->allowSdrSoftwareBrightness.value_or(m_state.allowSdrSoftwareBrightness);
next.colorPowerTradeoff = props->colorPowerTradeoff.value_or(m_state.colorPowerTradeoff);
next.dimming = props->dimming.value_or(m_state.dimming);
setState(next);
// allowSdrSoftwareBrightness might change our capabilities
Information newInfo = m_information;
newInfo.capabilities = computeCapabilities();
setInformation(newInfo);
if (!isEnabled() && m_pipeline->needsModeset()) {
m_gpu->maybeModeset(nullptr, nullptr);
}
m_renderLoop->setRefreshRate(refreshRate());
tryKmsColorOffloading();
Q_EMIT changed();
}
void DrmOutput::setBrightnessDevice(BrightnessDevice *device)
{
Output::setBrightnessDevice(device);
if (device && m_state.allowSdrSoftwareBrightness && device->observedBrightness().has_value()) {
// adopt the screen's initial brightness value if this brightness device is seen for the first time.
// This can't be done in output configuration store as we're not necessarily aware of the brightness device
// at that point
State next = m_state;
next.currentBrightness = device->observedBrightness();
next.brightnessSetting = *next.currentBrightness;
setState(next);
}
updateBrightness(m_state.currentBrightness.value_or(m_state.brightnessSetting), m_state.artificialHdrHeadroom);
}
void DrmOutput::updateBrightness(double newBrightness, double newArtificialHdrHeadroom)
{
if (!m_pipeline) {
// this can happen when the output gets hot-unplugged
// FIXME fix output lifetimes so that this doesn't happen anymore...
return;
}
if (m_brightnessDevice && !m_state.highDynamicRange) {
constexpr double minLuminance = 0.04;
const double effectiveBrightness = (minLuminance + newBrightness) * m_state.artificialHdrHeadroom - minLuminance;
m_brightnessDevice->setBrightness(effectiveBrightness);
}
State next = m_state;
std::tie(next.colorDescription, m_adaptedChannelFactors) = createColorDescription(std::make_shared<OutputChangeSet>(), newBrightness);
next.currentBrightness = newBrightness;
next.artificialHdrHeadroom = newArtificialHdrHeadroom;
setState(next);
tryKmsColorOffloading();
}
void DrmOutput::revertQueuedChanges()
{
m_pipeline->revertPendingChanges();
}
DrmOutputLayer *DrmOutput::primaryLayer() const
{
return m_pipeline->primaryLayer();
}
DrmOutputLayer *DrmOutput::cursorLayer() const
{
return m_pipeline->cursorLayer();
}
bool DrmOutput::setChannelFactors(const QVector3D &rgb)
{
if (rgb != m_channelFactors) {
m_channelFactors = rgb;
State next = m_state;
std::tie(next.colorDescription, m_adaptedChannelFactors) = createColorDescription(std::make_shared<OutputChangeSet>(), next.currentBrightness.value_or(m_state.brightnessSetting));
setState(next);
tryKmsColorOffloading();
}
return true;
}
void DrmOutput::tryKmsColorOffloading()
{
constexpr TransferFunction::Type blendingSpace = TransferFunction::gamma22;
const bool hdr = m_state.highDynamicRange && (capabilities() & Capability::HighDynamicRange);
const bool wcg = m_state.wideColorGamut && (capabilities() & Capability::WideColorGamut);
// offloading color operations doesn't make sense when we have to apply the icc shader anyways
const bool usesICC = m_state.colorProfileSource == ColorProfileSource::ICC && m_state.iccProfile && !hdr && !wcg;
const QVector3D channelFactors = adaptedChannelFactors();
if (colorPowerTradeoff() == ColorPowerTradeoff::PreferAccuracy) {
setScanoutColorDescription(colorDescription());
m_pipeline->setCrtcColorPipeline(ColorPipeline{});
m_pipeline->applyPendingChanges();
m_needsShadowBuffer = usesICC
|| colorDescription().transferFunction().type != blendingSpace
|| (channelFactors - QVector3D(1, 1, 1)).lengthSquared() > 0.0001;
return;
}
if (!m_pipeline->activePending() || !primaryLayer()) {
return;
}
const double maxLuminance = colorDescription().maxHdrLuminance().value_or(colorDescription().referenceLuminance());
const ColorDescription optimal = colorDescription().transferFunction().type == blendingSpace ? colorDescription() : colorDescription().withTransferFunction(TransferFunction(blendingSpace, 0, maxLuminance));
ColorPipeline colorPipeline = ColorPipeline::create(optimal, colorDescription(), RenderingIntent::RelativeColorimetric);
if (usesICC) {
colorPipeline.addTransferFunction(colorDescription().transferFunction());
colorPipeline.addMultiplier(1.0 / colorDescription().referenceLuminance());
colorPipeline.addMultiplier(channelFactors);
colorPipeline.add1DLUT(m_state.iccProfile->inverseTransferFunction());
if (m_state.iccProfile->vcgt()) {
colorPipeline.add1DLUT(m_state.iccProfile->vcgt());
}
} else {
colorPipeline.addTransferFunction(colorDescription().transferFunction());
colorPipeline.addMultiplier(channelFactors);
colorPipeline.addInverseTransferFunction(colorDescription().transferFunction());
}
m_pipeline->setCrtcColorPipeline(colorPipeline);
if (DrmPipeline::commitPipelines({m_pipeline}, DrmPipeline::CommitMode::Test) == DrmPipeline::Error::None) {
m_pipeline->applyPendingChanges();
setScanoutColorDescription(optimal);
m_needsShadowBuffer = false;
return;
}
if (colorDescription().transferFunction().type == blendingSpace && !usesICC) {
// allow falling back to applying the night light factors in non-linear space
// this isn't technically correct, but the difference is quite small and not worth
// losing a lot of performance and battery life over
colorPipeline = ColorPipeline::create(optimal, colorDescription(), RenderingIntent::RelativeColorimetric);
colorPipeline.addMultiplier(TransferFunction(blendingSpace, 0, 1).nitsToEncoded(channelFactors));
m_pipeline->setCrtcColorPipeline(colorPipeline);
if (DrmPipeline::commitPipelines({m_pipeline}, DrmPipeline::CommitMode::Test) == DrmPipeline::Error::None) {
m_pipeline->applyPendingChanges();
setScanoutColorDescription(optimal);
m_needsShadowBuffer = false;
return;
}
}
// fall back to using a shadow buffer for doing blending in gamma 2.2 and the channel factors
m_pipeline->setCrtcColorPipeline(ColorPipeline{});
m_pipeline->applyPendingChanges();
setScanoutColorDescription(colorDescription());
m_needsShadowBuffer = (channelFactors - QVector3D(1, 1, 1)).lengthSquared() > 0.0001 || usesICC;
}
void DrmOutput::setScanoutColorDescription(const ColorDescription &description)
{
if (m_scanoutColorDescription != description) {
m_scanoutColorDescription = description;
if (primaryLayer()) {
primaryLayer()->addRepaint(infiniteRegion());
}
if (cursorLayer()) {
cursorLayer()->addRepaint(infiniteRegion());
}
}
}
bool DrmOutput::needsShadowBuffer() const
{
return m_needsShadowBuffer;
}
QVector3D DrmOutput::adaptedChannelFactors() const
{
return m_adaptedChannelFactors;
}
const ColorDescription &DrmOutput::scanoutColorDescription() const
{
return m_scanoutColorDescription;
}
void DrmOutput::removePipeline()
{
m_pipeline = nullptr;
}
}
#include "moc_drm_output.cpp"
@@ -0,0 +1,107 @@
/*
KWin - the KDE window manager
This file is part of the KDE project.
SPDX-FileCopyrightText: 2015 Martin Gräßlin <mgraesslin@kde.org>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#pragma once
#include "drm_abstract_output.h"
#include "drm_object.h"
#include "drm_plane.h"
#include <QList>
#include <QObject>
#include <QPoint>
#include <QPointer>
#include <QSize>
#include <QTimer>
#include <chrono>
#include <xf86drmMode.h>
namespace KWin
{
class DrmConnector;
class DrmGpu;
class DrmPipeline;
class DumbSwapchain;
class DrmLease;
class OutputChangeSet;
class KWIN_EXPORT DrmOutput : public DrmAbstractOutput
{
Q_OBJECT
public:
explicit DrmOutput(const std::shared_ptr<DrmConnector> &connector, DrmPipeline *pipeline);
DrmConnector *connector() const;
DrmPipeline *pipeline() const;
bool present(const std::shared_ptr<OutputFrame> &frame) override;
DrmOutputLayer *primaryLayer() const override;
DrmOutputLayer *cursorLayer() const override;
bool queueChanges(const std::shared_ptr<OutputChangeSet> &properties);
void applyQueuedChanges(const std::shared_ptr<OutputChangeSet> &properties);
void revertQueuedChanges();
void updateDpmsMode(DpmsMode dpmsMode);
bool shouldDisableCursorPlane() const;
bool updateCursorLayer(std::optional<std::chrono::nanoseconds> allowedVrrDelay) override;
DrmLease *lease() const;
bool addLeaseObjects(QList<uint32_t> &objectList);
void leased(DrmLease *lease);
void leaseEnded();
bool setChannelFactors(const QVector3D &rgb) override;
/**
* channel factors adapted to the target color space + brightness setting multiplied in
*/
QVector3D adaptedChannelFactors() const;
void updateConnectorProperties();
/**
* @returns the color description / encoding that the buffers passed to the CRTC need to have, without a color pipeline to change it
*/
const ColorDescription &scanoutColorDescription() const;
/**
* @returns whether or not the renderer should apply channel factors
*/
bool needsShadowBuffer() const;
void removePipeline();
private:
bool setDrmDpmsMode(DpmsMode mode);
void setDpmsMode(DpmsMode mode) override;
void tryKmsColorOffloading();
std::pair<ColorDescription, QVector3D> createColorDescription(const std::shared_ptr<OutputChangeSet> &props, double brightness) const;
Capabilities computeCapabilities() const;
void updateInformation();
void setBrightnessDevice(BrightnessDevice *device) override;
void updateBrightness(double newBrightness, double newArtificialHdrHeadroom);
void setScanoutColorDescription(const ColorDescription &description);
QList<std::shared_ptr<OutputMode>> getModes() const;
DrmGpu *const m_gpu;
DrmPipeline *m_pipeline;
const std::shared_ptr<DrmConnector> m_connector;
QTimer m_turnOffTimer;
DrmLease *m_lease = nullptr;
QVector3D m_channelFactors = {1, 1, 1};
QVector3D m_adaptedChannelFactors = {1, 1, 1};
bool m_needsShadowBuffer = false;
ColorDescription m_scanoutColorDescription = ColorDescription::sRGB;
PresentationMode m_desiredPresentationMode = PresentationMode::VSync;
};
}
Q_DECLARE_METATYPE(KWin::DrmOutput *)
@@ -0,0 +1,725 @@
/*
KWin - the KDE window manager
This file is part of the KDE project.
SPDX-FileCopyrightText: 2021 Xaver Hugl <xaver.hugl@gmail.com>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "drm_pipeline.h"
#include <errno.h>
#include "core/iccprofile.h"
#include "core/session.h"
#include "drm_backend.h"
#include "drm_buffer.h"
#include "drm_commit.h"
#include "drm_commit_thread.h"
#include "drm_connector.h"
#include "drm_crtc.h"
#include "drm_egl_backend.h"
#include "drm_gpu.h"
#include "drm_layer.h"
#include "drm_logging.h"
#include "drm_output.h"
#include "drm_plane.h"
#include <drm_fourcc.h>
#include <gbm.h>
using namespace std::literals;
namespace KWin
{
static const QList<uint64_t> implicitModifier = {DRM_FORMAT_MOD_INVALID};
static const QHash<uint32_t, QList<uint64_t>> legacyFormats = {{DRM_FORMAT_XRGB8888, implicitModifier}};
static const QHash<uint32_t, QList<uint64_t>> legacyCursorFormats = {{DRM_FORMAT_ARGB8888, implicitModifier}};
DrmPipeline::DrmPipeline(DrmConnector *conn)
: m_connector(conn)
, m_commitThread(std::make_unique<DrmCommitThread>(conn->gpu(), conn->connectorName()))
{
}
DrmPipeline::~DrmPipeline()
{
// the commit thread may still access the pipeline until it's stopped
// so it must be deleted before everything else
m_commitThread.reset();
}
DrmPipeline::Error DrmPipeline::present(const std::shared_ptr<OutputFrame> &frame)
{
Q_ASSERT(m_pending.crtc);
if (gpu()->atomicModeSetting()) {
// test the full state, to take pending commits into account
if (auto err = DrmPipeline::commitPipelinesAtomic({this}, CommitMode::Test, frame, {}); err != Error::None) {
return err;
}
// only give the actual state update to the commit thread, so that it can potentially reorder the commits
auto primaryPlaneUpdate = std::make_unique<DrmAtomicCommit>(QList<DrmPipeline *>{this});
if (Error err = prepareAtomicPresentation(primaryPlaneUpdate.get(), frame); err != Error::None) {
return err;
}
if (m_pending.needsModesetProperties && !prepareAtomicModeset(primaryPlaneUpdate.get())) {
return Error::InvalidArguments;
}
m_next.needsModesetProperties = m_pending.needsModesetProperties = false;
m_commitThread->addCommit(std::move(primaryPlaneUpdate));
return Error::None;
} else {
return presentLegacy(frame);
}
}
void DrmPipeline::maybeModeset(const std::shared_ptr<OutputFrame> &frame)
{
m_modesetPresentPending = true;
gpu()->maybeModeset(this, frame);
}
DrmPipeline::Error DrmPipeline::commitPipelines(const QList<DrmPipeline *> &pipelines, CommitMode mode, const QList<DrmObject *> &unusedObjects)
{
Q_ASSERT(!pipelines.isEmpty());
if (pipelines[0]->gpu()->atomicModeSetting()) {
return commitPipelinesAtomic(pipelines, mode, nullptr, unusedObjects);
} else {
return commitPipelinesLegacy(pipelines, mode, unusedObjects);
}
}
DrmPipeline::Error DrmPipeline::commitPipelinesAtomic(const QList<DrmPipeline *> &pipelines, CommitMode mode, const std::shared_ptr<OutputFrame> &frame, const QList<DrmObject *> &unusedObjects)
{
auto commit = std::make_unique<DrmAtomicCommit>(pipelines);
if (mode == CommitMode::Test) {
// if there's a modeset pending, the tests on top of that state
// also have to allow modesets or they'll always fail
const bool wantsModeset = std::ranges::any_of(pipelines, [](DrmPipeline *pipeline) {
return pipeline->needsModeset();
});
if (wantsModeset) {
mode = CommitMode::TestAllowModeset;
}
}
for (const auto &pipeline : pipelines) {
if (Error err = pipeline->prepareAtomicCommit(commit.get(), mode, frame); err != Error::None) {
return err;
}
}
for (const auto &unused : unusedObjects) {
unused->disable(commit.get());
}
switch (mode) {
case CommitMode::TestAllowModeset: {
if (!commit->testAllowModeset()) {
qCDebug(KWIN_DRM) << "Atomic modeset test failed!" << strerror(errno);
return errnoToError();
}
const bool withoutModeset = std::ranges::all_of(pipelines, [&frame](DrmPipeline *pipeline) {
auto commit = std::make_unique<DrmAtomicCommit>(QVector<DrmPipeline *>{pipeline});
return pipeline->prepareAtomicCommit(commit.get(), CommitMode::TestAllowModeset, frame) == Error::None && commit->test();
});
for (const auto &pipeline : pipelines) {
pipeline->m_pending.needsModeset = !withoutModeset;
pipeline->m_pending.needsModesetProperties = true;
}
return Error::None;
}
case CommitMode::CommitModeset: {
// The kernel fails commits with DRM_MODE_PAGE_FLIP_EVENT when a crtc is disabled in the commit
// and already was disabled before, to work around some quirks in old userspace.
// Instead of using DRM_MODE_PAGE_FLIP_EVENT | DRM_MODE_ATOMIC_NONBLOCK, do the modeset in a blocking
// fashion without page flip events and trigger the pageflip notification directly
if (!commit->commitModeset()) {
qCCritical(KWIN_DRM) << "Atomic modeset commit failed!" << strerror(errno);
return errnoToError();
}
for (const auto pipeline : pipelines) {
pipeline->m_next.needsModeset = pipeline->m_pending.needsModeset = false;
}
commit->pageFlipped(std::chrono::steady_clock::now().time_since_epoch());
return Error::None;
}
case CommitMode::Test: {
if (!commit->test()) {
qCDebug(KWIN_DRM) << "Atomic test failed!" << strerror(errno);
return errnoToError();
}
return Error::None;
}
default:
Q_UNREACHABLE();
}
}
DrmPipeline::Error DrmPipeline::prepareAtomicCommit(DrmAtomicCommit *commit, CommitMode mode, const std::shared_ptr<OutputFrame> &frame)
{
if (activePending()) {
if (Error err = prepareAtomicPresentation(commit, frame); err != Error::None) {
return err;
}
if (m_pending.crtc->cursorPlane()) {
prepareAtomicCursor(commit);
}
if (mode == CommitMode::TestAllowModeset || mode == CommitMode::CommitModeset || m_pending.needsModesetProperties) {
if (!prepareAtomicModeset(commit)) {
return Error::InvalidArguments;
}
}
} else {
prepareAtomicDisable(commit);
}
return Error::None;
}
DrmPipeline::Error DrmPipeline::prepareAtomicPresentation(DrmAtomicCommit *commit, const std::shared_ptr<OutputFrame> &frame)
{
commit->setPresentationMode(m_pending.presentationMode);
if (m_connector->contentType.isValid()) {
commit->addEnum(m_connector->contentType, m_pending.contentType);
}
if (m_pending.crtc->vrrEnabled.isValid()) {
commit->setVrr(m_pending.crtc, m_pending.presentationMode == PresentationMode::AdaptiveSync || m_pending.presentationMode == PresentationMode::AdaptiveAsync);
}
if (m_cursorLayer->isEnabled() && m_primaryLayer->colorPipeline() != m_cursorLayer->colorPipeline()) {
return DrmPipeline::Error::InvalidArguments;
}
const ColorPipeline colorPipeline = m_primaryLayer->colorPipeline().merged(m_pending.crtcColorPipeline);
if (!m_pending.crtc->postBlendingPipeline) {
if (!colorPipeline.isIdentity()) {
return Error::InvalidArguments;
}
} else {
if (!m_pending.crtc->postBlendingPipeline->matchPipeline(commit, colorPipeline)) {
return Error::InvalidArguments;
}
}
if (!m_primaryLayer->checkTestBuffer()) {
qCWarning(KWIN_DRM) << "Checking test buffer failed!";
return Error::TestBufferFailed;
}
const auto fb = m_primaryLayer->currentBuffer();
if (!fb) {
return Error::InvalidArguments;
}
const auto primary = m_pending.crtc->primaryPlane();
const auto transform = m_primaryLayer->offloadTransform();
const auto planeTransform = DrmPlane::outputTransformToPlaneTransform(transform);
if (primary->rotation.isValid()) {
if (!primary->rotation.hasEnum(planeTransform)) {
return Error::InvalidArguments;
}
commit->addEnum(primary->rotation, planeTransform);
} else if (planeTransform != DrmPlane::Transformation::Rotate0) {
return Error::InvalidArguments;
}
primary->set(commit, m_primaryLayer->sourceRect().toRect(), m_primaryLayer->targetRect());
commit->addBuffer(m_pending.crtc->primaryPlane(), fb, frame);
if (fb->buffer()->dmabufAttributes()->format == DRM_FORMAT_NV12) {
if (!primary->colorEncoding.isValid() || !primary->colorRange.isValid()) {
// don't allow NV12 direct scanout if we don't know what the driver will do
return Error::InvalidArguments;
}
commit->addEnum(primary->colorEncoding, DrmPlane::ColorEncoding::BT709_YCbCr);
commit->addEnum(primary->colorRange, DrmPlane::ColorRange::Limited_YCbCr);
}
return Error::None;
}
void DrmPipeline::prepareAtomicCursor(DrmAtomicCommit *commit)
{
auto plane = m_pending.crtc->cursorPlane();
const auto layer = cursorLayer();
if (layer->isEnabled()) {
plane->set(commit, layer->sourceRect().toRect(), layer->targetRect());
commit->addProperty(plane->crtcId, m_pending.crtc->id());
commit->addBuffer(plane, layer->currentBuffer(), nullptr);
if (plane->vmHotspotX.isValid() && plane->vmHotspotY.isValid()) {
commit->addProperty(plane->vmHotspotX, std::round(layer->hotspot().x()));
commit->addProperty(plane->vmHotspotY, std::round(layer->hotspot().y()));
}
} else {
commit->addProperty(plane->crtcId, 0);
commit->addBuffer(plane, nullptr, nullptr);
}
}
void DrmPipeline::prepareAtomicDisable(DrmAtomicCommit *commit)
{
m_connector->disable(commit);
if (m_pending.crtc) {
m_pending.crtc->disable(commit);
m_pending.crtc->primaryPlane()->disable(commit);
if (auto cursor = m_pending.crtc->cursorPlane()) {
cursor->disable(commit);
}
}
}
static const auto s_forceScalingMode = []() -> std::optional<DrmConnector::ScalingMode> {
const auto env = qEnvironmentVariable("KWIN_DRM_FORCE_SCALING_MODE");
if (env == "NONE") {
return DrmConnector::ScalingMode::None;
} else if (env == "FULL") {
return DrmConnector::ScalingMode::Full;
} else if (env == "CENTER") {
return DrmConnector::ScalingMode::Center;
} else if (env == "FULL_ASPECT") {
return DrmConnector::ScalingMode::Full_Aspect;
} else {
return std::nullopt;
}
}();
bool DrmPipeline::prepareAtomicModeset(DrmAtomicCommit *commit)
{
commit->addProperty(m_connector->crtcId, m_pending.crtc->id());
if (m_connector->broadcastRGB.isValid()) {
commit->addEnum(m_connector->broadcastRGB, DrmConnector::rgbRangeToBroadcastRgb(m_pending.rgbRange));
}
if (m_connector->linkStatus.isValid()) {
commit->addEnum(m_connector->linkStatus, DrmConnector::LinkStatus::Good);
}
if (m_connector->overscan.isValid()) {
commit->addProperty(m_connector->overscan, m_pending.overscan);
} else if (m_connector->underscan.isValid()) {
const uint32_t hborder = calculateUnderscan();
commit->addEnum(m_connector->underscan, m_pending.overscan != 0 ? DrmConnector::UnderscanOptions::On : DrmConnector::UnderscanOptions::Off);
commit->addProperty(m_connector->underscanVBorder, m_pending.overscan);
commit->addProperty(m_connector->underscanHBorder, hborder);
}
if (m_connector->maxBpc.isValid()) {
commit->addProperty(m_connector->maxBpc, std::clamp<uint32_t>(m_pending.maxBpc, m_connector->maxBpc.minValue(), m_connector->maxBpc.maxValue()));
}
if (m_connector->hdrMetadata.isValid()) {
commit->addBlob(m_connector->hdrMetadata, createHdrMetadata(m_pending.hdr ? TransferFunction::PerceptualQuantizer : TransferFunction::gamma22));
} else if (m_pending.hdr) {
return false;
}
if (m_pending.wcg) {
if (!m_connector->colorspace.isValid() || !m_connector->colorspace.hasEnum(DrmConnector::Colorspace::BT2020_RGB)) {
return false;
}
commit->addEnum(m_connector->colorspace, DrmConnector::Colorspace::BT2020_RGB);
} else if (m_connector->colorspace.isValid()) {
commit->addEnum(m_connector->colorspace, DrmConnector::Colorspace::Default);
}
if (m_connector->scalingMode.isValid()) {
if (s_forceScalingMode.has_value()) {
if (m_connector->scalingMode.hasEnum(*s_forceScalingMode)) {
commit->addEnum(m_connector->scalingMode, *s_forceScalingMode);
} else if (m_connector->scalingMode.hasEnum(DrmConnector::ScalingMode::None)) {
commit->addEnum(m_connector->scalingMode, DrmConnector::ScalingMode::None);
}
} else if (m_connector->isInternal() && m_connector->scalingMode.hasEnum(DrmConnector::ScalingMode::Full_Aspect) && (m_pending.mode->flags() & OutputMode::Flag::Generated)) {
commit->addEnum(m_connector->scalingMode, DrmConnector::ScalingMode::Full_Aspect);
} else if (m_connector->scalingMode.hasEnum(DrmConnector::ScalingMode::None)) {
commit->addEnum(m_connector->scalingMode, DrmConnector::ScalingMode::None);
}
}
commit->addProperty(m_pending.crtc->active, 1);
commit->addBlob(m_pending.crtc->modeId, m_pending.mode->blob());
const auto primary = m_pending.crtc->primaryPlane();
commit->addProperty(primary->crtcId, m_pending.crtc->id());
if (primary->rotation.isValid()) {
commit->addEnum(primary->rotation, {DrmPlane::Transformation::Rotate0});
}
if (primary->alpha.isValid()) {
commit->addProperty(primary->alpha, primary->alpha.maxValue());
}
if (primary->pixelBlendMode.isValid()) {
commit->addEnum(primary->pixelBlendMode, DrmPlane::PixelBlendMode::PreMultiplied);
}
if (const auto cursor = m_pending.crtc->cursorPlane()) {
if (cursor->rotation.isValid()) {
commit->addEnum(cursor->rotation, DrmPlane::Transformations(DrmPlane::Transformation::Rotate0));
}
if (cursor->alpha.isValid()) {
commit->addProperty(cursor->alpha, cursor->alpha.maxValue());
}
if (cursor->pixelBlendMode.isValid()) {
commit->addEnum(cursor->pixelBlendMode, DrmPlane::PixelBlendMode::PreMultiplied);
}
prepareAtomicCursor(commit);
}
return true;
}
uint32_t DrmPipeline::calculateUnderscan()
{
const auto size = m_pending.mode->size();
const float aspectRatio = size.width() / static_cast<float>(size.height());
uint32_t hborder = m_pending.overscan * aspectRatio;
if (hborder > 128) {
// overscan only goes from 0-100 so we cut off the 101-128 value range of underscan_vborder
hborder = 128;
m_pending.overscan = 128 / aspectRatio;
}
return hborder;
}
DrmPipeline::Error DrmPipeline::errnoToError()
{
switch (errno) {
case EINVAL:
return Error::InvalidArguments;
case EBUSY:
return Error::FramePending;
case ENOMEM:
return Error::OutofMemory;
case EACCES:
return Error::NoPermission;
default:
return Error::Unknown;
}
}
bool DrmPipeline::updateCursor(std::optional<std::chrono::nanoseconds> allowedVrrDelay)
{
if (needsModeset() || !m_pending.crtc || !m_pending.active) {
return false;
}
if (amdgpuVrrWorkaroundActive() && m_cursorLayer->isEnabled()) {
return false;
}
// We need to make sure that on vmwgfx software cursor is selected
// until Broadcom fixes hw cursor issues with vmwgfx. Otherwise
// the cursor is missing.
if (gpu()->isVmwgfx()) {
return false;
}
// explicitly check for the cursor plane and not for AMS, as we might not always have one
if (m_pending.crtc->cursorPlane()) {
// test the full state, to take pending commits into account
if (DrmPipeline::commitPipelinesAtomic({this}, CommitMode::Test, nullptr, {}) != Error::None) {
return false;
}
// only give the actual state update to the commit thread, so that it can potentially reorder the commits
auto cursorOnly = std::make_unique<DrmAtomicCommit>(QList<DrmPipeline *>{this});
prepareAtomicCursor(cursorOnly.get());
cursorOnly->setAllowedVrrDelay(allowedVrrDelay);
m_commitThread->addCommit(std::move(cursorOnly));
return true;
} else {
return setCursorLegacy();
}
}
bool DrmPipeline::amdgpuVrrWorkaroundActive() const
{
static const bool s_env = qEnvironmentVariableIntValue("KWIN_DRM_DONT_FORCE_AMD_SW_CURSOR") == 1;
return !s_env && gpu()->isAmdgpu() && (m_pending.presentationMode == PresentationMode::AdaptiveSync || m_pending.presentationMode == PresentationMode::AdaptiveAsync);
}
void DrmPipeline::applyPendingChanges()
{
m_next = m_pending;
m_commitThread->setModeInfo(m_pending.mode->refreshRate(), m_pending.mode->vblankTime());
m_output->renderLoop()->setPresentationSafetyMargin(m_commitThread->safetyMargin());
m_output->renderLoop()->setRefreshRate(m_pending.mode->refreshRate());
}
DrmConnector *DrmPipeline::connector() const
{
return m_connector;
}
DrmGpu *DrmPipeline::gpu() const
{
return m_connector->gpu();
}
void DrmPipeline::pageFlipped(std::chrono::nanoseconds timestamp)
{
RenderLoopPrivate::get(m_output->renderLoop())->notifyVblank(timestamp);
m_commitThread->pageFlipped(timestamp);
if (gpu()->needsModeset()) {
gpu()->maybeModeset(nullptr, nullptr);
}
}
void DrmPipeline::setOutput(DrmOutput *output)
{
m_output = output;
}
DrmOutput *DrmPipeline::output() const
{
return m_output;
}
QHash<uint32_t, QList<uint64_t>> DrmPipeline::formats(DrmPlane::TypeIndex planeType) const
{
switch (planeType) {
case DrmPlane::TypeIndex::Primary:
return m_pending.formats;
case DrmPlane::TypeIndex::Cursor:
if (m_pending.crtc && m_pending.crtc->cursorPlane()) {
return m_pending.crtc->cursorPlane()->formats();
} else {
return legacyCursorFormats;
}
case DrmPlane::TypeIndex::Overlay:
return {};
}
Q_UNREACHABLE();
}
bool DrmPipeline::pruneModifier()
{
const DmaBufAttributes *dmabufAttributes = m_primaryLayer->currentBuffer() ? m_primaryLayer->currentBuffer()->buffer()->dmabufAttributes() : nullptr;
if (!dmabufAttributes) {
return false;
}
auto &modifiers = m_pending.formats[dmabufAttributes->format];
if (modifiers == implicitModifier) {
return false;
} else {
modifiers = implicitModifier;
return true;
}
}
QList<QSize> DrmPipeline::recommendedSizes(DrmPlane::TypeIndex planeType) const
{
switch (planeType) {
case DrmPlane::TypeIndex::Primary:
if (m_pending.crtc && m_pending.crtc->primaryPlane()) {
return m_pending.crtc->primaryPlane()->recommendedSizes();
} else {
return QList<QSize>{};
}
case DrmPlane::TypeIndex::Cursor:
if (m_pending.crtc && m_pending.crtc->cursorPlane()) {
return m_pending.crtc->cursorPlane()->recommendedSizes();
} else {
return QList<QSize>{gpu()->cursorSize()};
}
case DrmPlane::TypeIndex::Overlay:
return QList<QSize>{};
}
Q_UNREACHABLE();
}
bool DrmPipeline::needsModeset() const
{
return m_pending.needsModeset;
}
bool DrmPipeline::activePending() const
{
return m_pending.crtc && m_pending.mode && m_pending.active;
}
void DrmPipeline::revertPendingChanges()
{
m_pending = m_next;
}
DrmCommitThread *DrmPipeline::commitThread() const
{
return m_commitThread.get();
}
bool DrmPipeline::modesetPresentPending() const
{
return m_modesetPresentPending;
}
void DrmPipeline::resetModesetPresentPending()
{
m_modesetPresentPending = false;
}
DrmCrtc *DrmPipeline::crtc() const
{
return m_pending.crtc;
}
std::shared_ptr<DrmConnectorMode> DrmPipeline::mode() const
{
return m_pending.mode;
}
bool DrmPipeline::active() const
{
return m_pending.active;
}
bool DrmPipeline::enabled() const
{
return m_pending.enabled;
}
DrmPipelineLayer *DrmPipeline::primaryLayer() const
{
return m_primaryLayer.get();
}
DrmPipelineLayer *DrmPipeline::cursorLayer() const
{
return m_cursorLayer.get();
}
PresentationMode DrmPipeline::presentationMode() const
{
return m_pending.presentationMode;
}
uint32_t DrmPipeline::overscan() const
{
return m_pending.overscan;
}
Output::RgbRange DrmPipeline::rgbRange() const
{
return m_pending.rgbRange;
}
DrmConnector::DrmContentType DrmPipeline::contentType() const
{
return m_pending.contentType;
}
const std::shared_ptr<IccProfile> &DrmPipeline::iccProfile() const
{
return m_pending.iccProfile;
}
void DrmPipeline::setCrtc(DrmCrtc *crtc)
{
m_pending.crtc = crtc;
if (crtc) {
m_pending.formats = crtc->primaryPlane() ? crtc->primaryPlane()->formats() : legacyFormats;
} else {
m_pending.formats = {};
}
}
void DrmPipeline::setMode(const std::shared_ptr<DrmConnectorMode> &mode)
{
m_pending.mode = mode;
}
void DrmPipeline::setActive(bool active)
{
m_pending.active = active;
}
void DrmPipeline::setEnable(bool enable)
{
m_pending.enabled = enable;
}
void DrmPipeline::setLayers(const std::shared_ptr<DrmPipelineLayer> &primaryLayer, const std::shared_ptr<DrmPipelineLayer> &cursorLayer)
{
m_primaryLayer = primaryLayer;
m_cursorLayer = cursorLayer;
}
void DrmPipeline::setPresentationMode(PresentationMode mode)
{
m_pending.presentationMode = mode;
}
void DrmPipeline::setOverscan(uint32_t overscan)
{
m_pending.overscan = overscan;
}
void DrmPipeline::setRgbRange(Output::RgbRange range)
{
m_pending.rgbRange = range;
}
void DrmPipeline::setCrtcColorPipeline(const ColorPipeline &pipeline)
{
m_pending.crtcColorPipeline = pipeline;
}
void DrmPipeline::setHighDynamicRange(bool hdr)
{
m_pending.hdr = hdr;
}
void DrmPipeline::setWideColorGamut(bool wcg)
{
m_pending.wcg = wcg;
}
void DrmPipeline::setMaxBpc(uint32_t max)
{
m_pending.maxBpc = max;
}
void DrmPipeline::setContentType(DrmConnector::DrmContentType type)
{
m_pending.contentType = type;
}
void DrmPipeline::setIccProfile(const std::shared_ptr<IccProfile> &profile)
{
m_pending.iccProfile = profile;
}
std::shared_ptr<DrmBlob> DrmPipeline::createHdrMetadata(TransferFunction::Type transferFunction) const
{
if (transferFunction != TransferFunction::PerceptualQuantizer) {
// for sRGB / gamma 2.2, don't send any metadata, to ensure the non-HDR experience stays the same
return nullptr;
}
if (!m_connector->edid()->supportsPQ()) {
return nullptr;
}
const auto colorimetry = m_connector->edid()->colorimetry().value_or(Colorimetry::fromName(NamedColorimetry::BT709));
const xyY red = colorimetry.red().toxyY();
const xyY green = colorimetry.green().toxyY();
const xyY blue = colorimetry.blue().toxyY();
const xyY white = colorimetry.white().toxyY();
const auto to16Bit = [](float value) {
return uint16_t(std::round(value / 0.00002));
};
hdr_output_metadata data{
.metadata_type = 0,
.hdmi_metadata_type1 = hdr_metadata_infoframe{
// eotf types (from CTA-861-G page 85):
// - 0: traditional gamma, SDR
// - 1: traditional gamma, HDR
// - 2: SMPTE ST2084
// - 3: hybrid Log-Gamma based on BT.2100-0
// - 4-7: reserved
.eotf = uint8_t(2),
// there's only one type. 1-7 are reserved for future use
.metadata_type = 0,
// in 0.00002 nits
.display_primaries = {
{to16Bit(red.x), to16Bit(red.y)},
{to16Bit(green.x), to16Bit(green.y)},
{to16Bit(blue.x), to16Bit(blue.y)},
},
.white_point = {to16Bit(white.x), to16Bit(white.y)},
// in nits
.max_display_mastering_luminance = uint16_t(std::round(m_connector->edid()->desiredMaxFrameAverageLuminance().value_or(0))),
// in 0.0001 nits
.min_display_mastering_luminance = uint16_t(std::round(m_connector->edid()->desiredMinLuminance() * 10000)),
// in nits
.max_cll = uint16_t(std::round(m_connector->edid()->desiredMaxFrameAverageLuminance().value_or(0))),
.max_fall = uint16_t(std::round(m_connector->edid()->desiredMaxFrameAverageLuminance().value_or(0))),
},
};
return DrmBlob::create(gpu(), &data, sizeof(data));
}
std::chrono::nanoseconds DrmPipeline::presentationDeadline() const
{
return m_commitThread->safetyMargin();
}
}
@@ -0,0 +1,185 @@
/*
KWin - the KDE window manager
This file is part of the KDE project.
SPDX-FileCopyrightText: 2021 Xaver Hugl <xaver.hugl@gmail.com>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#pragma once
#include <QList>
#include <QPoint>
#include <QSize>
#include <chrono>
#include <xf86drmMode.h>
#include "core/colorpipeline.h"
#include "core/colorspace.h"
#include "core/output.h"
#include "core/renderloop_p.h"
#include "drm_blob.h"
#include "drm_connector.h"
#include "drm_plane.h"
namespace KWin
{
class DrmGpu;
class DrmConnector;
class DrmCrtc;
class DrmConnectorMode;
class DrmPipelineLayer;
class DrmCommitThread;
class OutputFrame;
class DrmPipeline
{
public:
DrmPipeline(DrmConnector *conn);
~DrmPipeline();
enum class Error {
None,
OutofMemory,
InvalidArguments,
NoPermission,
FramePending,
TestBufferFailed,
Unknown,
};
Q_ENUM(Error)
/**
* tests the pending commit first and commits it if the test passes
* if the test fails, there is a guarantee for no lasting changes
*/
Error present(const std::shared_ptr<OutputFrame> &frame);
void maybeModeset(const std::shared_ptr<OutputFrame> &frame);
void forceLegacyModeset();
bool needsModeset() const;
void applyPendingChanges();
void revertPendingChanges();
bool updateCursor(std::optional<std::chrono::nanoseconds> allowedVrrDelay);
DrmConnector *connector() const;
DrmGpu *gpu() const;
void pageFlipped(std::chrono::nanoseconds timestamp);
bool modesetPresentPending() const;
void resetModesetPresentPending();
DrmCommitThread *commitThread() const;
QHash<uint32_t, QList<uint64_t>> formats(DrmPlane::TypeIndex planeType) const;
bool pruneModifier();
QList<QSize> recommendedSizes(DrmPlane::TypeIndex planeType) const;
void setOutput(DrmOutput *output);
DrmOutput *output() const;
void setLayers(const std::shared_ptr<DrmPipelineLayer> &primaryLayer, const std::shared_ptr<DrmPipelineLayer> &cursorLayer);
DrmPipelineLayer *primaryLayer() const;
DrmPipelineLayer *cursorLayer() const;
std::chrono::nanoseconds presentationDeadline() const;
DrmCrtc *crtc() const;
std::shared_ptr<DrmConnectorMode> mode() const;
bool active() const;
bool activePending() const;
bool enabled() const;
PresentationMode presentationMode() const;
uint32_t overscan() const;
Output::RgbRange rgbRange() const;
DrmConnector::DrmContentType contentType() const;
const std::shared_ptr<IccProfile> &iccProfile() const;
void setCrtc(DrmCrtc *crtc);
void setMode(const std::shared_ptr<DrmConnectorMode> &mode);
void setActive(bool active);
void setEnable(bool enable);
void setPresentationMode(PresentationMode mode);
void setOverscan(uint32_t overscan);
void setRgbRange(Output::RgbRange range);
void setCrtcColorPipeline(const ColorPipeline &pipeline);
void setContentType(DrmConnector::DrmContentType type);
void setIccProfile(const std::shared_ptr<IccProfile> &profile);
void setHighDynamicRange(bool hdr);
void setWideColorGamut(bool wcg);
void setMaxBpc(uint32_t max);
/**
* amdgpu drops cursor updates with adaptive sync: https://gitlab.freedesktop.org/drm/amd/-/issues/2186
*/
bool amdgpuVrrWorkaroundActive() const;
enum class CommitMode {
Test,
TestAllowModeset,
CommitModeset
};
Q_ENUM(CommitMode)
static Error commitPipelines(const QList<DrmPipeline *> &pipelines, CommitMode mode, const QList<DrmObject *> &unusedObjects = {});
private:
bool isBufferForDirectScanout() const;
uint32_t calculateUnderscan();
static Error errnoToError();
std::shared_ptr<DrmBlob> createHdrMetadata(TransferFunction::Type transferFunction) const;
// legacy only
Error presentLegacy(const std::shared_ptr<OutputFrame> &frame);
Error legacyModeset();
Error setLegacyGamma();
Error applyPendingChangesLegacy();
bool setCursorLegacy();
static Error commitPipelinesLegacy(const QList<DrmPipeline *> &pipelines, CommitMode mode, const QList<DrmObject *> &unusedObjects);
// atomic modesetting only
Error prepareAtomicCommit(DrmAtomicCommit *commit, CommitMode mode, const std::shared_ptr<OutputFrame> &frame);
bool prepareAtomicModeset(DrmAtomicCommit *commit);
Error prepareAtomicPresentation(DrmAtomicCommit *commit, const std::shared_ptr<OutputFrame> &frame);
void prepareAtomicCursor(DrmAtomicCommit *commit);
void prepareAtomicDisable(DrmAtomicCommit *commit);
static Error commitPipelinesAtomic(const QList<DrmPipeline *> &pipelines, CommitMode mode, const std::shared_ptr<OutputFrame> &frame, const QList<DrmObject *> &unusedObjects);
DrmOutput *m_output = nullptr;
DrmConnector *m_connector = nullptr;
bool m_modesetPresentPending = false;
ColorPipeline m_currentLegacyGamma;
struct State
{
DrmCrtc *crtc = nullptr;
QHash<uint32_t, QList<uint64_t>> formats;
bool active = true; // whether or not the pipeline should be currently used
bool enabled = true; // whether or not the pipeline needs a crtc
bool needsModeset = false;
bool needsModesetProperties = false;
std::shared_ptr<DrmConnectorMode> mode;
uint32_t overscan = 0;
Output::RgbRange rgbRange = Output::RgbRange::Automatic;
PresentationMode presentationMode = PresentationMode::VSync;
ColorPipeline crtcColorPipeline;
DrmConnector::DrmContentType contentType = DrmConnector::DrmContentType::Graphics;
std::shared_ptr<IccProfile> iccProfile;
bool hdr = false;
bool wcg = false;
uint32_t maxBpc = 10;
};
// the state that is to be tested next
State m_pending;
// the state that will be applied at the next real atomic commit
State m_next;
std::unique_ptr<DrmCommitThread> m_commitThread;
std::shared_ptr<DrmPipelineLayer> m_primaryLayer;
std::shared_ptr<DrmPipelineLayer> m_cursorLayer;
};
}
@@ -0,0 +1,229 @@
/*
* KWin - the KDE window manager
* This file is part of the KDE project.
*
* SPDX-FileCopyrightText: 2021 Xaver Hugl <xaver.hugl@gmail.com>
*
* SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "core/graphicsbuffer.h"
#include "drm_buffer.h"
#include "drm_commit.h"
#include "drm_commit_thread.h"
#include "drm_connector.h"
#include "drm_crtc.h"
#include "drm_gpu.h"
#include "drm_layer.h"
#include "drm_logging.h"
#include "drm_pipeline.h"
#include <errno.h>
#include <gbm.h>
namespace KWin
{
DrmPipeline::Error DrmPipeline::presentLegacy(const std::shared_ptr<OutputFrame> &frame)
{
if (Error err = applyPendingChangesLegacy(); err != Error::None) {
return err;
}
const auto buffer = m_primaryLayer->currentBuffer();
if (m_primaryLayer->sourceRect() != m_primaryLayer->targetRect() || m_primaryLayer->targetRect() != QRect(QPoint(0, 0), buffer->buffer()->size())) {
return Error::InvalidArguments;
}
auto commit = std::make_unique<DrmLegacyCommit>(this, buffer, frame);
if (!commit->doPageflip(m_pending.presentationMode)) {
qCWarning(KWIN_DRM) << "Page flip failed:" << strerror(errno);
return errnoToError();
}
m_commitThread->setPendingCommit(std::move(commit));
return Error::None;
}
void DrmPipeline::forceLegacyModeset()
{
if (activePending()) {
legacyModeset();
setLegacyGamma();
}
}
DrmPipeline::Error DrmPipeline::legacyModeset()
{
if (!m_primaryLayer->checkTestBuffer()) {
return Error::TestBufferFailed;
}
const auto buffer = m_primaryLayer->currentBuffer();
if (m_primaryLayer->sourceRect() != QRect(QPoint(0, 0), buffer->buffer()->size())) {
return Error::InvalidArguments;
}
auto commit = std::make_unique<DrmLegacyCommit>(this, buffer, nullptr);
if (!commit->doModeset(m_connector, m_pending.mode.get())) {
qCWarning(KWIN_DRM) << "Modeset failed!" << strerror(errno);
return errnoToError();
}
return Error::None;
}
DrmPipeline::Error DrmPipeline::commitPipelinesLegacy(const QList<DrmPipeline *> &pipelines, CommitMode mode, const QList<DrmObject *> &unusedObjects)
{
Error err = Error::None;
for (const auto &pipeline : pipelines) {
err = pipeline->applyPendingChangesLegacy();
if (err != Error::None) {
break;
}
}
if (err != Error::None) {
// at least try to revert the config
for (const auto &pipeline : pipelines) {
pipeline->revertPendingChanges();
pipeline->applyPendingChangesLegacy();
}
} else {
for (const auto &pipeline : pipelines) {
pipeline->applyPendingChanges();
if (mode == CommitMode::CommitModeset && pipeline->activePending()) {
pipeline->pageFlipped(std::chrono::steady_clock::now().time_since_epoch());
}
}
for (const auto &obj : unusedObjects) {
if (auto crtc = dynamic_cast<DrmCrtc *>(obj)) {
drmModeSetCrtc(pipelines.front()->gpu()->fd(), crtc->id(), 0, 0, 0, nullptr, 0, nullptr);
}
}
}
return err;
}
DrmPipeline::Error DrmPipeline::applyPendingChangesLegacy()
{
if (!m_pending.active && m_pending.crtc) {
drmModeSetCursor(gpu()->fd(), m_pending.crtc->id(), 0, 0, 0);
}
if (activePending()) {
if (!m_primaryLayer->colorPipeline().isIdentity() || !m_cursorLayer->colorPipeline().isIdentity()) {
// while it's technically possible to set CRTC color management properties,
// it may result in glitches
return DrmPipeline::Error::InvalidArguments;
}
const bool shouldEnableVrr = m_pending.presentationMode == PresentationMode::AdaptiveSync || m_pending.presentationMode == PresentationMode::AdaptiveAsync;
if (m_pending.crtc->vrrEnabled.isValid() && !m_pending.crtc->vrrEnabled.setPropertyLegacy(shouldEnableVrr)) {
qCWarning(KWIN_DRM) << "Setting vrr failed!" << strerror(errno);
return errnoToError();
}
if (m_connector->broadcastRGB.isValid()) {
m_connector->broadcastRGB.setEnumLegacy(DrmConnector::rgbRangeToBroadcastRgb(m_pending.rgbRange));
}
if (m_connector->overscan.isValid()) {
m_connector->overscan.setPropertyLegacy(m_pending.overscan);
} else if (m_connector->underscan.isValid()) {
const uint32_t hborder = calculateUnderscan();
m_connector->underscan.setEnumLegacy(m_pending.overscan != 0 ? DrmConnector::UnderscanOptions::On : DrmConnector::UnderscanOptions::Off);
m_connector->underscanVBorder.setPropertyLegacy(m_pending.overscan);
m_connector->underscanHBorder.setPropertyLegacy(hborder);
}
if (m_connector->scalingMode.isValid() && m_connector->scalingMode.hasEnum(DrmConnector::ScalingMode::None)) {
m_connector->scalingMode.setEnumLegacy(DrmConnector::ScalingMode::None);
}
if (m_connector->hdrMetadata.isValid()) {
const auto blob = createHdrMetadata(m_pending.hdr ? TransferFunction::PerceptualQuantizer : TransferFunction::gamma22);
m_connector->hdrMetadata.setPropertyLegacy(blob ? blob->blobId() : 0);
} else if (m_pending.hdr) {
return DrmPipeline::Error::InvalidArguments;
}
if (m_connector->colorspace.isValid()) {
m_connector->colorspace.setEnumLegacy(m_pending.wcg ? DrmConnector::Colorspace::BT2020_RGB : DrmConnector::Colorspace::Default);
} else if (m_pending.wcg) {
return DrmPipeline::Error::InvalidArguments;
}
const auto currentModeContent = m_pending.crtc->queryCurrentMode();
if (m_pending.crtc != m_next.crtc || *m_pending.mode != currentModeContent) {
qCDebug(KWIN_DRM) << "Using legacy path to set mode" << m_pending.mode->nativeMode()->name;
Error err = legacyModeset();
if (err != Error::None) {
return err;
}
}
if (m_pending.crtcColorPipeline != m_currentLegacyGamma) {
if (Error err = setLegacyGamma(); err != Error::None) {
return err;
}
}
if (m_connector->contentType.isValid()) {
m_connector->contentType.setEnumLegacy(m_pending.contentType);
}
setCursorLegacy();
}
if (!m_connector->dpms.setPropertyLegacy(activePending() ? DRM_MODE_DPMS_ON : DRM_MODE_DPMS_OFF)) {
qCWarning(KWIN_DRM) << "Setting legacy dpms failed!" << strerror(errno);
return errnoToError();
}
return Error::None;
}
DrmPipeline::Error DrmPipeline::setLegacyGamma()
{
QList<uint16_t> red(m_pending.crtc->gammaRampSize());
QList<uint16_t> green(m_pending.crtc->gammaRampSize());
QList<uint16_t> blue(m_pending.crtc->gammaRampSize());
for (int i = 0; i < m_pending.crtc->gammaRampSize(); i++) {
const double input = i / double(m_pending.crtc->gammaRampSize() - 1);
QVector3D output = QVector3D(input, input, input);
for (const auto &op : m_pending.crtcColorPipeline.ops) {
if (auto tf = std::get_if<ColorTransferFunction>(&op.operation)) {
output = tf->tf.encodedToNits(output);
} else if (auto tf = std::get_if<InverseColorTransferFunction>(&op.operation)) {
output = tf->tf.nitsToEncoded(output);
} else if (auto mult = std::get_if<ColorMultiplier>(&op.operation)) {
output *= mult->factors;
} else {
// not supported
return Error::InvalidArguments;
}
}
red[i] = std::clamp(output.x(), 0.0f, 1.0f) * std::numeric_limits<uint16_t>::max();
green[i] = std::clamp(output.y(), 0.0f, 1.0f) * std::numeric_limits<uint16_t>::max();
blue[i] = std::clamp(output.z(), 0.0f, 1.0f) * std::numeric_limits<uint16_t>::max();
}
if (drmModeCrtcSetGamma(gpu()->fd(), m_pending.crtc->id(), m_pending.crtc->gammaRampSize(), red.data(), green.data(), blue.data()) != 0) {
qCWarning(KWIN_DRM) << "Setting gamma failed!" << strerror(errno);
return errnoToError();
}
m_currentLegacyGamma = m_pending.crtcColorPipeline;
return DrmPipeline::Error::None;
}
bool DrmPipeline::setCursorLegacy()
{
const auto bo = cursorLayer()->currentBuffer();
uint32_t handle = 0;
if (bo && bo->buffer() && cursorLayer()->isEnabled()) {
const DmaBufAttributes *attributes = bo->buffer()->dmabufAttributes();
if (drmPrimeFDToHandle(gpu()->fd(), attributes->fd[0].get(), &handle) != 0) {
qCWarning(KWIN_DRM) << "drmPrimeFDToHandle() failed";
return false;
}
}
struct drm_mode_cursor2 arg = {
.flags = DRM_MODE_CURSOR_BO | DRM_MODE_CURSOR_MOVE,
.crtc_id = m_pending.crtc->id(),
.x = int32_t(m_cursorLayer->targetRect().x()),
.y = int32_t(m_cursorLayer->targetRect().y()),
.width = (uint32_t)gpu()->cursorSize().width(),
.height = (uint32_t)gpu()->cursorSize().height(),
.handle = handle,
.hot_x = int32_t(m_cursorLayer->hotspot().x()),
.hot_y = int32_t(m_cursorLayer->hotspot().y()),
};
const int ret = drmIoctl(gpu()->fd(), DRM_IOCTL_MODE_CURSOR2, &arg);
if (handle != 0) {
drmCloseBufferHandle(gpu()->fd(), handle);
}
return ret == 0;
}
}
@@ -0,0 +1,236 @@
/*
KWin - the KDE window manager
This file is part of the KDE project.
SPDX-FileCopyrightText: 2016 Roman Gilg <subdiff@gmail.com>
SPDX-FileCopyrightText: 2022 Xaver Hugl <xaver.hugl@gmail.com>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "drm_plane.h"
#include "config-kwin.h"
#include "drm_buffer.h"
#include "drm_commit.h"
#include "drm_gpu.h"
#include "drm_logging.h"
#include "drm_pointer.h"
#include <drm_fourcc.h>
#include <ranges>
#include <span>
namespace KWin
{
DrmPlane::DrmPlane(DrmGpu *gpu, uint32_t planeId)
: DrmObject(gpu, planeId, DRM_MODE_OBJECT_PLANE)
, type(this, QByteArrayLiteral("type"), {
QByteArrayLiteral("Overlay"),
QByteArrayLiteral("Primary"),
QByteArrayLiteral("Cursor"),
})
, srcX(this, QByteArrayLiteral("SRC_X"))
, srcY(this, QByteArrayLiteral("SRC_Y"))
, srcW(this, QByteArrayLiteral("SRC_W"))
, srcH(this, QByteArrayLiteral("SRC_H"))
, crtcX(this, QByteArrayLiteral("CRTC_X"))
, crtcY(this, QByteArrayLiteral("CRTC_Y"))
, crtcW(this, QByteArrayLiteral("CRTC_W"))
, crtcH(this, QByteArrayLiteral("CRTC_H"))
, fbId(this, QByteArrayLiteral("FB_ID"))
, crtcId(this, QByteArrayLiteral("CRTC_ID"))
, rotation(this, QByteArrayLiteral("rotation"), {
QByteArrayLiteral("rotate-0"),
QByteArrayLiteral("rotate-90"),
QByteArrayLiteral("rotate-180"),
QByteArrayLiteral("rotate-270"),
QByteArrayLiteral("reflect-x"),
QByteArrayLiteral("reflect-y"),
})
, inFormats(this, QByteArrayLiteral("IN_FORMATS"))
, alpha(this, QByteArrayLiteral("alpha"))
, pixelBlendMode(this, QByteArrayLiteral("pixel blend mode"), {
QByteArrayLiteral("None"),
QByteArrayLiteral("Pre-multiplied"),
QByteArrayLiteral("Coverage"),
})
, colorEncoding(this, QByteArrayLiteral("COLOR_ENCODING"), {
QByteArrayLiteral("ITU-R BT.601 YCbCr"),
QByteArrayLiteral("ITU-R BT.709 YCbCr"),
QByteArrayLiteral("ITU-R BT.2020 YCbCr"),
})
, colorRange(this, QByteArrayLiteral("COLOR_RANGE"), {
QByteArrayLiteral("YCbCr limited range"),
QByteArrayLiteral("YCbCr full range"),
})
, vmHotspotX(this, QByteArrayLiteral("HOTSPOT_X"))
, vmHotspotY(this, QByteArrayLiteral("HOTSPOT_Y"))
, inFenceFd(this, QByteArrayLiteral("IN_FENCE_FD"))
, sizeHints(this, QByteArrayLiteral("SIZE_HINTS"))
{
}
bool DrmPlane::init()
{
return updateProperties();
}
bool DrmPlane::updateProperties()
{
DrmUniquePtr<drmModePlane> p(drmModeGetPlane(gpu()->fd(), id()));
if (!p) {
qCWarning(KWIN_DRM) << "Failed to get kernel plane" << id();
return false;
}
DrmPropertyList props = queryProperties();
type.update(props);
srcX.update(props);
srcY.update(props);
srcW.update(props);
srcH.update(props);
crtcX.update(props);
crtcY.update(props);
crtcW.update(props);
crtcH.update(props);
fbId.update(props);
crtcId.update(props);
rotation.update(props);
inFormats.update(props);
alpha.update(props);
pixelBlendMode.update(props);
colorEncoding.update(props);
colorRange.update(props);
vmHotspotX.update(props);
vmHotspotY.update(props);
inFenceFd.update(props);
sizeHints.update(props);
if (!type.isValid() || !srcX.isValid() || !srcY.isValid() || !srcW.isValid() || !srcH.isValid()
|| !crtcX.isValid() || !crtcY.isValid() || !crtcW.isValid() || !crtcH.isValid() || !fbId.isValid()) {
qCWarning(KWIN_DRM) << "Failed to update the basic plane properties";
return false;
}
m_possibleCrtcs = p->possible_crtcs;
// read formats from blob if available and if modifiers are supported, and from the plane object if not
m_supportedFormats.clear();
if (inFormats.isValid() && inFormats.immutableBlob() && gpu()->addFB2ModifiersSupported()) {
drmModeFormatModifierIterator iterator{};
while (drmModeFormatModifierBlobIterNext(inFormats.immutableBlob(), &iterator)) {
m_supportedFormats[iterator.fmt].push_back(iterator.mod);
}
} else {
// if we don't have modifier support, assume the cursor needs a linear buffer
const QList<uint64_t> modifiers = {type.enumValue() == TypeIndex::Cursor ? DRM_FORMAT_MOD_LINEAR : DRM_FORMAT_MOD_INVALID};
for (uint32_t i = 0; i < p->count_formats; i++) {
m_supportedFormats.insert(p->formats[i], modifiers);
}
if (m_supportedFormats.isEmpty()) {
qCWarning(KWIN_DRM) << "Driver doesn't advertise any formats for this plane. Falling back to XRGB8888 without explicit modifiers";
m_supportedFormats.insert(DRM_FORMAT_XRGB8888, modifiers);
}
}
m_sizeHints.clear();
if (sizeHints.isValid() && sizeHints.immutableBlob()) {
// TODO switch to drm_plane_size_hint once we require libdrm 2.4.122
struct SizeHint
{
uint16_t width;
uint16_t height;
};
std::span<SizeHint> hints(reinterpret_cast<SizeHint *>(sizeHints.immutableBlob()->data), sizeHints.immutableBlob()->length / sizeof(SizeHint));
std::ranges::transform(hints, std::back_inserter(m_sizeHints), [](const SizeHint &hint) {
return QSize(hint.width, hint.height);
});
}
if (m_sizeHints.empty() && type.enumValue() == TypeIndex::Cursor) {
m_sizeHints = {gpu()->cursorSize()};
}
return true;
}
void DrmPlane::set(DrmAtomicCommit *commit, const QRect &src, const QRect &dst)
{
// Src* are in 16.16 fixed point format
commit->addProperty(srcX, src.x() << 16);
commit->addProperty(srcY, src.y() << 16);
commit->addProperty(srcW, src.width() << 16);
commit->addProperty(srcH, src.height() << 16);
commit->addProperty(crtcX, dst.x());
commit->addProperty(crtcY, dst.y());
commit->addProperty(crtcW, dst.width());
commit->addProperty(crtcH, dst.height());
}
bool DrmPlane::isCrtcSupported(int pipeIndex) const
{
return (m_possibleCrtcs & (1 << pipeIndex));
}
QHash<uint32_t, QList<uint64_t>> DrmPlane::formats() const
{
return m_supportedFormats;
}
std::shared_ptr<DrmFramebuffer> DrmPlane::currentBuffer() const
{
return m_current;
}
void DrmPlane::setCurrentBuffer(const std::shared_ptr<DrmFramebuffer> &b)
{
m_current = b;
}
void DrmPlane::disable(DrmAtomicCommit *commit)
{
commit->addProperty(crtcId, 0);
commit->addBuffer(this, nullptr, nullptr);
}
void DrmPlane::releaseCurrentBuffer()
{
if (m_current) {
m_current->releaseBuffer();
}
}
DrmPlane::Transformations DrmPlane::outputTransformToPlaneTransform(OutputTransform transform)
{
// note that drm transformations are counter clockwise
switch (transform.kind()) {
case OutputTransform::Kind::Normal:
return Transformation::Rotate0;
case OutputTransform::Kind::Rotate90:
return Transformation::Rotate270;
case OutputTransform::Kind::Rotate180:
return Transformation::Rotate180;
case OutputTransform::Kind::Rotate270:
return Transformation::Rotate90;
case OutputTransform::Kind::FlipY:
return Transformation::Rotate0 | Transformation::ReflectY;
case OutputTransform::Kind::FlipY90:
return Transformation::Rotate270 | Transformation::ReflectY;
case OutputTransform::Kind::FlipY180:
return Transformation::Rotate180 | Transformation::ReflectY;
case OutputTransform::Kind::FlipY270:
return Transformation::Rotate90 | Transformation::ReflectY;
}
Q_UNREACHABLE();
}
bool DrmPlane::supportsTransformation(OutputTransform transform) const
{
return rotation.isValid() && rotation.hasEnum(outputTransformToPlaneTransform(transform));
}
QList<QSize> DrmPlane::recommendedSizes() const
{
return m_sizeHints;
}
}
#include "moc_drm_plane.cpp"
@@ -0,0 +1,113 @@
/*
KWin - the KDE window manager
This file is part of the KDE project.
SPDX-FileCopyrightText: 2016 Roman Gilg <subdiff@gmail.com>
SPDX-FileCopyrightText: 2022 Xaver Hugl <xaver.hugl@gmail.com>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#pragma once
#include "core/output.h"
#include "drm_object.h"
#include <QMap>
#include <QPoint>
#include <QSize>
#include <memory>
#include <qobjectdefs.h>
namespace KWin
{
class DrmFramebuffer;
class DrmCrtc;
class DrmPlane : public DrmObject
{
Q_GADGET
public:
DrmPlane(DrmGpu *gpu, uint32_t planeId);
bool init();
bool updateProperties() override;
void disable(DrmAtomicCommit *commit) override;
bool isCrtcSupported(int pipeIndex) const;
QHash<uint32_t, QList<uint64_t>> formats() const;
bool supportsTransformation(OutputTransform transform) const;
std::shared_ptr<DrmFramebuffer> currentBuffer() const;
void setCurrentBuffer(const std::shared_ptr<DrmFramebuffer> &b);
void releaseCurrentBuffer();
void set(DrmAtomicCommit *commit, const QRect &src, const QRect &dst);
QList<QSize> recommendedSizes() const;
enum class TypeIndex : uint64_t {
Overlay = 0,
Primary = 1,
Cursor = 2
};
enum class Transformation : uint32_t {
Rotate0 = 1 << 0,
Rotate90 = 1 << 1,
Rotate180 = 1 << 2,
Rotate270 = 1 << 3,
ReflectX = 1 << 4,
ReflectY = 1 << 5
};
Q_ENUM(Transformation)
Q_DECLARE_FLAGS(Transformations, Transformation)
static Transformations outputTransformToPlaneTransform(OutputTransform transform);
enum class PixelBlendMode : uint64_t {
None,
PreMultiplied,
Coverage
};
enum class ColorEncoding : uint64_t {
BT601_YCbCr,
BT709_YCbCr,
BT2020_YCbCr
};
enum class ColorRange : uint64_t {
Limited_YCbCr,
Full_YCbCr
};
DrmEnumProperty<TypeIndex> type;
DrmProperty srcX;
DrmProperty srcY;
DrmProperty srcW;
DrmProperty srcH;
DrmProperty crtcX;
DrmProperty crtcY;
DrmProperty crtcW;
DrmProperty crtcH;
DrmProperty fbId;
DrmProperty crtcId;
DrmEnumProperty<Transformations> rotation;
DrmProperty inFormats;
DrmProperty alpha;
DrmEnumProperty<PixelBlendMode> pixelBlendMode;
DrmEnumProperty<ColorEncoding> colorEncoding;
DrmEnumProperty<ColorRange> colorRange;
DrmProperty vmHotspotX;
DrmProperty vmHotspotY;
DrmProperty inFenceFd;
DrmProperty sizeHints;
private:
std::shared_ptr<DrmFramebuffer> m_current;
QHash<uint32_t, QList<uint64_t>> m_supportedFormats;
uint32_t m_possibleCrtcs;
QList<QSize> m_sizeHints;
};
}
Q_DECLARE_OPERATORS_FOR_FLAGS(KWin::DrmPlane::Transformations)
@@ -0,0 +1,150 @@
/*
KWin - the KDE window manager
This file is part of the KDE project.
SPDX-FileCopyrightText: 2015 Martin Gräßlin <mgraesslin@kde.org>
SPDX-FileCopyrightText: 2019 Vlad Zahorodnii <vlad.zahorodnii@kde.org>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#pragma once
#include <memory>
#include <xf86drm.h>
#include <xf86drmMode.h>
namespace KWin
{
template<typename T>
struct DrmDeleter;
template<>
struct DrmDeleter<drmVersion>
{
void operator()(drmVersion *version)
{
drmFreeVersion(version);
}
};
template<>
struct DrmDeleter<drmModeAtomicReq>
{
void operator()(drmModeAtomicReq *req)
{
drmModeAtomicFree(req);
}
};
template<>
struct DrmDeleter<drmModeConnector>
{
void operator()(drmModeConnector *connector)
{
drmModeFreeConnector(connector);
}
};
template<>
struct DrmDeleter<drmModeCrtc>
{
void operator()(drmModeCrtc *crtc)
{
drmModeFreeCrtc(crtc);
}
};
template<>
struct DrmDeleter<drmModeFB>
{
void operator()(drmModeFB *fb)
{
drmModeFreeFB(fb);
}
};
template<>
struct DrmDeleter<drmModeEncoder>
{
void operator()(drmModeEncoder *encoder)
{
drmModeFreeEncoder(encoder);
}
};
template<>
struct DrmDeleter<drmModeModeInfo>
{
void operator()(drmModeModeInfo *info)
{
drmModeFreeModeInfo(info);
}
};
template<>
struct DrmDeleter<drmModeObjectProperties>
{
void operator()(drmModeObjectProperties *properties)
{
drmModeFreeObjectProperties(properties);
}
};
template<>
struct DrmDeleter<drmModePlane>
{
void operator()(drmModePlane *plane)
{
drmModeFreePlane(plane);
}
};
template<>
struct DrmDeleter<drmModePlaneRes>
{
void operator()(drmModePlaneRes *resources)
{
drmModeFreePlaneResources(resources);
}
};
template<>
struct DrmDeleter<drmModePropertyRes>
{
void operator()(drmModePropertyRes *property)
{
drmModeFreeProperty(property);
}
};
template<>
struct DrmDeleter<drmModePropertyBlobRes>
{
void operator()(drmModePropertyBlobRes *blob)
{
drmModeFreePropertyBlob(blob);
}
};
template<>
struct DrmDeleter<drmModeRes>
{
void operator()(drmModeRes *resources)
{
drmModeFreeResources(resources);
}
};
template<>
struct DrmDeleter<drmModeLesseeListRes>
{
void operator()(drmModeLesseeListRes *ptr)
{
drmFree(ptr);
}
};
template<typename T>
using DrmUniquePtr = std::unique_ptr<T, DrmDeleter<T>>;
}
@@ -0,0 +1,155 @@
/*
KWin - the KDE window manager
This file is part of the KDE project.
SPDX-FileCopyrightText: 2016 Roman Gilg <subdiff@gmail.com>
SPDX-FileCopyrightText: 2021-2022 Xaver Hugl <xaver.hugl@gmail.com>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "drm_property.h"
#include "drm_gpu.h"
#include "drm_logging.h"
#include "drm_object.h"
#include <cerrno>
namespace KWin
{
DrmProperty::DrmProperty(DrmObject *obj, const QByteArray &name, const QList<QByteArray> &enumNames)
: m_obj(obj)
, m_propName(name)
, m_enumNames(enumNames)
{
}
bool DrmProperty::setPropertyLegacy(uint64_t value)
{
if (m_current == value) {
return true;
}
const int ret = drmModeObjectSetProperty(m_obj->gpu()->fd(), m_obj->id(), m_obj->type(), m_propId, value);
if (ret == 0) {
m_current = value;
return true;
}
qCWarning(KWIN_DRM) << "Failed to set property:" << m_propName << "for object ID:" << m_obj->id() << "to value:" << value << "error:" << strerror(-ret);
return false;
}
void DrmProperty::update(DrmPropertyList &propertyList)
{
if (const auto opt = propertyList.takeProperty(m_propName)) {
const auto &[prop, value] = *opt;
m_propId = prop->prop_id;
m_current = value;
m_flags = prop->flags;
if ((prop->flags & DRM_MODE_PROP_RANGE) || (prop->flags & DRM_MODE_PROP_SIGNED_RANGE)) {
Q_ASSERT(prop->count_values > 1);
m_minValue = prop->values[0];
m_maxValue = prop->values[1];
}
m_enumToPropertyMap.clear();
m_propertyToEnumMap.clear();
// bitmasks need translation too, not just enums
if (prop->flags & (DRM_MODE_PROP_ENUM | DRM_MODE_PROP_BITMASK)) {
for (int i = 0; i < prop->count_enums; i++) {
struct drm_mode_property_enum *en = &prop->enums[i];
int j = m_enumNames.indexOf(QByteArray(en->name));
if (j >= 0) {
if (m_flags & DRM_MODE_PROP_BITMASK) {
m_enumToPropertyMap[1 << j] = 1 << en->value;
m_propertyToEnumMap[1 << en->value] = 1 << j;
} else {
m_enumToPropertyMap[j] = en->value;
m_propertyToEnumMap[en->value] = j;
}
}
}
}
if ((m_flags & DRM_MODE_PROP_IMMUTABLE) && (m_flags & DRM_MODE_PROP_BLOB)) {
if (m_current != 0) {
m_immutableBlob.reset(drmModeGetPropertyBlob(m_obj->gpu()->fd(), m_current));
if (m_immutableBlob && (!m_immutableBlob->data || !m_immutableBlob->length)) {
m_immutableBlob.reset();
}
} else {
m_immutableBlob.reset();
}
}
} else {
m_propId = 0;
m_immutableBlob.reset();
m_enumToPropertyMap.clear();
m_propertyToEnumMap.clear();
}
}
uint64_t DrmProperty::value() const
{
return m_current;
}
bool DrmProperty::hasAllEnums() const
{
return m_enumToPropertyMap.count() == m_enumNames.count();
}
uint32_t DrmProperty::propId() const
{
return m_propId;
}
const QByteArray &DrmProperty::name() const
{
return m_propName;
}
bool DrmProperty::isImmutable() const
{
return m_flags & DRM_MODE_PROP_IMMUTABLE;
}
bool DrmProperty::isBitmask() const
{
return m_flags & DRM_MODE_PROP_BITMASK;
}
uint64_t DrmProperty::maxValue() const
{
return m_maxValue;
}
uint64_t DrmProperty::minValue() const
{
return m_minValue;
}
void DrmProperty::checkValueInRange(uint64_t value) const
{
if (Q_UNLIKELY(m_flags & DRM_MODE_PROP_RANGE && (value > m_maxValue || value < m_minValue))) {
qCWarning(KWIN_DRM) << "Range property value out of bounds." << m_propName << " value:" << value << "min:" << m_minValue << "max:" << m_maxValue;
}
if (Q_UNLIKELY(m_flags & DRM_MODE_PROP_SIGNED_RANGE && (int64_t(value) > int64_t(m_maxValue) || int64_t(value) < int64_t(m_minValue)))) {
qCWarning(KWIN_DRM) << "Signed range property value out of bounds." << m_propName << " value:" << int64_t(value) << "min:" << int64_t(m_minValue) << "max:" << int64_t(m_maxValue);
}
}
drmModePropertyBlobRes *DrmProperty::immutableBlob() const
{
return m_immutableBlob.get();
}
DrmObject *DrmProperty::drmObject() const
{
return m_obj;
}
bool DrmProperty::isValid() const
{
return m_propId != 0;
}
}
@@ -0,0 +1,140 @@
/*
KWin - the KDE window manager
This file is part of the KDE project.
SPDX-FileCopyrightText: 2016 Roman Gilg <subdiff@gmail.com>
SPDX-FileCopyrightText: 2021-2022 Xaver Hugl <xaver.hugl@gmail.com>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#pragma once
#include "drm_pointer.h"
#include "drm_logging.h"
#include <QByteArray>
#include <QList>
#include <QMap>
#include <xf86drmMode.h>
namespace KWin
{
class DrmObject;
class DrmPropertyList;
class DrmProperty
{
public:
DrmProperty(DrmObject *obj, const QByteArray &name, const QList<QByteArray> &enumNames = {});
const QByteArray &name() const;
DrmObject *drmObject() const;
uint32_t propId() const;
bool isImmutable() const;
bool isBitmask() const;
bool hasAllEnums() const;
uint64_t value() const;
drmModePropertyBlobRes *immutableBlob() const;
uint64_t minValue() const;
uint64_t maxValue() const;
bool isValid() const;
/**
* Prints a warning and returns false if @p value is unacceptable for the property
*/
void checkValueInRange(uint64_t value) const;
void update(DrmPropertyList &propertyList);
bool setPropertyLegacy(uint64_t value);
protected:
DrmObject *const m_obj;
const QByteArray m_propName;
const QList<QByteArray> m_enumNames;
uint32_t m_propId = 0;
// the last known value from the kernel
uint64_t m_current = 0;
DrmUniquePtr<drmModePropertyBlobRes> m_immutableBlob;
uint64_t m_minValue = -1;
uint64_t m_maxValue = -1;
QMap<uint64_t, uint64_t> m_enumToPropertyMap;
QMap<uint64_t, uint64_t> m_propertyToEnumMap;
uint32_t m_flags;
};
template<typename Enum>
class DrmEnumProperty : public DrmProperty
{
public:
DrmEnumProperty(DrmObject *obj, const QByteArray &name, const QList<QByteArray> &enumNames)
: DrmProperty(obj, name, enumNames)
{
}
Enum enumValue() const
{
return enumForValue(value());
}
bool hasEnum(Enum value) const
{
const uint64_t integerValue = static_cast<uint64_t>(value);
if (isBitmask()) {
for (uint64_t mask = 1; integerValue >= mask && mask != 0; mask <<= 1) {
if ((integerValue & mask) && !m_enumToPropertyMap.contains(mask)) {
return false;
}
}
return true;
} else {
return m_enumToPropertyMap.contains(integerValue);
}
}
Enum enumForValue(uint64_t value) const
{
if (isBitmask()) {
uint64_t ret = 0;
for (uint64_t mask = 1; value >= mask && mask != 0; mask <<= 1) {
if (value & mask) {
ret |= m_propertyToEnumMap[mask];
}
}
return static_cast<Enum>(ret);
} else {
return static_cast<Enum>(m_propertyToEnumMap[value]);
}
}
uint64_t valueForEnum(Enum enumValue) const
{
const uint64_t integer = static_cast<uint64_t>(enumValue);
if (isBitmask()) {
uint64_t set = 0;
for (uint64_t mask = 1; integer >= mask && mask != 0; mask <<= 1) {
if (integer & mask) {
set |= m_enumToPropertyMap[mask];
}
}
return set;
} else {
return m_enumToPropertyMap[integer];
}
}
bool setEnumLegacy(Enum value)
{
if (hasEnum(value)) {
return setPropertyLegacy(valueForEnum(value));
} else {
return false;
}
}
};
}
@@ -0,0 +1,75 @@
/*
KWin - the KDE window manager
This file is part of the KDE project.
SPDX-FileCopyrightText: 2015 Martin Gräßlin <mgraesslin@kde.org>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "drm_qpainter_backend.h"
#include "drm_backend.h"
#include "drm_gpu.h"
#include "drm_output.h"
#include "drm_pipeline.h"
#include "drm_qpainter_layer.h"
#include "drm_virtual_output.h"
#include <drm_fourcc.h>
namespace KWin
{
DrmQPainterBackend::DrmQPainterBackend(DrmBackend *backend)
: QPainterBackend()
, m_backend(backend)
{
m_backend->setRenderBackend(this);
m_backend->createLayers();
}
DrmQPainterBackend::~DrmQPainterBackend()
{
m_backend->releaseBuffers();
m_backend->setRenderBackend(nullptr);
}
DrmDevice *DrmQPainterBackend::drmDevice() const
{
return m_backend->primaryGpu()->drmDevice();
}
bool DrmQPainterBackend::present(Output *output, const std::shared_ptr<OutputFrame> &frame)
{
return static_cast<DrmAbstractOutput *>(output)->present(frame);
}
void DrmQPainterBackend::repairPresentation(Output *output)
{
// read back drm properties, most likely our info is out of date somehow
// or we need a modeset
QTimer::singleShot(0, m_backend, &DrmBackend::updateOutputs);
}
OutputLayer *DrmQPainterBackend::primaryLayer(Output *output)
{
return static_cast<DrmAbstractOutput *>(output)->primaryLayer();
}
OutputLayer *DrmQPainterBackend::cursorLayer(Output *output)
{
return static_cast<DrmAbstractOutput *>(output)->cursorLayer();
}
std::shared_ptr<DrmPipelineLayer> DrmQPainterBackend::createDrmPlaneLayer(DrmPipeline *pipeline, DrmPlane::TypeIndex type)
{
return std::make_shared<DrmQPainterLayer>(pipeline, type);
}
std::shared_ptr<DrmOutputLayer> DrmQPainterBackend::createLayer(DrmVirtualOutput *output)
{
return std::make_shared<DrmVirtualQPainterLayer>(output);
}
}
#include "moc_drm_qpainter_backend.cpp"
@@ -0,0 +1,44 @@
/*
KWin - the KDE window manager
This file is part of the KDE project.
SPDX-FileCopyrightText: 2015 Martin Gräßlin <mgraesslin@kde.org>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#pragma once
#include "drm_render_backend.h"
#include "platformsupport/scenes/qpainter/qpainterbackend.h"
#include <QList>
#include <QObject>
namespace KWin
{
class DrmBackend;
class DrmAbstractOutput;
class DrmQPainterLayer;
class DrmPipeline;
class DrmQPainterBackend : public QPainterBackend, public DrmRenderBackend
{
Q_OBJECT
public:
DrmQPainterBackend(DrmBackend *backend);
~DrmQPainterBackend();
DrmDevice *drmDevice() const override;
bool present(Output *output, const std::shared_ptr<OutputFrame> &frame) override;
void repairPresentation(Output *output) override;
OutputLayer *primaryLayer(Output *output) override;
OutputLayer *cursorLayer(Output *output) override;
std::shared_ptr<DrmPipelineLayer> createDrmPlaneLayer(DrmPipeline *pipeline, DrmPlane::TypeIndex type) override;
std::shared_ptr<DrmOutputLayer> createLayer(DrmVirtualOutput *output) override;
private:
DrmBackend *m_backend;
};
}
@@ -0,0 +1,150 @@
/*
KWin - the KDE window manager
This file is part of the KDE project.
SPDX-FileCopyrightText: 2022 Xaver Hugl <xaver.hugl@gmail.com>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "drm_qpainter_layer.h"
#include "core/graphicsbufferview.h"
#include "drm_buffer.h"
#include "drm_gpu.h"
#include "drm_logging.h"
#include "drm_pipeline.h"
#include "drm_virtual_output.h"
#include "platformsupport/scenes/qpainter/qpainterswapchain.h"
#include <cerrno>
#include <drm_fourcc.h>
namespace KWin
{
DrmQPainterLayer::DrmQPainterLayer(DrmPipeline *pipeline, DrmPlane::TypeIndex type)
: DrmPipelineLayer(pipeline, type)
{
}
std::optional<OutputLayerBeginFrameInfo> DrmQPainterLayer::doBeginFrame()
{
if (!doesSwapchainFit()) {
m_swapchain = std::make_shared<QPainterSwapchain>(m_pipeline->gpu()->drmDevice()->allocator(), targetRect().size(), m_pipeline->formats(m_type).contains(DRM_FORMAT_ARGB8888) ? DRM_FORMAT_ARGB8888 : DRM_FORMAT_XRGB8888);
m_damageJournal = DamageJournal();
}
m_currentBuffer = m_swapchain->acquire();
if (!m_currentBuffer) {
return std::nullopt;
}
m_renderTime = std::make_unique<CpuRenderTimeQuery>();
const QRegion repaint = m_damageJournal.accumulate(m_currentBuffer->age(), infiniteRegion());
return OutputLayerBeginFrameInfo{
.renderTarget = RenderTarget(m_currentBuffer->view()->image()),
.repaint = repaint,
};
}
bool DrmQPainterLayer::doEndFrame(const QRegion &renderedRegion, const QRegion &damagedRegion, OutputFrame *frame)
{
m_renderTime->end();
if (frame) {
frame->addRenderTimeQuery(std::move(m_renderTime));
}
m_currentFramebuffer = m_pipeline->gpu()->importBuffer(m_currentBuffer->buffer(), FileDescriptor{});
m_damageJournal.add(damagedRegion);
m_swapchain->release(m_currentBuffer);
if (!m_currentFramebuffer) {
qCWarning(KWIN_DRM, "Failed to create dumb framebuffer: %s", strerror(errno));
}
return m_currentFramebuffer != nullptr;
}
bool DrmQPainterLayer::checkTestBuffer()
{
if (!doesSwapchainFit()) {
m_swapchain = std::make_shared<QPainterSwapchain>(m_pipeline->gpu()->drmDevice()->allocator(), targetRect().size(), m_pipeline->formats(m_type).contains(DRM_FORMAT_ARGB8888) ? DRM_FORMAT_ARGB8888 : DRM_FORMAT_XRGB8888);
m_currentBuffer = m_swapchain->acquire();
if (m_currentBuffer) {
m_currentFramebuffer = m_pipeline->gpu()->importBuffer(m_currentBuffer->buffer(), FileDescriptor{});
m_swapchain->release(m_currentBuffer);
if (!m_currentFramebuffer) {
qCWarning(KWIN_DRM, "Failed to create dumb framebuffer: %s", strerror(errno));
}
} else {
m_currentFramebuffer.reset();
}
}
return m_currentFramebuffer != nullptr;
}
bool DrmQPainterLayer::doesSwapchainFit() const
{
return m_swapchain && m_swapchain->size() == targetRect().size();
}
std::shared_ptr<DrmFramebuffer> DrmQPainterLayer::currentBuffer() const
{
return m_currentFramebuffer;
}
void DrmQPainterLayer::releaseBuffers()
{
m_swapchain.reset();
}
DrmDevice *DrmQPainterLayer::scanoutDevice() const
{
return m_pipeline->gpu()->drmDevice();
}
QHash<uint32_t, QList<uint64_t>> DrmQPainterLayer::supportedDrmFormats() const
{
return m_pipeline->formats(m_type);
}
QList<QSize> DrmQPainterLayer::recommendedSizes() const
{
return m_pipeline->recommendedSizes(m_type);
}
DrmVirtualQPainterLayer::DrmVirtualQPainterLayer(DrmVirtualOutput *output)
: DrmOutputLayer(output)
{
}
std::optional<OutputLayerBeginFrameInfo> DrmVirtualQPainterLayer::doBeginFrame()
{
if (m_image.isNull() || m_image.size() != m_output->modeSize()) {
m_image = QImage(m_output->modeSize(), QImage::Format_RGB32);
}
m_renderTime = std::make_unique<CpuRenderTimeQuery>();
return OutputLayerBeginFrameInfo{
.renderTarget = RenderTarget(&m_image),
.repaint = QRegion(),
};
}
bool DrmVirtualQPainterLayer::doEndFrame(const QRegion &renderedRegion, const QRegion &damagedRegion, OutputFrame *frame)
{
m_renderTime->end();
frame->addRenderTimeQuery(std::move(m_renderTime));
return true;
}
void DrmVirtualQPainterLayer::releaseBuffers()
{
}
DrmDevice *DrmVirtualQPainterLayer::scanoutDevice() const
{
// TODO make this use GraphicsBuffers too?
return nullptr;
}
QHash<uint32_t, QList<uint64_t>> DrmVirtualQPainterLayer::supportedDrmFormats() const
{
return {{DRM_FORMAT_ARGB8888, QList<uint64_t>{DRM_FORMAT_MOD_LINEAR}}};
}
}
@@ -0,0 +1,66 @@
/*
KWin - the KDE window manager
This file is part of the KDE project.
SPDX-FileCopyrightText: 2022 Xaver Hugl <xaver.hugl@gmail.com>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#pragma once
#include "core/renderbackend.h"
#include "drm_layer.h"
#include "utils/damagejournal.h"
#include <QImage>
namespace KWin
{
class QPainterSwapchain;
class QPainterSwapchainSlot;
class DrmPipeline;
class DrmVirtualOutput;
class DrmQPainterBackend;
class DrmFramebuffer;
class DrmQPainterLayer : public DrmPipelineLayer
{
public:
explicit DrmQPainterLayer(DrmPipeline *pipeline, DrmPlane::TypeIndex type);
std::optional<OutputLayerBeginFrameInfo> doBeginFrame() override;
bool doEndFrame(const QRegion &renderedRegion, const QRegion &damagedRegion, OutputFrame *frame) override;
bool checkTestBuffer() override;
std::shared_ptr<DrmFramebuffer> currentBuffer() const override;
void releaseBuffers() override;
DrmDevice *scanoutDevice() const override;
QHash<uint32_t, QList<uint64_t>> supportedDrmFormats() const override;
QList<QSize> recommendedSizes() const override;
private:
bool doesSwapchainFit() const;
std::shared_ptr<QPainterSwapchain> m_swapchain;
std::shared_ptr<QPainterSwapchainSlot> m_currentBuffer;
std::shared_ptr<DrmFramebuffer> m_currentFramebuffer;
DamageJournal m_damageJournal;
std::unique_ptr<CpuRenderTimeQuery> m_renderTime;
};
class DrmVirtualQPainterLayer : public DrmOutputLayer
{
public:
explicit DrmVirtualQPainterLayer(DrmVirtualOutput *output);
std::optional<OutputLayerBeginFrameInfo> doBeginFrame() override;
bool doEndFrame(const QRegion &renderedRegion, const QRegion &damagedRegion, OutputFrame *frame) override;
void releaseBuffers() override;
DrmDevice *scanoutDevice() const override;
QHash<uint32_t, QList<uint64_t>> supportedDrmFormats() const override;
private:
QImage m_image;
std::unique_ptr<CpuRenderTimeQuery> m_renderTime;
};
}
@@ -0,0 +1,32 @@
/*
KWin - the KDE window manager
This file is part of the KDE project.
SPDX-FileCopyrightText: 2022 Xaver Hugl <xaver.hugl@gmail.com>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#pragma once
#include "drm_plane.h"
#include <memory>
namespace KWin
{
class DrmPipelineLayer;
class DrmVirtualOutput;
class DrmPipeline;
class DrmOutputLayer;
class DrmOverlayLayer;
class DrmRenderBackend
{
public:
virtual ~DrmRenderBackend() = default;
virtual std::shared_ptr<DrmPipelineLayer> createDrmPlaneLayer(DrmPipeline *pipeline, DrmPlane::TypeIndex type) = 0;
virtual std::shared_ptr<DrmOutputLayer> createLayer(DrmVirtualOutput *output) = 0;
};
}
@@ -0,0 +1,181 @@
/*
KWin - the KDE window manager
This file is part of the KDE project.
SPDX-FileCopyrightText: 2022 Xaver Hugl <xaver.hugl@gmail.com>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "drm_virtual_egl_layer.h"
#include "drm_egl_backend.h"
#include "drm_gpu.h"
#include "drm_logging.h"
#include "drm_virtual_output.h"
#include "opengl/eglnativefence.h"
#include "opengl/eglswapchain.h"
#include "opengl/glrendertimequery.h"
#include "scene/surfaceitem_wayland.h"
#include "wayland/surface.h"
#include <QRegion>
#include <drm_fourcc.h>
#include <errno.h>
#include <gbm.h>
#include <unistd.h>
namespace KWin
{
VirtualEglGbmLayer::VirtualEglGbmLayer(EglGbmBackend *eglBackend, DrmVirtualOutput *output)
: DrmOutputLayer(output)
, m_eglBackend(eglBackend)
{
}
VirtualEglGbmLayer::~VirtualEglGbmLayer()
{
releaseBuffers();
}
std::optional<OutputLayerBeginFrameInfo> VirtualEglGbmLayer::doBeginFrame()
{
// gbm surface
if (doesGbmSwapchainFit(m_gbmSwapchain.get())) {
m_oldGbmSwapchain.reset();
m_oldDamageJournal.clear();
} else {
if (doesGbmSwapchainFit(m_oldGbmSwapchain.get())) {
m_gbmSwapchain = m_oldGbmSwapchain;
m_damageJournal = m_oldDamageJournal;
} else {
if (const auto swapchain = createGbmSwapchain()) {
m_oldGbmSwapchain = m_gbmSwapchain;
m_oldDamageJournal = m_damageJournal;
m_gbmSwapchain = swapchain;
m_damageJournal = DamageJournal();
} else {
return std::nullopt;
}
}
}
if (!m_eglBackend->openglContext()->makeCurrent()) {
return std::nullopt;
}
auto slot = m_gbmSwapchain->acquire();
if (!slot) {
return std::nullopt;
}
m_currentSlot = slot;
m_query = std::make_unique<GLRenderTimeQuery>(m_eglBackend->openglContextRef());
m_query->begin();
const QRegion repair = m_damageJournal.accumulate(slot->age(), infiniteRegion());
return OutputLayerBeginFrameInfo{
.renderTarget = RenderTarget(slot->framebuffer()),
.repaint = repair,
};
}
bool VirtualEglGbmLayer::doEndFrame(const QRegion &renderedRegion, const QRegion &damagedRegion, OutputFrame *frame)
{
m_query->end();
frame->addRenderTimeQuery(std::move(m_query));
glFlush();
m_damageJournal.add(damagedRegion);
EGLNativeFence releaseFence{m_eglBackend->eglDisplayObject()};
m_gbmSwapchain->release(m_currentSlot, releaseFence.takeFileDescriptor());
return true;
}
std::shared_ptr<EglSwapchain> VirtualEglGbmLayer::createGbmSwapchain() const
{
static bool modifiersEnvSet = false;
static const bool modifiersEnv = qEnvironmentVariableIntValue("KWIN_DRM_USE_MODIFIERS", &modifiersEnvSet) != 0;
const bool allowModifiers = !modifiersEnvSet || modifiersEnv;
const auto tranches = m_eglBackend->tranches();
for (const auto &tranche : tranches) {
for (auto it = tranche.formatTable.constBegin(); it != tranche.formatTable.constEnd(); it++) {
const auto size = m_output->modeSize();
const auto format = it.key();
const auto modifiers = it.value();
if (allowModifiers && !modifiers.isEmpty()) {
if (auto swapchain = EglSwapchain::create(m_eglBackend->gpu()->drmDevice()->allocator(), m_eglBackend->openglContext(), size, format, modifiers)) {
return swapchain;
}
}
static const QList<uint64_t> implicitModifier{DRM_FORMAT_MOD_INVALID};
if (auto swapchain = EglSwapchain::create(m_eglBackend->gpu()->drmDevice()->allocator(), m_eglBackend->openglContext(), size, format, implicitModifier)) {
return swapchain;
}
}
}
qCWarning(KWIN_DRM) << "couldn't create a gbm swapchain for a virtual output!";
return nullptr;
}
bool VirtualEglGbmLayer::doesGbmSwapchainFit(EglSwapchain *swapchain) const
{
return swapchain && swapchain->size() == m_output->modeSize();
}
std::shared_ptr<GLTexture> VirtualEglGbmLayer::texture() const
{
if (m_scanoutBuffer) {
return m_eglBackend->importDmaBufAsTexture(*m_scanoutBuffer->dmabufAttributes());
} else if (m_currentSlot) {
return m_currentSlot->texture();
}
return nullptr;
}
bool VirtualEglGbmLayer::doImportScanoutBuffer(GraphicsBuffer *buffer, const ColorDescription &color, RenderingIntent intent, const std::shared_ptr<OutputFrame> &frame)
{
static bool valid;
static const bool directScanoutDisabled = qEnvironmentVariableIntValue("KWIN_DRM_NO_DIRECT_SCANOUT", &valid) == 1 && valid;
if (directScanoutDisabled) {
return false;
}
if (sourceRect() != targetRect() || targetRect().topLeft() != QPointF(0, 0) || targetRect().size() != m_output->modeSize() || targetRect().size() != buffer->size() || offloadTransform() != OutputTransform::Kind::Normal) {
return false;
}
m_scanoutBuffer = buffer;
m_scanoutColor = color;
return true;
}
void VirtualEglGbmLayer::releaseBuffers()
{
m_eglBackend->openglContext()->makeCurrent();
m_gbmSwapchain.reset();
m_oldGbmSwapchain.reset();
m_currentSlot.reset();
if (m_scanoutBuffer) {
m_scanoutBuffer->unref();
m_scanoutBuffer = nullptr;
}
}
DrmDevice *VirtualEglGbmLayer::scanoutDevice() const
{
return m_eglBackend->drmDevice();
}
QHash<uint32_t, QList<uint64_t>> VirtualEglGbmLayer::supportedDrmFormats() const
{
return m_eglBackend->supportedFormats();
}
const ColorDescription &VirtualEglGbmLayer::colorDescription() const
{
return m_scanoutBuffer ? m_scanoutColor : ColorDescription::sRGB;
}
}
@@ -0,0 +1,63 @@
/*
KWin - the KDE window manager
This file is part of the KDE project.
SPDX-FileCopyrightText: 2022 Xaver Hugl <xaver.hugl@gmail.com>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#pragma once
#include "core/graphicsbuffer.h"
#include "drm_layer.h"
#include "utils/damagejournal.h"
#include <QMap>
#include <QPointer>
#include <QRegion>
#include <epoxy/egl.h>
#include <optional>
namespace KWin
{
class EglSwapchain;
class EglSwapchainSlot;
class GLTexture;
class EglGbmBackend;
class DrmVirtualOutput;
class GLRenderTimeQuery;
class SurfaceInterface;
class VirtualEglGbmLayer : public DrmOutputLayer
{
public:
VirtualEglGbmLayer(EglGbmBackend *eglBackend, DrmVirtualOutput *output);
~VirtualEglGbmLayer() override;
std::optional<OutputLayerBeginFrameInfo> doBeginFrame() override;
bool doEndFrame(const QRegion &renderedRegion, const QRegion &damagedRegion, OutputFrame *frame) override;
std::shared_ptr<GLTexture> texture() const override;
void releaseBuffers() override;
DrmDevice *scanoutDevice() const override;
QHash<uint32_t, QList<uint64_t>> supportedDrmFormats() const override;
const ColorDescription &colorDescription() const;
private:
bool doImportScanoutBuffer(GraphicsBuffer *buffer, const ColorDescription &color, RenderingIntent intent, const std::shared_ptr<OutputFrame> &frame) override;
std::shared_ptr<EglSwapchain> createGbmSwapchain() const;
bool doesGbmSwapchainFit(EglSwapchain *swapchain) const;
GraphicsBufferRef m_scanoutBuffer;
ColorDescription m_scanoutColor = ColorDescription::sRGB;
DamageJournal m_damageJournal;
DamageJournal m_oldDamageJournal;
std::shared_ptr<EglSwapchain> m_gbmSwapchain;
std::shared_ptr<EglSwapchain> m_oldGbmSwapchain;
std::shared_ptr<EglSwapchainSlot> m_currentSlot;
std::unique_ptr<GLRenderTimeQuery> m_query;
EglGbmBackend *const m_eglBackend;
};
}
@@ -0,0 +1,90 @@
/*
KWin - the KDE window manager
This file is part of the KDE project.
SPDX-FileCopyrightText: 2018 Roman Gilg <subdiff@gmail.com>
SPDX-FileCopyrightText: 2021 Xaver Hugl <xaver.hugl@gmail.com>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "drm_virtual_output.h"
#include "core/renderbackend.h"
#include "drm_backend.h"
#include "drm_gpu.h"
#include "drm_layer.h"
#include "drm_render_backend.h"
#include "utils/softwarevsyncmonitor.h"
namespace KWin
{
DrmVirtualOutput::DrmVirtualOutput(DrmBackend *backend, const QString &name, const QString &description, const QSize &size, qreal scale)
: m_backend(backend)
, m_vsyncMonitor(SoftwareVsyncMonitor::create())
{
connect(m_vsyncMonitor.get(), &VsyncMonitor::vblankOccurred, this, &DrmVirtualOutput::vblank);
auto mode = std::make_shared<OutputMode>(size, 60000, OutputMode::Flag::Preferred);
m_renderLoop->setRefreshRate(mode->refreshRate());
setInformation(Information{
.name = QStringLiteral("Virtual-") + name,
.model = description,
.physicalSize = size,
});
setState(State{
.scale = scale,
.modes = {mode},
.currentMode = mode,
});
recreateSurface();
}
DrmVirtualOutput::~DrmVirtualOutput()
{
}
bool DrmVirtualOutput::present(const std::shared_ptr<OutputFrame> &frame)
{
m_frame = frame;
m_vsyncMonitor->arm();
Q_EMIT outputChange(frame->damage());
return true;
}
void DrmVirtualOutput::vblank(std::chrono::nanoseconds timestamp)
{
if (m_frame) {
m_frame->presented(timestamp, PresentationMode::VSync);
m_frame.reset();
}
}
void DrmVirtualOutput::setDpmsMode(DpmsMode mode)
{
State next = m_state;
next.dpmsMode = mode;
setState(next);
}
DrmOutputLayer *DrmVirtualOutput::primaryLayer() const
{
return m_layer.get();
}
DrmOutputLayer *DrmVirtualOutput::cursorLayer() const
{
return nullptr;
}
void DrmVirtualOutput::recreateSurface()
{
m_layer = m_backend->renderBackend()->createLayer(this);
}
}
#include "moc_drm_virtual_output.cpp"
@@ -0,0 +1,48 @@
/*
KWin - the KDE window manager
This file is part of the KDE project.
SPDX-FileCopyrightText: 2018 Roman Gilg <subdiff@gmail.com>
SPDX-FileCopyrightText: 2021 Xaver Hugl <xaver.hugl@gmail.com>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#pragma once
#include "drm_abstract_output.h"
#include <QObject>
#include <QRect>
namespace KWin
{
class SoftwareVsyncMonitor;
class VirtualBackend;
class DrmPipelineLayer;
class DrmVirtualOutput : public DrmAbstractOutput
{
Q_OBJECT
public:
explicit DrmVirtualOutput(DrmBackend *backend, const QString &name, const QString &description, const QSize &size, qreal scale);
~DrmVirtualOutput() override;
bool present(const std::shared_ptr<OutputFrame> &frame) override;
DrmOutputLayer *primaryLayer() const override;
DrmOutputLayer *cursorLayer() const override;
void recreateSurface();
private:
void vblank(std::chrono::nanoseconds timestamp);
void setDpmsMode(DpmsMode mode) override;
DrmBackend *const m_backend;
std::shared_ptr<DrmOutputLayer> m_layer;
std::shared_ptr<OutputFrame> m_frame;
std::unique_ptr<SoftwareVsyncMonitor> m_vsyncMonitor;
};
}
@@ -0,0 +1,51 @@
Documentation on the drm/kms API is sparse and split up in a few places and files, mostly in the Linux kernel code.
This file is meant to provide an overview for drm/kms and gbm to provide a starting point for understanding how the APIs and the drm backend work.
# drm/kms
In the drm API there is a few objects that we care about:
- `connector`s. A drm connector represents either an actual physical connector out of your graphics card or, in the case of DisplayPort MultiStream, a virtual one
- `crtc`s. A drm crtc represents hardware that can drive a connector uniquely - be it physical or virtual. Crtcs can also drive multiple connectors by cloning, aka showing the same stuff on all of them. The number of crtcs on your GPU or display device is a hard limit of how many display you can drive with it
- `plane`s. A drm plane represents scanout hardware that can be used for hardware composition, so cropping, scaling and rotation. There is multiple types of planes:
* `primary`: it can be asssumed that without the primary plane the output won't work. The kernel will always expose one per crtc
* `cursor`: cursor planes are what they sound like, they allow to use special hardware built for cursors. They have special restrictions like size (DRM_CAP_CURSOR_WIDTH, DRM_CAP_CURSOR_HEIGHT), scaling (on AMD gpus its scaling must be the same as with the primary scale) and even position on some very funky hardware (Apple stuff). Cursor planes are always optional
* `overlay`: overlay planes are what they sound like as well, they allow to use special hardware built for overlays (or underlays). The restrictions on this are of arbitrary complexity, you can never just assume they work. Overlay planes are also always optional
- `framebuffer`s, fb for short. These represent some sort of buffer that we'd like to show up on the screen and have to be created and destroyed by us
- `encoder`s. Can effectively be ignored, they were exposed in the API more or less by mistake and are just there for backwards compatibility
All drm objects have properties with a name and a value. Depending on the type this value can have a different meaning; some are a bitfield, some are just integer values and some are for arbitrary data blobs. Properties can be read-only (immutable) and are only informational, some are needed for functionality.
There's two APIs for drm, legacy and atomic modesetting (AMS).
Legacy only exposes connectors and crtcs, and only some of their properties. You first enable a connector and set a mode with `drmModeSetCrtc` and then push new frames with `drmModePageFlip`. `drmModePageFlip` has two flags we care about:
- `DRM_MODE_PAGE_FLIP_EVENT` tells the kernel to generate a page flip event for the crtc, which tells us when the new framebuffer has actually been set / when the old one is not needed anymore
- `DRM_MODE_PAGE_FLIP_ASYNC` tells the kernel that it should immediately apply the new framebuffer without waiting. This may cause tearing
For dynamic power management (dpms) you set the dpms property with `drmModeObjectSetProperty` and the kernel will handle the rest behind the scenes, or fail the request. Same story with `VRR_ENABLED`, `overscan` and similar.
With atomic modesetting all objects and properties are exposed. AMS works very differently from legacy: it has one generic function `drmModeAtomicCommit` that is used for pretty much everything. How this function works is that you first fill a `drmModeAtomicReq` with the properties you want to set, then you call `drmModeAtomicCommit` with some combination of flags. These flags decide on what the function actually does:
- `DRM_MODE_ATOMIC_TEST_ONLY` only tests whether or not the configuration would work but is guaranteed to not change anything
- `DRM_MODE_PAGE_FLIP_EVENT` tells the kernel that it should generate a page flip event for all crtcs that we change in the commit
- `DRM_MODE_ATOMIC_NONBLOCK` tells the kernel to make this function not blocking; it should not wait until things are actually applied before returning
- `DRM_MODE_ATOMIC_ALLOW_MODESET` tells the kernel that it is allowed to make our changes happen with a modeset, that is an event that can cause the display(s) to flicker or black out for a moment
- `DRM_MODE_PAGE_FLIP_ASYNC` is currently *not* supported. All requests with this flag set fail
Some upstream documentation can be found at https://www.kernel.org/doc/html/latest/gpu/drm-kms.html, https://01.org/linuxgraphics/gfx-docs/drm/drm-kms-properties.html and in the files at https://github.com/torvalds/linux/tree/master/drivers/gpu/drm.
For a lot of documentation on properties and capabilities of devices there's also https://drmdb.emersion.fr/
# gbm
The generic buffer manager API allows us to allocate buffers in graphics memory with a few properties. It's a relatively straight forward API:
- `gbm_bo` is a gbm buffer. It can be manually created and destroyed
- `gbm_surface` is a gbm surface, which allows us to create an egl surface that's using gbm buffers. With it we can render in egl and then create framebuffers from the things rendered in egl and present them on the display
- the `GBM_FORMAT_*` defines are just copies of the `DRM_FORMAT_*` defines in drm_fourcc.h and describe a buffer format. For example `DRM_FORMAT_XRGB8888` describes a buffer with 8 bits of red, 8 bits of green, 8 bits of blue and 8 bits of unused alpha (that's what the `X` stands for). Do not use the `GBM_BO_FORMAT_*` enum, it can cause problems! In general, ignore the buffer formats from the gbm header and instead use what drm_fourcc.h provides
- modifiers describe the actual memory layout that needs to be assumed for accessing the buffer. Older drivers like `radeon` don't support modifiers at all, on the other end of the spectrum the NVidia driver requires them. When we don't use functions that have us explicitly provide modifiers that's called an "implicit modifier" - that means the driver automatically picks a modifier for the use case. With implicit modifiers we have no guarantees about multi-gpu compatibility by default, instead the `GBM_BO_USE_LINEAR` usage flag has to be set when creating the buffer to enforce a linear format that all drivers can access without messing up the image
For gbm most of the upstream documentation is contained in https://gitlab.freedesktop.org/mesa/mesa/-/blob/main/src/gbm/main/gbm.c
@@ -0,0 +1,4 @@
target_sources(kwin PRIVATE
fakeinputbackend.cpp
fakeinputdevice.cpp
)
@@ -0,0 +1,299 @@
/*
SPDX-FileCopyrightText: 2021 Vlad Zahorodnii <vlad.zahorodnii@kde.org>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "fakeinputbackend.h"
#include "fakeinputdevice.h"
#include "wayland/display.h"
#include "wayland/qwayland-server-fake-input.h"
namespace KWin
{
static const quint32 s_version = 5;
class FakeInputBackendPrivate : public QtWaylandServer::org_kde_kwin_fake_input
{
public:
FakeInputBackendPrivate(FakeInputBackend *q, Display *display);
using FakeInputDeviceMap = std::map<Resource *, std::unique_ptr<FakeInputDevice>>;
FakeInputDevice *findDevice(Resource *resource);
void removeDeviceByResource(Resource *resource);
void removeDeviceByIterator(FakeInputDeviceMap::iterator it);
std::chrono::microseconds currentTime() const;
FakeInputBackend *q;
Display *display;
FakeInputDeviceMap devices;
protected:
void org_kde_kwin_fake_input_bind_resource(Resource *resource) override;
void org_kde_kwin_fake_input_destroy_resource(Resource *resource) override;
void org_kde_kwin_fake_input_authenticate(Resource *resource, const QString &application, const QString &reason) override;
void org_kde_kwin_fake_input_pointer_motion(Resource *resource, wl_fixed_t delta_x, wl_fixed_t delta_y) override;
void org_kde_kwin_fake_input_button(Resource *resource, uint32_t button, uint32_t state) override;
void org_kde_kwin_fake_input_axis(Resource *resource, uint32_t axis, wl_fixed_t value) override;
void org_kde_kwin_fake_input_touch_down(Resource *resource, uint32_t id, wl_fixed_t x, wl_fixed_t y) override;
void org_kde_kwin_fake_input_touch_motion(Resource *resource, uint32_t id, wl_fixed_t x, wl_fixed_t y) override;
void org_kde_kwin_fake_input_touch_up(Resource *resource, uint32_t id) override;
void org_kde_kwin_fake_input_touch_cancel(Resource *resource) override;
void org_kde_kwin_fake_input_touch_frame(Resource *resource) override;
void org_kde_kwin_fake_input_pointer_motion_absolute(Resource *resource, wl_fixed_t x, wl_fixed_t y) override;
void org_kde_kwin_fake_input_keyboard_key(Resource *resource, uint32_t button, uint32_t state) override;
void org_kde_kwin_fake_input_destroy(Resource *resource) override;
};
FakeInputBackendPrivate::FakeInputBackendPrivate(FakeInputBackend *q, Display *display)
: q(q)
, display(display)
{
}
void FakeInputBackendPrivate::org_kde_kwin_fake_input_bind_resource(Resource *resource)
{
auto device = new FakeInputDevice(q);
devices[resource] = std::unique_ptr<FakeInputDevice>(device);
Q_EMIT q->deviceAdded(device);
}
void FakeInputBackendPrivate::org_kde_kwin_fake_input_destroy(Resource *resource)
{
wl_resource_destroy(resource->handle);
}
void FakeInputBackendPrivate::org_kde_kwin_fake_input_destroy_resource(Resource *resource)
{
removeDeviceByResource(resource);
}
FakeInputDevice *FakeInputBackendPrivate::findDevice(Resource *resource)
{
return devices[resource].get();
}
void FakeInputBackendPrivate::removeDeviceByResource(Resource *resource)
{
if (const auto it = devices.find(resource); it != devices.end()) {
removeDeviceByIterator(it);
}
}
void FakeInputBackendPrivate::removeDeviceByIterator(FakeInputDeviceMap::iterator it)
{
const auto device = std::move(it->second);
for (const auto button : device->pressedButtons) {
Q_EMIT device->pointerButtonChanged(button, PointerButtonState::Released, currentTime(), device.get());
}
for (const auto key : device->pressedKeys) {
Q_EMIT device->keyChanged(key, KeyboardKeyState::Released, currentTime(), device.get());
}
if (!device->activeTouches.empty()) {
Q_EMIT device->touchCanceled(device.get());
}
devices.erase(it);
Q_EMIT q->deviceRemoved(device.get());
}
std::chrono::microseconds FakeInputBackendPrivate::currentTime() const
{
return std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::steady_clock::now().time_since_epoch());
}
void FakeInputBackendPrivate::org_kde_kwin_fake_input_authenticate(Resource *resource, const QString &application, const QString &reason)
{
FakeInputDevice *device = findDevice(resource);
if (device) {
// TODO: make secure
device->setAuthenticated(true);
}
}
void FakeInputBackendPrivate::org_kde_kwin_fake_input_pointer_motion(Resource *resource, wl_fixed_t delta_x, wl_fixed_t delta_y)
{
FakeInputDevice *device = findDevice(resource);
if (!device->isAuthenticated()) {
return;
}
const QPointF delta(wl_fixed_to_double(delta_x), wl_fixed_to_double(delta_y));
Q_EMIT device->pointerMotion(delta, delta, currentTime(), device);
Q_EMIT device->pointerFrame(device);
}
void FakeInputBackendPrivate::org_kde_kwin_fake_input_button(Resource *resource, uint32_t button, uint32_t state)
{
FakeInputDevice *device = findDevice(resource);
if (!device->isAuthenticated()) {
return;
}
PointerButtonState nativeState;
switch (state) {
case WL_POINTER_BUTTON_STATE_PRESSED:
nativeState = PointerButtonState::Pressed;
if (device->pressedButtons.contains(button)) {
return;
}
device->pressedButtons.insert(button);
break;
case WL_POINTER_BUTTON_STATE_RELEASED:
nativeState = PointerButtonState::Released;
if (!device->pressedButtons.remove(button)) {
return;
}
break;
default:
return;
}
Q_EMIT device->pointerButtonChanged(button, nativeState, currentTime(), device);
Q_EMIT device->pointerFrame(device);
}
void FakeInputBackendPrivate::org_kde_kwin_fake_input_axis(Resource *resource, uint32_t axis, wl_fixed_t value)
{
FakeInputDevice *device = findDevice(resource);
if (!device->isAuthenticated()) {
return;
}
PointerAxis nativeAxis;
switch (axis) {
case WL_POINTER_AXIS_HORIZONTAL_SCROLL:
nativeAxis = PointerAxis::Horizontal;
break;
case WL_POINTER_AXIS_VERTICAL_SCROLL:
nativeAxis = PointerAxis::Vertical;
break;
default:
return;
}
Q_EMIT device->pointerAxisChanged(nativeAxis, wl_fixed_to_double(value), 0, PointerAxisSource::Unknown, false, currentTime(), device);
Q_EMIT device->pointerFrame(device);
}
void FakeInputBackendPrivate::org_kde_kwin_fake_input_touch_down(Resource *resource, uint32_t id, wl_fixed_t x, wl_fixed_t y)
{
FakeInputDevice *device = findDevice(resource);
if (!device->isAuthenticated()) {
return;
}
if (device->activeTouches.contains(id)) {
return;
}
device->activeTouches.insert(id);
Q_EMIT device->touchDown(id, QPointF(wl_fixed_to_double(x), wl_fixed_to_double(y)), currentTime(), device);
}
void FakeInputBackendPrivate::org_kde_kwin_fake_input_touch_motion(Resource *resource, uint32_t id, wl_fixed_t x, wl_fixed_t y)
{
FakeInputDevice *device = findDevice(resource);
if (!device->isAuthenticated()) {
return;
}
if (!device->activeTouches.contains(id)) {
return;
}
Q_EMIT device->touchMotion(id, QPointF(wl_fixed_to_double(x), wl_fixed_to_double(y)), currentTime(), device);
}
void FakeInputBackendPrivate::org_kde_kwin_fake_input_touch_up(Resource *resource, uint32_t id)
{
FakeInputDevice *device = findDevice(resource);
if (!device->isAuthenticated()) {
return;
}
if (device->activeTouches.remove(id)) {
Q_EMIT device->touchUp(id, currentTime(), device);
}
}
void FakeInputBackendPrivate::org_kde_kwin_fake_input_touch_cancel(Resource *resource)
{
FakeInputDevice *device = findDevice(resource);
if (!device->isAuthenticated()) {
return;
}
device->activeTouches.clear();
Q_EMIT device->touchCanceled(device);
}
void FakeInputBackendPrivate::org_kde_kwin_fake_input_touch_frame(Resource *resource)
{
FakeInputDevice *device = findDevice(resource);
if (!device->isAuthenticated()) {
return;
}
Q_EMIT device->touchFrame(device);
}
void FakeInputBackendPrivate::org_kde_kwin_fake_input_pointer_motion_absolute(Resource *resource, wl_fixed_t x, wl_fixed_t y)
{
FakeInputDevice *device = findDevice(resource);
if (!device->isAuthenticated()) {
return;
}
Q_EMIT device->pointerMotionAbsolute(QPointF(wl_fixed_to_double(x), wl_fixed_to_double(y)), currentTime(), device);
Q_EMIT device->pointerFrame(device);
}
void FakeInputBackendPrivate::org_kde_kwin_fake_input_keyboard_key(Resource *resource, uint32_t key, uint32_t state)
{
FakeInputDevice *device = findDevice(resource);
if (!device->isAuthenticated()) {
return;
}
KeyboardKeyState nativeState;
switch (state) {
case WL_KEYBOARD_KEY_STATE_PRESSED:
nativeState = KeyboardKeyState::Pressed;
if (device->pressedKeys.contains(key)) {
return;
}
device->pressedKeys.insert(key);
break;
case WL_KEYBOARD_KEY_STATE_RELEASED:
if (!device->pressedKeys.remove(key)) {
return;
}
nativeState = KeyboardKeyState::Released;
break;
default:
return;
}
Q_EMIT device->keyChanged(key, nativeState, currentTime(), device);
}
FakeInputBackend::FakeInputBackend(Display *display)
: d(std::make_unique<FakeInputBackendPrivate>(this, display))
{
}
FakeInputBackend::~FakeInputBackend()
{
while (!d->devices.empty()) {
d->removeDeviceByIterator(d->devices.begin());
}
}
void FakeInputBackend::initialize()
{
d->init(*d->display, s_version);
}
} // namespace KWin
#include "moc_fakeinputbackend.cpp"
@@ -0,0 +1,35 @@
/*
SPDX-FileCopyrightText: 2021 Vlad Zahorodnii <vlad.zahorodnii@kde.org>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#pragma once
#include "core/inputbackend.h"
#include <map>
#include <memory>
namespace KWin
{
class Display;
class FakeInputBackendPrivate;
class FakeInputDevice;
class FakeInputBackend : public InputBackend
{
Q_OBJECT
public:
explicit FakeInputBackend(Display *display);
~FakeInputBackend();
void initialize() override;
private:
std::unique_ptr<FakeInputBackendPrivate> d;
};
} // namespace KWin
@@ -0,0 +1,85 @@
/*
SPDX-FileCopyrightText: 2021 Vlad Zahorodnii <vlad.zahorodnii@kde.org>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "fakeinputdevice.h"
namespace KWin
{
static int s_lastDeviceId = 0;
FakeInputDevice::FakeInputDevice(QObject *parent)
: InputDevice(parent)
, m_name(QStringLiteral("Fake Input Device %1").arg(++s_lastDeviceId))
{
}
bool FakeInputDevice::isAuthenticated() const
{
return m_authenticated;
}
void FakeInputDevice::setAuthenticated(bool authenticated)
{
m_authenticated = authenticated;
}
QString FakeInputDevice::name() const
{
return m_name;
}
bool FakeInputDevice::isEnabled() const
{
return true;
}
void FakeInputDevice::setEnabled(bool enabled)
{
}
bool FakeInputDevice::isKeyboard() const
{
return true;
}
bool FakeInputDevice::isPointer() const
{
return true;
}
bool FakeInputDevice::isTouchpad() const
{
return false;
}
bool FakeInputDevice::isTouch() const
{
return true;
}
bool FakeInputDevice::isTabletTool() const
{
return false;
}
bool FakeInputDevice::isTabletPad() const
{
return false;
}
bool FakeInputDevice::isTabletModeSwitch() const
{
return false;
}
bool FakeInputDevice::isLidSwitch() const
{
return false;
}
} // namespace KWin
#include "moc_fakeinputdevice.cpp"
@@ -0,0 +1,49 @@
/*
SPDX-FileCopyrightText: 2021 Vlad Zahorodnii <vlad.zahorodnii@kde.org>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#pragma once
#include "core/inputdevice.h"
#include <QSet>
namespace KWin
{
class KWIN_EXPORT FakeInputDevice : public InputDevice
{
Q_OBJECT
public:
explicit FakeInputDevice(QObject *parent = nullptr);
QString name() const override;
bool isEnabled() const override;
void setEnabled(bool enabled) override;
bool isKeyboard() const override;
bool isPointer() const override;
bool isTouchpad() const override;
bool isTouch() const override;
bool isTabletTool() const override;
bool isTabletPad() const override;
bool isTabletModeSwitch() const override;
bool isLidSwitch() const override;
void setAuthenticated(bool authenticated);
bool isAuthenticated() const;
QSet<quint32> pressedButtons;
QSet<quint32> pressedKeys;
QSet<quint32> activeTouches;
private:
QString m_name;
bool m_authenticated = false;
};
} // namespace KWin
@@ -0,0 +1,25 @@
qt_generate_dbus_interface(device.h org.kde.kwin.InputDevice.xml OPTIONS -A)
add_custom_target(
KWinInputDBusInterfaces
ALL
DEPENDS
${CMAKE_CURRENT_BINARY_DIR}/org.kde.kwin.InputDevice.xml
)
install(
FILES
${CMAKE_CURRENT_BINARY_DIR}/org.kde.kwin.InputDevice.xml
DESTINATION
${KDE_INSTALL_DBUSINTERFACEDIR}
)
target_sources(kwin PRIVATE
connection.cpp
context.cpp
device.cpp
events.cpp
libinput_logging.cpp
libinputbackend.cpp
)
target_link_libraries(kwin PRIVATE Libinput::Libinput)
@@ -0,0 +1,703 @@
/*
KWin - the KDE window manager
This file is part of the KDE project.
SPDX-FileCopyrightText: 2014 Martin Gräßlin <mgraesslin@kde.org>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "connection.h"
#include "context.h"
#include "device.h"
#include "events.h"
// TODO: Make it compile also in testing environment
#ifndef KWIN_BUILD_TESTING
#include "core/output.h"
#include "core/outputbackend.h"
#include "main.h"
#include "window.h"
#include "workspace.h"
#endif
#include "core/session.h"
#include "input_event.h"
#include "libinput_logging.h"
#include "utils/realtime.h"
#include "utils/udev.h"
#include <QDBusConnection>
#include <QMutexLocker>
#include <QSocketNotifier>
#include <cmath>
#include <libinput.h>
namespace KWin
{
namespace LibInput
{
class ConnectionAdaptor : public QObject
{
Q_OBJECT
Q_CLASSINFO("D-Bus Interface", "org.kde.KWin.InputDeviceManager")
Q_PROPERTY(QStringList devicesSysNames READ devicesSysNames CONSTANT)
private:
Connection *m_con;
public:
ConnectionAdaptor(Connection *con)
: QObject(con)
, m_con(con)
{
connect(con, &Connection::deviceAdded, this, [this](LibInput::Device *inputDevice) {
Q_EMIT deviceAdded(inputDevice->sysName());
});
connect(con, &Connection::deviceRemoved, this, [this](LibInput::Device *inputDevice) {
Q_EMIT deviceRemoved(inputDevice->sysName());
});
QDBusConnection::sessionBus().registerObject(QStringLiteral("/org/kde/KWin/InputDevice"),
QStringLiteral("org.kde.KWin.InputDeviceManager"),
this,
QDBusConnection::ExportAllProperties | QDBusConnection::ExportAllSignals);
}
~ConnectionAdaptor() override
{
QDBusConnection::sessionBus().unregisterObject(QStringLiteral("/org/kde/KWin/InputDeviceManager"));
}
QStringList devicesSysNames()
{
return m_con->devicesSysNames();
}
Q_SIGNALS:
void deviceAdded(QString sysName);
void deviceRemoved(QString sysName);
};
Connection *Connection::create(Session *session)
{
std::unique_ptr<Udev> udev = std::make_unique<Udev>();
if (!udev->isValid()) {
qCWarning(KWIN_LIBINPUT) << "Failed to initialize udev";
return nullptr;
}
std::unique_ptr<Context> context = std::make_unique<Context>(session, std::move(udev));
if (!context->isValid()) {
qCWarning(KWIN_LIBINPUT) << "Failed to create context from udev";
return nullptr;
}
if (!context->initialize()) {
qCWarning(KWIN_LIBINPUT) << "Failed to initialize context";
return nullptr;
}
return new Connection(std::move(context));
}
Connection::Connection(std::unique_ptr<Context> &&input)
: m_notifier(nullptr)
, m_connectionAdaptor(std::make_unique<ConnectionAdaptor>(this))
, m_input(std::move(input))
{
Q_ASSERT(m_input);
// need to connect to KGlobalSettings as the mouse KCM does not emit a dedicated signal
QDBusConnection::sessionBus().connect(QString(), QStringLiteral("/KGlobalSettings"), QStringLiteral("org.kde.KGlobalSettings"),
QStringLiteral("notifyChange"), this, SLOT(slotKGlobalSettingsNotifyChange(int, int)));
}
Connection::~Connection()
{
for (Device *device : std::as_const(m_devices)) {
Q_EMIT deviceRemoved(device);
}
m_eventQueue.clear();
qDeleteAll(m_devices);
qDeleteAll(m_tools);
}
void Connection::setup()
{
QMetaObject::invokeMethod(this, &Connection::doSetup, Qt::QueuedConnection);
}
void Connection::doSetup()
{
Q_ASSERT(!m_notifier);
gainRealTime();
m_notifier = std::make_unique<QSocketNotifier>(m_input->fileDescriptor(), QSocketNotifier::Read);
connect(m_notifier.get(), &QSocketNotifier::activated, this, &Connection::handleEvent);
connect(m_input->session(), &Session::activeChanged, this, [this](bool active) {
if (active) {
if (!m_input->isSuspended()) {
return;
}
m_input->resume();
} else {
deactivate();
}
});
handleEvent();
}
void Connection::deactivate()
{
if (m_input->isSuspended()) {
return;
}
m_input->suspend();
handleEvent();
}
void Connection::handleEvent()
{
QMutexLocker locker(&m_mutex);
const bool wasEmpty = m_eventQueue.empty();
do {
m_input->dispatch();
std::unique_ptr<Event> event = m_input->event();
if (!event) {
break;
}
m_eventQueue.push_back(std::move(event));
} while (true);
if (wasEmpty && !m_eventQueue.empty()) {
Q_EMIT eventsRead();
}
}
#ifndef KWIN_BUILD_TESTING
QPointF devicePointToGlobalPosition(const QPointF &devicePos, const Output *output)
{
QPointF pos = devicePos;
// TODO: Do we need to handle the flipped cases differently?
switch (output->transform().kind()) {
case OutputTransform::Normal:
case OutputTransform::FlipX:
break;
case OutputTransform::Rotate90:
case OutputTransform::FlipX90:
pos = QPointF(output->modeSize().height() - devicePos.y(), devicePos.x());
break;
case OutputTransform::Rotate180:
case OutputTransform::FlipX180:
pos = QPointF(output->modeSize().width() - devicePos.x(),
output->modeSize().height() - devicePos.y());
break;
case OutputTransform::Rotate270:
case OutputTransform::FlipX270:
pos = QPointF(devicePos.y(), output->modeSize().width() - devicePos.x());
break;
default:
Q_UNREACHABLE();
}
return output->geometry().topLeft() + pos / output->scale();
}
#endif
static QPointF tabletToolPosition(TabletToolEvent *event)
{
#ifndef KWIN_BUILD_TESTING
if (event->device()->isMapToWorkspace()) {
return workspace()->geometry().topLeft() + event->transformedPosition(workspace()->geometry().size());
} else {
Output *output = event->device()->output();
if (!output) {
output = workspace()->activeOutput();
}
return devicePointToGlobalPosition(event->transformedPosition(output->modeSize()), output);
}
#else
return QPointF();
#endif
}
TabletTool *Connection::getOrCreateTool(libinput_tablet_tool *handle)
{
for (TabletTool *tool : std::as_const(m_tools)) {
if (tool->handle() == handle) {
return tool;
}
}
auto tool = new TabletTool(handle);
tool->moveToThread(thread());
m_tools.append(tool);
return tool;
}
void Connection::processEvents()
{
QMutexLocker locker(&m_mutex);
while (m_eventQueue.size() != 0) {
std::unique_ptr<Event> event = std::move(m_eventQueue.front());
m_eventQueue.pop_front();
switch (event->type()) {
case LIBINPUT_EVENT_DEVICE_ADDED: {
auto device = new Device(event->nativeDevice());
device->moveToThread(thread());
m_devices << device;
applyDeviceConfig(device);
applyScreenToDevice(device);
connect(device, &Device::outputNameChanged, this, [this, device] {
// If the output name changes from something to empty we need to
// re-run the assignment heuristic so that an output is assinged
if (device->outputName().isEmpty()) {
applyScreenToDevice(device);
}
});
Q_EMIT deviceAdded(device);
break;
}
case LIBINPUT_EVENT_DEVICE_REMOVED: {
auto it = std::ranges::find_if(std::as_const(m_devices), [&event](Device *d) {
return event->device() == d;
});
if (it == m_devices.cend()) {
// we don't know this device
break;
}
auto device = *it;
m_devices.erase(it);
Q_EMIT deviceRemoved(device);
device->deleteLater();
break;
}
case LIBINPUT_EVENT_KEYBOARD_KEY: {
KeyEvent *ke = static_cast<KeyEvent *>(event.get());
const int seatKeyCount = libinput_event_keyboard_get_seat_key_count(*ke);
const int keyState = libinput_event_keyboard_get_key_state(*ke);
if ((keyState == LIBINPUT_KEY_STATE_PRESSED && seatKeyCount != 1) ||
(keyState == LIBINPUT_KEY_STATE_RELEASED && seatKeyCount != 0)) {
break;
}
Q_EMIT ke->device()->keyChanged(ke->key(), ke->state(), ke->time(), ke->device());
break;
}
case LIBINPUT_EVENT_POINTER_SCROLL_WHEEL: {
const PointerEvent *pointerEvent = static_cast<PointerEvent *>(event.get());
const auto axes = pointerEvent->axis();
for (const PointerAxis &axis : axes) {
Q_EMIT pointerEvent->device()->pointerAxisChanged(axis,
pointerEvent->scrollValue(axis),
pointerEvent->scrollValueV120(axis),
PointerAxisSource::Wheel,
pointerEvent->device()->isNaturalScroll(),
pointerEvent->time(),
pointerEvent->device());
}
Q_EMIT pointerEvent->device()->pointerFrame(pointerEvent->device());
break;
}
case LIBINPUT_EVENT_POINTER_SCROLL_FINGER: {
const PointerEvent *pointerEvent = static_cast<PointerEvent *>(event.get());
const auto axes = pointerEvent->axis();
for (const PointerAxis &axis : axes) {
Q_EMIT pointerEvent->device()->pointerAxisChanged(axis,
pointerEvent->scrollValue(axis),
0,
PointerAxisSource::Finger,
pointerEvent->device()->isNaturalScroll(),
pointerEvent->time(),
pointerEvent->device());
}
Q_EMIT pointerEvent->device()->pointerFrame(pointerEvent->device());
break;
}
case LIBINPUT_EVENT_POINTER_SCROLL_CONTINUOUS: {
const PointerEvent *pointerEvent = static_cast<PointerEvent *>(event.get());
const auto axes = pointerEvent->axis();
for (const PointerAxis &axis : axes) {
Q_EMIT pointerEvent->device()->pointerAxisChanged(axis,
pointerEvent->scrollValue(axis),
0,
PointerAxisSource::Continuous,
pointerEvent->device()->isNaturalScroll(),
pointerEvent->time(),
pointerEvent->device());
}
Q_EMIT pointerEvent->device()->pointerFrame(pointerEvent->device());
break;
}
case LIBINPUT_EVENT_POINTER_BUTTON: {
PointerEvent *pe = static_cast<PointerEvent *>(event.get());
const int seatButtonCount = libinput_event_pointer_get_seat_button_count(*pe);
const int buttonState = libinput_event_pointer_get_button_state(*pe);
if ((buttonState == LIBINPUT_BUTTON_STATE_PRESSED && seatButtonCount != 1) ||
(buttonState == LIBINPUT_BUTTON_STATE_RELEASED && seatButtonCount != 0)) {
break;
}
Q_EMIT pe->device()->pointerButtonChanged(pe->button(), pe->buttonState(), pe->time(), pe->device());
Q_EMIT pe->device()->pointerFrame(pe->device());
break;
}
case LIBINPUT_EVENT_POINTER_MOTION: {
PointerEvent *pe = static_cast<PointerEvent *>(event.get());
auto delta = pe->delta();
auto deltaNonAccel = pe->deltaUnaccelerated();
auto latestTime = pe->time();
auto it = m_eventQueue.begin();
while (it != m_eventQueue.end()) {
if ((*it)->type() == LIBINPUT_EVENT_POINTER_MOTION) {
std::unique_ptr<PointerEvent> p{static_cast<PointerEvent *>(it->release())};
delta += p->delta();
deltaNonAccel += p->deltaUnaccelerated();
latestTime = p->time();
it = m_eventQueue.erase(it);
} else {
break;
}
}
Q_EMIT pe->device()->pointerMotion(delta, deltaNonAccel, latestTime, pe->device());
Q_EMIT pe->device()->pointerFrame(pe->device());
break;
}
case LIBINPUT_EVENT_POINTER_MOTION_ABSOLUTE: {
PointerEvent *pe = static_cast<PointerEvent *>(event.get());
if (workspace()) {
Q_EMIT pe->device()->pointerMotionAbsolute(pe->absolutePos(workspace()->geometry().size()), pe->time(), pe->device());
Q_EMIT pe->device()->pointerFrame(pe->device());
}
break;
}
case LIBINPUT_EVENT_TOUCH_DOWN: {
#ifndef KWIN_BUILD_TESTING
TouchEvent *te = static_cast<TouchEvent *>(event.get());
const auto *output = te->device()->output();
if (!output) {
qCWarning(KWIN_LIBINPUT) << "Touch down received for device with no output assigned";
break;
}
const QPointF globalPos = devicePointToGlobalPosition(te->absolutePos(output->modeSize()), output);
Q_EMIT te->device()->touchDown(te->id(), globalPos, te->time(), te->device());
break;
#endif
}
case LIBINPUT_EVENT_TOUCH_UP: {
TouchEvent *te = static_cast<TouchEvent *>(event.get());
const auto *output = te->device()->output();
if (!output) {
break;
}
Q_EMIT te->device()->touchUp(te->id(), te->time(), te->device());
break;
}
case LIBINPUT_EVENT_TOUCH_MOTION: {
#ifndef KWIN_BUILD_TESTING
TouchEvent *te = static_cast<TouchEvent *>(event.get());
const auto *output = te->device()->output();
if (!output) {
break;
}
const QPointF globalPos = devicePointToGlobalPosition(te->absolutePos(output->modeSize()), output);
Q_EMIT te->device()->touchMotion(te->id(), globalPos, te->time(), te->device());
break;
#endif
}
case LIBINPUT_EVENT_TOUCH_CANCEL: {
Q_EMIT event->device()->touchCanceled(event->device());
break;
}
case LIBINPUT_EVENT_TOUCH_FRAME: {
Q_EMIT event->device()->touchFrame(event->device());
break;
}
case LIBINPUT_EVENT_GESTURE_PINCH_BEGIN: {
PinchGestureEvent *pe = static_cast<PinchGestureEvent *>(event.get());
Q_EMIT pe->device()->pinchGestureBegin(pe->fingerCount(), pe->time(), pe->device());
break;
}
case LIBINPUT_EVENT_GESTURE_PINCH_UPDATE: {
PinchGestureEvent *pe = static_cast<PinchGestureEvent *>(event.get());
Q_EMIT pe->device()->pinchGestureUpdate(pe->scale(), pe->angleDelta(), pe->delta(), pe->time(), pe->device());
break;
}
case LIBINPUT_EVENT_GESTURE_PINCH_END: {
PinchGestureEvent *pe = static_cast<PinchGestureEvent *>(event.get());
if (pe->isCancelled()) {
Q_EMIT pe->device()->pinchGestureCancelled(pe->time(), pe->device());
} else {
Q_EMIT pe->device()->pinchGestureEnd(pe->time(), pe->device());
}
break;
}
case LIBINPUT_EVENT_GESTURE_SWIPE_BEGIN: {
SwipeGestureEvent *se = static_cast<SwipeGestureEvent *>(event.get());
Q_EMIT se->device()->swipeGestureBegin(se->fingerCount(), se->time(), se->device());
break;
}
case LIBINPUT_EVENT_GESTURE_SWIPE_UPDATE: {
SwipeGestureEvent *se = static_cast<SwipeGestureEvent *>(event.get());
Q_EMIT se->device()->swipeGestureUpdate(se->delta(), se->time(), se->device());
break;
}
case LIBINPUT_EVENT_GESTURE_SWIPE_END: {
SwipeGestureEvent *se = static_cast<SwipeGestureEvent *>(event.get());
if (se->isCancelled()) {
Q_EMIT se->device()->swipeGestureCancelled(se->time(), se->device());
} else {
Q_EMIT se->device()->swipeGestureEnd(se->time(), se->device());
}
break;
}
case LIBINPUT_EVENT_GESTURE_HOLD_BEGIN: {
HoldGestureEvent *he = static_cast<HoldGestureEvent *>(event.get());
Q_EMIT he->device()->holdGestureBegin(he->fingerCount(), he->time(), he->device());
break;
}
case LIBINPUT_EVENT_GESTURE_HOLD_END: {
HoldGestureEvent *he = static_cast<HoldGestureEvent *>(event.get());
if (he->isCancelled()) {
Q_EMIT he->device()->holdGestureCancelled(he->time(), he->device());
} else {
Q_EMIT he->device()->holdGestureEnd(he->time(), he->device());
}
break;
}
case LIBINPUT_EVENT_SWITCH_TOGGLE: {
SwitchEvent *se = static_cast<SwitchEvent *>(event.get());
Q_EMIT se->device()->switchToggle(se->state(), se->time(), se->device());
break;
}
case LIBINPUT_EVENT_TABLET_TOOL_AXIS: {
auto *tte = static_cast<TabletToolEvent *>(event.get());
if (libinput_tablet_tool_config_pressure_range_is_available(tte->tool())) {
tte->device()->setSupportsPressureRange(true);
libinput_tablet_tool_config_pressure_range_set(tte->tool(), tte->device()->pressureRangeMin(), tte->device()->pressureRangeMax());
}
Q_EMIT event->device()->tabletToolAxisEvent(tabletToolPosition(tte),
tte->device()->pressureCurve().valueForProgress(tte->pressure()),
tte->xTilt(),
tte->yTilt(),
tte->rotation(),
tte->distance(),
tte->isTipDown(),
tte->isNearby(),
getOrCreateTool(tte->tool()),
tte->time(),
tte->device());
break;
}
case LIBINPUT_EVENT_TABLET_TOOL_PROXIMITY: {
auto *tte = static_cast<TabletToolEvent *>(event.get());
if (libinput_tablet_tool_config_pressure_range_is_available(tte->tool())) {
tte->device()->setSupportsPressureRange(true);
libinput_tablet_tool_config_pressure_range_set(tte->tool(), tte->device()->pressureRangeMin(), tte->device()->pressureRangeMax());
}
Q_EMIT event->device()->tabletToolProximityEvent(tabletToolPosition(tte),
tte->device()->pressureCurve().valueForProgress(tte->pressure()),
tte->xTilt(),
tte->yTilt(),
tte->rotation(),
tte->distance(),
tte->isTipDown(),
tte->isNearby(),
getOrCreateTool(tte->tool()),
tte->time(),
tte->device());
break;
}
case LIBINPUT_EVENT_TABLET_TOOL_TIP: {
auto *tte = static_cast<TabletToolEvent *>(event.get());
if (libinput_tablet_tool_config_pressure_range_is_available(tte->tool())) {
tte->device()->setSupportsPressureRange(true);
libinput_tablet_tool_config_pressure_range_set(tte->tool(), tte->device()->pressureRangeMin(), tte->device()->pressureRangeMax());
}
Q_EMIT event->device()->tabletToolTipEvent(tabletToolPosition(tte),
tte->device()->pressureCurve().valueForProgress(tte->pressure()),
tte->xTilt(),
tte->yTilt(),
tte->rotation(),
tte->distance(),
tte->isTipDown(),
tte->isNearby(),
getOrCreateTool(tte->tool()),
tte->time(),
tte->device());
break;
}
case LIBINPUT_EVENT_TABLET_TOOL_BUTTON: {
auto *tabletEvent = static_cast<TabletToolButtonEvent *>(event.get());
Q_EMIT event->device()->tabletToolButtonEvent(tabletEvent->buttonId(),
tabletEvent->isButtonPressed(),
getOrCreateTool(tabletEvent->tool()), tabletEvent->time(), tabletEvent->device());
break;
}
case LIBINPUT_EVENT_TABLET_PAD_BUTTON: {
auto *tabletEvent = static_cast<TabletPadButtonEvent *>(event.get());
Q_EMIT event->device()->tabletPadButtonEvent(tabletEvent->buttonId(),
tabletEvent->isButtonPressed(),
tabletEvent->time(), tabletEvent->device());
break;
}
case LIBINPUT_EVENT_TABLET_PAD_RING: {
auto *tabletEvent = static_cast<TabletPadRingEvent *>(event.get());
tabletEvent->position();
Q_EMIT event->device()->tabletPadRingEvent(tabletEvent->number(),
tabletEvent->position(),
tabletEvent->source() == LIBINPUT_TABLET_PAD_RING_SOURCE_FINGER,
tabletEvent->time(), tabletEvent->device());
break;
}
case LIBINPUT_EVENT_TABLET_PAD_STRIP: {
auto *tabletEvent = static_cast<TabletPadStripEvent *>(event.get());
Q_EMIT event->device()->tabletPadStripEvent(tabletEvent->number(),
tabletEvent->position(),
tabletEvent->source() == LIBINPUT_TABLET_PAD_STRIP_SOURCE_FINGER,
tabletEvent->time(), tabletEvent->device());
break;
}
default:
// nothing
break;
}
}
}
void Connection::updateScreens()
{
QMutexLocker locker(&m_mutex);
for (auto device : std::as_const(m_devices)) {
applyScreenToDevice(device);
}
}
void Connection::applyScreenToDevice(Device *device)
{
#ifndef KWIN_BUILD_TESTING
QMutexLocker locker(&m_mutex);
if (!device->isTouch() && !device->isTabletTool()) {
return;
}
Output *deviceOutput = nullptr;
const QList<Output *> outputs = kwinApp()->outputBackend()->outputs();
// let's try to find a screen for it
if (!device->outputName().isEmpty()) {
// we have an output name, try to find a screen with matching name
for (Output *output : outputs) {
if (!output->isEnabled()) {
continue;
}
if (output->name() == device->outputName()) {
deviceOutput = output;
break;
}
}
}
if (!deviceOutput && device->isTouch()) {
// do we have an internal screen?
Output *internalOutput = nullptr;
for (Output *output : outputs) {
if (!output->isEnabled()) {
continue;
}
if (output->isInternal()) {
internalOutput = output;
break;
}
}
auto testScreenMatches = [device](const Output *output) {
const auto &size = device->size();
const auto &screenSize = output->physicalSize();
return std::round(size.width()) == std::round(screenSize.width())
&& std::round(size.height()) == std::round(screenSize.height());
};
if (internalOutput && testScreenMatches(internalOutput)) {
deviceOutput = internalOutput;
}
// let's compare all screens for size
for (Output *output : outputs) {
if (!output->isEnabled()) {
continue;
}
if (testScreenMatches(output)) {
deviceOutput = output;
break;
}
}
if (!deviceOutput) {
// still not found
if (internalOutput) {
// we have an internal id, so let's use that
deviceOutput = internalOutput;
} else {
for (Output *output : outputs) {
// just take first screen, we have no clue
if (output->isEnabled()) {
deviceOutput = output;
break;
}
}
}
}
}
device->setOutput(deviceOutput);
// TODO: this is currently non-functional even on DRM. Needs orientation() override there.
device->setOrientation(Qt::PrimaryOrientation);
#endif
}
void Connection::applyDeviceConfig(Device *device)
{
KConfigGroup defaults = m_config->group(QStringLiteral("Libinput")).group(QStringLiteral("Defaults"));
if (defaults.isValid()) {
if (device->isAlphaNumericKeyboard() && defaults.hasGroup(QStringLiteral("Keyboard"))) {
defaults = defaults.group(QStringLiteral("Keyboard"));
} else if (device->isTouchpad() && defaults.hasGroup(QStringLiteral("Touchpad"))) {
// A Touchpad is a Pointer, so we need to check for it before Pointer.
defaults = defaults.group(QStringLiteral("Touchpad"));
} else if (device->isPointer() && defaults.hasGroup(QStringLiteral("Pointer"))) {
defaults = defaults.group(QStringLiteral("Pointer"));
}
device->setDefaultConfig(defaults);
}
// pass configuration to Device
device->setConfig(m_config->group(QStringLiteral("Libinput")).group(QString::number(device->vendor())).group(QString::number(device->product())).group(device->name()));
device->loadConfiguration();
}
void Connection::slotKGlobalSettingsNotifyChange(int type, int arg)
{
if (type == 3 /**SettingsChanged**/ && arg == 0 /** SETTINGS_MOUSE */) {
m_config->reparseConfiguration();
for (auto it = m_devices.constBegin(), end = m_devices.constEnd(); it != end; ++it) {
if ((*it)->isPointer()) {
applyDeviceConfig(*it);
}
}
}
}
QStringList Connection::devicesSysNames() const
{
QStringList sl;
for (Device *d : std::as_const(m_devices)) {
sl.append(d->sysName());
}
return sl;
}
}
}
#include "connection.moc"
#include "moc_connection.cpp"
@@ -0,0 +1,93 @@
/*
KWin - the KDE window manager
This file is part of the KDE project.
SPDX-FileCopyrightText: 2014 Martin Gräßlin <mgraesslin@kde.org>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#pragma once
#include "effect/globals.h"
#include <KSharedConfig>
#include <QList>
#include <QObject>
#include <QPointer>
#include <QRecursiveMutex>
#include <QSize>
#include <QStringList>
#include <deque>
class QSocketNotifier;
class QThread;
struct libinput_tablet_tool;
namespace KWin
{
class Session;
class Udev;
namespace LibInput
{
class Event;
class Device;
class Context;
class ConnectionAdaptor;
class TabletTool;
class KWIN_EXPORT Connection : public QObject
{
Q_OBJECT
public:
~Connection() override;
void setInputConfig(const KSharedConfigPtr &config)
{
m_config = config;
}
void setup();
void updateScreens();
void deactivate();
void processEvents();
QStringList devicesSysNames() const;
static Connection *create(Session *session);
Q_SIGNALS:
void deviceAdded(KWin::LibInput::Device *);
void deviceRemoved(KWin::LibInput::Device *);
void eventsRead();
private Q_SLOTS:
void slotKGlobalSettingsNotifyChange(int type, int arg);
private:
Connection(std::unique_ptr<Context> &&input);
void handleEvent();
void applyDeviceConfig(Device *device);
void applyScreenToDevice(Device *device);
void doSetup();
TabletTool *getOrCreateTool(libinput_tablet_tool *tool);
std::unique_ptr<QSocketNotifier> m_notifier;
QRecursiveMutex m_mutex;
std::deque<std::unique_ptr<Event>> m_eventQueue;
QList<Device *> m_devices;
QList<TabletTool *> m_tools;
KSharedConfigPtr m_config;
std::unique_ptr<ConnectionAdaptor> m_connectionAdaptor;
std::unique_ptr<Context> m_input;
std::unique_ptr<Udev> m_udev;
};
}
}
@@ -0,0 +1,176 @@
/*
KWin - the KDE window manager
This file is part of the KDE project.
SPDX-FileCopyrightText: 2014 Martin Gräßlin <mgraesslin@kde.org>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "context.h"
#include "events.h"
#include "libinput_logging.h"
#include "core/session.h"
#include "utils/udev.h"
#include <fcntl.h>
#include <unistd.h>
#include <cerrno>
namespace KWin
{
namespace LibInput
{
static void libinputLogHandler(libinput *libinput, libinput_log_priority priority, const char *format, va_list args)
{
char buf[1024];
if (std::vsnprintf(buf, 1023, format, args) <= 0) {
return;
}
switch (priority) {
case LIBINPUT_LOG_PRIORITY_DEBUG:
qCDebug(KWIN_LIBINPUT) << "Libinput:" << buf;
break;
case LIBINPUT_LOG_PRIORITY_INFO:
qCInfo(KWIN_LIBINPUT) << "Libinput:" << buf;
break;
case LIBINPUT_LOG_PRIORITY_ERROR:
default:
qCCritical(KWIN_LIBINPUT) << "Libinput:" << buf;
break;
}
}
Context::Context(Session *session, std::unique_ptr<Udev> &&udev)
: m_session(session)
, m_libinput(libinput_udev_create_context(&Context::s_interface, this, *udev.get()))
, m_suspended(false)
, m_udev(std::move(udev))
{
libinput_log_set_priority(m_libinput, LIBINPUT_LOG_PRIORITY_DEBUG);
libinput_log_set_handler(m_libinput, &libinputLogHandler);
}
Context::~Context()
{
if (m_libinput) {
libinput_unref(m_libinput);
}
}
bool Context::initialize()
{
if (!isValid()) {
return false;
}
return libinput_udev_assign_seat(m_libinput, m_session->seat().toUtf8().constData()) == 0;
}
Session *Context::session() const
{
return m_session;
}
int Context::fileDescriptor()
{
if (!isValid()) {
return -1;
}
return libinput_get_fd(m_libinput);
}
void Context::dispatch()
{
libinput_dispatch(m_libinput);
}
const struct libinput_interface Context::s_interface = {
Context::openRestrictedCallback,
Context::closeRestrictedCallBack,
};
int Context::openRestrictedCallback(const char *path, int flags, void *user_data)
{
return ((Context *)user_data)->openRestricted(path, flags);
}
void Context::closeRestrictedCallBack(int fd, void *user_data)
{
((Context *)user_data)->closeRestricted(fd);
}
int Context::openRestricted(const char *path, int flags)
{
int fd = m_session->openRestricted(path);
if (fd < 0) {
// failed
return fd;
}
// adjust flags - based on Weston (logind-util.c)
int fl = fcntl(fd, F_GETFL);
auto errorHandling = [fd, this]() {
closeRestricted(fd);
};
if (fl < 0) {
errorHandling();
return -1;
}
if (flags & O_NONBLOCK) {
fl |= O_NONBLOCK;
}
if (fcntl(fd, F_SETFL, fl) < 0) {
errorHandling();
return -1;
}
fl = fcntl(fd, F_GETFD);
if (fl < 0) {
errorHandling();
return -1;
}
if (!(flags & O_CLOEXEC)) {
fl &= ~FD_CLOEXEC;
}
if (fcntl(fd, F_SETFD, fl) < 0) {
errorHandling();
return -1;
}
return fd;
}
void Context::closeRestricted(int fd)
{
m_session->closeRestricted(fd);
}
std::unique_ptr<Event> Context::event()
{
return Event::create(libinput_get_event(m_libinput));
}
void Context::suspend()
{
if (m_suspended) {
return;
}
libinput_suspend(m_libinput);
m_suspended = true;
}
void Context::resume()
{
if (!m_suspended) {
return;
}
libinput_resume(m_libinput);
m_suspended = false;
}
}
}
@@ -0,0 +1,75 @@
/*
KWin - the KDE window manager
This file is part of the KDE project.
SPDX-FileCopyrightText: 2014 Martin Gräßlin <mgraesslin@kde.org>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#pragma once
#include <libinput.h>
#include <memory>
namespace KWin
{
class Session;
class Udev;
namespace LibInput
{
class Event;
class Context
{
public:
Context(Session *session, std::unique_ptr<Udev> &&udev);
~Context();
bool initialize();
bool isValid() const
{
return m_libinput != nullptr;
}
bool isSuspended() const
{
return m_suspended;
}
Session *session() const;
int fileDescriptor();
void dispatch();
void suspend();
void resume();
operator libinput *()
{
return m_libinput;
}
operator libinput *() const
{
return m_libinput;
}
/**
* Gets the next event, if there is no new event @c nullptr is returned
*/
std::unique_ptr<Event> event();
static int openRestrictedCallback(const char *path, int flags, void *user_data);
static void closeRestrictedCallBack(int fd, void *user_data);
static const struct libinput_interface s_interface;
private:
int openRestricted(const char *path, int flags);
void closeRestricted(int fd);
Session *m_session;
struct libinput *m_libinput;
bool m_suspended;
std::unique_ptr<Udev> m_udev;
};
}
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,870 @@
/*
KWin - the KDE window manager
This file is part of the KDE project.
SPDX-FileCopyrightText: 2016 Martin Gräßlin <mgraesslin@kde.org>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#pragma once
#include "core/inputdevice.h"
#include <libinput.h>
#include <KConfigGroup>
#include <QEasingCurve>
#include <QList>
#include <QMatrix4x4>
#include <QObject>
#include <QPointer>
#include <QSizeF>
struct libinput_device;
namespace KWin
{
class Output;
namespace LibInput
{
enum class ConfigKey;
class TabletTool : public InputDeviceTabletTool
{
Q_OBJECT
public:
explicit TabletTool(libinput_tablet_tool *handle);
~TabletTool() override;
libinput_tablet_tool *handle() const;
quint64 serialId() const override;
quint64 uniqueId() const override;
Type type() const override;
QList<Capability> capabilities() const override;
private:
libinput_tablet_tool *const m_handle;
};
class KWIN_EXPORT Device : public InputDevice
{
Q_OBJECT
Q_CLASSINFO("D-Bus Interface", "org.kde.KWin.InputDevice")
//
// general
Q_PROPERTY(bool keyboard READ isKeyboard CONSTANT)
Q_PROPERTY(bool alphaNumericKeyboard READ isAlphaNumericKeyboard CONSTANT)
Q_PROPERTY(bool pointer READ isPointer CONSTANT)
Q_PROPERTY(bool touchpad READ isTouchpad CONSTANT)
Q_PROPERTY(bool touch READ isTouch CONSTANT)
Q_PROPERTY(bool tabletTool READ isTabletTool CONSTANT)
Q_PROPERTY(bool tabletPad READ isTabletPad CONSTANT)
Q_PROPERTY(bool gestureSupport READ supportsGesture CONSTANT)
Q_PROPERTY(QString name READ name CONSTANT)
Q_PROPERTY(QString sysName READ sysName CONSTANT)
Q_PROPERTY(QString outputName READ outputName WRITE setOutputName NOTIFY outputNameChanged)
Q_PROPERTY(QSizeF size READ size CONSTANT)
Q_PROPERTY(quint32 product READ product CONSTANT)
Q_PROPERTY(quint32 vendor READ vendor CONSTANT)
Q_PROPERTY(bool supportsDisableEvents READ supportsDisableEvents CONSTANT)
Q_PROPERTY(bool enabled READ isEnabled WRITE setEnabled NOTIFY enabledChanged)
Q_PROPERTY(bool enabledByDefault READ isEnabledByDefault CONSTANT)
//
// advanced
Q_PROPERTY(int supportedButtons READ supportedButtons CONSTANT)
Q_PROPERTY(bool supportsCalibrationMatrix READ supportsCalibrationMatrix CONSTANT)
Q_PROPERTY(QString defaultCalibrationMatrix READ defaultCalibrationMatrix CONSTANT)
Q_PROPERTY(QString calibrationMatrix READ serializedCalibrationMatrix WRITE setCalibrationMatrix NOTIFY calibrationMatrixChanged)
Q_PROPERTY(Qt::ScreenOrientation orientation READ orientation WRITE setOrientation NOTIFY orientationChanged)
Q_PROPERTY(int orientationDBus READ orientation WRITE setOrientationDBus NOTIFY orientationChanged)
Q_PROPERTY(bool supportsLeftHanded READ supportsLeftHanded CONSTANT)
Q_PROPERTY(bool leftHandedEnabledByDefault READ leftHandedEnabledByDefault CONSTANT)
Q_PROPERTY(bool leftHanded READ isLeftHanded WRITE setLeftHanded NOTIFY leftHandedChanged)
Q_PROPERTY(bool supportsDisableEventsOnExternalMouse READ supportsDisableEventsOnExternalMouse CONSTANT)
Q_PROPERTY(bool disableEventsOnExternalMouseEnabledByDefault READ disableEventsOnExternalMouseEnabledByDefault CONSTANT)
Q_PROPERTY(bool disableEventsOnExternalMouse READ isDisableEventsOnExternalMouse WRITE setDisableEventsOnExternalMouse NOTIFY disableEventsOnExternalMouseChanged)
Q_PROPERTY(bool supportsDisableWhileTyping READ supportsDisableWhileTyping CONSTANT)
Q_PROPERTY(bool disableWhileTypingEnabledByDefault READ disableWhileTypingEnabledByDefault CONSTANT)
Q_PROPERTY(bool disableWhileTyping READ isDisableWhileTyping WRITE setDisableWhileTyping NOTIFY disableWhileTypingChanged)
//
// acceleration speed and profile
Q_PROPERTY(bool supportsPointerAcceleration READ supportsPointerAcceleration CONSTANT)
Q_PROPERTY(qreal defaultPointerAcceleration READ defaultPointerAcceleration CONSTANT)
Q_PROPERTY(qreal pointerAcceleration READ pointerAcceleration WRITE setPointerAcceleration NOTIFY pointerAccelerationChanged)
Q_PROPERTY(bool supportsPointerAccelerationProfileFlat READ supportsPointerAccelerationProfileFlat CONSTANT)
Q_PROPERTY(bool defaultPointerAccelerationProfileFlat READ defaultPointerAccelerationProfileFlat CONSTANT)
Q_PROPERTY(bool pointerAccelerationProfileFlat READ pointerAccelerationProfileFlat WRITE setPointerAccelerationProfileFlat NOTIFY pointerAccelerationProfileChanged)
Q_PROPERTY(bool supportsPointerAccelerationProfileAdaptive READ supportsPointerAccelerationProfileAdaptive CONSTANT)
Q_PROPERTY(bool defaultPointerAccelerationProfileAdaptive READ defaultPointerAccelerationProfileAdaptive CONSTANT)
Q_PROPERTY(bool pointerAccelerationProfileAdaptive READ pointerAccelerationProfileAdaptive WRITE setPointerAccelerationProfileAdaptive NOTIFY pointerAccelerationProfileChanged)
//
// tapping
Q_PROPERTY(int tapFingerCount READ tapFingerCount CONSTANT)
Q_PROPERTY(bool tapToClickEnabledByDefault READ tapToClickEnabledByDefault CONSTANT)
Q_PROPERTY(bool tapToClick READ isTapToClick WRITE setTapToClick NOTIFY tapToClickChanged)
Q_PROPERTY(bool supportsLmrTapButtonMap READ supportsLmrTapButtonMap CONSTANT)
Q_PROPERTY(bool lmrTapButtonMapEnabledByDefault READ lmrTapButtonMapEnabledByDefault CONSTANT)
Q_PROPERTY(bool lmrTapButtonMap READ lmrTapButtonMap WRITE setLmrTapButtonMap NOTIFY tapButtonMapChanged)
Q_PROPERTY(bool tapAndDragEnabledByDefault READ tapAndDragEnabledByDefault CONSTANT)
Q_PROPERTY(bool tapAndDrag READ isTapAndDrag WRITE setTapAndDrag NOTIFY tapAndDragChanged)
Q_PROPERTY(bool tapDragLockEnabledByDefault READ tapDragLockEnabledByDefault CONSTANT)
Q_PROPERTY(bool tapDragLock READ isTapDragLock WRITE setTapDragLock NOTIFY tapDragLockChanged)
Q_PROPERTY(bool supportsMiddleEmulation READ supportsMiddleEmulation CONSTANT)
Q_PROPERTY(bool middleEmulationEnabledByDefault READ middleEmulationEnabledByDefault CONSTANT)
Q_PROPERTY(bool middleEmulation READ isMiddleEmulation WRITE setMiddleEmulation NOTIFY middleEmulationChanged)
//
// scrolling
Q_PROPERTY(bool supportsNaturalScroll READ supportsNaturalScroll CONSTANT)
Q_PROPERTY(bool naturalScrollEnabledByDefault READ naturalScrollEnabledByDefault CONSTANT)
Q_PROPERTY(bool naturalScroll READ isNaturalScroll WRITE setNaturalScroll NOTIFY naturalScrollChanged)
Q_PROPERTY(bool supportsScrollTwoFinger READ supportsScrollTwoFinger CONSTANT)
Q_PROPERTY(bool scrollTwoFingerEnabledByDefault READ scrollTwoFingerEnabledByDefault CONSTANT)
Q_PROPERTY(bool scrollTwoFinger READ isScrollTwoFinger WRITE setScrollTwoFinger NOTIFY scrollMethodChanged)
Q_PROPERTY(bool supportsScrollEdge READ supportsScrollEdge CONSTANT)
Q_PROPERTY(bool scrollEdgeEnabledByDefault READ scrollEdgeEnabledByDefault CONSTANT)
Q_PROPERTY(bool scrollEdge READ isScrollEdge WRITE setScrollEdge NOTIFY scrollMethodChanged)
Q_PROPERTY(bool supportsScrollOnButtonDown READ supportsScrollOnButtonDown CONSTANT)
Q_PROPERTY(bool scrollOnButtonDownEnabledByDefault READ scrollOnButtonDownEnabledByDefault CONSTANT)
Q_PROPERTY(quint32 defaultScrollButton READ defaultScrollButton CONSTANT)
Q_PROPERTY(bool scrollOnButtonDown READ isScrollOnButtonDown WRITE setScrollOnButtonDown NOTIFY scrollMethodChanged)
Q_PROPERTY(quint32 scrollButton READ scrollButton WRITE setScrollButton NOTIFY scrollButtonChanged)
Q_PROPERTY(qreal scrollFactor READ scrollFactor WRITE setScrollFactor NOTIFY scrollFactorChanged)
//
// switches
Q_PROPERTY(bool switchDevice READ isSwitch CONSTANT)
Q_PROPERTY(bool lidSwitch READ isLidSwitch CONSTANT)
Q_PROPERTY(bool tabletModeSwitch READ isTabletModeSwitch CONSTANT)
// Click Methods
Q_PROPERTY(bool supportsClickMethodAreas READ supportsClickMethodAreas CONSTANT)
Q_PROPERTY(bool defaultClickMethodAreas READ defaultClickMethodAreas CONSTANT)
Q_PROPERTY(bool clickMethodAreas READ isClickMethodAreas WRITE setClickMethodAreas NOTIFY clickMethodChanged)
Q_PROPERTY(bool supportsClickMethodClickfinger READ supportsClickMethodClickfinger CONSTANT)
Q_PROPERTY(bool defaultClickMethodClickfinger READ defaultClickMethodClickfinger CONSTANT)
Q_PROPERTY(bool clickMethodClickfinger READ isClickMethodClickfinger WRITE setClickMethodClickfinger NOTIFY clickMethodChanged)
Q_PROPERTY(bool supportsOutputArea READ supportsOutputArea CONSTANT)
Q_PROPERTY(QRectF defaultOutputArea READ defaultOutputArea CONSTANT)
Q_PROPERTY(QRectF outputArea READ outputArea WRITE setOutputArea NOTIFY outputAreaChanged)
Q_PROPERTY(bool defaultMapToWorkspace READ defaultMapToWorkspace CONSTANT)
Q_PROPERTY(bool mapToWorkspace READ isMapToWorkspace WRITE setMapToWorkspace NOTIFY mapToWorkspaceChanged)
Q_PROPERTY(QString deviceGroupId READ deviceGroupId CONSTANT)
Q_PROPERTY(QString defaultPressureCurve READ defaultPressureCurve CONSTANT)
Q_PROPERTY(QString pressureCurve READ serializedPressureCurve WRITE setPressureCurve NOTIFY pressureCurveChanged)
Q_PROPERTY(quint32 tabletPadButtonCount READ tabletPadButtonCount CONSTANT)
Q_PROPERTY(bool supportsInputArea READ supportsInputArea CONSTANT)
Q_PROPERTY(QRectF defaultInputArea READ defaultInputArea CONSTANT)
Q_PROPERTY(QRectF inputArea READ inputArea WRITE setInputArea NOTIFY inputAreaChanged)
Q_PROPERTY(bool supportsPressureRange READ supportsPressureRange NOTIFY supportsPressureRangeChanged)
Q_PROPERTY(double pressureRangeMin READ pressureRangeMin WRITE setPressureRangeMin NOTIFY pressureRangeMinChanged)
Q_PROPERTY(double pressureRangeMax READ pressureRangeMax WRITE setPressureRangeMax NOTIFY pressureRangeMaxChanged)
Q_PROPERTY(double defaultPressureRangeMin READ defaultPressureRangeMin CONSTANT)
Q_PROPERTY(double defaultPressureRangeMax READ defaultPressureRangeMax CONSTANT)
public:
explicit Device(libinput_device *device, QObject *parent = nullptr);
~Device() override;
bool isKeyboard() const override
{
return m_keyboard;
}
/**
* Note that this has a lot of false positives
*/
bool isAlphaNumericKeyboard() const
{
return m_alphaNumericKeyboard;
}
bool isPointer() const override
{
return m_pointer;
}
bool isTouchpad() const override
{
return m_touchpad &&
// ignore all combined devices. E.g. a touchpad on a keyboard we don't want to toggle
// as that would result in the keyboard going off as well
!(m_keyboard || m_touch || m_tabletPad || m_tabletTool);
}
bool isTouch() const override
{
return m_touch;
}
bool isTabletTool() const override
{
return m_tabletTool;
}
bool isTabletPad() const override
{
return m_tabletPad;
}
bool supportsGesture() const
{
return m_supportsGesture;
}
QString name() const override
{
return m_name;
}
QString sysName() const
{
return m_sysName;
}
QString sysPath() const override
{
return m_sysPath;
}
QString outputName() const override
{
return m_outputName;
}
QSizeF size() const
{
return m_size;
}
quint32 product() const override
{
return m_product;
}
quint32 vendor() const override
{
return m_vendor;
}
void *group() const override;
Qt::MouseButtons supportedButtons() const
{
return m_supportedButtons;
}
int tapFingerCount() const
{
return m_tapFingerCount;
}
bool tapToClickEnabledByDefault() const
{
return defaultValue("TapToClick", m_tapToClickEnabledByDefault);
}
bool isTapToClick() const
{
return m_tapToClick;
}
/**
* Set the Device to tap to click if @p set is @c true.
*/
void setTapToClick(bool set);
bool tapAndDragEnabledByDefault() const
{
return defaultValue("TapAndDrag", m_tapAndDragEnabledByDefault);
}
bool isTapAndDrag() const
{
return m_tapAndDrag;
}
void setTapAndDrag(bool set);
bool tapDragLockEnabledByDefault() const
{
return defaultValue("TapDragLock", m_tapDragLockEnabledByDefault);
}
bool isTapDragLock() const
{
return m_tapDragLock;
}
void setTapDragLock(bool set);
bool supportsDisableWhileTyping() const
{
return m_supportsDisableWhileTyping;
}
bool disableWhileTypingEnabledByDefault() const
{
return defaultValue("DisableWhileTyping", m_disableWhileTypingEnabledByDefault);
}
bool supportsPointerAcceleration() const
{
return m_supportsPointerAcceleration;
}
bool supportsLeftHanded() const
{
return m_supportsLeftHanded;
}
bool supportsCalibrationMatrix() const
{
return m_supportsCalibrationMatrix;
}
bool supportsDisableEvents() const
{
return m_supportsDisableEvents;
}
bool supportsDisableEventsOnExternalMouse() const
{
return m_supportsDisableEventsOnExternalMouse;
}
bool supportsMiddleEmulation() const
{
return m_supportsMiddleEmulation;
}
bool supportsNaturalScroll() const
{
return m_supportsNaturalScroll;
}
bool supportsScrollTwoFinger() const
{
return (m_supportedScrollMethods & LIBINPUT_CONFIG_SCROLL_2FG);
}
bool supportsScrollEdge() const
{
return (m_supportedScrollMethods & LIBINPUT_CONFIG_SCROLL_EDGE);
}
bool supportsScrollOnButtonDown() const
{
return (m_supportedScrollMethods & LIBINPUT_CONFIG_SCROLL_ON_BUTTON_DOWN);
}
bool leftHandedEnabledByDefault() const
{
return defaultValue("LeftHanded", m_leftHandedEnabledByDefault);
}
bool middleEmulationEnabledByDefault() const
{
return defaultValue("MiddleButtonEmulation", m_middleEmulationEnabledByDefault);
}
bool naturalScrollEnabledByDefault() const
{
return defaultValue("NaturalScroll", m_naturalScrollEnabledByDefault);
}
enum libinput_config_scroll_method defaultScrollMethod() const
{
quint32 defaultScrollMethod = defaultValue("ScrollMethod", static_cast<quint32>(m_defaultScrollMethod));
return static_cast<libinput_config_scroll_method>(defaultScrollMethod);
}
quint32 defaultScrollMethodToInt() const
{
return static_cast<quint32>(defaultScrollMethod());
}
bool scrollTwoFingerEnabledByDefault() const
{
return defaultScrollMethod() == LIBINPUT_CONFIG_SCROLL_2FG;
}
bool scrollEdgeEnabledByDefault() const
{
return defaultScrollMethod() == LIBINPUT_CONFIG_SCROLL_EDGE;
}
bool scrollOnButtonDownEnabledByDefault() const
{
return defaultScrollMethod() == LIBINPUT_CONFIG_SCROLL_ON_BUTTON_DOWN;
}
bool supportsLmrTapButtonMap() const
{
return m_tapFingerCount > 1;
}
bool lmrTapButtonMapEnabledByDefault() const
{
quint32 lmrButtonMap = defaultValue("LmrTapButtonMap", static_cast<quint32>(m_defaultTapButtonMap));
return lmrButtonMap == LIBINPUT_CONFIG_TAP_MAP_LMR;
}
void setLmrTapButtonMap(bool set);
bool lmrTapButtonMap() const
{
return m_tapButtonMap & LIBINPUT_CONFIG_TAP_MAP_LMR;
}
quint32 defaultScrollButton() const
{
return m_defaultScrollButton;
}
bool isMiddleEmulation() const
{
return m_middleEmulation;
}
void setMiddleEmulation(bool set);
bool isNaturalScroll() const
{
return m_naturalScroll;
}
void setNaturalScroll(bool set);
void setScrollMethod(bool set, enum libinput_config_scroll_method method);
bool isScrollTwoFinger() const
{
return m_scrollMethod & LIBINPUT_CONFIG_SCROLL_2FG;
}
void setScrollTwoFinger(bool set)
{
setScrollMethod(set, LIBINPUT_CONFIG_SCROLL_2FG);
}
bool isScrollEdge() const
{
return m_scrollMethod & LIBINPUT_CONFIG_SCROLL_EDGE;
}
void setScrollEdge(bool set)
{
setScrollMethod(set, LIBINPUT_CONFIG_SCROLL_EDGE);
}
bool isScrollOnButtonDown() const
{
return m_scrollMethod & LIBINPUT_CONFIG_SCROLL_ON_BUTTON_DOWN;
}
void setScrollOnButtonDown(bool set)
{
setScrollMethod(set, LIBINPUT_CONFIG_SCROLL_ON_BUTTON_DOWN);
}
void activateScrollMethodFromInt(quint32 method)
{
setScrollMethod(true, (libinput_config_scroll_method)method);
}
quint32 scrollButton() const
{
return m_scrollButton;
}
void setScrollButton(quint32 button);
qreal scrollFactorDefault() const
{
return defaultValue("ScrollFactor", 1.0);
}
qreal scrollFactor() const
{
return m_scrollFactor;
}
void setScrollFactor(qreal factor);
void setDisableWhileTyping(bool set);
bool isDisableWhileTyping() const
{
return m_disableWhileTyping;
}
bool isLeftHanded() const
{
return m_leftHanded;
}
/**
* Sets the Device to left handed mode if @p set is @c true.
* If @p set is @c false the device is set to right handed mode
*/
void setLeftHanded(bool set);
QString defaultCalibrationMatrix() const
{
auto list = defaultValue("CalibrationMatrix", QList<float>{});
if (list.size() == 16) {
return serializeMatrix(QMatrix4x4{list.constData()});
}
return serializeMatrix(m_defaultCalibrationMatrix);
}
QMatrix4x4 calibrationMatrix() const
{
return m_calibrationMatrix;
}
void setCalibrationMatrix(const QString &value);
QString serializedCalibrationMatrix() const
{
return serializeMatrix(m_calibrationMatrix);
}
static QString serializeMatrix(const QMatrix4x4 &matrix);
static QMatrix4x4 deserializeMatrix(const QString &matrix);
QString defaultPressureCurve() const;
QEasingCurve pressureCurve() const;
QString serializedPressureCurve() const;
void setPressureCurve(const QString &curve);
static QString serializePressureCurve(const QEasingCurve &curve);
static QEasingCurve deserializePressureCurve(const QString &curve);
Qt::ScreenOrientation defaultOrientation() const
{
quint32 orientation = defaultValue("Orientation", static_cast<quint32>(Qt::PrimaryOrientation));
return static_cast<Qt::ScreenOrientation>(orientation);
}
Qt::ScreenOrientation orientation() const
{
return m_orientation;
}
void setOrientation(Qt::ScreenOrientation orientation);
void setOrientationDBus(int orientation)
{
setOrientation(Qt::ScreenOrientation(orientation));
}
qreal defaultPointerAcceleration() const
{
return m_defaultPointerAcceleration;
}
qreal pointerAcceleration() const
{
return m_pointerAcceleration;
}
/**
* @param acceleration mapped to range [-1,1] with -1 being the slowest, 1 being the fastest supported acceleration.
*/
void setPointerAcceleration(qreal acceleration);
void setPointerAccelerationFromString(const QString &acceleration)
{
setPointerAcceleration(acceleration.toDouble());
}
QString defaultPointerAccelerationToString() const
{
return QString::number(m_pointerAcceleration, 'f', 3);
}
bool supportsPointerAccelerationProfileFlat() const
{
return (m_supportedPointerAccelerationProfiles & LIBINPUT_CONFIG_ACCEL_PROFILE_FLAT);
}
bool supportsPointerAccelerationProfileAdaptive() const
{
return (m_supportedPointerAccelerationProfiles & LIBINPUT_CONFIG_ACCEL_PROFILE_ADAPTIVE);
}
bool defaultPointerAccelerationProfileFlat() const
{
return (m_defaultPointerAccelerationProfile & LIBINPUT_CONFIG_ACCEL_PROFILE_FLAT);
}
bool defaultPointerAccelerationProfileAdaptive() const
{
return (m_defaultPointerAccelerationProfile & LIBINPUT_CONFIG_ACCEL_PROFILE_ADAPTIVE);
}
bool pointerAccelerationProfileFlat() const
{
return (m_pointerAccelerationProfile & LIBINPUT_CONFIG_ACCEL_PROFILE_FLAT);
}
bool pointerAccelerationProfileAdaptive() const
{
return (m_pointerAccelerationProfile & LIBINPUT_CONFIG_ACCEL_PROFILE_ADAPTIVE);
}
void setPointerAccelerationProfile(bool set, enum libinput_config_accel_profile profile);
void setPointerAccelerationProfileFlat(bool set)
{
setPointerAccelerationProfile(set, LIBINPUT_CONFIG_ACCEL_PROFILE_FLAT);
}
void setPointerAccelerationProfileAdaptive(bool set)
{
setPointerAccelerationProfile(set, LIBINPUT_CONFIG_ACCEL_PROFILE_ADAPTIVE);
}
void setPointerAccelerationProfileFromInt(quint32 profile)
{
setPointerAccelerationProfile(true, (libinput_config_accel_profile)profile);
}
quint32 defaultPointerAccelerationProfileToInt() const
{
return defaultValue("PointerAccelerationProfile", static_cast<quint32>(m_defaultPointerAccelerationProfile));
}
bool supportsClickMethodAreas() const
{
return (m_supportedClickMethods & LIBINPUT_CONFIG_CLICK_METHOD_BUTTON_AREAS);
}
bool defaultClickMethodAreas() const
{
return (defaultClickMethod() == LIBINPUT_CONFIG_CLICK_METHOD_BUTTON_AREAS);
}
bool isClickMethodAreas() const
{
return (m_clickMethod == LIBINPUT_CONFIG_CLICK_METHOD_BUTTON_AREAS);
}
bool supportsClickMethodClickfinger() const
{
return (m_supportedClickMethods & LIBINPUT_CONFIG_CLICK_METHOD_CLICKFINGER);
}
bool defaultClickMethodClickfinger() const
{
return (defaultClickMethod() == LIBINPUT_CONFIG_CLICK_METHOD_CLICKFINGER);
}
bool isClickMethodClickfinger() const
{
return (m_clickMethod == LIBINPUT_CONFIG_CLICK_METHOD_CLICKFINGER);
}
void setClickMethod(bool set, enum libinput_config_click_method method);
void setClickMethodAreas(bool set)
{
setClickMethod(set, LIBINPUT_CONFIG_CLICK_METHOD_BUTTON_AREAS);
}
void setClickMethodClickfinger(bool set)
{
setClickMethod(set, LIBINPUT_CONFIG_CLICK_METHOD_CLICKFINGER);
}
void setClickMethodFromInt(quint32 method)
{
setClickMethod(true, (libinput_config_click_method)method);
}
libinput_config_click_method defaultClickMethod() const
{
return static_cast<libinput_config_click_method>(defaultClickMethodToInt());
}
quint32 defaultClickMethodToInt() const
{
return defaultValue("ClickMethod", static_cast<quint32>(m_defaultClickMethod));
}
bool isEnabled() const override
{
return m_enabled;
}
void setEnabled(bool enabled) override;
bool isEnabledByDefault() const
{
return defaultValue("Enabled", true);
}
bool disableEventsOnExternalMouseEnabledByDefault() const
{
return defaultValue("DisableEventsOnExternalMouse", m_disableEventsOnExternalMouseEnabledByDefault);
}
bool isDisableEventsOnExternalMouse() const
{
return m_disableEventsOnExternalMouse;
}
void setDisableEventsOnExternalMouse(bool set);
libinput_device *device() const
{
return m_device;
}
/**
* Sets the @p config to load the Device configuration from and to store each
* successful Device configuration.
*/
void setConfig(const KConfigGroup &config)
{
m_config = config;
}
void setDefaultConfig(const KConfigGroup &config)
{
m_defaultConfig = config;
}
/**
* Used to deserialize monitor data from KConfig when initializing a device
*/
void setOutputName(const QString &uuid) override;
QString defaultOutputName() const
{
return {};
}
/**
* Loads the configuration and applies it to the Device
*/
void loadConfiguration();
bool isSwitch() const
{
return m_switch;
}
bool isLidSwitch() const override
{
return m_lidSwitch;
}
bool isTabletModeSwitch() const override
{
return m_tabletSwitch;
}
int tabletPadButtonCount() const override;
int tabletPadRingCount() const override;
int tabletPadStripCount() const override;
int tabletPadModeCount() const override;
int tabletPadMode() const override;
Output *output() const;
void setOutput(Output *output);
LEDs leds() const override;
void setLeds(LEDs leds) override;
QRectF defaultOutputArea() const;
bool supportsOutputArea() const;
QRectF outputArea() const;
void setOutputArea(const QRectF &outputArea);
bool defaultMapToWorkspace() const
{
return defaultValue("MapToWorkspace", false);
}
bool isMapToWorkspace() const
{
return m_mapToWorkspace;
}
void setMapToWorkspace(bool mapToWorkspace);
QString deviceGroupId() const
{
return m_deviceGroupId;
}
bool supportsPressureRange() const;
void setSupportsPressureRange(bool supported);
double pressureRangeMin() const;
void setPressureRangeMin(double value);
double pressureRangeMax() const;
void setPressureRangeMax(double value);
double defaultPressureRangeMin() const;
double defaultPressureRangeMax() const;
bool supportsInputArea() const;
QRectF inputArea() const;
void setInputArea(const QRectF &inputArea);
QRectF defaultInputArea() const;
/**
* Gets the Device for @p native. @c null if there is no Device for @p native.
*/
static Device *get(libinput_device *native);
Q_SIGNALS:
void tapButtonMapChanged();
void calibrationMatrixChanged();
void orientationChanged();
void outputNameChanged();
void leftHandedChanged();
void disableWhileTypingChanged();
void pointerAccelerationChanged();
void pointerAccelerationProfileChanged();
void enabledChanged();
void disableEventsOnExternalMouseChanged();
void tapToClickChanged();
void tapAndDragChanged();
void tapDragLockChanged();
void middleEmulationChanged();
void naturalScrollChanged();
void scrollMethodChanged();
void scrollButtonChanged();
void scrollFactorChanged();
void clickMethodChanged();
void outputAreaChanged();
void mapToWorkspaceChanged();
void pressureCurveChanged();
void supportsPressureRangeChanged();
void pressureRangeMinChanged();
void pressureRangeMaxChanged();
void inputAreaChanged();
private:
template<typename T>
void writeEntry(const ConfigKey &key, const T &value);
template<typename T>
T defaultValue(const char *key, const T &fallback) const
{
if (m_defaultConfig.isValid() && m_defaultConfig.hasKey(key)) {
return m_defaultConfig.readEntry(key, fallback);
}
return fallback;
}
libinput_device *m_device;
bool m_keyboard;
bool m_alphaNumericKeyboard = false;
bool m_pointer;
bool m_touch;
bool m_tabletTool;
bool m_tabletPad;
bool m_supportsGesture;
bool m_switch = false;
bool m_lidSwitch = false;
bool m_tabletSwitch = false;
bool m_touchpad = false;
QString m_name;
QString m_sysName;
QString m_sysPath;
QString m_outputName;
QSizeF m_size;
quint32 m_product;
quint32 m_vendor;
Qt::MouseButtons m_supportedButtons = Qt::NoButton;
int m_tapFingerCount;
enum libinput_config_tap_button_map m_defaultTapButtonMap;
enum libinput_config_tap_button_map m_tapButtonMap;
bool m_tapToClickEnabledByDefault;
bool m_tapToClick;
bool m_tapAndDragEnabledByDefault;
bool m_tapAndDrag;
bool m_tapDragLockEnabledByDefault;
bool m_tapDragLock;
bool m_supportsDisableWhileTyping;
bool m_supportsPointerAcceleration;
bool m_supportsLeftHanded;
bool m_supportsCalibrationMatrix;
bool m_supportsDisableEvents;
bool m_supportsDisableEventsOnExternalMouse;
bool m_supportsMiddleEmulation;
bool m_supportsNaturalScroll;
quint32 m_supportedScrollMethods;
bool m_supportsScrollEdge;
bool m_supportsScrollOnButtonDown;
bool m_leftHandedEnabledByDefault;
bool m_middleEmulationEnabledByDefault;
bool m_naturalScrollEnabledByDefault;
enum libinput_config_scroll_method m_defaultScrollMethod;
quint32 m_defaultScrollButton;
bool m_disableWhileTypingEnabledByDefault;
bool m_disableWhileTyping;
bool m_middleEmulation;
bool m_leftHanded;
bool m_naturalScroll;
enum libinput_config_scroll_method m_scrollMethod;
quint32 m_scrollButton;
qreal m_defaultPointerAcceleration;
qreal m_pointerAcceleration;
qreal m_scrollFactor;
quint32 m_supportedPointerAccelerationProfiles;
enum libinput_config_accel_profile m_defaultPointerAccelerationProfile;
enum libinput_config_accel_profile m_pointerAccelerationProfile;
bool m_enabled;
bool m_disableEventsOnExternalMouseEnabledByDefault;
bool m_disableEventsOnExternalMouse;
KConfigGroup m_config;
KConfigGroup m_defaultConfig;
bool m_loading = false;
QPointer<Output> m_output;
Qt::ScreenOrientation m_orientation = Qt::PrimaryOrientation;
QMatrix4x4 m_defaultCalibrationMatrix;
QMatrix4x4 m_calibrationMatrix;
QEasingCurve m_pressureCurve;
quint32 m_supportedClickMethods;
enum libinput_config_click_method m_defaultClickMethod;
enum libinput_config_click_method m_clickMethod;
LEDs m_leds;
QRectF m_outputArea;
bool m_mapToWorkspace = false;
QString m_deviceGroupId;
bool m_supportsPressureRange;
double m_pressureRangeMin;
double m_pressureRangeMax;
double m_defaultPressureRangeMin;
double m_defaultPressureRangeMax;
QRectF m_inputArea;
};
}
}
Q_DECLARE_METATYPE(KWin::LibInput::Device *)
@@ -0,0 +1,374 @@
/*
KWin - the KDE window manager
This file is part of the KDE project.
SPDX-FileCopyrightText: 2014 Martin Gräßlin <mgraesslin@kde.org>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "events.h"
#include "device.h"
#include <QSize>
namespace KWin
{
namespace LibInput
{
std::unique_ptr<Event> Event::create(libinput_event *event)
{
if (!event) {
return nullptr;
}
const auto t = libinput_event_get_type(event);
// TODO: add touch events
// TODO: add device notify events
switch (t) {
case LIBINPUT_EVENT_KEYBOARD_KEY:
return std::make_unique<KeyEvent>(event);
case LIBINPUT_EVENT_POINTER_SCROLL_WHEEL:
case LIBINPUT_EVENT_POINTER_SCROLL_FINGER:
case LIBINPUT_EVENT_POINTER_SCROLL_CONTINUOUS:
case LIBINPUT_EVENT_POINTER_BUTTON:
case LIBINPUT_EVENT_POINTER_MOTION:
case LIBINPUT_EVENT_POINTER_MOTION_ABSOLUTE:
return std::make_unique<PointerEvent>(event, t);
case LIBINPUT_EVENT_TOUCH_DOWN:
case LIBINPUT_EVENT_TOUCH_UP:
case LIBINPUT_EVENT_TOUCH_MOTION:
case LIBINPUT_EVENT_TOUCH_CANCEL:
case LIBINPUT_EVENT_TOUCH_FRAME:
return std::make_unique<TouchEvent>(event, t);
case LIBINPUT_EVENT_GESTURE_SWIPE_BEGIN:
case LIBINPUT_EVENT_GESTURE_SWIPE_UPDATE:
case LIBINPUT_EVENT_GESTURE_SWIPE_END:
return std::make_unique<SwipeGestureEvent>(event, t);
case LIBINPUT_EVENT_GESTURE_PINCH_BEGIN:
case LIBINPUT_EVENT_GESTURE_PINCH_UPDATE:
case LIBINPUT_EVENT_GESTURE_PINCH_END:
return std::make_unique<PinchGestureEvent>(event, t);
case LIBINPUT_EVENT_GESTURE_HOLD_BEGIN:
case LIBINPUT_EVENT_GESTURE_HOLD_END:
return std::make_unique<HoldGestureEvent>(event, t);
case LIBINPUT_EVENT_TABLET_TOOL_AXIS:
case LIBINPUT_EVENT_TABLET_TOOL_PROXIMITY:
case LIBINPUT_EVENT_TABLET_TOOL_TIP:
return std::make_unique<TabletToolEvent>(event, t);
case LIBINPUT_EVENT_TABLET_TOOL_BUTTON:
return std::make_unique<TabletToolButtonEvent>(event, t);
case LIBINPUT_EVENT_TABLET_PAD_RING:
return std::make_unique<TabletPadRingEvent>(event, t);
case LIBINPUT_EVENT_TABLET_PAD_STRIP:
return std::make_unique<TabletPadStripEvent>(event, t);
case LIBINPUT_EVENT_TABLET_PAD_BUTTON:
return std::make_unique<TabletPadButtonEvent>(event, t);
case LIBINPUT_EVENT_SWITCH_TOGGLE:
return std::make_unique<SwitchEvent>(event, t);
default:
return std::unique_ptr<Event>{new Event(event, t)};
}
}
Event::Event(libinput_event *event, libinput_event_type type)
: m_event(event)
, m_type(type)
, m_device(nullptr)
{
}
Event::~Event()
{
libinput_event_destroy(m_event);
}
Device *Event::device() const
{
if (!m_device) {
m_device = Device::get(libinput_event_get_device(m_event));
}
return m_device;
}
libinput_device *Event::nativeDevice() const
{
if (m_device) {
return m_device->device();
}
return libinput_event_get_device(m_event);
}
KeyEvent::KeyEvent(libinput_event *event)
: Event(event, LIBINPUT_EVENT_KEYBOARD_KEY)
, m_keyboardEvent(libinput_event_get_keyboard_event(event))
{
}
KeyEvent::~KeyEvent() = default;
uint32_t KeyEvent::key() const
{
return libinput_event_keyboard_get_key(m_keyboardEvent);
}
KeyboardKeyState KeyEvent::state() const
{
switch (libinput_event_keyboard_get_key_state(m_keyboardEvent)) {
case LIBINPUT_KEY_STATE_PRESSED:
return KeyboardKeyState::Pressed;
case LIBINPUT_KEY_STATE_RELEASED:
return KeyboardKeyState::Released;
default:
Q_UNREACHABLE();
}
}
std::chrono::microseconds KeyEvent::time() const
{
return std::chrono::microseconds(libinput_event_keyboard_get_time_usec(m_keyboardEvent));
}
PointerEvent::PointerEvent(libinput_event *event, libinput_event_type type)
: Event(event, type)
, m_pointerEvent(libinput_event_get_pointer_event(event))
{
}
PointerEvent::~PointerEvent() = default;
QPointF PointerEvent::absolutePos() const
{
Q_ASSERT(type() == LIBINPUT_EVENT_POINTER_MOTION_ABSOLUTE);
return QPointF(libinput_event_pointer_get_absolute_x(m_pointerEvent),
libinput_event_pointer_get_absolute_y(m_pointerEvent));
}
QPointF PointerEvent::absolutePos(const QSize &size) const
{
Q_ASSERT(type() == LIBINPUT_EVENT_POINTER_MOTION_ABSOLUTE);
return QPointF(libinput_event_pointer_get_absolute_x_transformed(m_pointerEvent, size.width()),
libinput_event_pointer_get_absolute_y_transformed(m_pointerEvent, size.height()));
}
QPointF PointerEvent::delta() const
{
Q_ASSERT(type() == LIBINPUT_EVENT_POINTER_MOTION);
return QPointF(libinput_event_pointer_get_dx(m_pointerEvent), libinput_event_pointer_get_dy(m_pointerEvent));
}
QPointF PointerEvent::deltaUnaccelerated() const
{
Q_ASSERT(type() == LIBINPUT_EVENT_POINTER_MOTION);
return QPointF(libinput_event_pointer_get_dx_unaccelerated(m_pointerEvent), libinput_event_pointer_get_dy_unaccelerated(m_pointerEvent));
}
std::chrono::microseconds PointerEvent::time() const
{
return std::chrono::microseconds(libinput_event_pointer_get_time_usec(m_pointerEvent));
}
uint32_t PointerEvent::button() const
{
Q_ASSERT(type() == LIBINPUT_EVENT_POINTER_BUTTON);
return libinput_event_pointer_get_button(m_pointerEvent);
}
PointerButtonState PointerEvent::buttonState() const
{
Q_ASSERT(type() == LIBINPUT_EVENT_POINTER_BUTTON);
switch (libinput_event_pointer_get_button_state(m_pointerEvent)) {
case LIBINPUT_BUTTON_STATE_PRESSED:
return PointerButtonState::Pressed;
case LIBINPUT_BUTTON_STATE_RELEASED:
return PointerButtonState::Released;
default:
Q_UNREACHABLE();
}
}
QList<PointerAxis> PointerEvent::axis() const
{
QList<PointerAxis> a;
if (libinput_event_pointer_has_axis(m_pointerEvent, LIBINPUT_POINTER_AXIS_SCROLL_HORIZONTAL)) {
a << PointerAxis::Horizontal;
}
if (libinput_event_pointer_has_axis(m_pointerEvent, LIBINPUT_POINTER_AXIS_SCROLL_VERTICAL)) {
a << PointerAxis::Vertical;
}
return a;
}
qreal PointerEvent::scrollValue(PointerAxis axis) const
{
const libinput_pointer_axis a = axis == PointerAxis::Horizontal
? LIBINPUT_POINTER_AXIS_SCROLL_HORIZONTAL
: LIBINPUT_POINTER_AXIS_SCROLL_VERTICAL;
return libinput_event_pointer_get_scroll_value(m_pointerEvent, a) * device()->scrollFactor();
}
qint32 PointerEvent::scrollValueV120(PointerAxis axis) const
{
const libinput_pointer_axis a = (axis == PointerAxis::Horizontal)
? LIBINPUT_POINTER_AXIS_SCROLL_HORIZONTAL
: LIBINPUT_POINTER_AXIS_SCROLL_VERTICAL;
return libinput_event_pointer_get_scroll_value_v120(m_pointerEvent, a) * device()->scrollFactor();
}
TouchEvent::TouchEvent(libinput_event *event, libinput_event_type type)
: Event(event, type)
, m_touchEvent(libinput_event_get_touch_event(event))
{
}
TouchEvent::~TouchEvent() = default;
std::chrono::microseconds TouchEvent::time() const
{
return std::chrono::microseconds(libinput_event_touch_get_time_usec(m_touchEvent));
}
QPointF TouchEvent::absolutePos() const
{
Q_ASSERT(type() == LIBINPUT_EVENT_TOUCH_DOWN || type() == LIBINPUT_EVENT_TOUCH_MOTION);
return QPointF(libinput_event_touch_get_x(m_touchEvent),
libinput_event_touch_get_y(m_touchEvent));
}
QPointF TouchEvent::absolutePos(const QSize &size) const
{
Q_ASSERT(type() == LIBINPUT_EVENT_TOUCH_DOWN || type() == LIBINPUT_EVENT_TOUCH_MOTION);
return QPointF(libinput_event_touch_get_x_transformed(m_touchEvent, size.width()),
libinput_event_touch_get_y_transformed(m_touchEvent, size.height()));
}
qint32 TouchEvent::id() const
{
Q_ASSERT(type() != LIBINPUT_EVENT_TOUCH_FRAME);
return libinput_event_touch_get_seat_slot(m_touchEvent);
}
GestureEvent::GestureEvent(libinput_event *event, libinput_event_type type)
: Event(event, type)
, m_gestureEvent(libinput_event_get_gesture_event(event))
{
}
GestureEvent::~GestureEvent() = default;
std::chrono::microseconds GestureEvent::time() const
{
return std::chrono::microseconds(libinput_event_gesture_get_time_usec(m_gestureEvent));
}
int GestureEvent::fingerCount() const
{
return libinput_event_gesture_get_finger_count(m_gestureEvent);
}
QPointF GestureEvent::delta() const
{
return QPointF(libinput_event_gesture_get_dx_unaccelerated(m_gestureEvent),
libinput_event_gesture_get_dy_unaccelerated(m_gestureEvent));
}
bool GestureEvent::isCancelled() const
{
return libinput_event_gesture_get_cancelled(m_gestureEvent) != 0;
}
PinchGestureEvent::PinchGestureEvent(libinput_event *event, libinput_event_type type)
: GestureEvent(event, type)
{
}
PinchGestureEvent::~PinchGestureEvent() = default;
qreal PinchGestureEvent::scale() const
{
return libinput_event_gesture_get_scale(m_gestureEvent);
}
qreal PinchGestureEvent::angleDelta() const
{
return libinput_event_gesture_get_angle_delta(m_gestureEvent);
}
SwipeGestureEvent::SwipeGestureEvent(libinput_event *event, libinput_event_type type)
: GestureEvent(event, type)
{
}
SwipeGestureEvent::~SwipeGestureEvent() = default;
HoldGestureEvent::HoldGestureEvent(libinput_event *event, libinput_event_type type)
: GestureEvent(event, type)
{
}
HoldGestureEvent::~HoldGestureEvent() = default;
SwitchEvent::SwitchEvent(libinput_event *event, libinput_event_type type)
: Event(event, type)
, m_switchEvent(libinput_event_get_switch_event(event))
{
}
SwitchEvent::~SwitchEvent() = default;
SwitchState SwitchEvent::state() const
{
switch (libinput_event_switch_get_switch_state(m_switchEvent)) {
case LIBINPUT_SWITCH_STATE_OFF:
return SwitchState::Off;
case LIBINPUT_SWITCH_STATE_ON:
return SwitchState::On;
default:
Q_UNREACHABLE();
}
return SwitchState::Off;
}
std::chrono::microseconds SwitchEvent::time() const
{
return std::chrono::microseconds(libinput_event_switch_get_time_usec(m_switchEvent));
}
TabletToolEvent::TabletToolEvent(libinput_event *event, libinput_event_type type)
: Event(event, type)
, m_tabletToolEvent(libinput_event_get_tablet_tool_event(event))
{
}
QPointF TabletToolEvent::transformedPosition(const QSize &size) const
{
const QRectF outputArea = device()->outputArea();
return {(size.width() - 1) * outputArea.x() + libinput_event_tablet_tool_get_x_transformed(m_tabletToolEvent, (size.width() - 1) * outputArea.width()),
(size.height() - 1) * outputArea.y() + libinput_event_tablet_tool_get_y_transformed(m_tabletToolEvent, (size.height() - 1) * outputArea.height())};
}
TabletToolButtonEvent::TabletToolButtonEvent(libinput_event *event, libinput_event_type type)
: Event(event, type)
, m_tabletToolEvent(libinput_event_get_tablet_tool_event(event))
{
}
TabletPadButtonEvent::TabletPadButtonEvent(libinput_event *event, libinput_event_type type)
: Event(event, type)
, m_tabletPadEvent(libinput_event_get_tablet_pad_event(event))
{
}
TabletPadStripEvent::TabletPadStripEvent(libinput_event *event, libinput_event_type type)
: Event(event, type)
, m_tabletPadEvent(libinput_event_get_tablet_pad_event(event))
{
}
TabletPadRingEvent::TabletPadRingEvent(libinput_event *event, libinput_event_type type)
: Event(event, type)
, m_tabletPadEvent(libinput_event_get_tablet_pad_event(event))
{
}
}
}
@@ -0,0 +1,424 @@
/*
KWin - the KDE window manager
This file is part of the KDE project.
SPDX-FileCopyrightText: 2014 Martin Gräßlin <mgraesslin@kde.org>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#pragma once
#include "input.h"
#include <libinput.h>
namespace KWin
{
namespace LibInput
{
class Device;
class Event
{
public:
virtual ~Event();
libinput_event_type type() const;
Device *device() const;
libinput_device *nativeDevice() const;
operator libinput_event *()
{
return m_event;
}
operator libinput_event *() const
{
return m_event;
}
static std::unique_ptr<Event> create(libinput_event *event);
protected:
Event(libinput_event *event, libinput_event_type type);
private:
libinput_event *m_event;
libinput_event_type m_type;
mutable Device *m_device;
};
class KeyEvent : public Event
{
public:
KeyEvent(libinput_event *event);
~KeyEvent() override;
uint32_t key() const;
KeyboardKeyState state() const;
std::chrono::microseconds time() const;
operator libinput_event_keyboard *()
{
return m_keyboardEvent;
}
operator libinput_event_keyboard *() const
{
return m_keyboardEvent;
}
private:
libinput_event_keyboard *m_keyboardEvent;
};
class PointerEvent : public Event
{
public:
PointerEvent(libinput_event *event, libinput_event_type type);
~PointerEvent() override;
QPointF absolutePos() const;
QPointF absolutePos(const QSize &size) const;
QPointF delta() const;
QPointF deltaUnaccelerated() const;
uint32_t button() const;
PointerButtonState buttonState() const;
std::chrono::microseconds time() const;
QList<PointerAxis> axis() const;
qreal scrollValue(PointerAxis a) const;
qint32 scrollValueV120(PointerAxis axis) const;
operator libinput_event_pointer *()
{
return m_pointerEvent;
}
operator libinput_event_pointer *() const
{
return m_pointerEvent;
}
private:
libinput_event_pointer *m_pointerEvent;
};
class TouchEvent : public Event
{
public:
TouchEvent(libinput_event *event, libinput_event_type type);
~TouchEvent() override;
std::chrono::microseconds time() const;
QPointF absolutePos() const;
QPointF absolutePos(const QSize &size) const;
qint32 id() const;
operator libinput_event_touch *()
{
return m_touchEvent;
}
operator libinput_event_touch *() const
{
return m_touchEvent;
}
private:
libinput_event_touch *m_touchEvent;
};
class GestureEvent : public Event
{
public:
~GestureEvent() override;
std::chrono::microseconds time() const;
int fingerCount() const;
QPointF delta() const;
bool isCancelled() const;
operator libinput_event_gesture *()
{
return m_gestureEvent;
}
operator libinput_event_gesture *() const
{
return m_gestureEvent;
}
protected:
GestureEvent(libinput_event *event, libinput_event_type type);
libinput_event_gesture *m_gestureEvent;
};
class PinchGestureEvent : public GestureEvent
{
public:
PinchGestureEvent(libinput_event *event, libinput_event_type type);
~PinchGestureEvent() override;
qreal scale() const;
qreal angleDelta() const;
};
class SwipeGestureEvent : public GestureEvent
{
public:
SwipeGestureEvent(libinput_event *event, libinput_event_type type);
~SwipeGestureEvent() override;
};
class HoldGestureEvent : public GestureEvent
{
public:
HoldGestureEvent(libinput_event *event, libinput_event_type type);
~HoldGestureEvent() override;
};
class SwitchEvent : public Event
{
public:
SwitchEvent(libinput_event *event, libinput_event_type type);
~SwitchEvent() override;
SwitchState state() const;
std::chrono::microseconds time() const;
private:
libinput_event_switch *m_switchEvent;
};
class TabletToolEvent : public Event
{
public:
TabletToolEvent(libinput_event *event, libinput_event_type type);
std::chrono::microseconds time() const
{
return std::chrono::microseconds(libinput_event_tablet_tool_get_time_usec(m_tabletToolEvent));
}
bool xHasChanged() const
{
return libinput_event_tablet_tool_x_has_changed(m_tabletToolEvent);
}
bool yHasChanged() const
{
return libinput_event_tablet_tool_y_has_changed(m_tabletToolEvent);
}
bool pressureHasChanged() const
{
return libinput_event_tablet_tool_pressure_has_changed(m_tabletToolEvent);
}
bool distanceHasChanged() const
{
return libinput_event_tablet_tool_distance_has_changed(m_tabletToolEvent);
}
bool tiltXHasChanged() const
{
return libinput_event_tablet_tool_tilt_x_has_changed(m_tabletToolEvent);
}
bool tiltYHasChanged() const
{
return libinput_event_tablet_tool_tilt_y_has_changed(m_tabletToolEvent);
}
bool rotationHasChanged() const
{
return libinput_event_tablet_tool_rotation_has_changed(m_tabletToolEvent);
}
bool sliderHasChanged() const
{
return libinput_event_tablet_tool_slider_has_changed(m_tabletToolEvent);
}
// uncomment when depending on libinput 1.14 or when implementing totems
// bool sizeMajorHasChanged() const { return
// libinput_event_tablet_tool_size_major_has_changed(m_tabletToolEvent); } bool
// sizeMinorHasChanged() const { return
// libinput_event_tablet_tool_size_minor_has_changed(m_tabletToolEvent); }
bool wheelHasChanged() const
{
return libinput_event_tablet_tool_wheel_has_changed(m_tabletToolEvent);
}
QPointF position() const
{
return {libinput_event_tablet_tool_get_x(m_tabletToolEvent),
libinput_event_tablet_tool_get_y(m_tabletToolEvent)};
}
QPointF delta() const
{
return {libinput_event_tablet_tool_get_dx(m_tabletToolEvent),
libinput_event_tablet_tool_get_dy(m_tabletToolEvent)};
}
qreal pressure() const
{
return libinput_event_tablet_tool_get_pressure(m_tabletToolEvent);
}
qreal distance() const
{
return libinput_event_tablet_tool_get_distance(m_tabletToolEvent);
}
qreal xTilt() const
{
return libinput_event_tablet_tool_get_tilt_x(m_tabletToolEvent);
}
qreal yTilt() const
{
return libinput_event_tablet_tool_get_tilt_y(m_tabletToolEvent);
}
qreal rotation() const
{
return libinput_event_tablet_tool_get_rotation(m_tabletToolEvent);
}
qreal sliderPosition() const
{
return libinput_event_tablet_tool_get_slider_position(m_tabletToolEvent);
}
// Uncomment when depending on libinput 1.14 or when implementing totems
// qreal sizeMajor() const { return
// libinput_event_tablet_tool_get_size_major(m_tabletToolEvent); }
// qreal sizeMinor() const {
// return libinput_event_tablet_tool_get_size_minor(m_tabletToolEvent); }
qreal wheelDelta() const
{
return libinput_event_tablet_tool_get_wheel_delta(m_tabletToolEvent);
}
int wheelDeltaDiscrete() const
{
return libinput_event_tablet_tool_get_wheel_delta_discrete(m_tabletToolEvent);
}
bool isTipDown() const
{
const auto state = libinput_event_tablet_tool_get_tip_state(m_tabletToolEvent);
return state == LIBINPUT_TABLET_TOOL_TIP_DOWN;
}
bool isNearby() const
{
const auto state = libinput_event_tablet_tool_get_proximity_state(m_tabletToolEvent);
return state == LIBINPUT_TABLET_TOOL_PROXIMITY_STATE_IN;
}
QPointF transformedPosition(const QSize &size) const;
struct libinput_tablet_tool *tool()
{
return libinput_event_tablet_tool_get_tool(m_tabletToolEvent);
}
private:
libinput_event_tablet_tool *m_tabletToolEvent;
};
class TabletToolButtonEvent : public Event
{
public:
TabletToolButtonEvent(libinput_event *event, libinput_event_type type);
uint buttonId() const
{
return libinput_event_tablet_tool_get_button(m_tabletToolEvent);
}
bool isButtonPressed() const
{
const auto state = libinput_event_tablet_tool_get_button_state(m_tabletToolEvent);
return state == LIBINPUT_BUTTON_STATE_PRESSED;
}
struct libinput_tablet_tool *tool()
{
return libinput_event_tablet_tool_get_tool(m_tabletToolEvent);
}
std::chrono::microseconds time() const
{
return std::chrono::microseconds(libinput_event_tablet_tool_get_time_usec(m_tabletToolEvent));
}
private:
libinput_event_tablet_tool *m_tabletToolEvent;
};
class TabletPadRingEvent : public Event
{
public:
TabletPadRingEvent(libinput_event *event, libinput_event_type type);
int position() const
{
return libinput_event_tablet_pad_get_ring_position(m_tabletPadEvent);
}
int number() const
{
return libinput_event_tablet_pad_get_ring_number(m_tabletPadEvent);
}
libinput_tablet_pad_ring_axis_source source() const
{
return libinput_event_tablet_pad_get_ring_source(m_tabletPadEvent);
}
std::chrono::microseconds time() const
{
return std::chrono::microseconds(libinput_event_tablet_pad_get_time_usec(m_tabletPadEvent));
}
private:
libinput_event_tablet_pad *m_tabletPadEvent;
};
class TabletPadStripEvent : public Event
{
public:
TabletPadStripEvent(libinput_event *event, libinput_event_type type);
int position() const
{
return libinput_event_tablet_pad_get_strip_position(m_tabletPadEvent);
}
int number() const
{
return libinput_event_tablet_pad_get_strip_number(m_tabletPadEvent);
}
libinput_tablet_pad_strip_axis_source source() const
{
return libinput_event_tablet_pad_get_strip_source(m_tabletPadEvent);
}
std::chrono::microseconds time() const
{
return std::chrono::microseconds(libinput_event_tablet_pad_get_time_usec(m_tabletPadEvent));
}
private:
libinput_event_tablet_pad *m_tabletPadEvent;
};
class TabletPadButtonEvent : public Event
{
public:
TabletPadButtonEvent(libinput_event *event, libinput_event_type type);
uint buttonId() const
{
return libinput_event_tablet_pad_get_button_number(m_tabletPadEvent);
}
bool isButtonPressed() const
{
const auto state = libinput_event_tablet_pad_get_button_state(m_tabletPadEvent);
return state == LIBINPUT_BUTTON_STATE_PRESSED;
}
std::chrono::microseconds time() const
{
return std::chrono::microseconds(libinput_event_tablet_pad_get_time_usec(m_tabletPadEvent));
}
private:
libinput_event_tablet_pad *m_tabletPadEvent;
};
inline libinput_event_type Event::type() const
{
return m_type;
}
}
}
@@ -0,0 +1,10 @@
/*
KWin - the KDE window manager
This file is part of the KDE project.
SPDX-FileCopyrightText: 2015 Martin Gräßlin <mgraesslin@kde.org>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "libinput_logging.h"
Q_LOGGING_CATEGORY(KWIN_LIBINPUT, "kwin_libinput", QtWarningMsg)
@@ -0,0 +1,12 @@
/*
KWin - the KDE window manager
This file is part of the KDE project.
SPDX-FileCopyrightText: 2015 Martin Gräßlin <mgraesslin@kde.org>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#pragma once
#include <QDebug>
#include <QLoggingCategory>
Q_DECLARE_LOGGING_CATEGORY(KWIN_LIBINPUT)
@@ -0,0 +1,58 @@
/*
SPDX-FileCopyrightText: 2021 Vlad Zahorodnii <vlad.zahorodnii@kde.org>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "libinputbackend.h"
#include "connection.h"
#include "device.h"
namespace KWin
{
LibinputBackend::LibinputBackend(Session *session, QObject *parent)
: InputBackend(parent)
{
m_thread.setObjectName(QStringLiteral("libinput-connection"));
m_thread.start();
m_connection = LibInput::Connection::create(session);
m_connection->moveToThread(&m_thread);
connect(
m_connection, &LibInput::Connection::eventsRead, this, [this]() {
m_connection->processEvents();
},
Qt::QueuedConnection);
// Direct connection because the deviceAdded() and the deviceRemoved() signals are emitted
// from the main thread.
connect(m_connection, &LibInput::Connection::deviceAdded,
this, &InputBackend::deviceAdded, Qt::DirectConnection);
connect(m_connection, &LibInput::Connection::deviceRemoved,
this, &InputBackend::deviceRemoved, Qt::DirectConnection);
}
LibinputBackend::~LibinputBackend()
{
m_connection->deleteLater();
m_thread.quit();
m_thread.wait();
}
void LibinputBackend::initialize()
{
m_connection->setInputConfig(config());
m_connection->setup();
}
void LibinputBackend::updateScreens()
{
m_connection->updateScreens();
}
} // namespace KWin
#include "moc_libinputbackend.cpp"
@@ -0,0 +1,39 @@
/*
SPDX-FileCopyrightText: 2021 Vlad Zahorodnii <vlad.zahorodnii@kde.org>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#pragma once
#include "core/inputbackend.h"
#include <QThread>
namespace KWin
{
class Session;
namespace LibInput
{
class Connection;
}
class KWIN_EXPORT LibinputBackend : public InputBackend
{
Q_OBJECT
public:
explicit LibinputBackend(Session *session, QObject *parent = nullptr);
~LibinputBackend() override;
void initialize() override;
void updateScreens() override;
private:
QThread m_thread;
LibInput::Connection *m_connection = nullptr;
};
} // namespace KWin
@@ -0,0 +1,7 @@
target_sources(kwin PRIVATE
virtual_backend.cpp
virtual_egl_backend.cpp
virtual_logging.cpp
virtual_output.cpp
virtual_qpainter_backend.cpp
)
@@ -0,0 +1,150 @@
/*
KWin - the KDE window manager
This file is part of the KDE project.
SPDX-FileCopyrightText: 2015 Martin Gräßlin <mgraesslin@kde.org>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "virtual_backend.h"
#include "core/drmdevice.h"
#include "virtual_egl_backend.h"
#include "virtual_output.h"
#include "virtual_qpainter_backend.h"
#include <fcntl.h>
#include <gbm.h>
#include <xf86drm.h>
namespace KWin
{
static std::unique_ptr<DrmDevice> findRenderDevice()
{
const int deviceCount = drmGetDevices2(0, nullptr, 0);
if (deviceCount <= 0) {
return nullptr;
}
QList<drmDevice *> devices(deviceCount);
if (drmGetDevices2(0, devices.data(), devices.size()) < 0) {
return nullptr;
}
auto deviceCleanup = qScopeGuard([&devices]() {
drmFreeDevices(devices.data(), devices.size());
});
for (drmDevice *device : std::as_const(devices)) {
// If it's a vgem device, prefer the primary node because gbm will attempt to allocate
// dumb buffers and they can be allocated only on the primary node.
int nodeType = DRM_NODE_RENDER;
if (device->bustype == DRM_BUS_PLATFORM) {
if (strcmp(device->businfo.platform->fullname, "vgem") == 0) {
nodeType = DRM_NODE_PRIMARY;
}
}
if (device->available_nodes & (1 << nodeType)) {
if (auto ret = DrmDevice::open(device->nodes[nodeType])) {
return ret;
}
}
}
return nullptr;
}
VirtualBackend::VirtualBackend(QObject *parent)
: OutputBackend(parent)
, m_drmDevice(findRenderDevice())
{
}
VirtualBackend::~VirtualBackend()
{
}
bool VirtualBackend::initialize()
{
return true;
}
QList<CompositingType> VirtualBackend::supportedCompositors() const
{
QList<CompositingType> compositingTypes;
if (m_drmDevice) {
compositingTypes.append(OpenGLCompositing);
}
compositingTypes.append(QPainterCompositing);
return compositingTypes;
}
DrmDevice *VirtualBackend::drmDevice() const
{
return m_drmDevice.get();
}
std::unique_ptr<QPainterBackend> VirtualBackend::createQPainterBackend()
{
return std::make_unique<VirtualQPainterBackend>(this);
}
std::unique_ptr<OpenGLBackend> VirtualBackend::createOpenGLBackend()
{
return std::make_unique<VirtualEglBackend>(this);
}
Outputs VirtualBackend::outputs() const
{
return m_outputs;
}
VirtualOutput *VirtualBackend::createOutput(const OutputInfo &info)
{
VirtualOutput *output = new VirtualOutput(this, info.internal, info.physicalSizeInMM, info.panelOrientation, info.edid, info.edidIdentifierOverride, info.connectorName, info.mstPath);
output->init(info.geometry.topLeft(), info.geometry.size() * info.scale, info.scale, info.modes);
m_outputs.append(output);
Q_EMIT outputAdded(output);
output->updateEnabled(true);
return output;
}
Output *VirtualBackend::addOutput(const OutputInfo &info)
{
VirtualOutput *output = createOutput(info);
Q_EMIT outputsQueried();
return output;
}
void VirtualBackend::setVirtualOutputs(const QList<OutputInfo> &infos)
{
const QList<VirtualOutput *> removed = m_outputs;
for (const auto &info : infos) {
createOutput(info);
}
for (VirtualOutput *output : removed) {
output->updateEnabled(false);
m_outputs.removeOne(output);
Q_EMIT outputRemoved(output);
output->unref();
}
Q_EMIT outputsQueried();
}
void VirtualBackend::setEglDisplay(std::unique_ptr<EglDisplay> &&display)
{
m_display = std::move(display);
}
EglDisplay *VirtualBackend::sceneEglDisplayObject() const
{
return m_display.get();
}
} // namespace KWin
#include "moc_virtual_backend.cpp"
@@ -0,0 +1,72 @@
/*
KWin - the KDE window manager
This file is part of the KDE project.
SPDX-FileCopyrightText: 2015 Martin Gräßlin <mgraesslin@kde.org>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#pragma once
#include "core/output.h"
#include "core/outputbackend.h"
#include "utils/filedescriptor.h"
#include <QRect>
namespace KWin
{
class VirtualBackend;
class VirtualOutput;
class DrmDevice;
class KWIN_EXPORT VirtualBackend : public OutputBackend
{
Q_OBJECT
public:
VirtualBackend(QObject *parent = nullptr);
~VirtualBackend() override;
bool initialize() override;
std::unique_ptr<QPainterBackend> createQPainterBackend() override;
std::unique_ptr<OpenGLBackend> createOpenGLBackend() override;
struct OutputInfo
{
QRect geometry;
double scale = 1;
bool internal = false;
QSize physicalSizeInMM;
QList<std::tuple<QSize, uint64_t, OutputMode::Flags>> modes;
OutputTransform panelOrientation = OutputTransform::Kind::Normal;
QByteArray edid;
std::optional<QByteArray> edidIdentifierOverride;
std::optional<QString> connectorName;
std::optional<QByteArray> mstPath;
};
Output *addOutput(const OutputInfo &info);
void setVirtualOutputs(const QList<OutputInfo> &infos);
Outputs outputs() const override;
QList<CompositingType> supportedCompositors() const override;
void setEglDisplay(std::unique_ptr<EglDisplay> &&display);
EglDisplay *sceneEglDisplayObject() const override;
DrmDevice *drmDevice() const;
Q_SIGNALS:
void virtualOutputsSet(bool countChanged);
private:
VirtualOutput *createOutput(const OutputInfo &info);
QList<VirtualOutput *> m_outputs;
std::unique_ptr<DrmDevice> m_drmDevice;
std::unique_ptr<EglDisplay> m_display;
};
} // namespace KWin
@@ -0,0 +1,198 @@
/*
KWin - the KDE window manager
This file is part of the KDE project.
SPDX-FileCopyrightText: 2015 Martin Gräßlin <mgraesslin@kde.org>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "virtual_egl_backend.h"
#include "core/drmdevice.h"
#include "core/gbmgraphicsbufferallocator.h"
#include "opengl/eglswapchain.h"
#include "opengl/glrendertimequery.h"
#include "opengl/glutils.h"
#include "platformsupport/scenes/opengl/basiceglsurfacetexture_wayland.h"
#include "utils/softwarevsyncmonitor.h"
#include "virtual_backend.h"
#include "virtual_logging.h"
#include "virtual_output.h"
#include <drm_fourcc.h>
namespace KWin
{
VirtualEglLayer::VirtualEglLayer(Output *output, VirtualEglBackend *backend)
: OutputLayer(output)
, m_backend(backend)
{
}
std::shared_ptr<GLTexture> VirtualEglLayer::texture() const
{
return m_current->texture();
}
std::optional<OutputLayerBeginFrameInfo> VirtualEglLayer::doBeginFrame()
{
m_backend->makeCurrent();
const QSize nativeSize = m_output->modeSize();
if (!m_swapchain || m_swapchain->size() != nativeSize) {
m_swapchain = EglSwapchain::create(m_backend->drmDevice()->allocator(), m_backend->openglContext(), nativeSize, DRM_FORMAT_XRGB8888, {DRM_FORMAT_MOD_INVALID});
if (!m_swapchain) {
return std::nullopt;
}
}
m_current = m_swapchain->acquire();
if (!m_current) {
return std::nullopt;
}
m_query = std::make_unique<GLRenderTimeQuery>(m_backend->openglContextRef());
m_query->begin();
return OutputLayerBeginFrameInfo{
.renderTarget = RenderTarget(m_current->framebuffer()),
.repaint = infiniteRegion(),
};
}
bool VirtualEglLayer::doEndFrame(const QRegion &renderedRegion, const QRegion &damagedRegion, OutputFrame *frame)
{
m_query->end();
frame->addRenderTimeQuery(std::move(m_query));
glFlush(); // flush pending rendering commands.
return true;
}
DrmDevice *VirtualEglLayer::scanoutDevice() const
{
return m_backend->drmDevice();
}
QHash<uint32_t, QList<uint64_t>> VirtualEglLayer::supportedDrmFormats() const
{
return m_backend->supportedFormats();
}
VirtualEglBackend::VirtualEglBackend(VirtualBackend *b)
: AbstractEglBackend()
, m_backend(b)
{
}
VirtualEglBackend::~VirtualEglBackend()
{
m_outputs.clear();
cleanup();
}
VirtualBackend *VirtualEglBackend::backend() const
{
return m_backend;
}
DrmDevice *VirtualEglBackend::drmDevice() const
{
return m_backend->drmDevice();
}
bool VirtualEglBackend::initializeEgl()
{
initClientExtensions();
if (!m_backend->sceneEglDisplayObject()) {
for (const QByteArray &extension : {QByteArrayLiteral("EGL_EXT_platform_base"), QByteArrayLiteral("EGL_KHR_platform_gbm")}) {
if (!hasClientExtension(extension)) {
qCWarning(KWIN_VIRTUAL) << extension << "client extension is not supported by the platform";
return false;
}
}
m_backend->setEglDisplay(EglDisplay::create(eglGetPlatformDisplayEXT(EGL_PLATFORM_GBM_KHR, m_backend->drmDevice()->gbmDevice(), nullptr)));
}
auto display = m_backend->sceneEglDisplayObject();
if (!display) {
return false;
}
setEglDisplay(display);
return true;
}
void VirtualEglBackend::init()
{
if (!initializeEgl()) {
setFailed("Could not initialize egl");
return;
}
if (!initRenderingContext()) {
setFailed("Could not initialize rendering context");
return;
}
if (checkGLError("Init")) {
setFailed("Error during init of EglGbmBackend");
return;
}
setSupportsBufferAge(false);
initWayland();
const auto outputs = m_backend->outputs();
for (Output *output : outputs) {
addOutput(output);
}
connect(m_backend, &VirtualBackend::outputAdded, this, &VirtualEglBackend::addOutput);
connect(m_backend, &VirtualBackend::outputRemoved, this, &VirtualEglBackend::removeOutput);
}
bool VirtualEglBackend::initRenderingContext()
{
return createContext(EGL_NO_CONFIG_KHR) && makeCurrent();
}
void VirtualEglBackend::addOutput(Output *output)
{
makeCurrent();
m_outputs[output] = std::make_unique<VirtualEglLayer>(output, this);
}
void VirtualEglBackend::removeOutput(Output *output)
{
makeCurrent();
m_outputs.erase(output);
}
std::unique_ptr<SurfaceTexture> VirtualEglBackend::createSurfaceTextureWayland(SurfacePixmap *pixmap)
{
return std::make_unique<BasicEGLSurfaceTextureWayland>(this, pixmap);
}
OutputLayer *VirtualEglBackend::primaryLayer(Output *output)
{
return m_outputs[output].get();
}
bool VirtualEglBackend::present(Output *output, const std::shared_ptr<OutputFrame> &frame)
{
static_cast<VirtualOutput *>(output)->present(frame);
return true;
}
std::pair<std::shared_ptr<KWin::GLTexture>, ColorDescription> VirtualEglBackend::textureForOutput(Output *output) const
{
auto it = m_outputs.find(output);
if (it == m_outputs.end()) {
return {nullptr, ColorDescription::sRGB};
}
return std::make_pair(it->second->texture(), ColorDescription::sRGB);
}
} // namespace
#include "moc_virtual_egl_backend.cpp"
@@ -0,0 +1,77 @@
/*
KWin - the KDE window manager
This file is part of the KDE project.
SPDX-FileCopyrightText: 2015 Martin Gräßlin <mgraesslin@kde.org>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#pragma once
#include "core/outputlayer.h"
#include "platformsupport/scenes/opengl/abstract_egl_backend.h"
#include <chrono>
#include <memory>
namespace KWin
{
class EglSwapchainSlot;
class EglSwapchain;
class GraphicsBufferAllocator;
class VirtualBackend;
class GLFramebuffer;
class GLTexture;
class VirtualEglBackend;
class GLRenderTimeQuery;
class VirtualEglLayer : public OutputLayer
{
public:
VirtualEglLayer(Output *output, VirtualEglBackend *backend);
std::optional<OutputLayerBeginFrameInfo> doBeginFrame() override;
bool doEndFrame(const QRegion &renderedRegion, const QRegion &damagedRegion, OutputFrame *frame) override;
std::shared_ptr<GLTexture> texture() const;
DrmDevice *scanoutDevice() const override;
QHash<uint32_t, QList<uint64_t>> supportedDrmFormats() const override;
private:
VirtualEglBackend *const m_backend;
std::shared_ptr<EglSwapchain> m_swapchain;
std::shared_ptr<EglSwapchainSlot> m_current;
std::unique_ptr<GLRenderTimeQuery> m_query;
};
/**
* @brief OpenGL Backend using Egl on a GBM surface.
*/
class VirtualEglBackend : public AbstractEglBackend
{
Q_OBJECT
public:
VirtualEglBackend(VirtualBackend *b);
~VirtualEglBackend() override;
std::unique_ptr<SurfaceTexture> createSurfaceTextureWayland(SurfacePixmap *pixmap) override;
std::pair<std::shared_ptr<KWin::GLTexture>, ColorDescription> textureForOutput(Output *output) const override;
OutputLayer *primaryLayer(Output *output) override;
bool present(Output *output, const std::shared_ptr<OutputFrame> &frame) override;
void init() override;
VirtualBackend *backend() const;
DrmDevice *drmDevice() const override;
private:
bool initializeEgl();
bool initRenderingContext();
void addOutput(Output *output);
void removeOutput(Output *output);
VirtualBackend *m_backend;
std::map<Output *, std::unique_ptr<VirtualEglLayer>> m_outputs;
};
} // namespace KWin
@@ -0,0 +1,9 @@
/*
SPDX-FileCopyrightText: 2022 Vlad Zahorodnii <vlad.zahorodnii@kde.org>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "virtual_logging.h"
Q_LOGGING_CATEGORY(KWIN_VIRTUAL, "kwin_platform_virtual", QtWarningMsg)
@@ -0,0 +1,11 @@
/*
SPDX-FileCopyrightText: 2022 Vlad Zahorodnii <vlad.zahorodnii@kde.org>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#pragma once
#include <QLoggingCategory>
Q_DECLARE_LOGGING_CATEGORY(KWIN_VIRTUAL)
@@ -0,0 +1,121 @@
/*
KWin - the KDE window manager
This file is part of the KDE project.
SPDX-FileCopyrightText: 2018 Roman Gilg <subdiff@gmail.com>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "virtual_output.h"
#include "virtual_backend.h"
#include "compositor.h"
#include "core/outputconfiguration.h"
#include "core/outputlayer.h"
#include "core/renderbackend.h"
#include "core/renderloop.h"
#include "utils/softwarevsyncmonitor.h"
namespace KWin
{
VirtualOutput::VirtualOutput(VirtualBackend *parent, bool internal, const QSize &physicalSizeInMM, OutputTransform panelOrientation, const QByteArray &edid, std::optional<QByteArray> edidIdentifierOverride, const std::optional<QString> &connectorName, const std::optional<QByteArray> &mstPath)
: Output(parent)
, m_backend(parent)
, m_renderLoop(std::make_unique<RenderLoop>(this))
, m_vsyncMonitor(SoftwareVsyncMonitor::create())
{
connect(m_vsyncMonitor.get(), &VsyncMonitor::vblankOccurred, this, &VirtualOutput::vblank);
static int identifier = -1;
m_identifier = ++identifier;
setInformation(Information{
.name = connectorName.value_or(QStringLiteral("Virtual-%1").arg(identifier)),
.physicalSize = physicalSizeInMM,
.edid = Edid{edid, edidIdentifierOverride},
.panelOrientation = panelOrientation,
.internal = internal,
.mstPath = mstPath.value_or(QByteArray()),
});
}
VirtualOutput::~VirtualOutput()
{
}
RenderLoop *VirtualOutput::renderLoop() const
{
return m_renderLoop.get();
}
void VirtualOutput::present(const std::shared_ptr<OutputFrame> &frame)
{
m_frame = frame;
m_vsyncMonitor->arm();
Q_EMIT outputChange(frame->damage());
}
void VirtualOutput::init(const QPoint &logicalPosition, const QSize &pixelSize, qreal scale, const QList<std::tuple<QSize, uint64_t, OutputMode::Flags>> &modes)
{
QList<std::shared_ptr<OutputMode>> modeList;
for (const auto &mode : modes) {
const auto &[size, refresh, flags] = mode;
modeList.push_back(std::make_shared<OutputMode>(size, refresh, flags));
}
if (modeList.empty()) {
modeList.push_back(std::make_shared<OutputMode>(pixelSize, 60000, OutputMode::Flag::Preferred));
}
m_renderLoop->setRefreshRate(modeList.front()->refreshRate());
m_vsyncMonitor->setRefreshRate(modeList.front()->refreshRate());
setState(State{
.position = logicalPosition,
.scale = scale,
.modes = modeList,
.currentMode = modeList.front(),
});
}
void VirtualOutput::applyChanges(const OutputConfiguration &config)
{
auto props = config.constChangeSet(this);
if (!props) {
return;
}
Q_EMIT aboutToChange(props.get());
State next = m_state;
next.enabled = props->enabled.value_or(m_state.enabled);
next.transform = props->transform.value_or(m_state.transform);
next.position = props->pos.value_or(m_state.position);
next.scale = props->scale.value_or(m_state.scale);
next.desiredModeSize = props->desiredModeSize.value_or(m_state.desiredModeSize);
next.desiredModeRefreshRate = props->desiredModeRefreshRate.value_or(m_state.desiredModeRefreshRate);
next.currentMode = props->mode.value_or(m_state.currentMode).lock();
if (!next.currentMode) {
next.currentMode = next.modes.front();
}
setState(next);
m_renderLoop->setRefreshRate(next.currentMode->refreshRate());
m_vsyncMonitor->setRefreshRate(next.currentMode->refreshRate());
Q_EMIT changed();
}
void VirtualOutput::updateEnabled(bool enabled)
{
State next = m_state;
next.enabled = enabled;
setState(next);
}
void VirtualOutput::vblank(std::chrono::nanoseconds timestamp)
{
if (m_frame) {
m_frame->presented(timestamp, PresentationMode::VSync);
m_frame.reset();
}
}
}
#include "moc_virtual_output.cpp"
@@ -0,0 +1,54 @@
/*
KWin - the KDE window manager
This file is part of the KDE project.
SPDX-FileCopyrightText: 2018 Roman Gilg <subdiff@gmail.com>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#pragma once
#include "core/output.h"
#include <QObject>
#include <QRect>
namespace KWin
{
class SoftwareVsyncMonitor;
class VirtualBackend;
class OutputFrame;
class VirtualOutput : public Output
{
Q_OBJECT
public:
explicit VirtualOutput(VirtualBackend *parent, bool internal, const QSize &physicalSizeInMM, OutputTransform panelOrientation, const QByteArray &edid, std::optional<QByteArray> edidIdentifierOverride, const std::optional<QString> &connectorName, const std::optional<QByteArray> &mstPath);
~VirtualOutput() override;
RenderLoop *renderLoop() const override;
void present(const std::shared_ptr<OutputFrame> &frame);
void init(const QPoint &logicalPosition, const QSize &pixelSize, qreal scale, const QList<std::tuple<QSize, uint64_t, OutputMode::Flags>> &modes);
void updateEnabled(bool enabled);
void applyChanges(const OutputConfiguration &config) override;
private:
void vblank(std::chrono::nanoseconds timestamp);
Q_DISABLE_COPY(VirtualOutput);
friend class VirtualBackend;
VirtualBackend *m_backend;
std::unique_ptr<RenderLoop> m_renderLoop;
std::unique_ptr<SoftwareVsyncMonitor> m_vsyncMonitor;
int m_gammaSize = 200;
bool m_gammaResult = true;
int m_identifier;
std::shared_ptr<OutputFrame> m_frame;
};
} // namespace KWin
@@ -0,0 +1,115 @@
/*
KWin - the KDE window manager
This file is part of the KDE project.
SPDX-FileCopyrightText: 2015 Martin Gräßlin <mgraesslin@kde.org>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "virtual_qpainter_backend.h"
#include "core/drmdevice.h"
#include "core/graphicsbufferview.h"
#include "core/shmgraphicsbufferallocator.h"
#include "platformsupport/scenes/qpainter/qpainterswapchain.h"
#include "utils/softwarevsyncmonitor.h"
#include "virtual_backend.h"
#include "virtual_output.h"
#include <drm_fourcc.h>
namespace KWin
{
VirtualQPainterLayer::VirtualQPainterLayer(Output *output, VirtualQPainterBackend *backend)
: OutputLayer(output)
, m_backend(backend)
{
}
VirtualQPainterLayer::~VirtualQPainterLayer()
{
}
std::optional<OutputLayerBeginFrameInfo> VirtualQPainterLayer::doBeginFrame()
{
const QSize nativeSize(m_output->modeSize());
if (!m_swapchain || m_swapchain->size() != nativeSize) {
m_swapchain = std::make_unique<QPainterSwapchain>(m_backend->graphicsBufferAllocator(), nativeSize, DRM_FORMAT_XRGB8888);
}
m_current = m_swapchain->acquire();
if (!m_current) {
return std::nullopt;
}
m_renderTime = std::make_unique<CpuRenderTimeQuery>();
return OutputLayerBeginFrameInfo{
.renderTarget = RenderTarget(m_current->view()->image()),
.repaint = m_output->rect(),
};
}
bool VirtualQPainterLayer::doEndFrame(const QRegion &renderedRegion, const QRegion &damagedRegion, OutputFrame *frame)
{
m_renderTime->end();
frame->addRenderTimeQuery(std::move(m_renderTime));
return true;
}
QImage *VirtualQPainterLayer::image()
{
return m_current->view()->image();
}
DrmDevice *VirtualQPainterLayer::scanoutDevice() const
{
return m_backend->drmDevice();
}
QHash<uint32_t, QList<uint64_t>> VirtualQPainterLayer::supportedDrmFormats() const
{
return {{DRM_FORMAT_ARGB8888, {DRM_FORMAT_MOD_LINEAR}}};
}
VirtualQPainterBackend::VirtualQPainterBackend(VirtualBackend *backend)
: m_allocator(std::make_unique<ShmGraphicsBufferAllocator>())
{
connect(backend, &VirtualBackend::outputAdded, this, &VirtualQPainterBackend::addOutput);
connect(backend, &VirtualBackend::outputRemoved, this, &VirtualQPainterBackend::removeOutput);
const auto outputs = backend->outputs();
for (Output *output : outputs) {
addOutput(output);
}
}
VirtualQPainterBackend::~VirtualQPainterBackend() = default;
void VirtualQPainterBackend::addOutput(Output *output)
{
m_outputs[output] = std::make_unique<VirtualQPainterLayer>(output, this);
}
void VirtualQPainterBackend::removeOutput(Output *output)
{
m_outputs.erase(output);
}
GraphicsBufferAllocator *VirtualQPainterBackend::graphicsBufferAllocator() const
{
return m_allocator.get();
}
bool VirtualQPainterBackend::present(Output *output, const std::shared_ptr<OutputFrame> &frame)
{
static_cast<VirtualOutput *>(output)->present(frame);
return true;
}
VirtualQPainterLayer *VirtualQPainterBackend::primaryLayer(Output *output)
{
return m_outputs[output].get();
}
}
#include "moc_virtual_qpainter_backend.cpp"
@@ -0,0 +1,69 @@
/*
KWin - the KDE window manager
This file is part of the KDE project.
SPDX-FileCopyrightText: 2015 Martin Gräßlin <mgraesslin@kde.org>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#pragma once
#include "core/outputlayer.h"
#include "core/renderbackend.h"
#include "platformsupport/scenes/qpainter/qpainterbackend.h"
#include <QList>
#include <QMap>
#include <QObject>
#include <chrono>
#include <memory>
namespace KWin
{
class GraphicsBufferAllocator;
class QPainterSwapchainSlot;
class QPainterSwapchain;
class VirtualBackend;
class VirtualQPainterBackend;
class VirtualQPainterLayer : public OutputLayer
{
public:
VirtualQPainterLayer(Output *output, VirtualQPainterBackend *backend);
~VirtualQPainterLayer() override;
std::optional<OutputLayerBeginFrameInfo> doBeginFrame() override;
bool doEndFrame(const QRegion &renderedRegion, const QRegion &damagedRegion, OutputFrame *frame) override;
QImage *image();
DrmDevice *scanoutDevice() const override;
QHash<uint32_t, QList<uint64_t>> supportedDrmFormats() const override;
private:
VirtualQPainterBackend *const m_backend;
std::unique_ptr<QPainterSwapchain> m_swapchain;
std::shared_ptr<QPainterSwapchainSlot> m_current;
std::unique_ptr<CpuRenderTimeQuery> m_renderTime;
};
class VirtualQPainterBackend : public QPainterBackend
{
Q_OBJECT
public:
VirtualQPainterBackend(VirtualBackend *backend);
~VirtualQPainterBackend() override;
GraphicsBufferAllocator *graphicsBufferAllocator() const;
bool present(Output *output, const std::shared_ptr<OutputFrame> &frame) override;
VirtualQPainterLayer *primaryLayer(Output *output) override;
private:
void addOutput(Output *output);
void removeOutput(Output *output);
std::unique_ptr<GraphicsBufferAllocator> m_allocator;
std::map<Output *, std::unique_ptr<VirtualQPainterLayer>> m_outputs;
};
} // namespace KWin
@@ -0,0 +1,10 @@
target_sources(kwin PRIVATE
wayland_backend.cpp
wayland_display.cpp
wayland_egl_backend.cpp
wayland_logging.cpp
wayland_output.cpp
wayland_qpainter_backend.cpp
)
target_link_libraries(kwin PRIVATE Plasma::KWaylandClient Wayland::Client gbm::gbm)
@@ -0,0 +1,714 @@
/*
KWin - the KDE window manager
This file is part of the KDE project.
SPDX-FileCopyrightText: 2019 Roman Gilg <subdiff@gmail.com>
SPDX-FileCopyrightText: 2013 Martin Gräßlin <mgraesslin@kde.org>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "wayland_backend.h"
#include "core/drmdevice.h"
#include "input.h"
#include "wayland_display.h"
#include "wayland_egl_backend.h"
#include "wayland_logging.h"
#include "wayland_output.h"
#include "wayland_qpainter_backend.h"
#include <KWayland/Client/keyboard.h>
#include <KWayland/Client/pointer.h>
#include <KWayland/Client/pointergestures.h>
#include <KWayland/Client/relativepointer.h>
#include <KWayland/Client/seat.h>
#include <KWayland/Client/surface.h>
#include <KWayland/Client/touch.h>
#include <QAbstractEventDispatcher>
#include <drm_fourcc.h>
#include <fcntl.h>
#include <gbm.h>
#include <linux/input.h>
#include <unistd.h>
#include <wayland-client-core.h>
#include "wayland-linux-dmabuf-unstable-v1-client-protocol.h"
namespace KWin
{
namespace Wayland
{
using namespace KWayland::Client;
inline static QPointF sizeToPoint(const QSizeF &size)
{
return QPointF(size.width(), size.height());
}
WaylandInputDevice::WaylandInputDevice(KWayland::Client::Keyboard *keyboard, WaylandSeat *seat)
: m_seat(seat)
, m_keyboard(keyboard)
{
connect(keyboard, &Keyboard::left, this, [this](quint32 time) {
for (quint32 key : std::as_const(m_pressedKeys)) {
Q_EMIT keyChanged(key, KeyboardKeyState::Released, std::chrono::milliseconds(time), this);
}
m_pressedKeys.clear();
});
connect(keyboard, &Keyboard::keyChanged, this, [this](quint32 key, Keyboard::KeyState nativeState, quint32 time) {
KeyboardKeyState state;
switch (nativeState) {
case Keyboard::KeyState::Pressed:
if (key == KEY_RIGHTCTRL) {
m_seat->backend()->togglePointerLock();
}
state = KeyboardKeyState::Pressed;
m_pressedKeys.insert(key);
break;
case Keyboard::KeyState::Released:
m_pressedKeys.remove(key);
state = KeyboardKeyState::Released;
break;
default:
Q_UNREACHABLE();
}
Q_EMIT keyChanged(key, state, std::chrono::milliseconds(time), this);
});
}
WaylandInputDevice::WaylandInputDevice(KWayland::Client::Pointer *pointer, WaylandSeat *seat)
: m_seat(seat)
, m_pointer(pointer)
{
connect(pointer, &Pointer::entered, this, [this](quint32 serial, const QPointF &relativeToSurface) {
WaylandOutput *output = m_seat->backend()->findOutput(m_pointer->enteredSurface());
Q_ASSERT(output);
output->cursor()->setPointer(m_pointer.get());
});
connect(pointer, &Pointer::left, this, [this]() {
// wl_pointer.leave carries the wl_surface, but KWayland::Client::Pointer::left does not.
const auto outputs = m_seat->backend()->outputs();
for (Output *output : outputs) {
WaylandOutput *waylandOutput = static_cast<WaylandOutput *>(output);
if (waylandOutput->cursor()->pointer()) {
waylandOutput->cursor()->setPointer(nullptr);
}
}
});
connect(pointer, &Pointer::motion, this, [this](const QPointF &relativeToSurface, quint32 time) {
WaylandOutput *output = m_seat->backend()->findOutput(m_pointer->enteredSurface());
Q_ASSERT(output);
const QPointF absolutePos = output->geometry().topLeft() + relativeToSurface;
Q_EMIT pointerMotionAbsolute(absolutePos, std::chrono::milliseconds(time), this);
});
connect(pointer, &Pointer::buttonStateChanged, this, [this](quint32 serial, quint32 time, quint32 button, Pointer::ButtonState nativeState) {
PointerButtonState state;
switch (nativeState) {
case Pointer::ButtonState::Pressed:
state = PointerButtonState::Pressed;
break;
case Pointer::ButtonState::Released:
state = PointerButtonState::Released;
break;
default:
Q_UNREACHABLE();
}
Q_EMIT pointerButtonChanged(button, state, std::chrono::milliseconds(time), this);
});
// TODO: Send discreteDelta and source as well.
connect(pointer, &Pointer::axisChanged, this, [this](quint32 time, Pointer::Axis nativeAxis, qreal delta) {
PointerAxis axis;
switch (nativeAxis) {
case Pointer::Axis::Horizontal:
axis = PointerAxis::Horizontal;
break;
case Pointer::Axis::Vertical:
axis = PointerAxis::Vertical;
break;
default:
Q_UNREACHABLE();
}
Q_EMIT pointerAxisChanged(axis, delta, 0, PointerAxisSource::Unknown, false, std::chrono::milliseconds(time), this);
});
connect(pointer, &Pointer::frame, this, [this]() {
Q_EMIT pointerFrame(this);
});
KWayland::Client::PointerGestures *pointerGestures = m_seat->backend()->display()->pointerGestures();
if (pointerGestures) {
m_pinchGesture.reset(pointerGestures->createPinchGesture(m_pointer.get(), this));
connect(m_pinchGesture.get(), &PointerPinchGesture::started, this, [this](quint32 serial, quint32 time) {
Q_EMIT pinchGestureBegin(m_pinchGesture->fingerCount(), std::chrono::milliseconds(time), this);
});
connect(m_pinchGesture.get(), &PointerPinchGesture::updated, this, [this](const QSizeF &delta, qreal scale, qreal rotation, quint32 time) {
Q_EMIT pinchGestureUpdate(scale, rotation, sizeToPoint(delta), std::chrono::milliseconds(time), this);
});
connect(m_pinchGesture.get(), &PointerPinchGesture::ended, this, [this](quint32 serial, quint32 time) {
Q_EMIT pinchGestureEnd(std::chrono::milliseconds(time), this);
});
connect(m_pinchGesture.get(), &PointerPinchGesture::cancelled, this, [this](quint32 serial, quint32 time) {
Q_EMIT pinchGestureCancelled(std::chrono::milliseconds(time), this);
});
m_swipeGesture.reset(pointerGestures->createSwipeGesture(m_pointer.get(), this));
connect(m_swipeGesture.get(), &PointerSwipeGesture::started, this, [this](quint32 serial, quint32 time) {
Q_EMIT swipeGestureBegin(m_swipeGesture->fingerCount(), std::chrono::milliseconds(time), this);
});
connect(m_swipeGesture.get(), &PointerSwipeGesture::updated, this, [this](const QSizeF &delta, quint32 time) {
Q_EMIT swipeGestureUpdate(sizeToPoint(delta), std::chrono::milliseconds(time), this);
});
connect(m_swipeGesture.get(), &PointerSwipeGesture::ended, this, [this](quint32 serial, quint32 time) {
Q_EMIT swipeGestureEnd(std::chrono::milliseconds(time), this);
});
connect(m_swipeGesture.get(), &PointerSwipeGesture::cancelled, this, [this](quint32 serial, quint32 time) {
Q_EMIT swipeGestureCancelled(std::chrono::milliseconds(time), this);
});
}
}
WaylandInputDevice::WaylandInputDevice(KWayland::Client::RelativePointer *relativePointer, WaylandSeat *seat)
: m_seat(seat)
, m_relativePointer(relativePointer)
{
connect(relativePointer, &RelativePointer::relativeMotion, this, [this](const QSizeF &delta, const QSizeF &deltaNonAccelerated, quint64 timestamp) {
Q_EMIT pointerMotion(sizeToPoint(delta), sizeToPoint(deltaNonAccelerated), std::chrono::microseconds(timestamp), this);
});
}
WaylandInputDevice::WaylandInputDevice(KWayland::Client::Touch *touch, WaylandSeat *seat)
: m_seat(seat)
, m_touch(touch)
{
connect(touch, &Touch::sequenceCanceled, this, [this]() {
Q_EMIT touchCanceled(this);
});
connect(touch, &Touch::frameEnded, this, [this]() {
Q_EMIT touchFrame(this);
});
connect(touch, &Touch::sequenceStarted, this, [this](TouchPoint *tp) {
auto o = m_seat->backend()->findOutput(tp->surface());
Q_ASSERT(o);
const QPointF position = o->geometry().topLeft() + tp->position();
Q_EMIT touchDown(tp->id(), position, std::chrono::milliseconds(tp->time()), this);
});
connect(touch, &Touch::pointAdded, this, [this](TouchPoint *tp) {
auto o = m_seat->backend()->findOutput(tp->surface());
Q_ASSERT(o);
const QPointF position = o->geometry().topLeft() + tp->position();
Q_EMIT touchDown(tp->id(), position, std::chrono::milliseconds(tp->time()), this);
});
connect(touch, &Touch::pointRemoved, this, [this](TouchPoint *tp) {
Q_EMIT touchUp(tp->id(), std::chrono::milliseconds(tp->time()), this);
});
connect(touch, &Touch::pointMoved, this, [this](TouchPoint *tp) {
auto o = m_seat->backend()->findOutput(tp->surface());
Q_ASSERT(o);
const QPointF position = o->geometry().topLeft() + tp->position();
Q_EMIT touchMotion(tp->id(), position, std::chrono::milliseconds(tp->time()), this);
});
}
WaylandInputDevice::~WaylandInputDevice()
{
}
QString WaylandInputDevice::name() const
{
return QString();
}
bool WaylandInputDevice::isEnabled() const
{
return true;
}
void WaylandInputDevice::setEnabled(bool enabled)
{
}
bool WaylandInputDevice::isKeyboard() const
{
return m_keyboard != nullptr;
}
bool WaylandInputDevice::isPointer() const
{
return m_pointer || m_relativePointer;
}
bool WaylandInputDevice::isTouchpad() const
{
return false;
}
bool WaylandInputDevice::isTouch() const
{
return m_touch != nullptr;
}
bool WaylandInputDevice::isTabletTool() const
{
return false;
}
bool WaylandInputDevice::isTabletPad() const
{
return false;
}
bool WaylandInputDevice::isTabletModeSwitch() const
{
return false;
}
bool WaylandInputDevice::isLidSwitch() const
{
return false;
}
KWayland::Client::Pointer *WaylandInputDevice::nativePointer() const
{
return m_pointer.get();
}
WaylandInputBackend::WaylandInputBackend(WaylandBackend *backend, QObject *parent)
: InputBackend(parent)
, m_backend(backend)
{
}
void WaylandInputBackend::initialize()
{
WaylandSeat *seat = m_backend->seat();
if (seat->relativePointerDevice()) {
Q_EMIT deviceAdded(seat->relativePointerDevice());
}
if (seat->pointerDevice()) {
Q_EMIT deviceAdded(seat->pointerDevice());
}
if (seat->keyboardDevice()) {
Q_EMIT deviceAdded(seat->keyboardDevice());
}
if (seat->touchDevice()) {
Q_EMIT deviceAdded(seat->touchDevice());
}
connect(seat, &WaylandSeat::deviceAdded, this, &InputBackend::deviceAdded);
connect(seat, &WaylandSeat::deviceRemoved, this, &InputBackend::deviceRemoved);
}
WaylandSeat::WaylandSeat(KWayland::Client::Seat *nativeSeat, WaylandBackend *backend)
: QObject(nullptr)
, m_seat(nativeSeat)
, m_backend(backend)
{
auto updateKeyboardDevice = [this](){
if (m_seat->hasKeyboard()) {
createKeyboardDevice();
} else {
destroyKeyboardDevice();
}
};
updateKeyboardDevice();
connect(m_seat, &Seat::hasKeyboardChanged, this, updateKeyboardDevice);
auto updatePointerDevice = [this]() {
if (m_seat->hasPointer()) {
createPointerDevice();
} else {
destroyPointerDevice();
}
};
updatePointerDevice();
connect(m_seat, &Seat::hasPointerChanged, this, updatePointerDevice);
auto updateTouchDevice = [this]() {
if (m_seat->hasTouch()) {
createTouchDevice();
} else {
destroyTouchDevice();
}
};
updateTouchDevice();
connect(m_seat, &Seat::hasTouchChanged, this, updateTouchDevice);
}
WaylandSeat::~WaylandSeat()
{
destroyPointerDevice();
destroyKeyboardDevice();
destroyTouchDevice();
}
void WaylandSeat::createPointerDevice()
{
m_pointerDevice = std::make_unique<WaylandInputDevice>(m_seat->createPointer(), this);
Q_EMIT deviceAdded(m_pointerDevice.get());
}
void WaylandSeat::destroyPointerDevice()
{
if (m_pointerDevice) {
Q_EMIT deviceRemoved(m_pointerDevice.get());
destroyRelativePointer();
m_pointerDevice.reset();
}
}
void WaylandSeat::createRelativePointer()
{
KWayland::Client::RelativePointerManager *manager = m_backend->display()->relativePointerManager();
if (manager) {
m_relativePointerDevice = std::make_unique<WaylandInputDevice>(manager->createRelativePointer(m_pointerDevice->nativePointer()), this);
Q_EMIT deviceAdded(m_relativePointerDevice.get());
}
}
void WaylandSeat::destroyRelativePointer()
{
if (m_relativePointerDevice) {
Q_EMIT deviceRemoved(m_relativePointerDevice.get());
m_relativePointerDevice.reset();
}
}
void WaylandSeat::createKeyboardDevice()
{
m_keyboardDevice = std::make_unique<WaylandInputDevice>(m_seat->createKeyboard(), this);
Q_EMIT deviceAdded(m_keyboardDevice.get());
}
void WaylandSeat::destroyKeyboardDevice()
{
if (m_keyboardDevice) {
Q_EMIT deviceRemoved(m_keyboardDevice.get());
m_keyboardDevice.reset();
}
}
void WaylandSeat::createTouchDevice()
{
m_touchDevice = std::make_unique<WaylandInputDevice>(m_seat->createTouch(), this);
Q_EMIT deviceAdded(m_touchDevice.get());
}
void WaylandSeat::destroyTouchDevice()
{
if (m_touchDevice) {
Q_EMIT deviceRemoved(m_touchDevice.get());
m_touchDevice.reset();
}
}
WaylandBackend::WaylandBackend(const WaylandBackendOptions &options, QObject *parent)
: OutputBackend(parent)
, m_options(options)
{
}
WaylandBackend::~WaylandBackend()
{
m_eglDisplay.reset();
destroyOutputs();
m_buffers.clear();
m_seat.reset();
m_display.reset();
qCDebug(KWIN_WAYLAND_BACKEND) << "Destroyed Wayland display";
}
bool WaylandBackend::initialize()
{
m_display = std::make_unique<WaylandDisplay>();
if (!m_display->initialize(m_options.socketName)) {
return false;
}
if (WaylandLinuxDmabufV1 *dmabuf = m_display->linuxDmabuf()) {
m_drmDevice = DrmDevice::open(dmabuf->mainDevice());
if (!m_drmDevice) {
qCWarning(KWIN_WAYLAND_BACKEND) << "Failed to open drm render node" << dmabuf->mainDevice();
}
}
createOutputs();
m_seat = std::make_unique<WaylandSeat>(m_display->seat(), this);
QAbstractEventDispatcher *dispatcher = QAbstractEventDispatcher::instance();
QObject::connect(dispatcher, &QAbstractEventDispatcher::aboutToBlock, m_display.get(), &WaylandDisplay::flush);
QObject::connect(dispatcher, &QAbstractEventDispatcher::awake, m_display.get(), &WaylandDisplay::flush);
connect(this, &WaylandBackend::pointerLockChanged, this, [this](bool locked) {
if (locked) {
m_seat->createRelativePointer();
} else {
m_seat->destroyRelativePointer();
}
});
return true;
}
void WaylandBackend::createOutputs()
{
// we need to multiply the initial window size with the scale in order to
// create an output window of this size in the end
const QSize pixelSize = m_options.outputSize * m_options.outputScale;
for (int i = 0; i < m_options.outputCount; i++) {
WaylandOutput *output = createOutput(QStringLiteral("WL-%1").arg(i), pixelSize, m_options.outputScale);
m_outputs << output;
Q_EMIT outputAdded(output);
output->updateEnabled(true);
}
Q_EMIT outputsQueried();
}
WaylandOutput *WaylandBackend::createOutput(const QString &name, const QSize &size, qreal scale)
{
WaylandOutput *waylandOutput = new WaylandOutput(name, this);
waylandOutput->init(size, scale);
// Wait until the output window is configured by the host compositor.
while (!waylandOutput->isReady()) {
wl_display_roundtrip(m_display->nativeDisplay());
}
return waylandOutput;
}
void WaylandBackend::destroyOutputs()
{
while (!m_outputs.isEmpty()) {
WaylandOutput *output = m_outputs.takeLast();
output->updateEnabled(false);
Q_EMIT outputRemoved(output);
delete output;
}
}
std::unique_ptr<InputBackend> WaylandBackend::createInputBackend()
{
return std::make_unique<WaylandInputBackend>(this);
}
std::unique_ptr<OpenGLBackend> WaylandBackend::createOpenGLBackend()
{
return std::make_unique<WaylandEglBackend>(this);
}
std::unique_ptr<QPainterBackend> WaylandBackend::createQPainterBackend()
{
return std::make_unique<WaylandQPainterBackend>(this);
}
WaylandOutput *WaylandBackend::findOutput(KWayland::Client::Surface *nativeSurface) const
{
for (WaylandOutput *output : m_outputs) {
if (output->surface() == nativeSurface) {
return output;
}
}
return nullptr;
}
bool WaylandBackend::supportsPointerLock()
{
return m_display->pointerConstraints() && m_display->relativePointerManager();
}
void WaylandBackend::togglePointerLock()
{
if (!supportsPointerLock()) {
return;
}
if (!m_seat) {
return;
}
auto pointer = m_seat->pointerDevice()->nativePointer();
if (!pointer) {
return;
}
if (m_outputs.isEmpty()) {
return;
}
for (auto output : std::as_const(m_outputs)) {
output->lockPointer(m_seat->pointerDevice()->nativePointer(), !m_pointerLockRequested);
}
m_pointerLockRequested = !m_pointerLockRequested;
}
QList<CompositingType> WaylandBackend::supportedCompositors() const
{
QList<CompositingType> ret;
if (m_display->linuxDmabuf() && m_drmDevice) {
ret.append(OpenGLCompositing);
}
ret.append(QPainterCompositing);
return ret;
}
Outputs WaylandBackend::outputs() const
{
return m_outputs;
}
Output *WaylandBackend::createVirtualOutput(const QString &name, const QString &description, const QSize &size, double scale)
{
return createOutput(name, size * scale, scale);
}
void WaylandBackend::removeVirtualOutput(Output *output)
{
WaylandOutput *waylandOutput = dynamic_cast<WaylandOutput *>(output);
if (waylandOutput && m_outputs.removeAll(waylandOutput)) {
waylandOutput->updateEnabled(false);
Q_EMIT outputRemoved(waylandOutput);
Q_EMIT outputsQueried();
waylandOutput->unref();
}
}
static wl_buffer *importDmaBufBuffer(WaylandDisplay *display, const DmaBufAttributes *attributes)
{
zwp_linux_buffer_params_v1 *params = zwp_linux_dmabuf_v1_create_params(display->linuxDmabuf()->handle());
for (int i = 0; i < attributes->planeCount; ++i) {
zwp_linux_buffer_params_v1_add(params,
attributes->fd[i].get(),
i,
attributes->offset[i],
attributes->pitch[i],
attributes->modifier >> 32,
attributes->modifier & 0xffffffff);
}
wl_buffer *buffer = zwp_linux_buffer_params_v1_create_immed(params, attributes->width, attributes->height, attributes->format, 0);
zwp_linux_buffer_params_v1_destroy(params);
return buffer;
}
static wl_buffer *importShmBuffer(WaylandDisplay *display, const ShmAttributes *attributes)
{
wl_shm_format format;
switch (attributes->format) {
case DRM_FORMAT_ARGB8888:
format = WL_SHM_FORMAT_ARGB8888;
break;
case DRM_FORMAT_XRGB8888:
format = WL_SHM_FORMAT_XRGB8888;
break;
default:
format = static_cast<wl_shm_format>(attributes->format);
break;
}
wl_shm_pool *pool = wl_shm_create_pool(display->shm(), attributes->fd.get(), attributes->size.height() * attributes->stride);
wl_buffer *buffer = wl_shm_pool_create_buffer(pool,
attributes->offset,
attributes->size.width(),
attributes->size.height(),
attributes->stride,
format);
wl_shm_pool_destroy(pool);
return buffer;
}
wl_buffer *WaylandBackend::importBuffer(GraphicsBuffer *graphicsBuffer)
{
auto &buffer = m_buffers[graphicsBuffer];
if (!buffer) {
wl_buffer *handle = nullptr;
if (const DmaBufAttributes *attributes = graphicsBuffer->dmabufAttributes()) {
handle = importDmaBufBuffer(m_display.get(), attributes);
} else if (const ShmAttributes *attributes = graphicsBuffer->shmAttributes()) {
handle = importShmBuffer(m_display.get(), attributes);
} else {
qCWarning(KWIN_WAYLAND_BACKEND) << graphicsBuffer << "has unknown type";
return nullptr;
}
buffer = std::make_unique<WaylandBuffer>(handle, graphicsBuffer);
connect(buffer.get(), &WaylandBuffer::defunct, this, [this, graphicsBuffer]() {
m_buffers.erase(graphicsBuffer);
});
static const wl_buffer_listener listener = {
.release = [](void *userData, wl_buffer *buffer) {
WaylandBuffer *slot = static_cast<WaylandBuffer *>(userData);
slot->unlock();
},
};
wl_buffer_add_listener(handle, &listener, buffer.get());
}
buffer->lock();
return buffer->handle();
}
void WaylandBackend::setEglDisplay(std::unique_ptr<EglDisplay> &&display)
{
m_eglDisplay = std::move(display);
}
EglDisplay *WaylandBackend::sceneEglDisplayObject() const
{
return m_eglDisplay.get();
}
DrmDevice *WaylandBackend::drmDevice() const
{
return m_drmDevice.get();
}
WaylandBuffer::WaylandBuffer(wl_buffer *handle, GraphicsBuffer *graphicsBuffer)
: m_graphicsBuffer(graphicsBuffer)
, m_handle(handle)
{
connect(graphicsBuffer, &GraphicsBuffer::destroyed, this, &WaylandBuffer::defunct);
}
WaylandBuffer::~WaylandBuffer()
{
m_graphicsBuffer->disconnect(this);
if (m_locked) {
m_graphicsBuffer->unref();
}
wl_buffer_destroy(m_handle);
}
wl_buffer *WaylandBuffer::handle() const
{
return m_handle;
}
void WaylandBuffer::lock()
{
if (!m_locked) {
m_locked = true;
m_graphicsBuffer->ref();
}
}
void WaylandBuffer::unlock()
{
if (m_locked) {
m_locked = false;
m_graphicsBuffer->unref();
}
}
}
} // KWin
#include "moc_wayland_backend.cpp"
@@ -0,0 +1,269 @@
/*
KWin - the KDE window manager
This file is part of the KDE project.
SPDX-FileCopyrightText: 2019 Roman Gilg <subdiff@gmail.com>
SPDX-FileCopyrightText: 2013 Martin Gräßlin <mgraesslin@kde.org>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#pragma once
#include "config-kwin.h"
// KWin
#include "core/inputbackend.h"
#include "core/inputdevice.h"
#include "core/outputbackend.h"
#include "effect/globals.h"
#include "utils/filedescriptor.h"
// Qt
#include <QHash>
#include <QImage>
#include <QObject>
#include <QPoint>
#include <QSize>
struct wl_buffer;
struct wl_display;
namespace KWayland
{
namespace Client
{
class Keyboard;
class Pointer;
class PointerSwipeGesture;
class PointerPinchGesture;
class RelativePointer;
class Seat;
class Surface;
class Touch;
}
}
namespace KWin
{
class GraphicsBuffer;
class DrmDevice;
namespace Wayland
{
class WaylandBackend;
class WaylandSeat;
class WaylandOutput;
class WaylandEglBackend;
class WaylandDisplay;
class WaylandInputDevice : public InputDevice
{
Q_OBJECT
public:
WaylandInputDevice(KWayland::Client::Touch *touch, WaylandSeat *seat);
WaylandInputDevice(KWayland::Client::Keyboard *keyboard, WaylandSeat *seat);
WaylandInputDevice(KWayland::Client::RelativePointer *relativePointer, WaylandSeat *seat);
WaylandInputDevice(KWayland::Client::Pointer *pointer, WaylandSeat *seat);
~WaylandInputDevice() override;
QString name() const override;
bool isEnabled() const override;
void setEnabled(bool enabled) override;
bool isKeyboard() const override;
bool isPointer() const override;
bool isTouchpad() const override;
bool isTouch() const override;
bool isTabletTool() const override;
bool isTabletPad() const override;
bool isTabletModeSwitch() const override;
bool isLidSwitch() const override;
KWayland::Client::Pointer *nativePointer() const;
private:
WaylandSeat *const m_seat;
std::unique_ptr<KWayland::Client::Keyboard> m_keyboard;
std::unique_ptr<KWayland::Client::Touch> m_touch;
std::unique_ptr<KWayland::Client::RelativePointer> m_relativePointer;
std::unique_ptr<KWayland::Client::Pointer> m_pointer;
std::unique_ptr<KWayland::Client::PointerPinchGesture> m_pinchGesture;
std::unique_ptr<KWayland::Client::PointerSwipeGesture> m_swipeGesture;
QSet<quint32> m_pressedKeys;
};
class WaylandInputBackend : public InputBackend
{
Q_OBJECT
public:
explicit WaylandInputBackend(WaylandBackend *backend, QObject *parent = nullptr);
void initialize() override;
private:
WaylandBackend *m_backend;
};
class WaylandSeat : public QObject
{
Q_OBJECT
public:
WaylandSeat(KWayland::Client::Seat *nativeSeat, WaylandBackend *backend);
~WaylandSeat() override;
WaylandBackend *backend() const
{
return m_backend;
}
WaylandInputDevice *pointerDevice() const
{
return m_pointerDevice.get();
}
WaylandInputDevice *relativePointerDevice() const
{
return m_relativePointerDevice.get();
}
WaylandInputDevice *keyboardDevice() const
{
return m_keyboardDevice.get();
}
WaylandInputDevice *touchDevice() const
{
return m_touchDevice.get();
}
void createRelativePointer();
void destroyRelativePointer();
Q_SIGNALS:
void deviceAdded(WaylandInputDevice *device);
void deviceRemoved(WaylandInputDevice *device);
private:
void createPointerDevice();
void destroyPointerDevice();
void createKeyboardDevice();
void destroyKeyboardDevice();
void createTouchDevice();
void destroyTouchDevice();
KWayland::Client::Seat *m_seat;
WaylandBackend *m_backend;
std::unique_ptr<WaylandInputDevice> m_pointerDevice;
std::unique_ptr<WaylandInputDevice> m_relativePointerDevice;
std::unique_ptr<WaylandInputDevice> m_keyboardDevice;
std::unique_ptr<WaylandInputDevice> m_touchDevice;
};
class WaylandBuffer : public QObject
{
Q_OBJECT
public:
WaylandBuffer(wl_buffer *handle, GraphicsBuffer *graphicsBuffer);
~WaylandBuffer() override;
wl_buffer *handle() const;
void lock();
void unlock();
Q_SIGNALS:
void defunct();
private:
GraphicsBuffer *m_graphicsBuffer;
wl_buffer *m_handle;
bool m_locked = false;
};
struct WaylandBackendOptions
{
QString socketName;
int outputCount = 1;
qreal outputScale = 1;
QSize outputSize = QSize(1024, 768);
};
/**
* @brief Class encapsulating all Wayland data structures needed by the Egl backend.
*
* It creates the connection to the Wayland Compositor, sets up the registry and creates
* the Wayland output surfaces and its shell mappings.
*/
class KWIN_EXPORT WaylandBackend : public OutputBackend
{
Q_OBJECT
public:
explicit WaylandBackend(const WaylandBackendOptions &options, QObject *parent = nullptr);
~WaylandBackend() override;
bool initialize() override;
std::unique_ptr<InputBackend> createInputBackend() override;
std::unique_ptr<OpenGLBackend> createOpenGLBackend() override;
std::unique_ptr<QPainterBackend> createQPainterBackend() override;
WaylandDisplay *display() const
{
return m_display.get();
}
WaylandSeat *seat() const
{
return m_seat.get();
}
bool supportsPointerLock();
void togglePointerLock();
QList<CompositingType> supportedCompositors() const override;
WaylandOutput *findOutput(KWayland::Client::Surface *nativeSurface) const;
Outputs outputs() const override;
QList<WaylandOutput *> waylandOutputs() const
{
return m_outputs;
}
Output *createVirtualOutput(const QString &name, const QString &description, const QSize &size, double scale) override;
void removeVirtualOutput(Output *output) override;
wl_buffer *importBuffer(GraphicsBuffer *graphicsBuffer);
DrmDevice *drmDevice() const;
void setEglBackend(WaylandEglBackend *eglBackend)
{
m_eglBackend = eglBackend;
}
void setEglDisplay(std::unique_ptr<EglDisplay> &&display);
EglDisplay *sceneEglDisplayObject() const override;
Q_SIGNALS:
void pointerLockChanged(bool locked);
private:
void createOutputs();
void destroyOutputs();
WaylandOutput *createOutput(const QString &name, const QSize &size, qreal scale);
WaylandBackendOptions m_options;
std::unique_ptr<WaylandDisplay> m_display;
std::unique_ptr<WaylandSeat> m_seat;
WaylandEglBackend *m_eglBackend = nullptr;
QList<WaylandOutput *> m_outputs;
bool m_pointerLockRequested = false;
std::unique_ptr<DrmDevice> m_drmDevice;
std::unique_ptr<EglDisplay> m_eglDisplay;
std::map<GraphicsBuffer *, std::unique_ptr<WaylandBuffer>> m_buffers;
};
} // namespace Wayland
} // namespace KWin
@@ -0,0 +1,482 @@
/*
SPDX-FileCopyrightText: 2022 Vlad Zahorodnii <vlad.zahorodnii@kde.org>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "wayland_display.h"
#include "utils/memorymap.h"
#include "wayland_logging.h"
#include <KWayland/Client/compositor.h>
#include <KWayland/Client/pointerconstraints.h>
#include <KWayland/Client/pointergestures.h>
#include <KWayland/Client/registry.h>
#include <KWayland/Client/relativepointer.h>
#include <KWayland/Client/seat.h>
#include <KWayland/Client/subcompositor.h>
#include <KWayland/Client/xdgdecoration.h>
#include <KWayland/Client/xdgshell.h>
#include <QMutex>
#include <QThread>
#include <QWaitCondition>
#include <drm_fourcc.h>
#include <fcntl.h>
#include <poll.h>
#include <span>
#include <unistd.h>
#include <wayland-client.h>
#include <xf86drm.h>
// Generated in src/wayland.
#include "wayland-linux-dmabuf-unstable-v1-client-protocol.h"
#include "wayland-pointer-constraints-unstable-v1-client-protocol.h"
#include "wayland-pointer-gestures-unstable-v1-server-protocol.h"
#include "wayland-presentation-time-client-protocol.h"
#include "wayland-relative-pointer-unstable-v1-client-protocol.h"
#include "wayland-xdg-decoration-unstable-v1-client-protocol.h"
#include "wayland-xdg-shell-client-protocol.h"
namespace KWin
{
namespace Wayland
{
class WaylandEventThread : public QThread
{
Q_OBJECT
public:
WaylandEventThread(wl_display *display)
: m_display(display)
, m_fd(wl_display_get_fd(display))
, m_quitPipe{-1, -1}
, m_reading(true)
, m_quitting(false)
{
if (pipe2(m_quitPipe, O_CLOEXEC) == -1) {
qCWarning(KWIN_WAYLAND_BACKEND) << "Failed to create quite pipe in WaylandEventThread";
}
}
~WaylandEventThread() override
{
if (m_quitPipe[0] != -1) {
close(m_quitPipe[0]);
close(m_quitPipe[1]);
}
}
void dispatch()
{
while (true) {
if (wl_display_dispatch_pending(m_display) < 0) {
qFatal("Wayland connection broke");
}
wl_display_flush(m_display);
if (m_reading.loadAcquire()) {
break;
}
if (wl_display_prepare_read(m_display) == 0) {
QMutexLocker lock(&m_mutex);
m_reading.storeRelease(true);
m_cond.wakeOne();
break;
}
}
}
void stop()
{
if (m_quitPipe[1] != -1) {
write(m_quitPipe[1], "\0", 1);
}
m_mutex.lock();
m_quitting = true;
m_cond.wakeOne();
m_mutex.unlock();
wait();
}
Q_SIGNALS:
void available();
protected:
void run() override
{
while (true) {
m_reading.storeRelease(false);
Q_EMIT available();
m_mutex.lock();
while (!m_reading.loadRelaxed() && !m_quitting) {
m_cond.wait(&m_mutex);
}
m_mutex.unlock();
if (m_quitting) {
break;
}
pollfd fds[2] = { { m_fd, POLLIN, 0 }, { m_quitPipe[0], POLLIN, 0 } };
poll(fds, 2, -1);
if (fds[1].revents & POLLIN) {
wl_display_cancel_read(m_display);
break;
}
if (fds[0].revents & POLLIN) {
wl_display_read_events(m_display);
} else {
wl_display_cancel_read(m_display);
}
}
}
private:
wl_display *const m_display;
int m_fd;
int m_quitPipe[2];
QAtomicInteger<bool> m_reading;
QMutex m_mutex;
QWaitCondition m_cond;
bool m_quitting;
};
static dev_t deserializeDeviceId(wl_array *data)
{
Q_ASSERT(sizeof(dev_t) == data->size);
dev_t ret;
std::memcpy(&ret, data->data, data->size);
return ret;
}
class WaylandLinuxDmabufFeedbackV1
{
public:
WaylandLinuxDmabufFeedbackV1(zwp_linux_dmabuf_feedback_v1 *feedback)
: feedback(feedback)
{
static const struct zwp_linux_dmabuf_feedback_v1_listener feedbackListener = {
.done = done,
.format_table = format_table,
.main_device = main_device,
.tranche_done = tranche_done,
.tranche_target_device = tranche_target_device,
.tranche_formats = tranche_formats,
.tranche_flags = tranche_flags,
};
zwp_linux_dmabuf_feedback_v1_add_listener(feedback, &feedbackListener, this);
}
~WaylandLinuxDmabufFeedbackV1()
{
zwp_linux_dmabuf_feedback_v1_destroy(feedback);
}
zwp_linux_dmabuf_feedback_v1 *feedback;
QByteArray mainDevice;
dev_t mainDeviceId = 0;
dev_t trancheDeviceId = 0;
MemoryMap formatTable;
QHash<uint32_t, QList<uint64_t>> formats;
private:
static void done(void *data, zwp_linux_dmabuf_feedback_v1 *zwp_linux_dmabuf_feedback_v1)
{
// Nothing to do
}
static void format_table(void *data, zwp_linux_dmabuf_feedback_v1 *zwp_linux_dmabuf_feedback_v1, int32_t fd, uint32_t size)
{
WaylandLinuxDmabufFeedbackV1 *feedback = static_cast<WaylandLinuxDmabufFeedbackV1 *>(data);
feedback->formatTable = MemoryMap(size, PROT_READ, MAP_PRIVATE, fd, 0);
close(fd);
}
static void main_device(void *data, zwp_linux_dmabuf_feedback_v1 *zwp_linux_dmabuf_feedback_v1, wl_array *deviceId)
{
WaylandLinuxDmabufFeedbackV1 *feedback = static_cast<WaylandLinuxDmabufFeedbackV1 *>(data);
feedback->mainDeviceId = deserializeDeviceId(deviceId);
drmDevice *device = nullptr;
if (drmGetDeviceFromDevId(feedback->mainDeviceId, 0, &device) != 0) {
qCWarning(KWIN_WAYLAND_BACKEND) << "drmGetDeviceFromDevId() failed";
return;
}
if (device->available_nodes & (1 << DRM_NODE_RENDER)) {
feedback->mainDevice = QByteArray(device->nodes[DRM_NODE_RENDER]);
} else if (device->available_nodes & (1 << DRM_NODE_PRIMARY)) {
// We can't reliably find the render node from the primary node if the display and
// render devices are split, so just fallback to the primary node.
feedback->mainDevice = QByteArray(device->nodes[DRM_NODE_PRIMARY]);
}
drmFreeDevice(&device);
}
static void tranche_done(void *data, zwp_linux_dmabuf_feedback_v1 *zwp_linux_dmabuf_feedback_v1)
{
WaylandLinuxDmabufFeedbackV1 *feedback = static_cast<WaylandLinuxDmabufFeedbackV1 *>(data);
feedback->trancheDeviceId = 0;
}
static void tranche_target_device(void *data, zwp_linux_dmabuf_feedback_v1 *zwp_linux_dmabuf_feedback_v1, wl_array *deviceId)
{
WaylandLinuxDmabufFeedbackV1 *feedback = static_cast<WaylandLinuxDmabufFeedbackV1 *>(data);
feedback->trancheDeviceId = deserializeDeviceId(deviceId);
}
static void tranche_formats(void *data, zwp_linux_dmabuf_feedback_v1 *zwp_linux_dmabuf_feedback_v1, wl_array *indices)
{
WaylandLinuxDmabufFeedbackV1 *feedback = static_cast<WaylandLinuxDmabufFeedbackV1 *>(data);
if (!feedback->formatTable.isValid()) {
return;
}
if (feedback->mainDeviceId != feedback->trancheDeviceId) {
return;
}
struct linux_dmabuf_feedback_v1_table_entry
{
uint32_t format;
uint32_t pad; // unused
uint64_t modifier;
};
const auto entries = static_cast<linux_dmabuf_feedback_v1_table_entry *>(feedback->formatTable.data());
for (const uint16_t &index : std::span(static_cast<uint16_t *>(indices->data), indices->size / sizeof(uint16_t))) {
const linux_dmabuf_feedback_v1_table_entry &entry = entries[index];
feedback->formats[entry.format].append(entry.modifier);
}
}
static void tranche_flags(void *data, zwp_linux_dmabuf_feedback_v1 *zwp_linux_dmabuf_feedback_v1, uint32_t flags)
{
// Nothing to do
}
};
WaylandLinuxDmabufV1::WaylandLinuxDmabufV1(wl_registry *registry, uint32_t name, uint32_t version)
{
m_dmabuf = static_cast<zwp_linux_dmabuf_v1 *>(wl_registry_bind(registry, name, &zwp_linux_dmabuf_v1_interface, version));
static const struct zwp_linux_dmabuf_v1_listener dmabufListener = {
.format = format,
.modifier = modifier,
};
zwp_linux_dmabuf_v1_add_listener(m_dmabuf, &dmabufListener, this);
m_defaultFeedback = std::make_unique<WaylandLinuxDmabufFeedbackV1>(zwp_linux_dmabuf_v1_get_default_feedback(m_dmabuf));
}
WaylandLinuxDmabufV1::~WaylandLinuxDmabufV1()
{
zwp_linux_dmabuf_v1_destroy(m_dmabuf);
}
zwp_linux_dmabuf_v1 *WaylandLinuxDmabufV1::handle() const
{
return m_dmabuf;
}
QByteArray WaylandLinuxDmabufV1::mainDevice() const
{
return m_defaultFeedback->mainDevice;
}
QHash<uint32_t, QList<uint64_t>> WaylandLinuxDmabufV1::formats() const
{
return m_defaultFeedback->formats;
}
void WaylandLinuxDmabufV1::format(void *data, struct zwp_linux_dmabuf_v1 *zwp_linux_dmabuf_v1, uint32_t format)
{
// Not sent in v4 and onward.
}
void WaylandLinuxDmabufV1::modifier(void *data, struct zwp_linux_dmabuf_v1 *zwp_linux_dmabuf_v1, uint32_t format, uint32_t modifier_hi, uint32_t modifier_lo)
{
// Not sent in v4 and onward.
}
WaylandDisplay::WaylandDisplay()
{
}
WaylandDisplay::~WaylandDisplay()
{
m_eventThread->stop();
m_eventThread.reset();
m_compositor.reset();
m_pointerConstraints.reset();
m_pointerGestures.reset();
m_relativePointerManager.reset();
m_seat.reset();
m_xdgDecorationManager.reset();
m_xdgShell.reset();
m_linuxDmabuf.reset();
if (m_shm) {
wl_shm_destroy(m_shm);
}
if (m_presentationTime) {
wp_presentation_destroy(m_presentationTime);
}
if (m_registry) {
wl_registry_destroy(m_registry);
}
if (m_display) {
wl_display_disconnect(m_display);
}
}
void WaylandDisplay::flush()
{
m_eventThread->dispatch();
}
bool WaylandDisplay::initialize(const QString &socketName)
{
m_display = wl_display_connect(socketName.toUtf8());
if (!m_display) {
return false;
}
m_eventThread = std::make_unique<WaylandEventThread>(m_display);
connect(m_eventThread.get(), &WaylandEventThread::available, this, &WaylandDisplay::flush, Qt::QueuedConnection);
m_eventThread->start();
static wl_registry_listener registryListener {
.global = registry_global,
.global_remove = registry_global_remove,
};
m_registry = wl_display_get_registry(m_display);
wl_registry_add_listener(m_registry, &registryListener, this);
wl_display_roundtrip(m_display);
wl_display_roundtrip(m_display); // get dmabuf formats
return true;
}
wl_display *WaylandDisplay::nativeDisplay() const
{
return m_display;
}
KWayland::Client::Compositor *WaylandDisplay::compositor() const
{
return m_compositor.get();
}
KWayland::Client::PointerConstraints *WaylandDisplay::pointerConstraints() const
{
return m_pointerConstraints.get();
}
KWayland::Client::PointerGestures *WaylandDisplay::pointerGestures() const
{
return m_pointerGestures.get();
}
KWayland::Client::RelativePointerManager *WaylandDisplay::relativePointerManager() const
{
return m_relativePointerManager.get();
}
wl_shm *WaylandDisplay::shm() const
{
return m_shm;
}
KWayland::Client::Seat *WaylandDisplay::seat() const
{
return m_seat.get();
}
KWayland::Client::XdgShell *WaylandDisplay::xdgShell() const
{
return m_xdgShell.get();
}
KWayland::Client::XdgDecorationManager *WaylandDisplay::xdgDecorationManager() const
{
return m_xdgDecorationManager.get();
}
WaylandLinuxDmabufV1 *WaylandDisplay::linuxDmabuf() const
{
return m_linuxDmabuf.get();
}
wp_presentation *WaylandDisplay::presentationTime() const
{
return m_presentationTime;
}
void WaylandDisplay::registry_global(void *data, wl_registry *registry, uint32_t name, const char *interface, uint32_t version)
{
WaylandDisplay *display = static_cast<WaylandDisplay *>(data);
if (strcmp(interface, wl_compositor_interface.name) == 0) {
if (version < 4) {
qFatal("wl_compositor version 4 or later is required");
}
display->m_compositor = std::make_unique<KWayland::Client::Compositor>();
display->m_compositor->setup(static_cast<wl_compositor *>(wl_registry_bind(registry, name, &wl_compositor_interface, std::min(version, 4u))));
} else if (strcmp(interface, wl_shm_interface.name) == 0) {
display->m_shm = static_cast<wl_shm *>(wl_registry_bind(registry, name, &wl_shm_interface, std::min(version, 1u)));
} else if (strcmp(interface, wl_seat_interface.name) == 0) {
display->m_seat = std::make_unique<KWayland::Client::Seat>();
display->m_seat->setup(static_cast<wl_seat *>(wl_registry_bind(registry, name, &wl_seat_interface, std::min(version, 5u))));
} else if (strcmp(interface, xdg_wm_base_interface.name) == 0) {
display->m_xdgShell = std::make_unique<KWayland::Client::XdgShellStable>();
display->m_xdgShell->setup(static_cast<xdg_wm_base *>(wl_registry_bind(registry, name, &xdg_wm_base_interface, std::min(version, 1u))));
} else if (strcmp(interface, zwp_pointer_constraints_v1_interface.name) == 0) {
display->m_pointerConstraints = std::make_unique<KWayland::Client::PointerConstraints>();
display->m_pointerConstraints->setup(static_cast<zwp_pointer_constraints_v1 *>(wl_registry_bind(registry, name, &zwp_pointer_constraints_v1_interface, std::min(version, 1u))));
} else if (strcmp(interface, zwp_pointer_gestures_v1_interface.name) == 0) {
display->m_pointerGestures = std::make_unique<KWayland::Client::PointerGestures>();
display->m_pointerGestures->setup(static_cast<zwp_pointer_gestures_v1 *>(wl_registry_bind(registry, name, &zwp_pointer_gestures_v1_interface, std::min(version, 1u))));
} else if (strcmp(interface, zwp_relative_pointer_manager_v1_interface.name) == 0) {
display->m_relativePointerManager = std::make_unique<KWayland::Client::RelativePointerManager>();
display->m_relativePointerManager->setup(static_cast<zwp_relative_pointer_manager_v1 *>(wl_registry_bind(registry, name, &zwp_relative_pointer_manager_v1_interface, std::min(version, 1u))));
} else if (strcmp(interface, zxdg_decoration_manager_v1_interface.name) == 0) {
display->m_xdgDecorationManager = std::make_unique<KWayland::Client::XdgDecorationManager>();
display->m_xdgDecorationManager->setup(static_cast<zxdg_decoration_manager_v1 *>(wl_registry_bind(registry, name, &zxdg_decoration_manager_v1_interface, std::min(version, 1u))));
} else if (strcmp(interface, zwp_linux_dmabuf_v1_interface.name) == 0) {
if (version < 4) {
qWarning("zwp_linux_dmabuf_v1 v4 or newer is needed");
return;
}
display->m_linuxDmabuf = std::make_unique<WaylandLinuxDmabufV1>(registry, name, std::min(version, 4u));
} else if (strcmp(interface, wp_presentation_interface.name) == 0) {
display->m_presentationTime = reinterpret_cast<wp_presentation *>(wl_registry_bind(registry, name, &wp_presentation_interface, std::min(version, 2u)));
}
}
void WaylandDisplay::registry_global_remove(void *data, wl_registry *registry, uint32_t name)
{
}
}
}
#include "wayland_display.moc"
#include "moc_wayland_display.cpp"
@@ -0,0 +1,105 @@
/*
SPDX-FileCopyrightText: 2022 Vlad Zahorodnii <vlad.zahorodnii@kde.org>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#pragma once
#include <QHash>
#include <QObject>
#include <memory>
struct wl_display;
struct wl_registry;
struct wl_shm;
struct zwp_linux_dmabuf_v1;
struct wp_presentation;
namespace KWayland
{
namespace Client
{
class Compositor;
class PointerConstraints;
class PointerGestures;
class RelativePointerManager;
class Seat;
class XdgDecorationManager;
class XdgShell;
}
}
namespace KWin
{
namespace Wayland
{
class WaylandEventThread;
class WaylandLinuxDmabufFeedbackV1;
class WaylandLinuxDmabufV1
{
public:
WaylandLinuxDmabufV1(wl_registry *registry, uint32_t name, uint32_t version);
~WaylandLinuxDmabufV1();
zwp_linux_dmabuf_v1 *handle() const;
QByteArray mainDevice() const;
QHash<uint32_t, QList<uint64_t>> formats() const;
private:
static void format(void *data, struct zwp_linux_dmabuf_v1 *zwp_linux_dmabuf_v1, uint32_t format);
static void modifier(void *data, struct zwp_linux_dmabuf_v1 *zwp_linux_dmabuf_v1, uint32_t format, uint32_t modifier_hi, uint32_t modifier_lo);
zwp_linux_dmabuf_v1 *m_dmabuf;
std::unique_ptr<WaylandLinuxDmabufFeedbackV1> m_defaultFeedback;
};
class WaylandDisplay : public QObject
{
Q_OBJECT
public:
WaylandDisplay();
~WaylandDisplay() override;
bool initialize(const QString &socketName);
wl_display *nativeDisplay() const;
KWayland::Client::Compositor *compositor() const;
KWayland::Client::PointerConstraints *pointerConstraints() const;
KWayland::Client::PointerGestures *pointerGestures() const;
KWayland::Client::RelativePointerManager *relativePointerManager() const;
KWayland::Client::Seat *seat() const;
KWayland::Client::XdgDecorationManager *xdgDecorationManager() const;
wl_shm *shm() const;
KWayland::Client::XdgShell *xdgShell() const;
WaylandLinuxDmabufV1 *linuxDmabuf() const;
wp_presentation *presentationTime() const;
public Q_SLOTS:
void flush();
private:
static void registry_global(void *data, wl_registry *registry, uint32_t name, const char *interface, uint32_t version);
static void registry_global_remove(void *data, wl_registry *registry, uint32_t name);
wl_display *m_display = nullptr;
wl_registry *m_registry = nullptr;
wl_shm *m_shm = nullptr;
wp_presentation *m_presentationTime = nullptr;
std::unique_ptr<WaylandEventThread> m_eventThread;
std::unique_ptr<WaylandLinuxDmabufV1> m_linuxDmabuf;
std::unique_ptr<KWayland::Client::Compositor> m_compositor;
std::unique_ptr<KWayland::Client::PointerConstraints> m_pointerConstraints;
std::unique_ptr<KWayland::Client::PointerGestures> m_pointerGestures;
std::unique_ptr<KWayland::Client::RelativePointerManager> m_relativePointerManager;
std::unique_ptr<KWayland::Client::Seat> m_seat;
std::unique_ptr<KWayland::Client::XdgDecorationManager> m_xdgDecorationManager;
std::unique_ptr<KWayland::Client::XdgShell> m_xdgShell;
};
}
}
@@ -0,0 +1,358 @@
/*
KWin - the KDE window manager
This file is part of the KDE project.
SPDX-FileCopyrightText: 2019 Roman Gilg <subdiff@gmail.com>
SPDX-FileCopyrightText: 2013 Martin Gräßlin <mgraesslin@kde.org>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "wayland_egl_backend.h"
#include "core/drmdevice.h"
#include "core/gbmgraphicsbufferallocator.h"
#include "opengl/eglswapchain.h"
#include "opengl/glrendertimequery.h"
#include "opengl/glutils.h"
#include "platformsupport/scenes/opengl/basiceglsurfacetexture_wayland.h"
#include "scene/surfaceitem_wayland.h"
#include "wayland/surface.h"
#include "wayland_backend.h"
#include "wayland_display.h"
#include "wayland_logging.h"
#include "wayland_output.h"
#include <KWayland/Client/surface.h>
#include <cmath>
#include <drm_fourcc.h>
#include <fcntl.h>
#include <unistd.h>
namespace KWin
{
namespace Wayland
{
static const bool bufferAgeEnabled = qEnvironmentVariable("KWIN_USE_BUFFER_AGE") != QStringLiteral("0");
WaylandEglPrimaryLayer::WaylandEglPrimaryLayer(WaylandOutput *output, WaylandEglBackend *backend)
: OutputLayer(output)
, m_backend(backend)
{
}
WaylandEglPrimaryLayer::~WaylandEglPrimaryLayer()
{
}
GLFramebuffer *WaylandEglPrimaryLayer::fbo() const
{
return m_buffer->framebuffer();
}
std::shared_ptr<GLTexture> WaylandEglPrimaryLayer::texture() const
{
return m_buffer->texture();
}
std::optional<OutputLayerBeginFrameInfo> WaylandEglPrimaryLayer::doBeginFrame()
{
if (!m_backend->openglContext()->makeCurrent()) {
qCCritical(KWIN_WAYLAND_BACKEND) << "Make Context Current failed";
return std::nullopt;
}
const QSize nativeSize = m_output->modeSize();
if (!m_swapchain || m_swapchain->size() != nativeSize) {
const QHash<uint32_t, QList<uint64_t>> formatTable = m_backend->backend()->display()->linuxDmabuf()->formats();
for (const uint32_t &candidateFormat : {DRM_FORMAT_XRGB2101010, DRM_FORMAT_XRGB8888}) {
auto it = formatTable.constFind(candidateFormat);
if (it == formatTable.constEnd()) {
continue;
}
m_swapchain = EglSwapchain::create(m_backend->drmDevice()->allocator(), m_backend->openglContext(), nativeSize, it.key(), it.value());
if (m_swapchain) {
break;
}
}
if (!m_swapchain) {
qCWarning(KWIN_WAYLAND_BACKEND) << "Could not find a suitable render format";
return std::nullopt;
}
}
m_buffer = m_swapchain->acquire();
if (!m_buffer) {
return std::nullopt;
}
const QRegion repair = bufferAgeEnabled ? m_damageJournal.accumulate(m_buffer->age(), infiniteRegion()) : infiniteRegion();
m_query = std::make_unique<GLRenderTimeQuery>(m_backend->openglContextRef());
m_query->begin();
return OutputLayerBeginFrameInfo{
.renderTarget = RenderTarget(m_buffer->framebuffer()),
.repaint = repair,
};
}
bool WaylandEglPrimaryLayer::doEndFrame(const QRegion &renderedRegion, const QRegion &damagedRegion, OutputFrame *frame)
{
m_query->end();
frame->addRenderTimeQuery(std::move(m_query));
// Flush rendering commands to the dmabuf.
glFlush();
EGLNativeFence releaseFence{m_backend->eglDisplayObject()};
static_cast<WaylandOutput *>(m_output)->setPrimaryBuffer(m_backend->backend()->importBuffer(m_buffer->buffer()));
m_swapchain->release(m_buffer, releaseFence.takeFileDescriptor());
m_damageJournal.add(damagedRegion);
return true;
}
bool WaylandEglPrimaryLayer::doImportScanoutBuffer(GraphicsBuffer *buffer, const ColorDescription &color, RenderingIntent intent, const std::shared_ptr<OutputFrame> &frame)
{
// TODO use viewporter to relax this check
if (sourceRect() != targetRect() || targetRect() != QRectF(QPointF(0, 0), m_output->modeSize())) {
return false;
}
if (offloadTransform() != OutputTransform::Kind::Normal || color != ColorDescription::sRGB) {
return false;
}
auto presentationBuffer = m_backend->backend()->importBuffer(buffer);
if (presentationBuffer) {
static_cast<WaylandOutput *>(m_output)->setPrimaryBuffer(presentationBuffer);
}
return presentationBuffer;
}
DrmDevice *WaylandEglPrimaryLayer::scanoutDevice() const
{
return m_backend->drmDevice();
}
QHash<uint32_t, QList<uint64_t>> WaylandEglPrimaryLayer::supportedDrmFormats() const
{
return m_backend->backend()->display()->linuxDmabuf()->formats();
}
WaylandEglCursorLayer::WaylandEglCursorLayer(WaylandOutput *output, WaylandEglBackend *backend)
: OutputLayer(output)
, m_backend(backend)
{
}
WaylandEglCursorLayer::~WaylandEglCursorLayer()
{
m_backend->openglContext()->makeCurrent();
}
std::optional<OutputLayerBeginFrameInfo> WaylandEglCursorLayer::doBeginFrame()
{
if (!m_backend->openglContext()->makeCurrent()) {
qCCritical(KWIN_WAYLAND_BACKEND) << "Make Context Current failed";
return std::nullopt;
}
const auto tmp = targetRect().size().expandedTo(QSize(64, 64));
const QSize bufferSize(std::ceil(tmp.width()), std::ceil(tmp.height()));
if (!m_swapchain || m_swapchain->size() != bufferSize) {
const QHash<uint32_t, QList<uint64_t>> formatTable = m_backend->backend()->display()->linuxDmabuf()->formats();
uint32_t format = DRM_FORMAT_INVALID;
QList<uint64_t> modifiers;
for (const uint32_t &candidateFormat : {DRM_FORMAT_ARGB2101010, DRM_FORMAT_ARGB8888}) {
auto it = formatTable.constFind(candidateFormat);
if (it != formatTable.constEnd()) {
format = it.key();
modifiers = it.value();
break;
}
}
if (format == DRM_FORMAT_INVALID) {
qCWarning(KWIN_WAYLAND_BACKEND) << "Could not find a suitable render format";
return std::nullopt;
}
m_swapchain = EglSwapchain::create(m_backend->drmDevice()->allocator(), m_backend->openglContext(), bufferSize, format, modifiers);
if (!m_swapchain) {
return std::nullopt;
}
}
m_buffer = m_swapchain->acquire();
if (!m_buffer) {
return std::nullopt;
}
m_query = std::make_unique<GLRenderTimeQuery>(m_backend->openglContextRef());
m_query->begin();
return OutputLayerBeginFrameInfo{
.renderTarget = RenderTarget(m_buffer->framebuffer()),
.repaint = infiniteRegion(),
};
}
bool WaylandEglCursorLayer::doEndFrame(const QRegion &renderedRegion, const QRegion &damagedRegion, OutputFrame *frame)
{
m_query->end();
if (frame) {
frame->addRenderTimeQuery(std::move(m_query));
}
// Flush rendering commands to the dmabuf.
glFlush();
wl_buffer *buffer = m_backend->backend()->importBuffer(m_buffer->buffer());
Q_ASSERT(buffer);
static_cast<WaylandOutput *>(m_output)->cursor()->update(buffer, scale(), hotspot().toPoint());
EGLNativeFence releaseFence{m_backend->eglDisplayObject()};
m_swapchain->release(m_buffer, releaseFence.takeFileDescriptor());
return true;
}
DrmDevice *WaylandEglCursorLayer::scanoutDevice() const
{
return m_backend->drmDevice();
}
QHash<uint32_t, QList<uint64_t>> WaylandEglCursorLayer::supportedDrmFormats() const
{
return m_backend->supportedFormats();
}
WaylandEglBackend::WaylandEglBackend(WaylandBackend *b)
: AbstractEglBackend()
, m_backend(b)
{
connect(m_backend, &WaylandBackend::outputAdded, this, &WaylandEglBackend::createEglWaylandOutput);
connect(m_backend, &WaylandBackend::outputRemoved, this, [this](Output *output) {
m_outputs.erase(output);
});
b->setEglBackend(this);
}
WaylandEglBackend::~WaylandEglBackend()
{
cleanup();
}
WaylandBackend *WaylandEglBackend::backend() const
{
return m_backend;
}
DrmDevice *WaylandEglBackend::drmDevice() const
{
return m_backend->drmDevice();
}
void WaylandEglBackend::cleanupSurfaces()
{
m_outputs.clear();
}
bool WaylandEglBackend::createEglWaylandOutput(Output *waylandOutput)
{
m_outputs[waylandOutput] = Layers{
.primaryLayer = std::make_unique<WaylandEglPrimaryLayer>(static_cast<WaylandOutput *>(waylandOutput), this),
.cursorLayer = std::make_unique<WaylandEglCursorLayer>(static_cast<WaylandOutput *>(waylandOutput), this),
};
return true;
}
bool WaylandEglBackend::initializeEgl()
{
initClientExtensions();
if (!m_backend->sceneEglDisplayObject()) {
for (const QByteArray &extension : {QByteArrayLiteral("EGL_EXT_platform_base"), QByteArrayLiteral("EGL_KHR_platform_gbm")}) {
if (!hasClientExtension(extension)) {
qCWarning(KWIN_WAYLAND_BACKEND) << extension << "client extension is not supported by the platform";
return false;
}
}
m_backend->setEglDisplay(EglDisplay::create(eglGetPlatformDisplayEXT(EGL_PLATFORM_GBM_KHR, m_backend->drmDevice()->gbmDevice(), nullptr)));
}
const auto display = m_backend->sceneEglDisplayObject();
if (!display) {
return false;
}
setEglDisplay(display);
return true;
}
void WaylandEglBackend::init()
{
if (!initializeEgl()) {
setFailed("Could not initialize egl");
return;
}
if (!initRenderingContext()) {
setFailed("Could not initialize rendering context");
return;
}
initWayland();
}
bool WaylandEglBackend::initRenderingContext()
{
if (!createContext(EGL_NO_CONFIG_KHR)) {
return false;
}
auto waylandOutputs = m_backend->waylandOutputs();
// we only allow to start with at least one output
if (waylandOutputs.isEmpty()) {
return false;
}
for (auto *out : waylandOutputs) {
if (!createEglWaylandOutput(out)) {
return false;
}
}
if (m_outputs.empty()) {
qCCritical(KWIN_WAYLAND_BACKEND) << "Create Window Surfaces failed";
return false;
}
return makeCurrent();
}
std::pair<std::shared_ptr<KWin::GLTexture>, ColorDescription> WaylandEglBackend::textureForOutput(KWin::Output *output) const
{
return std::make_pair(m_outputs.at(output).primaryLayer->texture(), ColorDescription::sRGB);
}
std::unique_ptr<SurfaceTexture> WaylandEglBackend::createSurfaceTextureWayland(SurfacePixmap *pixmap)
{
return std::make_unique<BasicEGLSurfaceTextureWayland>(this, pixmap);
}
bool WaylandEglBackend::present(Output *output, const std::shared_ptr<OutputFrame> &frame)
{
static_cast<WaylandOutput *>(output)->present(frame);
return true;
}
OutputLayer *WaylandEglBackend::primaryLayer(Output *output)
{
return m_outputs[output].primaryLayer.get();
}
OutputLayer *WaylandEglBackend::cursorLayer(Output *output)
{
return m_outputs[output].cursorLayer.get();
}
}
}
#include "moc_wayland_egl_backend.cpp"
@@ -0,0 +1,128 @@
/*
KWin - the KDE window manager
This file is part of the KDE project.
SPDX-FileCopyrightText: 2013 Martin Gräßlin <mgraesslin@kde.org>
SPDX-FileCopyrightText: 2019 Roman Gilg <subdiff@gmail.com>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#pragma once
#include "core/outputlayer.h"
#include "opengl/eglnativefence.h"
#include "platformsupport/scenes/opengl/abstract_egl_backend.h"
#include "utils/damagejournal.h"
#include <memory>
struct wl_buffer;
namespace KWin
{
class EglSwapchainSlot;
class EglSwapchain;
class GLFramebuffer;
class GraphicsBufferAllocator;
class GLRenderTimeQuery;
namespace Wayland
{
class WaylandBackend;
class WaylandOutput;
class WaylandEglBackend;
class WaylandEglPrimaryLayer : public OutputLayer
{
public:
WaylandEglPrimaryLayer(WaylandOutput *output, WaylandEglBackend *backend);
~WaylandEglPrimaryLayer() override;
GLFramebuffer *fbo() const;
std::shared_ptr<GLTexture> texture() const;
std::optional<OutputLayerBeginFrameInfo> doBeginFrame() override;
bool doEndFrame(const QRegion &renderedRegion, const QRegion &damagedRegion, OutputFrame *frame) override;
bool doImportScanoutBuffer(GraphicsBuffer *buffer, const ColorDescription &color, RenderingIntent intent, const std::shared_ptr<OutputFrame> &frame) override;
DrmDevice *scanoutDevice() const override;
QHash<uint32_t, QList<uint64_t>> supportedDrmFormats() const override;
private:
DamageJournal m_damageJournal;
std::shared_ptr<EglSwapchain> m_swapchain;
std::shared_ptr<EglSwapchainSlot> m_buffer;
std::unique_ptr<GLRenderTimeQuery> m_query;
WaylandEglBackend *const m_backend;
friend class WaylandEglBackend;
};
class WaylandEglCursorLayer : public OutputLayer
{
Q_OBJECT
public:
WaylandEglCursorLayer(WaylandOutput *output, WaylandEglBackend *backend);
~WaylandEglCursorLayer() override;
std::optional<OutputLayerBeginFrameInfo> doBeginFrame() override;
bool doEndFrame(const QRegion &renderedRegion, const QRegion &damagedRegion, OutputFrame *frame) override;
DrmDevice *scanoutDevice() const override;
QHash<uint32_t, QList<uint64_t>> supportedDrmFormats() const override;
private:
WaylandEglBackend *m_backend;
std::shared_ptr<EglSwapchain> m_swapchain;
std::shared_ptr<EglSwapchainSlot> m_buffer;
std::unique_ptr<GLRenderTimeQuery> m_query;
};
/**
* @brief OpenGL Backend using Egl on a Wayland surface.
*
* This Backend is the basis for a session compositor running on top of a Wayland system compositor.
* It creates a Surface as large as the screen and maps it as a fullscreen shell surface on the
* system compositor. The OpenGL context is created on the Wayland surface, so for rendering X11 is
* not involved.
*
* Also in repainting the backend is currently still rather limited. Only supported mode is fullscreen
* repaints, which is obviously not optimal. Best solution is probably to go for buffer_age extension
* and make it the only available solution next to fullscreen repaints.
*/
class WaylandEglBackend : public AbstractEglBackend
{
Q_OBJECT
public:
WaylandEglBackend(WaylandBackend *b);
~WaylandEglBackend() override;
WaylandBackend *backend() const;
DrmDevice *drmDevice() const override;
std::unique_ptr<SurfaceTexture> createSurfaceTextureWayland(SurfacePixmap *pixmap) override;
void init() override;
bool present(Output *output, const std::shared_ptr<OutputFrame> &frame) override;
OutputLayer *primaryLayer(Output *output) override;
OutputLayer *cursorLayer(Output *output) override;
std::pair<std::shared_ptr<KWin::GLTexture>, ColorDescription> textureForOutput(KWin::Output *output) const override;
private:
bool initializeEgl();
bool initRenderingContext();
bool createEglWaylandOutput(Output *output);
void cleanupSurfaces() override;
struct Layers
{
std::unique_ptr<WaylandEglPrimaryLayer> primaryLayer;
std::unique_ptr<WaylandEglCursorLayer> cursorLayer;
};
WaylandBackend *m_backend;
std::map<Output *, Layers> m_outputs;
};
} // namespace Wayland
} // namespace KWin
@@ -0,0 +1,10 @@
/*
KWin - the KDE window manager
This file is part of the KDE project.
SPDX-FileCopyrightText: 2015 Martin Gräßlin <mgraesslin@kde.org>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "wayland_logging.h"
Q_LOGGING_CATEGORY(KWIN_WAYLAND_BACKEND, "kwin_wayland_backend", QtWarningMsg)
@@ -0,0 +1,14 @@
/*
KWin - the KDE window manager
This file is part of the KDE project.
SPDX-FileCopyrightText: 2015 Martin Gräßlin <mgraesslin@kde.org>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#pragma once
#include <QDebug>
#include <QLoggingCategory>
Q_DECLARE_LOGGING_CATEGORY(KWIN_WAYLAND_BACKEND)
@@ -0,0 +1,420 @@
/*
KWin - the KDE window manager
This file is part of the KDE project.
SPDX-FileCopyrightText: 2019 Roman Gilg <subdiff@gmail.com>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "wayland_output.h"
#include "compositor.h"
#include "core/outputlayer.h"
#include "core/renderbackend.h"
#include "core/renderloop_p.h"
#include "wayland_backend.h"
#include "wayland_display.h"
#include <KWayland/Client/compositor.h>
#include <KWayland/Client/pointer.h>
#include <KWayland/Client/pointerconstraints.h>
#include <KWayland/Client/surface.h>
#include <KWayland/Client/xdgdecoration.h>
#include "wayland-presentation-time-client-protocol.h"
#include <KLocalizedString>
#include <QPainter>
#include <cmath>
namespace KWin
{
namespace Wayland
{
using namespace KWayland::Client;
static const int s_refreshRate = 60000; // TODO: can we get refresh rate data from Wayland host?
WaylandCursor::WaylandCursor(WaylandBackend *backend)
: m_surface(backend->display()->compositor()->createSurface())
{
}
WaylandCursor::~WaylandCursor() = default;
KWayland::Client::Pointer *WaylandCursor::pointer() const
{
return m_pointer;
}
void WaylandCursor::setPointer(KWayland::Client::Pointer *pointer)
{
if (m_pointer == pointer) {
return;
}
m_pointer = pointer;
if (m_pointer) {
m_pointer->setCursor(m_surface.get(), m_hotspot);
}
}
void WaylandCursor::setEnabled(bool enable)
{
if (m_enabled != enable) {
m_enabled = enable;
sync();
}
}
void WaylandCursor::update(wl_buffer *buffer, qreal scale, const QPoint &hotspot)
{
if (m_buffer != buffer || m_scale != scale || m_hotspot != hotspot) {
m_buffer = buffer;
m_scale = scale;
m_hotspot = hotspot;
sync();
}
}
void WaylandCursor::sync()
{
if (!m_enabled) {
m_surface->attachBuffer(KWayland::Client::Buffer::Ptr());
m_surface->commit(KWayland::Client::Surface::CommitFlag::None);
} else {
m_surface->attachBuffer(m_buffer);
m_surface->setScale(std::ceil(m_scale));
m_surface->damageBuffer(QRect(0, 0, INT32_MAX, INT32_MAX));
m_surface->commit(KWayland::Client::Surface::CommitFlag::None);
}
if (m_pointer) {
m_pointer->setCursor(m_surface.get(), m_hotspot);
}
}
WaylandOutput::WaylandOutput(const QString &name, WaylandBackend *backend)
: Output(backend)
, m_renderLoop(std::make_unique<RenderLoop>(this))
, m_surface(backend->display()->compositor()->createSurface())
, m_xdgShellSurface(backend->display()->xdgShell()->createSurface(m_surface.get()))
, m_backend(backend)
, m_cursor(std::make_unique<WaylandCursor>(backend))
{
if (KWayland::Client::XdgDecorationManager *manager = m_backend->display()->xdgDecorationManager()) {
m_xdgDecoration.reset(manager->getToplevelDecoration(m_xdgShellSurface.get()));
m_xdgDecoration->setMode(KWayland::Client::XdgDecoration::Mode::ServerSide);
}
setInformation(Information{
.name = name,
.model = name,
.capabilities = Capability::Dpms,
});
m_turnOffTimer.setSingleShot(true);
m_turnOffTimer.setInterval(dimAnimationTime());
connect(&m_turnOffTimer, &QTimer::timeout, this, [this] {
updateDpmsMode(DpmsMode::Off);
});
m_configureThrottleTimer.setSingleShot(true);
connect(&m_configureThrottleTimer, &QTimer::timeout, this, [this]() {
applyConfigure(m_pendingConfigureSize, m_pendingConfigureSerial);
});
connect(m_surface.get(), &KWayland::Client::Surface::frameRendered, this, [this]() {
Q_ASSERT(m_frame);
m_frame->presented(std::chrono::steady_clock::now().time_since_epoch(), PresentationMode::VSync);
m_frame.reset();
});
updateWindowTitle();
connect(m_xdgShellSurface.get(), &XdgShellSurface::configureRequested, this, &WaylandOutput::handleConfigure);
connect(m_xdgShellSurface.get(), &XdgShellSurface::closeRequested, qApp, &QCoreApplication::quit);
connect(this, &WaylandOutput::enabledChanged, this, &WaylandOutput::updateWindowTitle);
connect(this, &WaylandOutput::dpmsModeChanged, this, &WaylandOutput::updateWindowTitle);
}
WaylandOutput::~WaylandOutput()
{
if (m_presentationFeedback) {
wp_presentation_feedback_destroy(m_presentationFeedback);
m_presentationFeedback = nullptr;
}
m_xdgDecoration.reset();
m_xdgShellSurface.reset();
m_surface.reset();
}
void WaylandOutput::setPrimaryBuffer(wl_buffer *buffer)
{
m_presentationBuffer = buffer;
}
static void handleDiscarded(void *data,
struct wp_presentation_feedback *wp_presentation_feedback)
{
reinterpret_cast<WaylandOutput *>(data)->frameDiscarded();
}
static void handlePresented(void *data,
struct wp_presentation_feedback *wp_presentation_feedback,
uint32_t tv_sec_hi,
uint32_t tv_sec_lo,
uint32_t tv_nsec,
uint32_t refresh,
uint32_t seq_hi,
uint32_t seq_lo,
uint32_t flags)
{
const auto timestamp = std::chrono::seconds((uint64_t(tv_sec_hi) << 32) | tv_sec_lo) + std::chrono::nanoseconds(tv_nsec);
uint32_t refreshRate = 60'000;
if (refresh != 0) {
refreshRate = 1'000'000'000'000 / refresh;
}
reinterpret_cast<WaylandOutput *>(data)->framePresented(timestamp, refreshRate);
}
static void handleSyncOutput(void *data, struct wp_presentation_feedback *, struct wl_output *)
{
// intentionally ignored
}
static constexpr struct wp_presentation_feedback_listener s_presentationListener{
.sync_output = handleSyncOutput,
.presented = handlePresented,
.discarded = handleDiscarded,
};
void WaylandOutput::present(const std::shared_ptr<OutputFrame> &frame)
{
if (!m_presentationBuffer) {
return;
}
m_surface->attachBuffer(m_presentationBuffer);
m_surface->damage(frame->damage());
m_surface->setScale(std::ceil(scale()));
m_presentationBuffer = nullptr;
if (auto presentationTime = m_backend->display()->presentationTime()) {
m_presentationFeedback = wp_presentation_feedback(presentationTime, *m_surface);
wp_presentation_feedback_add_listener(m_presentationFeedback, &s_presentationListener, this);
m_surface->commit(KWayland::Client::Surface::CommitFlag::None);
} else {
m_surface->commit(KWayland::Client::Surface::CommitFlag::FrameCallback);
}
m_frame = frame;
Q_EMIT outputChange(frame->damage());
}
void WaylandOutput::frameDiscarded()
{
m_frame.reset();
if (m_presentationFeedback) {
wp_presentation_feedback_destroy(m_presentationFeedback);
m_presentationFeedback = nullptr;
}
}
void WaylandOutput::framePresented(std::chrono::nanoseconds timestamp, uint32_t refreshRate)
{
if (refreshRate != this->refreshRate()) {
m_refreshRate = refreshRate;
const auto mode = std::make_shared<OutputMode>(pixelSize(), m_refreshRate);
State next = m_state;
next.modes = {mode};
next.currentMode = mode;
setState(next);
m_renderLoop->setRefreshRate(m_refreshRate);
}
m_frame->presented(timestamp, PresentationMode::VSync);
m_frame.reset();
if (m_presentationFeedback) {
wp_presentation_feedback_destroy(m_presentationFeedback);
m_presentationFeedback = nullptr;
}
}
bool WaylandOutput::isReady() const
{
return m_ready;
}
KWayland::Client::Surface *WaylandOutput::surface() const
{
return m_surface.get();
}
WaylandCursor *WaylandOutput::cursor() const
{
return m_cursor.get();
}
WaylandBackend *WaylandOutput::backend() const
{
return m_backend;
}
RenderLoop *WaylandOutput::renderLoop() const
{
return m_renderLoop.get();
}
bool WaylandOutput::updateCursorLayer(std::optional<std::chrono::nanoseconds> allowedVrrDelay)
{
if (m_hasPointerLock) {
m_cursor->setEnabled(false);
return false;
} else {
m_cursor->setEnabled(Compositor::self()->backend()->cursorLayer(this)->isEnabled());
// the layer already takes care of updating the image
return true;
}
}
void WaylandOutput::init(const QSize &pixelSize, qreal scale)
{
m_renderLoop->setRefreshRate(m_refreshRate);
auto mode = std::make_shared<OutputMode>(pixelSize, m_refreshRate);
State initialState;
initialState.modes = {mode};
initialState.currentMode = mode;
initialState.scale = scale;
setState(initialState);
m_surface->commit(KWayland::Client::Surface::CommitFlag::None);
}
void WaylandOutput::resize(const QSize &pixelSize)
{
auto mode = std::make_shared<OutputMode>(pixelSize, m_refreshRate);
State next = m_state;
next.modes = {mode};
next.currentMode = mode;
setState(next);
Q_EMIT m_backend->outputsQueried();
}
void WaylandOutput::setDpmsMode(DpmsMode mode)
{
if (mode == DpmsMode::Off) {
if (!m_turnOffTimer.isActive()) {
Q_EMIT aboutToTurnOff(std::chrono::milliseconds(m_turnOffTimer.interval()));
m_turnOffTimer.start();
}
} else {
m_turnOffTimer.stop();
if (mode != dpmsMode()) {
updateDpmsMode(mode);
Q_EMIT wakeUp();
}
}
}
void WaylandOutput::updateDpmsMode(DpmsMode dpmsMode)
{
State next = m_state;
next.dpmsMode = dpmsMode;
setState(next);
}
void WaylandOutput::updateEnabled(bool enabled)
{
State next = m_state;
next.enabled = enabled;
setState(next);
}
void WaylandOutput::handleConfigure(const QSize &size, XdgShellSurface::States states, quint32 serial)
{
if (!m_ready) {
m_ready = true;
applyConfigure(size, serial);
} else {
// Output resizing is a resource intensive task, so the configure events are throttled.
m_pendingConfigureSerial = serial;
m_pendingConfigureSize = size;
if (!m_configureThrottleTimer.isActive()) {
m_configureThrottleTimer.start(1000000 / m_state.currentMode->refreshRate());
}
}
}
void WaylandOutput::applyConfigure(const QSize &size, quint32 serial)
{
m_xdgShellSurface->ackConfigure(serial);
if (!size.isEmpty()) {
resize(size * scale());
}
}
void WaylandOutput::updateWindowTitle()
{
QString grab;
if (m_hasPointerLock) {
grab = i18n("Press right control to ungrab pointer");
} else if (m_backend->display()->pointerConstraints()) {
grab = i18n("Press right control key to grab pointer");
}
QString title = i18nc("Title of nested KWin Wayland with Wayland socket identifier as argument",
"KDE Wayland Compositor %1", name());
if (!isEnabled()) {
title += i18n("- Output disabled");
} else if (dpmsMode() != DpmsMode::On) {
title += i18n("- Output dimmed");
} else if (!grab.isEmpty()) {
title += QStringLiteral("") + grab;
}
m_xdgShellSurface->setTitle(title);
}
void WaylandOutput::lockPointer(Pointer *pointer, bool lock)
{
if (!lock) {
const bool surfaceWasLocked = m_pointerLock && m_hasPointerLock;
m_pointerLock.reset();
m_hasPointerLock = false;
if (surfaceWasLocked) {
updateWindowTitle();
updateCursorLayer(std::nullopt);
Q_EMIT m_backend->pointerLockChanged(false);
}
return;
}
Q_ASSERT(!m_pointerLock);
m_pointerLock.reset(m_backend->display()->pointerConstraints()->lockPointer(surface(), pointer, nullptr, PointerConstraints::LifeTime::OneShot));
if (!m_pointerLock->isValid()) {
m_pointerLock.reset();
return;
}
connect(m_pointerLock.get(), &LockedPointer::locked, this, [this]() {
m_hasPointerLock = true;
updateWindowTitle();
updateCursorLayer(std::nullopt);
Q_EMIT m_backend->pointerLockChanged(true);
});
connect(m_pointerLock.get(), &LockedPointer::unlocked, this, [this]() {
m_pointerLock.reset();
m_hasPointerLock = false;
updateWindowTitle();
updateCursorLayer(std::nullopt);
Q_EMIT m_backend->pointerLockChanged(false);
});
}
}
}
#include "moc_wayland_output.cpp"
@@ -0,0 +1,117 @@
/*
KWin - the KDE window manager
This file is part of the KDE project.
SPDX-FileCopyrightText: 2019 Roman Gilg <subdiff@gmail.com>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#pragma once
#include "core/output.h"
#include <KWayland/Client/xdgshell.h>
#include <QObject>
#include <QTimer>
namespace KWayland
{
namespace Client
{
class Surface;
class Pointer;
class LockedPointer;
class XdgDecoration;
}
}
struct wl_buffer;
struct wp_presentation_feedback;
namespace KWin
{
class OutputFrame;
namespace Wayland
{
class WaylandBackend;
class WaylandCursor
{
public:
explicit WaylandCursor(WaylandBackend *backend);
~WaylandCursor();
KWayland::Client::Pointer *pointer() const;
void setPointer(KWayland::Client::Pointer *pointer);
void setEnabled(bool enable);
void update(wl_buffer *buffer, qreal scale, const QPoint &hotspot);
private:
void sync();
KWayland::Client::Pointer *m_pointer = nullptr;
std::unique_ptr<KWayland::Client::Surface> m_surface;
wl_buffer *m_buffer = nullptr;
QPoint m_hotspot;
qreal m_scale = 1;
bool m_enabled = true;
};
class WaylandOutput : public Output
{
Q_OBJECT
public:
WaylandOutput(const QString &name, WaylandBackend *backend);
~WaylandOutput() override;
RenderLoop *renderLoop() const override;
bool updateCursorLayer(std::optional<std::chrono::nanoseconds> allowedVrrDelay) override;
void init(const QSize &pixelSize, qreal scale);
bool isReady() const;
KWayland::Client::Surface *surface() const;
WaylandCursor *cursor() const;
WaylandBackend *backend() const;
void lockPointer(KWayland::Client::Pointer *pointer, bool lock);
void resize(const QSize &pixelSize);
void setDpmsMode(DpmsMode mode) override;
void updateDpmsMode(DpmsMode dpmsMode);
void updateEnabled(bool enabled);
void present(const std::shared_ptr<OutputFrame> &frame);
void setPrimaryBuffer(wl_buffer *buffer);
void frameDiscarded();
void framePresented(std::chrono::nanoseconds timestamp, uint32_t refreshRate);
private:
void handleConfigure(const QSize &size, KWayland::Client::XdgShellSurface::States states, quint32 serial);
void updateWindowTitle();
void applyConfigure(const QSize &size, quint32 serial);
std::unique_ptr<RenderLoop> m_renderLoop;
std::unique_ptr<KWayland::Client::Surface> m_surface;
std::unique_ptr<KWayland::Client::XdgShellSurface> m_xdgShellSurface;
std::unique_ptr<KWayland::Client::LockedPointer> m_pointerLock;
std::unique_ptr<KWayland::Client::XdgDecoration> m_xdgDecoration;
WaylandBackend *const m_backend;
std::unique_ptr<WaylandCursor> m_cursor;
QTimer m_turnOffTimer;
bool m_hasPointerLock = false;
bool m_ready = false;
std::shared_ptr<OutputFrame> m_frame;
wl_buffer *m_presentationBuffer = nullptr;
quint32 m_pendingConfigureSerial = 0;
QSize m_pendingConfigureSize;
QTimer m_configureThrottleTimer;
wp_presentation_feedback *m_presentationFeedback = nullptr;
uint32_t m_refreshRate = 60'000;
};
} // namespace Wayland
} // namespace KWin
@@ -0,0 +1,188 @@
/*
KWin - the KDE window manager
This file is part of the KDE project.
SPDX-FileCopyrightText: 2019 Roman Gilg <subdiff@gmail.com>
SPDX-FileCopyrightText: 2013, 2015 Martin Gräßlin <mgraesslin@kde.org>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "wayland_qpainter_backend.h"
#include "core/graphicsbufferview.h"
#include "core/shmgraphicsbufferallocator.h"
#include "platformsupport/scenes/qpainter/qpainterswapchain.h"
#include "wayland_backend.h"
#include "wayland_output.h"
#include <KWayland/Client/surface.h>
#include <cmath>
#include <drm_fourcc.h>
#include <wayland-client-protocol.h>
namespace KWin
{
namespace Wayland
{
WaylandQPainterPrimaryLayer::WaylandQPainterPrimaryLayer(WaylandOutput *output, WaylandQPainterBackend *backend)
: OutputLayer(output)
, m_waylandOutput(output)
, m_backend(backend)
{
}
WaylandQPainterPrimaryLayer::~WaylandQPainterPrimaryLayer()
{
}
QRegion WaylandQPainterPrimaryLayer::accumulateDamage(int bufferAge) const
{
return m_damageJournal.accumulate(bufferAge, infiniteRegion());
}
std::optional<OutputLayerBeginFrameInfo> WaylandQPainterPrimaryLayer::doBeginFrame()
{
const QSize nativeSize(m_waylandOutput->modeSize());
if (!m_swapchain || m_swapchain->size() != nativeSize) {
m_swapchain = std::make_unique<QPainterSwapchain>(m_backend->graphicsBufferAllocator(), nativeSize, DRM_FORMAT_XRGB8888);
}
m_back = m_swapchain->acquire();
if (!m_back) {
return std::nullopt;
}
m_renderTime = std::make_unique<CpuRenderTimeQuery>();
return OutputLayerBeginFrameInfo{
.renderTarget = RenderTarget(m_back->view()->image()),
.repaint = accumulateDamage(m_back->age()),
};
}
bool WaylandQPainterPrimaryLayer::doEndFrame(const QRegion &renderedRegion, const QRegion &damagedRegion, OutputFrame *frame)
{
m_renderTime->end();
frame->addRenderTimeQuery(std::move(m_renderTime));
m_damageJournal.add(damagedRegion);
m_waylandOutput->setPrimaryBuffer(m_waylandOutput->backend()->importBuffer(m_back->buffer()));
m_swapchain->release(m_back);
return true;
}
DrmDevice *WaylandQPainterPrimaryLayer::scanoutDevice() const
{
return m_backend->drmDevice();
}
QHash<uint32_t, QList<uint64_t>> WaylandQPainterPrimaryLayer::supportedDrmFormats() const
{
return {{DRM_FORMAT_ARGB8888, {DRM_FORMAT_MOD_LINEAR}}};
}
WaylandQPainterCursorLayer::WaylandQPainterCursorLayer(WaylandOutput *output, WaylandQPainterBackend *backend)
: OutputLayer(output)
, m_backend(backend)
{
}
WaylandQPainterCursorLayer::~WaylandQPainterCursorLayer()
{
}
std::optional<OutputLayerBeginFrameInfo> WaylandQPainterCursorLayer::doBeginFrame()
{
const auto tmp = targetRect().size().expandedTo(QSize(64, 64));
const QSize bufferSize(std::ceil(tmp.width()), std::ceil(tmp.height()));
if (!m_swapchain || m_swapchain->size() != bufferSize) {
m_swapchain = std::make_unique<QPainterSwapchain>(m_backend->graphicsBufferAllocator(), bufferSize, DRM_FORMAT_ARGB8888);
}
m_back = m_swapchain->acquire();
if (!m_back) {
return std::nullopt;
}
m_renderTime = std::make_unique<CpuRenderTimeQuery>();
return OutputLayerBeginFrameInfo{
.renderTarget = RenderTarget(m_back->view()->image()),
.repaint = infiniteRegion(),
};
}
bool WaylandQPainterCursorLayer::doEndFrame(const QRegion &renderedRegion, const QRegion &damagedRegion, OutputFrame *frame)
{
if (frame) {
frame->addRenderTimeQuery(std::move(m_renderTime));
}
wl_buffer *buffer = static_cast<WaylandOutput *>(m_output)->backend()->importBuffer(m_back->buffer());
Q_ASSERT(buffer);
static_cast<WaylandOutput *>(m_output)->cursor()->update(buffer, scale(), hotspot().toPoint());
m_swapchain->release(m_back);
return true;
}
DrmDevice *WaylandQPainterCursorLayer::scanoutDevice() const
{
return m_backend->drmDevice();
}
QHash<uint32_t, QList<uint64_t>> WaylandQPainterCursorLayer::supportedDrmFormats() const
{
return {{DRM_FORMAT_ARGB8888, {DRM_FORMAT_MOD_LINEAR}}};
}
WaylandQPainterBackend::WaylandQPainterBackend(Wayland::WaylandBackend *b)
: QPainterBackend()
, m_backend(b)
, m_allocator(std::make_unique<ShmGraphicsBufferAllocator>())
{
const auto waylandOutputs = m_backend->waylandOutputs();
for (auto *output : waylandOutputs) {
createOutput(output);
}
connect(m_backend, &WaylandBackend::outputAdded, this, &WaylandQPainterBackend::createOutput);
connect(m_backend, &WaylandBackend::outputRemoved, this, [this](Output *waylandOutput) {
m_outputs.erase(waylandOutput);
});
}
WaylandQPainterBackend::~WaylandQPainterBackend()
{
}
void WaylandQPainterBackend::createOutput(Output *waylandOutput)
{
m_outputs[waylandOutput] = Layers{
.primaryLayer = std::make_unique<WaylandQPainterPrimaryLayer>(static_cast<WaylandOutput *>(waylandOutput), this),
.cursorLayer = std::make_unique<WaylandQPainterCursorLayer>(static_cast<WaylandOutput *>(waylandOutput), this),
};
}
GraphicsBufferAllocator *WaylandQPainterBackend::graphicsBufferAllocator() const
{
return m_allocator.get();
}
bool WaylandQPainterBackend::present(Output *output, const std::shared_ptr<OutputFrame> &frame)
{
static_cast<WaylandOutput *>(output)->present(frame);
return true;
}
OutputLayer *WaylandQPainterBackend::primaryLayer(Output *output)
{
return m_outputs[output].primaryLayer.get();
}
OutputLayer *WaylandQPainterBackend::cursorLayer(Output *output)
{
return m_outputs[output].cursorLayer.get();
}
}
}
#include "moc_wayland_qpainter_backend.cpp"
@@ -0,0 +1,107 @@
/*
KWin - the KDE window manager
This file is part of the KDE project.
SPDX-FileCopyrightText: 2019 Roman Gilg <subdiff@gmail.com>
SPDX-FileCopyrightText: 2013, 2015 Martin Gräßlin <mgraesslin@kde.org>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#pragma once
#include "core/outputlayer.h"
#include "platformsupport/scenes/qpainter/qpainterbackend.h"
#include "utils/damagejournal.h"
#include <QImage>
#include <QObject>
#include <chrono>
namespace KWin
{
class Output;
class GraphicsBufferAllocator;
class QPainterSwapchainSlot;
class QPainterSwapchain;
namespace Wayland
{
class WaylandBackend;
class WaylandDisplay;
class WaylandOutput;
class WaylandQPainterBackend;
class WaylandQPainterPrimaryLayer : public OutputLayer
{
public:
WaylandQPainterPrimaryLayer(WaylandOutput *output, WaylandQPainterBackend *backend);
~WaylandQPainterPrimaryLayer() override;
std::optional<OutputLayerBeginFrameInfo> doBeginFrame() override;
bool doEndFrame(const QRegion &renderedRegion, const QRegion &damagedRegion, OutputFrame *frame) override;
DrmDevice *scanoutDevice() const override;
QHash<uint32_t, QList<uint64_t>> supportedDrmFormats() const override;
QRegion accumulateDamage(int bufferAge) const;
private:
WaylandOutput *m_waylandOutput;
WaylandQPainterBackend *m_backend;
DamageJournal m_damageJournal;
std::unique_ptr<QPainterSwapchain> m_swapchain;
std::shared_ptr<QPainterSwapchainSlot> m_back;
std::unique_ptr<CpuRenderTimeQuery> m_renderTime;
friend class WaylandQPainterBackend;
};
class WaylandQPainterCursorLayer : public OutputLayer
{
Q_OBJECT
public:
WaylandQPainterCursorLayer(WaylandOutput *output, WaylandQPainterBackend *backend);
~WaylandQPainterCursorLayer() override;
std::optional<OutputLayerBeginFrameInfo> doBeginFrame() override;
bool doEndFrame(const QRegion &renderedRegion, const QRegion &damagedRegion, OutputFrame *frame) override;
DrmDevice *scanoutDevice() const override;
QHash<uint32_t, QList<uint64_t>> supportedDrmFormats() const override;
private:
WaylandQPainterBackend *m_backend;
std::unique_ptr<QPainterSwapchain> m_swapchain;
std::shared_ptr<QPainterSwapchainSlot> m_back;
std::unique_ptr<CpuRenderTimeQuery> m_renderTime;
};
class WaylandQPainterBackend : public QPainterBackend
{
Q_OBJECT
public:
explicit WaylandQPainterBackend(WaylandBackend *b);
~WaylandQPainterBackend() override;
GraphicsBufferAllocator *graphicsBufferAllocator() const;
bool present(Output *output, const std::shared_ptr<OutputFrame> &frame) override;
OutputLayer *primaryLayer(Output *output) override;
OutputLayer *cursorLayer(Output *output) override;
private:
void createOutput(Output *waylandOutput);
struct Layers
{
std::unique_ptr<WaylandQPainterPrimaryLayer> primaryLayer;
std::unique_ptr<WaylandQPainterCursorLayer> cursorLayer;
};
WaylandBackend *m_backend;
std::unique_ptr<GraphicsBufferAllocator> m_allocator;
std::map<Output *, Layers> m_outputs;
};
} // namespace Wayland
} // namespace KWin
@@ -0,0 +1,6 @@
if (KWIN_BUILD_X11_BACKEND)
add_subdirectory(standalone)
endif()
if (X11_XCB_FOUND)
add_subdirectory(windowed)
endif()
@@ -0,0 +1,50 @@
if ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-int-to-pointer-cast")
endif()
if ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-int-to-void-pointer-cast")
endif()
set(X11PLATFORM_SOURCES
kwinxrenderutils.cpp
x11_common_logging.cpp
x11_standalone_backend.cpp
x11_standalone_cursor.cpp
x11_standalone_edge.cpp
x11_standalone_effects.cpp
x11_standalone_effects_keyboard_interception_filter.cpp
x11_standalone_effects_mouse_interception_filter.cpp
x11_standalone_egl_backend.cpp
x11_standalone_keyboard.cpp
x11_standalone_logging.cpp
x11_standalone_non_composited_outline.cpp
x11_standalone_output.cpp
x11_standalone_overlaywindow.cpp
x11_standalone_placeholderoutput.cpp
x11_standalone_screenedges_filter.cpp
x11_standalone_windowselector.cpp
x11_standalone_xfixes_cursor_event_filter.cpp
)
add_library(KWinX11Platform OBJECT ${X11PLATFORM_SOURCES})
target_link_libraries(KWinX11Platform kwin KF6::Crash KF6::I18n X11::X11 XCB::XKB PkgConfig::XKBX11 Qt::GuiPrivate Libdrm::Libdrm
XCB::COMPOSITE XCB::KEYSYMS XCB::RANDR)
if (X11_Xi_FOUND)
target_sources(KWinX11Platform PRIVATE x11_standalone_xinputintegration.cpp)
target_link_libraries(KWinX11Platform X11::Xi)
endif()
if (HAVE_GLX)
target_sources(KWinX11Platform PRIVATE
glxcontext.cpp
x11_standalone_glx_backend.cpp
x11_standalone_glx_context_attribute_builder.cpp
x11_standalone_glxconvenience.cpp
x11_standalone_omlsynccontrolvsyncmonitor.cpp
x11_standalone_sgivideosyncvsyncmonitor.cpp
)
endif()
if (HAVE_DL_LIBRARY)
target_link_libraries(KWinX11Platform ${DL_LIBRARY})
endif()
@@ -0,0 +1,45 @@
/*
KWin - the KDE window manager
This file is part of the KDE project.
SPDX-FileCopyrightText: 2018 Martin Flöser <mgraesslin@kde.org>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#pragma once
#include <xcb/xcb.h>
#include <cstring>
namespace KWin
{
class GeEventMemMover
{
public:
GeEventMemMover(xcb_generic_event_t *event)
: m_event(reinterpret_cast<xcb_ge_generic_event_t *>(event))
{
// xcb event structs contain stuff that wasn't on the wire, the full_sequence field
// adds an extra 4 bytes and generic events cookie data is on the wire right after the standard 32 bytes.
// Move this data back to have the same layout in memory as it was on the wire
// and allow casting, overwriting the full_sequence field.
memmove((char *)m_event + 32, (char *)m_event + 36, m_event->length * 4);
}
~GeEventMemMover()
{
// move memory layout back, so that Qt can do the same without breaking
memmove((char *)m_event + 36, (char *)m_event + 32, m_event->length * 4);
}
xcb_ge_generic_event_t *operator->() const
{
return m_event;
}
private:
xcb_ge_generic_event_t *m_event;
};
}
@@ -0,0 +1,190 @@
/*
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 "glxcontext.h"
#include "opengl/glvertexbuffer_p.h"
#include "x11_standalone_glx_context_attribute_builder.h"
#include "x11_standalone_logging.h"
#include <QDebug>
#include <QOpenGLContext>
#include <dlfcn.h>
namespace KWin
{
typedef void (*glXFuncPtr)();
static glXFuncPtr getProcAddress(const char *name)
{
glXFuncPtr ret = nullptr;
#if HAVE_GLX
ret = glXGetProcAddress((const GLubyte *)name);
#endif
#if HAVE_DL_LIBRARY
if (ret == nullptr) {
ret = (glXFuncPtr)dlsym(RTLD_DEFAULT, name);
}
#endif
return ret;
}
glXSwapIntervalMESA_func glXSwapIntervalMESA;
GlxContext::GlxContext(::Display *display, GLXWindow window, GLXContext handle)
: OpenGlContext(false)
, m_display(display)
, m_window(window)
, m_handle(handle)
, m_shaderManager(std::make_unique<ShaderManager>())
, m_streamingBuffer(std::make_unique<GLVertexBuffer>(GLVertexBuffer::Stream))
, m_indexBuffer(std::make_unique<IndexBuffer>())
, m_glXSwapIntervalMESA((glXSwapIntervalMESA_func)getProcAddress("glXSwapIntervalMESA"))
{
glResolveFunctions(&getProcAddress);
initDebugOutput();
setShaderManager(m_shaderManager.get());
setStreamingBuffer(m_streamingBuffer.get());
setIndexBuffer(m_indexBuffer.get());
// It is not legal to not have a vertex array object bound in a core context
// to make code handling old and new OpenGL versions easier, bind a dummy vao that's used for everything
if (!isOpenGLES() && hasOpenglExtension(QByteArrayLiteral("GL_ARB_vertex_array_object"))) {
glGenVertexArrays(1, &m_vao);
glBindVertexArray(m_vao);
}
}
GlxContext::~GlxContext()
{
makeCurrent();
if (m_vao) {
glDeleteVertexArrays(1, &m_vao);
}
m_shaderManager.reset();
m_streamingBuffer.reset();
m_indexBuffer.reset();
glXDestroyContext(m_display, m_handle);
}
bool GlxContext::makeCurrent()
{
if (QOpenGLContext *context = QOpenGLContext::currentContext()) {
// Workaround to tell Qt that no QOpenGLContext is current
context->doneCurrent();
}
const bool ret = glXMakeCurrent(m_display, m_window, m_handle);
if (ret) {
s_currentContext = this;
}
return ret;
}
void GlxContext::doneCurrent() const
{
glXMakeCurrent(m_display, None, nullptr);
s_currentContext = nullptr;
}
void GlxContext::glXSwapIntervalMESA(unsigned int interval)
{
if (m_glXSwapIntervalMESA) {
m_glXSwapIntervalMESA(interval);
}
}
std::unique_ptr<GlxContext> GlxContext::create(GlxBackend *backend, GLXFBConfig fbconfig, GLXWindow glxWindow)
{
QOpenGLContext *qtGlobalShareContext = QOpenGLContext::globalShareContext();
GLXContext globalShareContext = nullptr;
if (qtGlobalShareContext) {
qDebug(KWIN_X11STANDALONE) << "Global share context format:" << qtGlobalShareContext->format();
const auto nativeHandle = qtGlobalShareContext->nativeInterface<QNativeInterface::QGLXContext>();
if (nativeHandle) {
globalShareContext = nativeHandle->nativeContext();
} else {
qCDebug(KWIN_X11STANDALONE) << "Invalid QOpenGLContext::globalShareContext()";
return nullptr;
}
}
if (!globalShareContext) {
qCWarning(KWIN_X11STANDALONE) << "QOpenGLContext::globalShareContext() is required";
return nullptr;
}
GLXContext handle = nullptr;
// Use glXCreateContextAttribsARB() when it's available
if (backend->hasExtension(QByteArrayLiteral("GLX_ARB_create_context"))) {
const bool have_robustness = backend->hasExtension(QByteArrayLiteral("GLX_ARB_create_context_robustness"));
const bool haveVideoMemoryPurge = backend->hasExtension(QByteArrayLiteral("GLX_NV_robustness_video_memory_purge"));
std::vector<GlxContextAttributeBuilder> candidates;
// core
if (have_robustness) {
if (haveVideoMemoryPurge) {
GlxContextAttributeBuilder purgeMemoryCore;
purgeMemoryCore.setVersion(3, 1);
purgeMemoryCore.setRobust(true);
purgeMemoryCore.setResetOnVideoMemoryPurge(true);
candidates.emplace_back(std::move(purgeMemoryCore));
}
GlxContextAttributeBuilder robustCore;
robustCore.setVersion(3, 1);
robustCore.setRobust(true);
candidates.emplace_back(std::move(robustCore));
}
GlxContextAttributeBuilder core;
core.setVersion(3, 1);
candidates.emplace_back(std::move(core));
// legacy
if (have_robustness) {
if (haveVideoMemoryPurge) {
GlxContextAttributeBuilder purgeMemoryLegacy;
purgeMemoryLegacy.setRobust(true);
purgeMemoryLegacy.setResetOnVideoMemoryPurge(true);
candidates.emplace_back(std::move(purgeMemoryLegacy));
}
GlxContextAttributeBuilder robustLegacy;
robustLegacy.setRobust(true);
candidates.emplace_back(std::move(robustLegacy));
}
GlxContextAttributeBuilder legacy;
legacy.setVersion(2, 1);
candidates.emplace_back(std::move(legacy));
for (auto it = candidates.begin(); it != candidates.end(); it++) {
const auto attribs = it->build();
handle = glXCreateContextAttribsARB(backend->display(), fbconfig, globalShareContext, true, attribs.data());
if (handle) {
qCDebug(KWIN_X11STANDALONE) << "Created GLX context with attributes:" << &(*it);
break;
}
}
}
if (!handle) {
handle = glXCreateNewContext(backend->display(), fbconfig, GLX_RGBA_TYPE, globalShareContext, true);
}
if (!handle) {
qCDebug(KWIN_X11STANDALONE) << "Failed to create an OpenGL context.";
return nullptr;
}
// KWin doesn't support indirect rendering
if (!glXIsDirect(backend->display(), handle)) {
return nullptr;
}
if (!glXMakeCurrent(backend->display(), glxWindow, handle)) {
glXDestroyContext(backend->display(), handle);
return nullptr;
}
auto ret = std::make_unique<GlxContext>(backend->display(), glxWindow, handle);
s_currentContext = ret.get();
if (!ret->checkSupported()) {
return nullptr;
}
return ret;
}
}

Some files were not shown because too many files have changed in this diff Show More