wayland/screencopy: add screencopy

This commit is contained in:
outfoxxed 2025-01-14 04:43:05 -08:00
parent 918dd2392d
commit cd429142a4
Signed by untrusted user: outfoxxed
GPG key ID: 4C88A185FB89301E
37 changed files with 3149 additions and 3 deletions

View file

@ -0,0 +1,18 @@
find_package(PkgConfig REQUIRED)
pkg_check_modules(dmabuf-deps REQUIRED IMPORTED_TARGET libdrm gbm egl)
qt_add_library(quickshell-wayland-buffer STATIC
manager.cpp
dmabuf.cpp
shm.cpp
)
wl_proto(wlp-linux-dmabuf linux-dmabuf-v1 "${WAYLAND_PROTOCOLS}/stable/linux-dmabuf")
target_link_libraries(quickshell-wayland-buffer PRIVATE
Qt::Quick Qt::WaylandClient Qt::WaylandClientPrivate wayland-client
PkgConfig::dmabuf-deps
wlp-linux-dmabuf
)
qs_pch(quickshell-wayland-buffer SET large)

View file

@ -0,0 +1,659 @@
#include "dmabuf.hpp"
#include <algorithm>
#include <array>
#include <csignal>
#include <cstdint>
#include <memory>
#include <string>
#include <utility>
#include <EGL/egl.h>
#include <EGL/eglext.h>
#include <GL/gl.h>
#include <GLES3/gl32.h>
#include <fcntl.h>
#include <gbm.h>
#include <libdrm/drm_fourcc.h>
#include <qcontainerfwd.h>
#include <qdebug.h>
#include <qlist.h>
#include <qlogging.h>
#include <qloggingcategory.h>
#include <qopenglcontext.h>
#include <qopenglcontext_platform.h>
#include <qpair.h>
#include <qquickwindow.h>
#include <qscopedpointer.h>
#include <qsgtexture_platform.h>
#include <qtclasshelpermacros.h>
#include <qwayland-linux-dmabuf-v1.h>
#include <qwaylandclientextension.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <unistd.h>
#include <wayland-client-protocol.h>
#include <wayland-linux-dmabuf-v1-client-protocol.h>
#include <wayland-util.h>
#include <xf86drm.h>
#include "../../core/stacklist.hpp"
#include "manager.hpp"
#include "manager_p.hpp"
namespace qs::wayland::buffer::dmabuf {
namespace {
Q_LOGGING_CATEGORY(logDmabuf, "quickshell.wayland.buffer.dmabuf", QtWarningMsg);
LinuxDmabufManager* MANAGER = nullptr; // NOLINT
class FourCCStr {
public:
explicit FourCCStr(uint32_t code)
: chars(
{static_cast<char>(code >> 0 & 0xff),
static_cast<char>(code >> 8 & 0xff),
static_cast<char>(code >> 16 & 0xff),
static_cast<char>(code >> 24 & 0xff),
'\0'}
) {
for (auto i = 3; i != 0; i--) {
if (chars[i] == ' ') chars[i] = '\0';
else break;
}
}
[[nodiscard]] const char* cStr() const { return this->chars.data(); }
private:
std::array<char, 5> chars {};
};
class FourCCModStr {
public:
explicit FourCCModStr(uint64_t code): drmStr(drmGetFormatModifierName(code)) {}
~FourCCModStr() {
if (this->drmStr) drmFree(this->drmStr);
}
Q_DISABLE_COPY_MOVE(FourCCModStr);
[[nodiscard]] const char* cStr() const { return this->drmStr; }
private:
char* drmStr;
};
QDebug& operator<<(QDebug& debug, const FourCCStr& fourcc) {
debug << fourcc.cStr();
return debug;
}
QDebug& operator<<(QDebug& debug, const FourCCModStr& fourcc) {
debug << fourcc.cStr();
return debug;
}
} // namespace
QDebug& operator<<(QDebug& debug, const WlDmaBuffer* buffer) {
auto saver = QDebugStateSaver(debug);
debug.nospace();
if (buffer) {
debug << "WlDmaBuffer(" << static_cast<const void*>(buffer) << ", size=" << buffer->width << 'x'
<< buffer->height << ", format=" << FourCCStr(buffer->format) << ", modifier=`"
<< FourCCModStr(buffer->modifier) << "`)";
} else {
debug << "WlDmaBuffer(0x0)";
}
return debug;
}
GbmDeviceHandle::~GbmDeviceHandle() {
if (device) {
MANAGER->unrefGbmDevice(this->device);
}
}
// This will definitely backfire later
void LinuxDmabufFormatSelection::ensureSorted() {
if (this->sorted) return;
auto beginIter = this->formats.begin();
auto xrgbIter = std::ranges::find_if(this->formats, [](const auto& format) {
return format.first == DRM_FORMAT_XRGB8888;
});
if (xrgbIter != this->formats.end()) {
std::swap(*beginIter, *xrgbIter);
++beginIter;
}
auto argbIter = std::ranges::find_if(this->formats, [](const auto& format) {
return format.first == DRM_FORMAT_ARGB8888;
});
if (argbIter != this->formats.end()) std::swap(*beginIter, *argbIter);
this->sorted = true;
}
LinuxDmabufFeedback::LinuxDmabufFeedback(::zwp_linux_dmabuf_feedback_v1* feedback)
: zwp_linux_dmabuf_feedback_v1(feedback) {}
LinuxDmabufFeedback::~LinuxDmabufFeedback() { this->destroy(); }
void LinuxDmabufFeedback::zwp_linux_dmabuf_feedback_v1_format_table(int32_t fd, uint32_t size) {
this->formatTableSize = size;
this->formatTable = mmap(nullptr, size, PROT_READ, MAP_PRIVATE, fd, 0);
if (this->formatTable == MAP_FAILED) {
this->formatTable = nullptr;
qCFatal(logDmabuf) << "Failed to mmap format table.";
}
qCDebug(logDmabuf) << "Got format table";
}
void LinuxDmabufFeedback::zwp_linux_dmabuf_feedback_v1_main_device(wl_array* device) {
if (device->size != sizeof(dev_t)) {
qCFatal(logDmabuf) << "The size of dev_t used by the compositor and quickshell is mismatched. "
"Try recompiling both.";
}
this->mainDevice = *reinterpret_cast<dev_t*>(device->data);
qCDebug(logDmabuf) << "Got main device id" << this->mainDevice;
}
void LinuxDmabufFeedback::zwp_linux_dmabuf_feedback_v1_tranche_target_device(wl_array* device) {
if (device->size != sizeof(dev_t)) {
qCFatal(logDmabuf) << "The size of dev_t used by the compositor and quickshell is mismatched. "
"Try recompiling both.";
}
auto& tranche = this->tranches.emplaceBack();
tranche.device = *reinterpret_cast<dev_t*>(device->data);
qCDebug(logDmabuf) << "Got target device id" << this->mainDevice;
}
void LinuxDmabufFeedback::zwp_linux_dmabuf_feedback_v1_tranche_flags(uint32_t flags) {
this->tranches.back().flags = flags;
qCDebug(logDmabuf) << "Got target device flags" << flags;
}
void LinuxDmabufFeedback::zwp_linux_dmabuf_feedback_v1_tranche_formats(wl_array* indices) {
struct FormatTableEntry {
uint32_t format;
uint32_t padding;
uint64_t modifier;
};
static_assert(sizeof(FormatTableEntry) == 16, "Format table entry was not packed to 16 bytes.");
if (this->formatTable == nullptr) {
qCFatal(logDmabuf) << "Received tranche formats before format table.";
}
auto& tranche = this->tranches.back();
auto* table = reinterpret_cast<FormatTableEntry*>(this->formatTable);
auto* indexTable = reinterpret_cast<uint16_t*>(indices->data);
auto indexTableLength = indices->size / sizeof(uint16_t);
uint32_t lastFormat = 0;
LinuxDmabufModifiers* lastModifiers = nullptr;
LinuxDmabufModifiers* modifiers = nullptr;
for (uint16_t ti = 0; ti != indexTableLength; ++ti) {
auto i = indexTable[ti]; // NOLINT
const auto& entry = table[i]; // NOLINT
// Compositors usually send a single format's modifiers as a block.
if (!modifiers || entry.format != lastFormat) {
// We can often share modifier lists between formats
if (lastModifiers && modifiers->modifiers == lastModifiers->modifiers) {
// avoids storing a second list
modifiers->modifiers = lastModifiers->modifiers;
}
lastFormat = entry.format;
lastModifiers = modifiers;
auto modifiersIter = std::ranges::find_if(tranche.formats.formats, [&](const auto& pair) {
return pair.first == entry.format;
});
if (modifiersIter == tranche.formats.formats.end()) {
tranche.formats.formats.push(qMakePair(entry.format, LinuxDmabufModifiers()));
modifiers = &(--tranche.formats.formats.end())->second;
} else {
modifiers = &modifiersIter->second;
}
}
if (entry.modifier == DRM_FORMAT_MOD_INVALID) {
modifiers->implicit = true;
} else {
modifiers->modifiers.push(entry.modifier);
}
}
if (lastModifiers && modifiers && modifiers->modifiers == lastModifiers->modifiers) {
modifiers->modifiers = lastModifiers->modifiers;
}
}
void LinuxDmabufFeedback::zwp_linux_dmabuf_feedback_v1_tranche_done() {
qCDebug(logDmabuf) << "Got tranche end.";
}
void LinuxDmabufFeedback::zwp_linux_dmabuf_feedback_v1_done() {
qCDebug(logDmabuf) << "Got feedback done.";
if (this->formatTable) {
munmap(this->formatTable, this->formatTableSize);
this->formatTable = nullptr;
}
if (logDmabuf().isDebugEnabled()) {
qCDebug(logDmabuf) << "Dmabuf tranches:";
for (auto& tranche: this->tranches) {
qCDebug(logDmabuf) << " Tranche on device" << tranche.device;
// will be sorted on first use otherwise
tranche.formats.ensureSorted();
for (auto& [format, modifiers]: tranche.formats.formats) {
qCDebug(logDmabuf) << " Format" << FourCCStr(format);
if (modifiers.implicit) {
qCDebug(logDmabuf) << " Implicit Modifier";
}
for (const auto& modifier: modifiers.modifiers) {
qCDebug(logDmabuf) << " Explicit Modifier" << FourCCModStr(modifier);
}
}
}
}
// Copy tranches to the manager. If the compositor ever updates
// our tranches, we'll start from a clean slate.
MANAGER->tranches = this->tranches;
this->tranches.clear();
MANAGER->feedbackDone();
}
LinuxDmabufManager::LinuxDmabufManager(WlBufferManagerPrivate* manager)
: QWaylandClientExtensionTemplate(5)
, manager(manager) {
MANAGER = this;
this->initialize();
if (this->isActive()) {
qCDebug(logDmabuf) << "Requesting default dmabuf feedback...";
new LinuxDmabufFeedback(this->get_default_feedback());
}
}
void LinuxDmabufManager::feedbackDone() { this->manager->dmabufReady(); }
GbmDeviceHandle LinuxDmabufManager::getGbmDevice(dev_t handle) {
struct DrmFree {
static void cleanup(drmDevice* d) { drmFreeDevice(&d); }
};
std::string renderNodeStorage;
std::string* renderNode = nullptr;
auto sharedDevice = std::ranges::find_if(this->gbmDevices, [&](const SharedGbmDevice& d) {
return d.handle == handle;
});
if (sharedDevice != this->gbmDevices.end()) {
renderNode = &sharedDevice->renderNode;
} else {
drmDevice* drmDevPtr = nullptr;
if (auto error = drmGetDeviceFromDevId(handle, 0, &drmDevPtr); error != 0) {
qCWarning(logDmabuf) << "Failed to get drm device information from handle:"
<< qt_error_string(error);
return nullptr;
}
auto drmDev = QScopedPointer<drmDevice, DrmFree>(drmDevPtr);
if (!(drmDev->available_nodes & (1 << DRM_NODE_RENDER))) {
qCDebug(logDmabuf) << "Cannot create GBM device: DRM device does not have render node.";
return nullptr;
}
renderNodeStorage = drmDev->nodes[DRM_NODE_RENDER]; // NOLINT
renderNode = &renderNodeStorage;
sharedDevice = std::ranges::find_if(this->gbmDevices, [&](const SharedGbmDevice& d) {
return d.renderNode == renderNodeStorage;
});
}
if (sharedDevice != this->gbmDevices.end()) {
qCDebug(logDmabuf) << "Used existing GBM device on render node" << *renderNode;
++sharedDevice->refcount;
return sharedDevice->device;
} else {
auto fd = open(renderNode->c_str(), O_RDWR | O_CLOEXEC);
if (fd < 0) {
qCDebug(logDmabuf) << "Could not open render node" << *renderNode << ":"
<< qt_error_string(fd);
return nullptr;
}
auto* device = gbm_create_device(fd);
if (!device) {
qCDebug(logDmabuf) << "Failed to create GBM device from render node" << *renderNode;
close(fd);
return nullptr;
}
qCDebug(logDmabuf) << "Created GBM device on render node" << *renderNode;
this->gbmDevices.push_back({
.handle = handle,
.renderNode = std::move(renderNodeStorage),
.device = device,
.refcount = 1,
});
return device;
}
}
void LinuxDmabufManager::unrefGbmDevice(gbm_device* device) {
auto iter = std::ranges::find_if(this->gbmDevices, [device](const SharedGbmDevice& d) {
return d.device == device;
});
if (iter == this->gbmDevices.end()) return;
qCDebug(logDmabuf) << "Lost reference to GBM device" << device;
if (--iter->refcount == 0) {
auto fd = gbm_device_get_fd(iter->device);
gbm_device_destroy(iter->device);
close(fd);
this->gbmDevices.erase(iter);
qCDebug(logDmabuf) << "Destroyed GBM device" << device;
}
}
GbmDeviceHandle LinuxDmabufManager::dupHandle(const GbmDeviceHandle& handle) {
if (!handle) return GbmDeviceHandle();
auto iter = std::ranges::find_if(this->gbmDevices, [&handle](const SharedGbmDevice& d) {
return d.device == *handle;
});
if (iter == this->gbmDevices.end()) return GbmDeviceHandle();
qCDebug(logDmabuf) << "Duplicated GBM device handle" << *handle;
++iter->refcount;
return GbmDeviceHandle(*handle);
}
WlBuffer* LinuxDmabufManager::createDmabuf(const WlBufferRequest& request) {
for (auto& tranche: this->tranches) {
if (request.dmabuf.device != 0 && tranche.device != request.dmabuf.device) {
continue;
}
LinuxDmabufFormatSelection formats;
for (const auto& format: request.dmabuf.formats) {
if (!format.modifiers.isEmpty()) {
formats.formats.push(
qMakePair(format.format, LinuxDmabufModifiers {.modifiers = format.modifiers})
);
} else {
for (const auto& trancheFormat: tranche.formats.formats) {
if (trancheFormat.first == format.format) {
formats.formats.push(trancheFormat);
}
}
}
}
if (formats.formats.isEmpty()) continue;
formats.ensureSorted();
auto gbmDevice = this->getGbmDevice(tranche.device);
if (!gbmDevice) {
qCWarning(logDmabuf) << "Hit unusable tranche device while trying to create dmabuf.";
continue;
}
for (const auto& [format, modifiers]: formats.formats) {
if (auto* buf =
this->createDmabuf(gbmDevice, format, modifiers, request.width, request.height))
{
return buf;
}
}
}
qCWarning(logDmabuf) << "Unable to create dmabuf for request: No matching formats.";
return nullptr;
}
WlBuffer* LinuxDmabufManager::createDmabuf(
GbmDeviceHandle& device,
uint32_t format,
const LinuxDmabufModifiers& modifiers,
uint32_t width,
uint32_t height
) {
auto buffer = std::unique_ptr<WlDmaBuffer>(new WlDmaBuffer());
auto& bo = buffer->bo;
const uint32_t flags = GBM_BO_USE_RENDERING;
if (modifiers.modifiers.isEmpty()) {
if (!modifiers.implicit) {
qCritical(logDmabuf
) << "Failed to create gbm_bo: format supports no implicit OR explicit modifiers.";
return nullptr;
}
qCDebug(logDmabuf) << "Creating gbm_bo without modifiers...";
bo = gbm_bo_create(*device, width, height, format, flags);
} else {
qCDebug(logDmabuf) << "Creating gbm_bo with modifiers...";
STACKLIST_VLA_VIEW(uint64_t, modifiers.modifiers, modifiersData);
bo = gbm_bo_create_with_modifiers2(
*device,
width,
height,
format,
modifiersData,
modifiers.modifiers.length(),
flags
);
}
if (!bo) {
qCritical(logDmabuf) << "Failed to create gbm_bo.";
return nullptr;
}
buffer->planeCount = gbm_bo_get_plane_count(bo);
buffer->planes = new WlDmaBuffer::Plane[buffer->planeCount]();
buffer->modifier = gbm_bo_get_modifier(bo);
auto params = QtWayland::zwp_linux_buffer_params_v1(this->create_params());
for (auto i = 0; i < buffer->planeCount; ++i) {
auto& plane = buffer->planes[i]; // NOLINT
plane.fd = gbm_bo_get_fd_for_plane(bo, i);
if (plane.fd < 0) {
qCritical(logDmabuf) << "Failed to get gbm_bo fd for plane" << i << qt_error_string(plane.fd);
params.destroy();
gbm_bo_destroy(bo);
return nullptr;
}
plane.stride = gbm_bo_get_stride_for_plane(bo, i);
plane.offset = gbm_bo_get_offset(bo, i);
params.add(
plane.fd,
i,
plane.offset,
plane.stride,
buffer->modifier >> 32,
buffer->modifier & 0xffffffff
);
}
buffer->mBuffer =
params.create_immed(static_cast<int32_t>(width), static_cast<int32_t>(height), format, 0);
params.destroy();
buffer->device = this->dupHandle(device);
buffer->width = width;
buffer->height = height;
buffer->format = format;
qCDebug(logDmabuf) << "Created dmabuf" << buffer.get();
return buffer.release();
}
WlDmaBuffer::WlDmaBuffer(WlDmaBuffer&& other) noexcept
: device(std::move(other.device))
, bo(other.bo)
, mBuffer(other.mBuffer)
, planes(other.planes) {
other.mBuffer = nullptr;
other.bo = nullptr;
other.planeCount = 0;
}
WlDmaBuffer& WlDmaBuffer::operator=(WlDmaBuffer&& other) noexcept {
this->~WlDmaBuffer();
new (this) WlDmaBuffer(std::move(other));
return *this;
}
WlDmaBuffer::~WlDmaBuffer() {
if (this->mBuffer) {
wl_buffer_destroy(this->mBuffer);
}
if (this->bo) {
gbm_bo_destroy(this->bo);
qCDebug(logDmabuf) << "Destroyed" << this << "freeing bo" << this->bo;
}
for (auto i = 0; i < this->planeCount; ++i) {
const auto& plane = this->planes[i]; // NOLINT
if (plane.fd) close(plane.fd);
}
delete[] this->planes;
}
bool WlDmaBuffer::isCompatible(const WlBufferRequest& request) const {
if (request.width != this->width || request.height != this->height) return false;
auto matchingFormat = std::ranges::find_if(request.dmabuf.formats, [&](const auto& format) {
return format.format == this->format
&& (format.modifiers.isEmpty()
|| std::ranges::find(format.modifiers, this->modifier) != format.modifiers.end());
});
return matchingFormat != request.dmabuf.formats.end();
}
WlBufferQSGTexture* WlDmaBuffer::createQsgTexture(QQuickWindow* window) const {
static auto* glEGLImageTargetTexture2DOES = []() {
auto* fn = reinterpret_cast<PFNGLEGLIMAGETARGETTEXTURE2DOESPROC>(
eglGetProcAddress("glEGLImageTargetTexture2DOES")
);
if (!fn) {
qCFatal(logDmabuf) << "Failed to create QSG texture from WlDmaBuffer: "
"glEGLImageTargetTexture2DOES is missing.";
}
return fn;
}();
auto* context = QOpenGLContext::currentContext();
if (!context) {
qCFatal(logDmabuf) << "Failed to create QSG texture from WlDmaBuffer: No GL context.";
}
auto* qEglContext = context->nativeInterface<QNativeInterface::QEGLContext>();
if (!qEglContext) {
qCFatal(logDmabuf) << "Failed to create QSG texture from WlDmaBuffer: No EGL context.";
}
auto* display = qEglContext->display();
// clang-format off
auto attribs = std::array<EGLAttrib, 6 * 2 + 1> {
EGL_WIDTH, this->width,
EGL_HEIGHT, this->height,
EGL_LINUX_DRM_FOURCC_EXT, this->format,
EGL_DMA_BUF_PLANE0_FD_EXT, this->planes[0].fd, // NOLINT
EGL_DMA_BUF_PLANE0_OFFSET_EXT, this->planes[0].offset, // NOLINT
EGL_DMA_BUF_PLANE0_PITCH_EXT, this->planes[0].stride, // NOLINT
EGL_NONE
};
// clang-format on
auto* eglImage =
eglCreateImage(display, EGL_NO_CONTEXT, EGL_LINUX_DMA_BUF_EXT, nullptr, attribs.data());
if (eglImage == EGL_NO_IMAGE) {
qFatal() << "failed to make egl image" << eglGetError();
return nullptr;
}
window->beginExternalCommands();
GLuint glTexture = 0;
glGenTextures(1, &glTexture);
glBindTexture(GL_TEXTURE_2D, glTexture);
glEGLImageTargetTexture2DOES(GL_TEXTURE_2D, eglImage);
glBindTexture(GL_TEXTURE_2D, 0);
window->endExternalCommands();
auto* qsgTexture = QNativeInterface::QSGOpenGLTexture::fromNative(
glTexture,
window,
QSize(static_cast<int>(this->width), static_cast<int>(this->height))
);
auto* tex = new WlDmaBufferQSGTexture(eglImage, glTexture, qsgTexture);
qCDebug(logDmabuf) << "Created WlDmaBufferQSGTexture" << tex << "from" << this;
return tex;
}
WlDmaBufferQSGTexture::~WlDmaBufferQSGTexture() {
auto* context = QOpenGLContext::currentContext();
auto* display = context->nativeInterface<QNativeInterface::QEGLContext>()->display();
if (this->glTexture) glDeleteTextures(1, &this->glTexture);
if (this->eglImage) eglDestroyImage(display, this->eglImage);
delete this->qsgTexture;
qCDebug(logDmabuf) << "WlDmaBufferQSGTexture" << this << "destroyed.";
}
} // namespace qs::wayland::buffer::dmabuf

