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