forked from quickshell/quickshell
wayland/screencopy: add screencopy
This commit is contained in:
parent
918dd2392d
commit
cd429142a4
37 changed files with 3149 additions and 3 deletions
18
src/wayland/buffer/CMakeLists.txt
Normal file
18
src/wayland/buffer/CMakeLists.txt
Normal 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)
|
659
src/wayland/buffer/dmabuf.cpp
Normal file
659
src/wayland/buffer/dmabuf.cpp
Normal 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
|
195
src/wayland/buffer/dmabuf.hpp
Normal file
195
src/wayland/buffer/dmabuf.hpp
Normal 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
|
114
src/wayland/buffer/manager.cpp
Normal file
114
src/wayland/buffer/manager.cpp
Normal 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
|
134
src/wayland/buffer/manager.hpp
Normal file
134
src/wayland/buffer/manager.hpp
Normal 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
|
20
src/wayland/buffer/manager_p.hpp
Normal file
20
src/wayland/buffer/manager_p.hpp
Normal 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
|
45
src/wayland/buffer/qsg.hpp
Normal file
45
src/wayland/buffer/qsg.hpp
Normal 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
|
91
src/wayland/buffer/shm.cpp
Normal file
91
src/wayland/buffer/shm.cpp
Normal 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
|
63
src/wayland/buffer/shm.hpp
Normal file
63
src/wayland/buffer/shm.hpp
Normal 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
|
Loading…
Add table
Add a link
Reference in a new issue