View file

@ -0,0 +1,195 @@
#pragma once
#include <cstdint>
#include <EGL/egl.h>
#include <gbm.h>
#include <qcontainerfwd.h>
#include <qhash.h>
#include <qlist.h>
#include <qquickwindow.h>
#include <qsgtexture.h>
#include <qsize.h>
#include <qtclasshelpermacros.h>
#include <qtypes.h>
#include <qwayland-linux-dmabuf-v1.h>
#include <qwaylandclientextension.h>
#include <sys/types.h>
#include <wayland-linux-dmabuf-v1-client-protocol.h>
#include <wayland-util.h>
#include "manager.hpp"
#include "qsg.hpp"
namespace qs::wayland::buffer {
class WlBufferManagerPrivate;
}
namespace qs::wayland::buffer::dmabuf {
class LinuxDmabufManager;
class GbmDeviceHandle {
public:
GbmDeviceHandle() = default;
GbmDeviceHandle(gbm_device* device): device(device) {}
GbmDeviceHandle(GbmDeviceHandle&& other) noexcept: device(other.device) {
other.device = nullptr;
}
~GbmDeviceHandle();
Q_DISABLE_COPY(GbmDeviceHandle);
GbmDeviceHandle& operator=(GbmDeviceHandle&& other) noexcept {
this->device = other.device;
other.device = nullptr;
return *this;
}
[[nodiscard]] gbm_device* operator*() const { return this->device; }
[[nodiscard]] operator bool() const { return this->device; }
private:
gbm_device* device = nullptr;
};
class WlDmaBufferQSGTexture: public WlBufferQSGTexture {
public:
~WlDmaBufferQSGTexture() override;
Q_DISABLE_COPY_MOVE(WlDmaBufferQSGTexture);
[[nodiscard]] QSGTexture* texture() const override { return this->qsgTexture; }
private:
WlDmaBufferQSGTexture(EGLImage eglImage, GLuint glTexture, QSGTexture* qsgTexture)
: eglImage(eglImage)
, glTexture(glTexture)
, qsgTexture(qsgTexture) {}
EGLImage eglImage;
GLuint glTexture;
QSGTexture* qsgTexture;
friend class WlDmaBuffer;
};
class WlDmaBuffer: public WlBuffer {
public:
~WlDmaBuffer() override;
Q_DISABLE_COPY(WlDmaBuffer);
WlDmaBuffer(WlDmaBuffer&& other) noexcept;
WlDmaBuffer& operator=(WlDmaBuffer&& other) noexcept;
[[nodiscard]] wl_buffer* buffer() const override { return this->mBuffer; }
[[nodiscard]] QSize size() const override {
return QSize(static_cast<int>(this->width), static_cast<int>(this->height));
}
[[nodiscard]] bool isCompatible(const WlBufferRequest& request) const override;
[[nodiscard]] WlBufferQSGTexture* createQsgTexture(QQuickWindow* window) const override;
private:
WlDmaBuffer() noexcept = default;
struct Plane {
int fd = 0;
uint32_t offset = 0;
uint32_t stride = 0;
};
GbmDeviceHandle device;
gbm_bo* bo = nullptr;
wl_buffer* mBuffer = nullptr;
int planeCount = 0;
Plane* planes = nullptr;
uint32_t format = 0;
uint64_t modifier = 0;
uint32_t width = 0;
uint32_t height = 0;
friend class LinuxDmabufManager;
friend QDebug& operator<<(QDebug& debug, const WlDmaBuffer* buffer);
};
QDebug& operator<<(QDebug& debug, const WlDmaBuffer* buffer);
struct LinuxDmabufModifiers {
StackList<uint64_t, 10> modifiers;
bool implicit = false;
};
struct LinuxDmabufFormatSelection {
bool sorted = false;
StackList<QPair<uint32_t, LinuxDmabufModifiers>, 2> formats;
void ensureSorted();
};
struct LinuxDmabufTranche {
dev_t device = 0;
uint32_t flags = 0;
LinuxDmabufFormatSelection formats;
};
class LinuxDmabufFeedback: public QtWayland::zwp_linux_dmabuf_feedback_v1 {
public:
explicit LinuxDmabufFeedback(::zwp_linux_dmabuf_feedback_v1* feedback);
~LinuxDmabufFeedback() override;
Q_DISABLE_COPY_MOVE(LinuxDmabufFeedback);
protected:
void zwp_linux_dmabuf_feedback_v1_main_device(wl_array* device) override;
void zwp_linux_dmabuf_feedback_v1_format_table(int32_t fd, uint32_t size) override;
void zwp_linux_dmabuf_feedback_v1_tranche_target_device(wl_array* device) override;
void zwp_linux_dmabuf_feedback_v1_tranche_flags(uint32_t flags) override;
void zwp_linux_dmabuf_feedback_v1_tranche_formats(wl_array* indices) override;
void zwp_linux_dmabuf_feedback_v1_tranche_done() override;
void zwp_linux_dmabuf_feedback_v1_done() override;
private:
dev_t mainDevice = 0;
QList<LinuxDmabufTranche> tranches;
void* formatTable = nullptr;
uint32_t formatTableSize = 0;
};
class LinuxDmabufManager
: public QWaylandClientExtensionTemplate<LinuxDmabufManager>
, public QtWayland::zwp_linux_dmabuf_v1 {
public:
explicit LinuxDmabufManager(WlBufferManagerPrivate* manager);
[[nodiscard]] WlBuffer* createDmabuf(const WlBufferRequest& request);
[[nodiscard]] WlBuffer* createDmabuf(
GbmDeviceHandle& device,
uint32_t format,
const LinuxDmabufModifiers& modifiers,
uint32_t width,
uint32_t height
);
private:
struct SharedGbmDevice {
dev_t handle = 0;
std::string renderNode;
gbm_device* device = nullptr;
qsizetype refcount = 0;
};
void feedbackDone();
GbmDeviceHandle getGbmDevice(dev_t handle);
void unrefGbmDevice(gbm_device* device);
GbmDeviceHandle dupHandle(const GbmDeviceHandle& handle);
QList<LinuxDmabufTranche> tranches;
QList<SharedGbmDevice> gbmDevices;
WlBufferManagerPrivate* manager;
friend class LinuxDmabufFeedback;
friend class GbmDeviceHandle;
};
} // namespace qs::wayland::buffer::dmabuf

