From 30ac12598610b4a21ea7ec3fe4c83e00e8b61581 Mon Sep 17 00:00:00 2001 From: Vlad Zahorodnii Date: Wed, 31 May 2023 09:52:52 +0300 Subject: [PATCH] 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. --- src/backends/wayland/wayland_backend.cpp | 17 +-- src/backends/wayland/wayland_backend.h | 2 +- src/backends/wayland/wayland_display.cpp | 144 ++++++++++++++++++- src/backends/wayland/wayland_display.h | 4 +- src/backends/wayland/wayland_egl_backend.cpp | 36 +++-- src/utils/memorymap.h | 73 ++++++++++ 6 files changed, 250 insertions(+), 26 deletions(-) create mode 100644 src/utils/memorymap.h diff --git a/src/backends/wayland/wayland_backend.cpp b/src/backends/wayland/wayland_backend.cpp index 542ad37f3b..7958127537 100644 --- a/src/backends/wayland/wayland_backend.cpp +++ b/src/backends/wayland/wayland_backend.cpp @@ -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(m_display->seat(), this); diff --git a/src/backends/wayland/wayland_backend.h b/src/backends/wayland/wayland_backend.h index cf64ed284f..3c97fc621c 100644 --- a/src/backends/wayland/wayland_backend.h +++ b/src/backends/wayland/wayland_backend.h @@ -274,7 +274,7 @@ private: std::unique_ptr m_dpmsFilter; bool m_pointerLockRequested = false; FileDescriptor m_drmFileDescriptor; - gbm_device *m_gbmDevice; + gbm_device *m_gbmDevice = nullptr; std::unique_ptr m_eglDisplay; std::map> m_buffers; }; diff --git a/src/backends/wayland/wayland_display.cpp b/src/backends/wayland/wayland_display.cpp index 27d0fc3110..002ff3d217 100644 --- a/src/backends/wayland/wayland_display.cpp +++ b/src/backends/wayland/wayland_display.cpp @@ -5,6 +5,7 @@ */ #include "wayland_display.h" +#include "utils/memorymap.h" #include "wayland_logging.h" #include @@ -24,8 +25,10 @@ #include #include #include +#include #include #include +#include // 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> 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(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(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(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(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(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(feedback->formatTable.data()); + for (const uint16_t &index : std::span(static_cast(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(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(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> 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(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(data); - dmabuf->m_formats[format].append((static_cast(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(); display->m_xdgDecorationManager->setup(static_cast(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(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(registry, name, std::min(version, 4u)); } } diff --git a/src/backends/wayland/wayland_display.h b/src/backends/wayland/wayland_display.h index 2206aef34d..83b08043ba 100644 --- a/src/backends/wayland/wayland_display.h +++ b/src/backends/wayland/wayland_display.h @@ -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> 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> m_formats; + std::unique_ptr m_defaultFeedback; }; class WaylandDisplay : public QObject diff --git a/src/backends/wayland/wayland_egl_backend.cpp b/src/backends/wayland/wayland_egl_backend.cpp index fd2c0a4034..8c127f411e 100644 --- a/src/backends/wayland/wayland_egl_backend.cpp +++ b/src/backends/wayland/wayland_egl_backend.cpp @@ -142,13 +142,21 @@ std::optional 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> formatTable = m_backend->backend()->display()->linuxDmabuf()->formats(); + uint32_t format = DRM_FORMAT_INVALID; + QVector 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 modifiers = dmabuf->formats().value(format); m_swapchain = std::make_unique(nativeSize, format, modifiers, m_backend); } @@ -213,13 +221,21 @@ std::optional 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> formatTable = m_backend->backend()->display()->linuxDmabuf()->formats(); + uint32_t format = DRM_FORMAT_INVALID; + QVector 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 modifiers = dmabuf->formats().value(format); m_swapchain = std::make_unique(bufferSize, format, modifiers, m_backend); } diff --git a/src/utils/memorymap.h b/src/utils/memorymap.h new file mode 100644 index 0000000000..3504159e65 --- /dev/null +++ b/src/utils/memorymap.h @@ -0,0 +1,73 @@ +/* + SPDX-FileCopyrightText: 2023 Vlad Zahorodnii + + SPDX-License-Identifier: GPL-2.0-or-later +*/ + +#pragma once + +#include +#include + +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