backends/wayland: Use linux dmabuf feedback to get main device
Currently, the render node is hardcoded. It works okay as long as the main device and the hardcoded node are the same. But it breaks on multi gpu setups where the render device is not /dev/dri/renderD128.
This commit is contained in:
parent
7db140d03b
commit
30ac125986
6 changed files with 250 additions and 26 deletions
|
@ -415,14 +415,6 @@ WaylandBackend::WaylandBackend(const WaylandBackendOptions &options, QObject *pa
|
|||
: OutputBackend(parent)
|
||||
, m_options(options)
|
||||
{
|
||||
char const *drm_render_node = "/dev/dri/renderD128";
|
||||
m_drmFileDescriptor = FileDescriptor(open(drm_render_node, O_RDWR | O_CLOEXEC));
|
||||
if (!m_drmFileDescriptor.isValid()) {
|
||||
qCWarning(KWIN_WAYLAND_BACKEND) << "Failed to open drm render node" << drm_render_node;
|
||||
m_gbmDevice = nullptr;
|
||||
return;
|
||||
}
|
||||
m_gbmDevice = gbm_create_device(m_drmFileDescriptor.get());
|
||||
}
|
||||
|
||||
WaylandBackend::~WaylandBackend()
|
||||
|
@ -448,6 +440,15 @@ bool WaylandBackend::initialize()
|
|||
return false;
|
||||
}
|
||||
|
||||
if (WaylandLinuxDmabufV1 *dmabuf = m_display->linuxDmabuf()) {
|
||||
m_drmFileDescriptor = FileDescriptor(open(dmabuf->mainDevice(), O_RDWR | O_CLOEXEC));
|
||||
if (m_drmFileDescriptor.isValid()) {
|
||||
m_gbmDevice = gbm_create_device(m_drmFileDescriptor.get());
|
||||
} else {
|
||||
qCWarning(KWIN_WAYLAND_BACKEND) << "Failed to open drm render node" << dmabuf->mainDevice();
|
||||
}
|
||||
}
|
||||
|
||||
createOutputs();
|
||||
|
||||
m_seat = std::make_unique<WaylandSeat>(m_display->seat(), this);
|
||||
|
|
|
@ -274,7 +274,7 @@ private:
|
|||
std::unique_ptr<DpmsInputEventFilter> m_dpmsFilter;
|
||||
bool m_pointerLockRequested = false;
|
||||
FileDescriptor m_drmFileDescriptor;
|
||||
gbm_device *m_gbmDevice;
|
||||
gbm_device *m_gbmDevice = nullptr;
|
||||
std::unique_ptr<EglDisplay> m_eglDisplay;
|
||||
std::map<GraphicsBuffer *, std::unique_ptr<WaylandBuffer>> m_buffers;
|
||||
};
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
*/
|
||||
|
||||
#include "wayland_display.h"
|
||||
#include "utils/memorymap.h"
|
||||
#include "wayland_logging.h"
|
||||
|
||||
#include <KWayland/Client/compositor.h>
|
||||
|
@ -24,8 +25,10 @@
|
|||
#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"
|
||||
|
@ -148,6 +151,126 @@ private:
|
|||
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, QVector<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;
|
||||
feedback->formatTable = MemoryMap{};
|
||||
}
|
||||
|
||||
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));
|
||||
|
@ -157,6 +280,8 @@ WaylandLinuxDmabufV1::WaylandLinuxDmabufV1(wl_registry *registry, uint32_t name,
|
|||
.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()
|
||||
|
@ -169,21 +294,24 @@ zwp_linux_dmabuf_v1 *WaylandLinuxDmabufV1::handle() const
|
|||
return m_dmabuf;
|
||||
}
|
||||
|
||||
QByteArray WaylandLinuxDmabufV1::mainDevice() const
|
||||
{
|
||||
return m_defaultFeedback->mainDevice;
|
||||
}
|
||||
|
||||
QHash<uint32_t, QVector<uint64_t>> WaylandLinuxDmabufV1::formats() const
|
||||
{
|
||||
return m_formats;
|
||||
return m_defaultFeedback->formats;
|
||||
}
|
||||
|
||||
void WaylandLinuxDmabufV1::format(void *data, struct zwp_linux_dmabuf_v1 *zwp_linux_dmabuf_v1, uint32_t format)
|
||||
{
|
||||
WaylandLinuxDmabufV1 *dmabuf = static_cast<WaylandLinuxDmabufV1 *>(data);
|
||||
dmabuf->m_formats[format].append(DRM_FORMAT_MOD_INVALID);
|
||||
// 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)
|
||||
{
|
||||
WaylandLinuxDmabufV1 *dmabuf = static_cast<WaylandLinuxDmabufV1 *>(data);
|
||||
dmabuf->m_formats[format].append((static_cast<uint64_t>(modifier_hi) << 32) | modifier_lo);
|
||||
// Not sent in v4 and onward.
|
||||
}
|
||||
|
||||
WaylandDisplay::WaylandDisplay()
|
||||
|
@ -324,7 +452,11 @@ void WaylandDisplay::registry_global(void *data, wl_registry *registry, uint32_t
|
|||
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) {
|
||||
display->m_linuxDmabuf = std::make_unique<WaylandLinuxDmabufV1>(registry, name, std::min(version, 3u));
|
||||
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));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -36,6 +36,7 @@ namespace Wayland
|
|||
{
|
||||
|
||||
class WaylandEventThread;
|
||||
class WaylandLinuxDmabufFeedbackV1;
|
||||
|
||||
class WaylandLinuxDmabufV1
|
||||
{
|
||||
|
@ -44,6 +45,7 @@ public:
|
|||
~WaylandLinuxDmabufV1();
|
||||
|
||||
zwp_linux_dmabuf_v1 *handle() const;
|
||||
QByteArray mainDevice() const;
|
||||
QHash<uint32_t, QVector<uint64_t>> formats() const;
|
||||
|
||||
private:
|
||||
|
@ -51,7 +53,7 @@ private:
|
|||
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;
|
||||
QHash<uint32_t, QVector<uint64_t>> m_formats;
|
||||
std::unique_ptr<WaylandLinuxDmabufFeedbackV1> m_defaultFeedback;
|
||||
};
|
||||
|
||||
class WaylandDisplay : public QObject
|
||||
|
|
|
@ -142,13 +142,21 @@ std::optional<OutputLayerBeginFrameInfo> WaylandEglPrimaryLayer::beginFrame()
|
|||
|
||||
const QSize nativeSize = m_waylandOutput->modeSize();
|
||||
if (!m_swapchain || m_swapchain->size() != nativeSize) {
|
||||
const WaylandLinuxDmabufV1 *dmabuf = m_backend->backend()->display()->linuxDmabuf();
|
||||
const uint32_t format = DRM_FORMAT_XRGB8888;
|
||||
if (!dmabuf->formats().contains(format)) {
|
||||
qCCritical(KWIN_WAYLAND_BACKEND) << "DRM_FORMAT_XRGB8888 is unsupported";
|
||||
const QHash<uint32_t, QVector<uint64_t>> formatTable = m_backend->backend()->display()->linuxDmabuf()->formats();
|
||||
uint32_t format = DRM_FORMAT_INVALID;
|
||||
QVector<uint64_t> modifiers;
|
||||
for (const uint32_t &candidateFormat : {DRM_FORMAT_XRGB2101010, DRM_FORMAT_XRGB8888}) {
|
||||
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;
|
||||
}
|
||||
const QVector<uint64_t> modifiers = dmabuf->formats().value(format);
|
||||
m_swapchain = std::make_unique<WaylandEglLayerSwapchain>(nativeSize, format, modifiers, m_backend);
|
||||
}
|
||||
|
||||
|
@ -213,13 +221,21 @@ std::optional<OutputLayerBeginFrameInfo> WaylandEglCursorLayer::beginFrame()
|
|||
const auto tmp = size().expandedTo(QSize(64, 64));
|
||||
const QSize bufferSize(std::ceil(tmp.width()), std::ceil(tmp.height()));
|
||||
if (!m_swapchain || m_swapchain->size() != bufferSize) {
|
||||
const WaylandLinuxDmabufV1 *dmabuf = m_backend->backend()->display()->linuxDmabuf();
|
||||
const uint32_t format = DRM_FORMAT_ARGB8888;
|
||||
if (!dmabuf->formats().contains(format)) {
|
||||
qCCritical(KWIN_WAYLAND_BACKEND) << "DRM_FORMAT_ARGB8888 is unsupported";
|
||||
const QHash<uint32_t, QVector<uint64_t>> formatTable = m_backend->backend()->display()->linuxDmabuf()->formats();
|
||||
uint32_t format = DRM_FORMAT_INVALID;
|
||||
QVector<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;
|
||||
}
|
||||
const QVector<uint64_t> modifiers = dmabuf->formats().value(format);
|
||||
m_swapchain = std::make_unique<WaylandEglLayerSwapchain>(bufferSize, format, modifiers, m_backend);
|
||||
}
|
||||
|
||||
|
|
73
src/utils/memorymap.h
Normal file
73
src/utils/memorymap.h
Normal file
|
@ -0,0 +1,73 @@
|
|||
/*
|
||||
SPDX-FileCopyrightText: 2023 Vlad Zahorodnii <vlad.zahorodnii@kde.org>
|
||||
|
||||
SPDX-License-Identifier: GPL-2.0-or-later
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <sys/mman.h>
|
||||
#include <utility>
|
||||
|
||||
namespace KWin
|
||||
{
|
||||
|
||||
class MemoryMap
|
||||
{
|
||||
public:
|
||||
MemoryMap()
|
||||
: m_data(MAP_FAILED)
|
||||
, m_size(0)
|
||||
{
|
||||
}
|
||||
|
||||
MemoryMap(int size, int prot, int flags, int fd, off_t offset)
|
||||
: m_data(mmap(nullptr, size, prot, flags, fd, offset))
|
||||
, m_size(size)
|
||||
{
|
||||
}
|
||||
|
||||
MemoryMap(MemoryMap &&other)
|
||||
: m_data(std::exchange(other.m_data, MAP_FAILED))
|
||||
, m_size(std::exchange(other.m_size, 0))
|
||||
{
|
||||
}
|
||||
|
||||
~MemoryMap()
|
||||
{
|
||||
if (m_data != MAP_FAILED) {
|
||||
munmap(m_data, m_size);
|
||||
}
|
||||
}
|
||||
|
||||
MemoryMap &operator=(MemoryMap &&other)
|
||||
{
|
||||
if (m_data != MAP_FAILED) {
|
||||
munmap(m_data, m_size);
|
||||
}
|
||||
m_data = std::exchange(other.m_data, MAP_FAILED);
|
||||
m_size = std::exchange(other.m_size, 0);
|
||||
return *this;
|
||||
}
|
||||
|
||||
inline bool isValid() const
|
||||
{
|
||||
return m_data != MAP_FAILED;
|
||||
}
|
||||
|
||||
inline void *data() const
|
||||
{
|
||||
return m_data;
|
||||
}
|
||||
|
||||
inline int size() const
|
||||
{
|
||||
return m_size;
|
||||
}
|
||||
|
||||
private:
|
||||
void *m_data;
|
||||
int m_size;
|
||||
};
|
||||
|
||||
} // namespace KWin
|
Loading…
Reference in a new issue