View file

@ -0,0 +1,114 @@
#include "manager.hpp"
#include <qdebug.h>
#include <qlogging.h>
#include <qloggingcategory.h>
#include <qmatrix4x4.h>
#include <qnamespace.h>
#include <qquickwindow.h>
#include <qtenvironmentvariables.h>
#include <qtmetamacros.h>
#include <qvectornd.h>
#include "dmabuf.hpp"
#include "manager_p.hpp"
#include "qsg.hpp"
#include "shm.hpp"
namespace qs::wayland::buffer {
WlBuffer* WlBufferSwapchain::createBackbuffer(const WlBufferRequest& request, bool* newBuffer) {
auto& buffer = this->presentSecondBuffer ? this->buffer1 : this->buffer2;
if (!buffer || !buffer->isCompatible(request)) {
buffer.reset(WlBufferManager::instance()->createBuffer(request));
if (newBuffer) *newBuffer = true;
}
return buffer.get();
}
WlBufferManager::WlBufferManager(): p(new WlBufferManagerPrivate(this)) {}
WlBufferManager::~WlBufferManager() { delete this->p; }
WlBufferManager* WlBufferManager::instance() {
static auto* instance = new WlBufferManager();
return instance;
}
bool WlBufferManager::isReady() const { return this->p->mReady; }
[[nodiscard]] WlBuffer* WlBufferManager::createBuffer(const WlBufferRequest& request) {
static const bool dmabufDisabled = qEnvironmentVariableIsSet("QS_DISABLE_DMABUF");
if (!dmabufDisabled) {
if (auto* buf = this->p->dmabuf.createDmabuf(request)) return buf;
qCWarning(shm::logShm) << "DMA buffer creation failed, falling back to SHM.";
}
return shm::ShmbufManager::createShmbuf(request);
}
WlBufferManagerPrivate::WlBufferManagerPrivate(WlBufferManager* manager)
: manager(manager)
, dmabuf(this) {}
void WlBufferManagerPrivate::dmabufReady() {
this->mReady = true;
emit this->manager->ready();
}
WlBufferQSGDisplayNode::WlBufferQSGDisplayNode(QQuickWindow* window)
: window(window)
, imageNode(window->createImageNode()) {
this->appendChildNode(this->imageNode);
}
void WlBufferQSGDisplayNode::setRect(const QRectF& rect) {
const auto* buffer = (this->presentSecondBuffer ? this->buffer2 : this->buffer1).first;
if (!buffer) return;
auto matrix = QMatrix4x4();
auto center = rect.center();
auto centerX = static_cast<float>(center.x());
auto centerY = static_cast<float>(center.y());
matrix.translate(centerX, centerY);
buffer->transform.apply(matrix);
matrix.translate(-centerX, -centerY);
auto viewRect = matrix.mapRect(rect);
auto bufferSize = buffer->size().toSizeF();
bufferSize.scale(viewRect.width(), viewRect.height(), Qt::KeepAspectRatio);
this->imageNode->setRect(
viewRect.x() + viewRect.width() / 2 - bufferSize.width() / 2,
viewRect.y() + viewRect.height() / 2 - bufferSize.height() / 2,
bufferSize.width(),
bufferSize.height()
);
this->setMatrix(matrix);
}
void WlBufferQSGDisplayNode::syncSwapchain(const WlBufferSwapchain& swapchain) {
auto* buffer = swapchain.frontbuffer();
auto& texture = swapchain.presentSecondBuffer ? this->buffer2 : this->buffer1;
if (swapchain.presentSecondBuffer == this->presentSecondBuffer && texture.first == buffer) {
return;
}
this->presentSecondBuffer = swapchain.presentSecondBuffer;
if (texture.first == buffer) {
texture.second->sync(texture.first, this->window);
} else {
texture.first = buffer;
texture.second.reset(buffer->createQsgTexture(this->window));
}
this->imageNode->setTexture(texture.second->texture());
}
} // namespace qs::wayland::buffer

View file

@ -0,0 +1,134 @@
#pragma once
#include <cstdint>
#include <memory>
#include <qhash.h>
#include <qlist.h>
#include <qmatrix4x4.h>
#include <qobject.h>
#include <qtclasshelpermacros.h>
#include <qtmetamacros.h>
#include <qvariant.h>
#include <wayland-client-protocol.h>
#include <wayland-client.h>
#include "../../core/stacklist.hpp"
class QQuickWindow;
namespace qs::wayland::buffer {
class WlBufferManagerPrivate;
class WlBufferQSGTexture;
struct WlBufferTransform {
enum Transform : uint8_t {
Normal0 = 0,
Normal90 = 1,
Normal180 = 2,
Normal270 = 3,
Flipped0 = 4,
Flipped90 = 5,
Flipped180 = 6,
Flipped270 = 7,
} transform = Normal0;
WlBufferTransform() = default;
WlBufferTransform(uint8_t transform): transform(static_cast<Transform>(transform)) {}
[[nodiscard]] int degrees() const { return 90 * (this->transform & 0b11111011); }
[[nodiscard]] bool flip() const { return this->transform & 0b00000100; }
void apply(QMatrix4x4& matrix) const {
matrix.rotate(this->flip() ? 180 : 0, 0, 1, 0);
matrix.rotate(static_cast<float>(this->degrees()), 0, 0, 1);
}
};
struct WlBufferRequest {
uint32_t width = 0;
uint32_t height = 0;
struct DmaFormat {
DmaFormat() = default;
DmaFormat(uint32_t format): format(format) {}
uint32_t format = 0;
StackList<uint64_t, 10> modifiers;
};
struct {
StackList<uint32_t, 1> formats;
} shm;
struct {
dev_t device = 0;
StackList<DmaFormat, 1> formats;
} dmabuf;
};
class WlBuffer {
public:
virtual ~WlBuffer() = default;
Q_DISABLE_COPY_MOVE(WlBuffer);
[[nodiscard]] virtual wl_buffer* buffer() const = 0;
[[nodiscard]] virtual QSize size() const = 0;
[[nodiscard]] virtual bool isCompatible(const WlBufferRequest& request) const = 0;
[[nodiscard]] operator bool() const { return this->buffer(); }
// Must be called from render thread.
[[nodiscard]] virtual WlBufferQSGTexture* createQsgTexture(QQuickWindow* window) const = 0;
WlBufferTransform transform;
protected:
explicit WlBuffer() = default;
};
class WlBufferSwapchain {
public:
[[nodiscard]] WlBuffer*
createBackbuffer(const WlBufferRequest& request, bool* newBuffer = nullptr);
void swapBuffers() { this->presentSecondBuffer = !this->presentSecondBuffer; }
[[nodiscard]] WlBuffer* backbuffer() const {
return this->presentSecondBuffer ? this->buffer1.get() : this->buffer2.get();
}
[[nodiscard]] WlBuffer* frontbuffer() const {
return this->presentSecondBuffer ? this->buffer2.get() : this->buffer1.get();
}
private:
std::unique_ptr<WlBuffer> buffer1;
std::unique_ptr<WlBuffer> buffer2;
bool presentSecondBuffer = false;
friend class WlBufferQSGDisplayNode;
};
class WlBufferManager: public QObject {
Q_OBJECT;
public:
~WlBufferManager() override;
Q_DISABLE_COPY_MOVE(WlBufferManager);
static WlBufferManager* instance();
[[nodiscard]] bool isReady() const;
[[nodiscard]] WlBuffer* createBuffer(const WlBufferRequest& request);
signals:
void ready();
private:
explicit WlBufferManager();
WlBufferManagerPrivate* p;
};
} // namespace qs::wayland::buffer

View file

@ -0,0 +1,20 @@
#pragma once
#include "dmabuf.hpp"
#include "manager.hpp"
namespace qs::wayland::buffer {
class WlBufferManagerPrivate {
public:
explicit WlBufferManagerPrivate(WlBufferManager* manager);
void dmabufReady();
WlBufferManager* manager;
dmabuf::LinuxDmabufManager dmabuf;
bool mReady = false;
};
} // namespace qs::wayland::buffer

View file

@ -0,0 +1,45 @@
#pragma once
#include <memory>
#include <qcontainerfwd.h>
#include <qquickwindow.h>
#include <qsgimagenode.h>
#include <qsgnode.h>
#include <qsgtexture.h>
#include <qvectornd.h>
#include "manager.hpp"
namespace qs::wayland::buffer {
// Interact only from QSG thread.
class WlBufferQSGTexture {
public:
virtual ~WlBufferQSGTexture() = default;
Q_DISABLE_COPY_MOVE(WlBufferQSGTexture);
[[nodiscard]] virtual QSGTexture* texture() const = 0;
virtual void sync(const WlBuffer* /*buffer*/, QQuickWindow* /*window*/) {}
protected:
WlBufferQSGTexture() = default;
};
// Interact only from QSG thread.
class WlBufferQSGDisplayNode: public QSGTransformNode {
public:
explicit WlBufferQSGDisplayNode(QQuickWindow* window);
void syncSwapchain(const WlBufferSwapchain& swapchain);
void setRect(const QRectF& rect);
private:
QQuickWindow* window;
QSGImageNode* imageNode;
QPair<WlBuffer*, std::unique_ptr<WlBufferQSGTexture>> buffer1;
QPair<WlBuffer*, std::unique_ptr<WlBufferQSGTexture>> buffer2;
bool presentSecondBuffer = false;
};
} // namespace qs::wayland::buffer

View file

@ -0,0 +1,91 @@
#include "shm.hpp"
#include <algorithm>
#include <memory>
#include <private/qwaylanddisplay_p.h>
#include <private/qwaylandintegration_p.h>
#include <private/qwaylandshm_p.h>
#include <private/qwaylandshmbackingstore_p.h>
#include <qdebug.h>
#include <qlogging.h>
#include <qloggingcategory.h>
#include <qquickwindow.h>
#include <qsize.h>
#include <wayland-client-protocol.h>
#include "manager.hpp"
namespace qs::wayland::buffer::shm {
Q_LOGGING_CATEGORY(logShm, "quickshell.wayland.buffer.shm", QtWarningMsg);
bool WlShmBuffer::isCompatible(const WlBufferRequest& request) const {
if (QSize(static_cast<int>(request.width), static_cast<int>(request.height)) != this->size()) {
return false;
}
auto matchingFormat = std::ranges::find(request.shm.formats, this->format);
return matchingFormat != request.shm.formats.end();
}
QDebug& operator<<(QDebug& debug, const WlShmBuffer* buffer) {
auto saver = QDebugStateSaver(debug);
debug.nospace();
if (buffer) {
auto fmt = QtWaylandClient::QWaylandShm::formatFrom(
static_cast<::wl_shm_format>(buffer->format) // NOLINT
);
debug << "WlShmBuffer(" << static_cast<const void*>(buffer) << ", size=" << buffer->size()
<< ", format=" << fmt << ')';
} else {
debug << "WlShmBuffer(0x0)";
}
return debug;
}
WlShmBuffer::~WlShmBuffer() { qCDebug(logShm) << "Destroyed" << this; }
WlBufferQSGTexture* WlShmBuffer::createQsgTexture(QQuickWindow* window) const {
auto* texture = new WlShmBufferQSGTexture();
// If the QWaylandShmBuffer is destroyed before the QSGTexture, we'll hit a UAF
// in the render thread.
texture->shmBuffer = this->shmBuffer;
texture->qsgTexture.reset(window->createTextureFromImage(*this->shmBuffer->image()));
texture->sync(this, window);
return texture;
}
void WlShmBufferQSGTexture::sync(const WlBuffer* /*unused*/, QQuickWindow* window) {
// This is both dumb and expensive. We should use an RHI texture and render images into
// it more intelligently, but shm buffers are already a horribly slow fallback path,
// to the point where it barely matters.
this->qsgTexture.reset(window->createTextureFromImage(*this->shmBuffer->image()));
}
WlBuffer* ShmbufManager::createShmbuf(const WlBufferRequest& request) {
if (request.shm.formats.isEmpty()) return nullptr;
static const auto* waylandIntegration = QtWaylandClient::QWaylandIntegration::instance();
auto* display = waylandIntegration->display();
// Its probably fine...
auto format = request.shm.formats[0];
auto* buffer = new WlShmBuffer(
new QtWaylandClient::QWaylandShmBuffer(
display,
QSize(static_cast<int>(request.width), static_cast<int>(request.height)),
QtWaylandClient::QWaylandShm::formatFrom(static_cast<::wl_shm_format>(format)) // NOLINT
),
format
);
qCDebug(logShm) << "Created shmbuf" << buffer;
return buffer;
}
} // namespace qs::wayland::buffer::shm

View file

@ -0,0 +1,63 @@
#pragma once
#include <memory>
#include <private/qwaylandshmbackingstore_p.h>
#include <qquickwindow.h>
#include <qsgtexture.h>
#include <qsize.h>
#include <qtclasshelpermacros.h>
#include <wayland-client-protocol.h>
#include "manager.hpp"
#include "qsg.hpp"
namespace qs::wayland::buffer::shm {
Q_DECLARE_LOGGING_CATEGORY(logShm);
class WlShmBuffer: public WlBuffer {
public:
~WlShmBuffer() override;
Q_DISABLE_COPY_MOVE(WlShmBuffer);
[[nodiscard]] wl_buffer* buffer() const override { return this->shmBuffer->buffer(); }
[[nodiscard]] QSize size() const override { return this->shmBuffer->size(); }
[[nodiscard]] bool isCompatible(const WlBufferRequest& request) const override;
[[nodiscard]] WlBufferQSGTexture* createQsgTexture(QQuickWindow* window) const override;
private:
WlShmBuffer(QtWaylandClient::QWaylandShmBuffer* shmBuffer, uint32_t format)
: shmBuffer(shmBuffer)
, format(format) {}
std::shared_ptr<QtWaylandClient::QWaylandShmBuffer> shmBuffer;
uint32_t format;
friend class WlShmBufferQSGTexture;
friend class ShmbufManager;
friend QDebug& operator<<(QDebug& debug, const WlShmBuffer* buffer);
};
QDebug& operator<<(QDebug& debug, const WlShmBuffer* buffer);
class WlShmBufferQSGTexture: public WlBufferQSGTexture {
public:
[[nodiscard]] QSGTexture* texture() const override { return this->qsgTexture.get(); }
void sync(const WlBuffer* buffer, QQuickWindow* window) override;
private:
WlShmBufferQSGTexture() = default;
std::shared_ptr<QtWaylandClient::QWaylandShmBuffer> shmBuffer;
std::unique_ptr<QSGTexture> qsgTexture;
friend class WlShmBuffer;
};
class ShmbufManager {
public:
[[nodiscard]] static WlBuffer* createShmbuf(const WlBufferRequest& request);
};
} // namespace qs::wayland::buffer::shm