wayland/screencopy: intersect dmabuf and render formats + cleanup

Fixes 99% of MGPU cases.
This commit is contained in:
outfoxxed committed 2026-04-13 00:41:08 -07:00
1 parent d4c92973b5
commit fb08eced44
10 files changed
+556 -170

No files matched your search

+1
View File
@@ -74,6 +74,7 @@ set shell id.
- Fixed JsonAdapter serialization for lists.
- Fixed pipewire crashes after hotplugging devices and changing default outputs.
- Fixed launches failing for `--daemonize` on some systems.
- Fixed screencopy crashing when used across GPUs.
## Packaging Changes
+428 -121
View File
@@ -1,6 +1,7 @@
#include "dmabuf.hpp"
#include <algorithm>
#include <array>
#include <cstddef>
#include <cstdint>
#include <memory>
#include <string>
@@ -9,6 +10,7 @@
#include <EGL/egl.h>
#include <EGL/eglext.h>
#include <EGL/eglplatform.h>
#include <GL/gl.h>
#include <fcntl.h>
#include <gbm.h>
@@ -17,6 +19,7 @@
#include <private/qrhivulkan_p.h>
#include <qcontainerfwd.h>
#include <qdebug.h>
#include <qdir.h>
#include <qlist.h>
#include <qlogging.h>
#include <qloggingcategory.h>
@@ -24,7 +27,7 @@
#include <qopenglcontext_platform.h>
#include <qpair.h>
#include <qquickwindow.h>
#include <qscopedpointer.h>
#include <qscopeguard.h>
#include <qsgrendererinterface.h>
#include <qsgtexture_platform.h>
#include <qtypes.h>
@@ -33,6 +36,8 @@
#include <qwayland-linux-dmabuf-v1.h>
#include <qwaylandclientextension.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <sys/sysmacros.h>
#include <sys/types.h>
#include <unistd.h>
#include <vulkan/vulkan_core.h>
@@ -54,23 +59,33 @@ QS_LOGGING_CATEGORY(logDmabuf, "quickshell.wayland.buffer.dmabuf", QtWarningMsg)
LinuxDmabufManager* MANAGER = nullptr; // NOLINT
struct SupportedDrmFormat {
uint32_t drmFormat;
VkFormat vkFormat;
};
// XRGB/ARGB intentionally map to the same VK format.
constexpr std::array<SupportedDrmFormat, 11> SUPPORTED_VK_FORMATS = {{
{.drmFormat = DRM_FORMAT_ARGB8888, .vkFormat = VK_FORMAT_B8G8R8A8_UNORM},
{.drmFormat = DRM_FORMAT_XRGB8888, .vkFormat = VK_FORMAT_B8G8R8A8_UNORM},
{.drmFormat = DRM_FORMAT_ABGR8888, .vkFormat = VK_FORMAT_R8G8B8A8_UNORM},
{.drmFormat = DRM_FORMAT_XBGR8888, .vkFormat = VK_FORMAT_R8G8B8A8_UNORM},
{.drmFormat = DRM_FORMAT_ARGB2101010, .vkFormat = VK_FORMAT_A2R10G10B10_UNORM_PACK32},
{.drmFormat = DRM_FORMAT_XRGB2101010, .vkFormat = VK_FORMAT_A2R10G10B10_UNORM_PACK32},
{.drmFormat = DRM_FORMAT_ABGR2101010, .vkFormat = VK_FORMAT_A2B10G10R10_UNORM_PACK32},
{.drmFormat = DRM_FORMAT_XBGR2101010, .vkFormat = VK_FORMAT_A2B10G10R10_UNORM_PACK32},
{.drmFormat = DRM_FORMAT_ABGR16161616F, .vkFormat = VK_FORMAT_R16G16B16A16_SFLOAT},
{.drmFormat = DRM_FORMAT_RGB565, .vkFormat = VK_FORMAT_R5G6B5_UNORM_PACK16},
{.drmFormat = DRM_FORMAT_BGR565, .vkFormat = VK_FORMAT_B5G6R5_UNORM_PACK16},
}};
VkFormat drmFormatToVkFormat(uint32_t drmFormat) {
// NOLINTBEGIN(bugprone-branch-clone): XRGB/ARGB intentionally map to the same VK format
switch (drmFormat) {
case DRM_FORMAT_ARGB8888: return VK_FORMAT_B8G8R8A8_UNORM;
case DRM_FORMAT_XRGB8888: return VK_FORMAT_B8G8R8A8_UNORM;
case DRM_FORMAT_ABGR8888: return VK_FORMAT_R8G8B8A8_UNORM;
case DRM_FORMAT_XBGR8888: return VK_FORMAT_R8G8B8A8_UNORM;
case DRM_FORMAT_ARGB2101010: return VK_FORMAT_A2R10G10B10_UNORM_PACK32;
case DRM_FORMAT_XRGB2101010: return VK_FORMAT_A2R10G10B10_UNORM_PACK32;
case DRM_FORMAT_ABGR2101010: return VK_FORMAT_A2B10G10R10_UNORM_PACK32;
case DRM_FORMAT_XBGR2101010: return VK_FORMAT_A2B10G10R10_UNORM_PACK32;
case DRM_FORMAT_ABGR16161616F: return VK_FORMAT_R16G16B16A16_SFLOAT;
case DRM_FORMAT_RGB565: return VK_FORMAT_R5G6B5_UNORM_PACK16;
case DRM_FORMAT_BGR565: return VK_FORMAT_B5G6R5_UNORM_PACK16;
default: return VK_FORMAT_UNDEFINED;
}
// NOLINTEND(bugprone-branch-clone)
const auto* it = std::ranges::find_if(SUPPORTED_VK_FORMATS, [&](const SupportedDrmFormat& f) {
return f.drmFormat == drmFormat;
});
if (it == SUPPORTED_VK_FORMATS.end()) return VK_FORMAT_UNDEFINED;
return it->vkFormat;
}
bool drmFormatHasAlpha(uint32_t drmFormat) {
@@ -84,6 +99,13 @@ bool drmFormatHasAlpha(uint32_t drmFormat) {
}
}
template <typename T>
T eglProc(const char* name) {
auto* proc = reinterpret_cast<T>(eglGetProcAddress(name));
if (!proc) qCCritical(logDmabuf) << "Failed to look up EGL proc:" << name;
return proc;
}
} // namespace
QDebug& operator<<(QDebug& debug, const FourCCStr& fourcc) {
@@ -111,10 +133,13 @@ QDebug& operator<<(QDebug& debug, const WlDmaBuffer* buffer) {
return debug;
}
GbmDeviceHandle::~GbmDeviceHandle() {
if (this->device) {
MANAGER->unrefGbmDevice(this->device);
}
GbmDevice::~GbmDevice() {
qCDebug(logDmabuf) << "Destroying GBM device on render node" << this->renderNode.c_str();
auto fd = gbm_device_get_fd(this->device);
gbm_device_destroy(this->device);
close(fd);
MANAGER->gbmDevices.removeIf([](const std::weak_ptr<GbmDevice>& d) { return d.expired(); });
}
// Prefer ARGB over XRGB: XRGB has undefined alpha bytes which cause
@@ -291,106 +316,329 @@ LinuxDmabufManager::LinuxDmabufManager(WlBufferManagerPrivate* manager)
}
}
void LinuxDmabufManager::feedbackDone() { this->manager->dmabufReady(); }
bool LinuxDmabufManager::initRenderFormats(QQuickWindow* window) {
auto* ri = window->rendererInterface();
if (ri->graphicsApi() == QSGRendererInterface::Vulkan) {
return this->initRenderFormatsVk(window);
} else {
return this->initRenderFormatsGl(window);
}
}
GbmDeviceHandle LinuxDmabufManager::getGbmDevice(dev_t handle) {
struct DrmFree {
static void cleanup(drmDevice* d) { drmFreeDevice(&d); }
bool LinuxDmabufManager::initRenderFormatsGl(QQuickWindow* window) {
auto* ri = window->rendererInterface();
auto* context = static_cast<QOpenGLContext*>(
ri->getResource(window, QSGRendererInterface::OpenGLContextResource)
);
if (!context) {
qCCritical(logDmabuf) << "Failed to query render formats: No GL context.";
return false;
}
auto* qEglContext = context->nativeInterface<QNativeInterface::QEGLContext>();
if (!qEglContext) {
qCCritical(logDmabuf) << "Failed to query render formats: No EGL context.";
return false;
}
auto* display = qEglContext->display();
static auto* eglQueryDisplayAttribEXT =
eglProc<PFNEGLQUERYDISPLAYATTRIBEXTPROC>("eglQueryDisplayAttribEXT");
static auto* eglQueryDeviceStringEXT =
eglProc<PFNEGLQUERYDEVICESTRINGEXTPROC>("eglQueryDeviceStringEXT");
static auto* eglQueryDmaBufFormatsEXT =
eglProc<PFNEGLQUERYDMABUFFORMATSEXTPROC>("eglQueryDmaBufFormatsEXT");
static auto* eglQueryDmaBufModifiersEXT =
eglProc<PFNEGLQUERYDMABUFMODIFIERSEXTPROC>("eglQueryDmaBufModifiersEXT");
if (!eglQueryDisplayAttribEXT || !eglQueryDeviceStringEXT || !eglQueryDmaBufFormatsEXT
|| !eglQueryDmaBufModifiersEXT)
{
return false;
}
EGLAttrib deviceAttrib = 0;
if (!eglQueryDisplayAttribEXT(display, EGL_DEVICE_EXT, &deviceAttrib)) {
qCCritical(logDmabuf) << "Failed to find render device: device display attrib missing.";
return false;
}
auto* dev = reinterpret_cast<EGLDeviceEXT>(deviceAttrib); // NOLINT
if (const auto* renderNode = eglQueryDeviceStringEXT(dev, EGL_DRM_RENDER_NODE_FILE_EXT)) {
this->renderNode = renderNode;
} else if (const auto* primaryNode = eglQueryDeviceStringEXT(dev, EGL_DRM_DEVICE_FILE_EXT)) {
this->renderNode = primaryNode;
} else {
qCCritical(logDmabuf) << "Failed to find render device: no render or primary node found.";
return false;
}
qCDebug(logDmabuf) << "Found render node:" << this->renderNode;
EGLint numFormats = 0;
if (!eglQueryDmaBufFormatsEXT(display, 0, nullptr, &numFormats)) {
qCCritical(logDmabuf) << "Failed to query render formats: eglQueryDmaBufFormatsEXT failed.";
return false;
}
if (numFormats == 0) {
qCCritical(logDmabuf) << "Failed to query render formats: zero formats.";
return false;
}
auto formats = std::vector<EGLint>(numFormats);
if (!eglQueryDmaBufFormatsEXT(display, numFormats, formats.data(), &numFormats)) {
qCCritical(logDmabuf) << "Failed to query render formats: eglQueryDmaBufFormatsEXT failed.";
return false;
}
qCDebug(logDmabuf) << "Render formats:";
for (auto format: formats) {
qCDebug(logDmabuf) << " Format" << FourCCStr(format);
qCDebug(logDmabuf) << " Implicit Modifier";
EGLint numModifiers = 0;
if (!eglQueryDmaBufModifiersEXT(display, format, 0, nullptr, nullptr, &numModifiers)) {
qCCritical(logDmabuf) << "Failed to query render formats: eglQueryDmaBufModifiersEXT failed.";
return false;
}
auto modifiers = std::vector<EGLuint64KHR>(numModifiers);
auto externalOnly = std::vector<EGLBoolean>(numModifiers);
if (!eglQueryDmaBufModifiersEXT(
display,
format,
numModifiers,
modifiers.data(),
externalOnly.data(),
&numModifiers
))
{
qCCritical(logDmabuf) << "Failed to query render formats: eglQueryDmaBufModifiersEXT failed.";
return false;
}
LinuxDmabufModifiers mods;
for (size_t i = 0; i != modifiers.size(); ++i) {
// External-only modifiers are required for some MGPU cases (usually involving nvidia).
// The work has not been done to support importing these, so they'll fall back to SHM.
auto external = externalOnly[i] == EGL_TRUE;
auto modifier = modifiers[i];
if (external) {
qCDebug(logDmabuf) << " Explicit Modifier" << FourCCModStr(modifier)
<< "(external / skipped)";
} else {
qCDebug(logDmabuf) << " Explicit Modifier" << FourCCModStr(modifier);
mods.modifiers.push(modifier);
}
}
// It seems like EGL always supports implicit modifiers, however they don't work across GPUs so
// renderdev must be checked to match the tranche.
mods.implicit = true;
this->renderFormats.formats.push(qMakePair(format, mods));
}
return true;
}
bool LinuxDmabufManager::initRenderFormatsVk(QQuickWindow* window) {
auto* ri = window->rendererInterface();
auto* vkInst = window->vulkanInstance();
if (!vkInst) {
qCCritical(logDmabuf) << "Failed to query render formats: no QVulkanInstance.";
return false;
}
auto* vkDevicePtr =
static_cast<VkDevice*>(ri->getResource(window, QSGRendererInterface::DeviceResource));
auto* vkPhysDevicePtr = static_cast<VkPhysicalDevice*>(
ri->getResource(window, QSGRendererInterface::PhysicalDeviceResource)
);
if (!vkDevicePtr || !vkPhysDevicePtr) {
qCCritical(logDmabuf) << "Failed to query render formats: could not get Vulkan device.";
return false;
}
auto* physDevice = *vkPhysDevicePtr;
auto* instFuncs = vkInst->functions();
auto drmProps = VkPhysicalDeviceDrmPropertiesEXT {};
drmProps.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DRM_PROPERTIES_EXT;
auto props2 = VkPhysicalDeviceProperties2 {
.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2,
.pNext = &drmProps,
.properties = {}
};
instFuncs->vkGetPhysicalDeviceProperties2(physDevice, &props2);
auto devDri = QDir("/dev/dri");
auto entries = devDri.entryList(QDir::System);
auto findNode = [&](uint32_t maj, uint32_t min) -> QString {
for (const QString& file: entries) {
auto path = devDri.filePath(file);
struct stat st = {};
if (::stat(path.toLocal8Bit().constData(), &st) != 0) continue;
if (major(st.st_rdev) == maj && minor(st.st_rdev) == min) {
return path;
}
}
return {};
};
QString node;
if (drmProps.hasRender) node = findNode(drmProps.renderMajor, drmProps.renderMinor);
else if (drmProps.hasPrimary) {
node = findNode(drmProps.primaryMajor, drmProps.primaryMinor);
}
if (!node.isEmpty()) {
this->renderNode = node.toLocal8Bit();
qCDebug(logDmabuf) << "Found render node:" << this->renderNode;
}
qCDebug(logDmabuf) << "Render formats:";
for (const auto& format: SUPPORTED_VK_FORMATS) {
auto modList = VkDrmFormatModifierPropertiesListEXT {
.sType = VK_STRUCTURE_TYPE_DRM_FORMAT_MODIFIER_PROPERTIES_LIST_EXT,
.pNext = nullptr,
.drmFormatModifierCount = 0,
.pDrmFormatModifierProperties = nullptr,
};
auto props2 = VkFormatProperties2 {
.sType = VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2,
.pNext = &modList,
.formatProperties = {},
};
instFuncs->vkGetPhysicalDeviceFormatProperties2(physDevice, format.vkFormat, &props2);
if (modList.drmFormatModifierCount == 0) continue;
auto vkMods = std::vector<VkDrmFormatModifierPropertiesEXT>(modList.drmFormatModifierCount);
modList.pDrmFormatModifierProperties = vkMods.data();
instFuncs->vkGetPhysicalDeviceFormatProperties2(physDevice, format.vkFormat, &props2);
qCDebug(logDmabuf) << " Format" << FourCCStr(format.drmFormat);
LinuxDmabufModifiers mods;
for (const auto& m: vkMods) {
auto modInfo = VkPhysicalDeviceImageDrmFormatModifierInfoEXT {
.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_IMAGE_DRM_FORMAT_MODIFIER_INFO_EXT,
.pNext = nullptr,
.drmFormatModifier = m.drmFormatModifier,
.sharingMode = VK_SHARING_MODE_EXCLUSIVE,
.queueFamilyIndexCount = 0,
.pQueueFamilyIndices = nullptr,
};
auto info = VkPhysicalDeviceImageFormatInfo2 {
.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_IMAGE_FORMAT_INFO_2,
.pNext = &modInfo,
.format = format.vkFormat,
.type = VK_IMAGE_TYPE_2D,
.tiling = VK_IMAGE_TILING_DRM_FORMAT_MODIFIER_EXT,
.usage = VK_IMAGE_USAGE_SAMPLED_BIT,
.flags = {},
};
auto props = VkImageFormatProperties2 {
.sType = VK_STRUCTURE_TYPE_IMAGE_FORMAT_PROPERTIES_2,
.pNext = nullptr,
.imageFormatProperties = {},
};
auto r = instFuncs->vkGetPhysicalDeviceImageFormatProperties2(physDevice, &info, &props);
if (r == VK_SUCCESS) {
qCDebug(logDmabuf) << " Explicit Modifier" << FourCCModStr(m.drmFormatModifier);
mods.modifiers.push(m.drmFormatModifier);
} else if (r == VK_ERROR_FORMAT_NOT_SUPPORTED) {
qCDebug(logDmabuf) << " Explicit Modifier" << FourCCModStr(m.drmFormatModifier)
<< "(not usable)";
} else {
qCDebug(logDmabuf) << " Explicit Modifier" << FourCCModStr(m.drmFormatModifier)
<< "(unknown error)";
}
}
if (mods.modifiers.isEmpty()) continue;
// We don't enable implicit modifiers because they seem to be broken in our current impl.
this->renderFormats.formats.push(qMakePair(format.drmFormat, mods));
}
return true;
}
void LinuxDmabufManager::feedbackDone() { this->manager->dmabufReady(); }
std::shared_ptr<GbmDevice> LinuxDmabufManager::getGbmDevice(dev_t handle) {
for (const auto& weak: this->gbmDevices) {
auto shared = weak.lock();
if (shared->handle == handle) {
qCDebug(logDmabuf) << "Used existing GBM device on render node" << shared->renderNode.c_str();
return shared;
}
}
drmDevice* drmDev = nullptr;
if (auto error = drmGetDeviceFromDevId(handle, 0, &drmDev); error != 0) {
qCWarning(logDmabuf) << "Failed to get drm device information from handle:"
<< qt_error_string(error);
return nullptr;
}
auto freeDrmDev = qScopeGuard([&] { drmFreeDevice(&drmDev); });
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;
}
if (drmDev->available_nodes & (1 << DRM_NODE_RENDER)) {
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 if (drmDev->available_nodes & (1 << DRM_NODE_PRIMARY)) {
renderNodeStorage = drmDev->nodes[DRM_NODE_PRIMARY]; // NOLINT
} 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;
qCDebug(logDmabuf) << "Cannot create GBM device: DRM device does not have render node.";
return nullptr;
}
}
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;
for (const auto& weak: this->gbmDevices) {
auto shared = weak.lock();
if (shared->renderNode == renderNodeStorage) {
qCDebug(logDmabuf) << "Used existing GBM device on render node" << renderNodeStorage.c_str();
return shared;
}
}
qCDebug(logDmabuf) << "Lost reference to GBM device" << device;
auto fd = open(renderNodeStorage.c_str(), O_RDWR | O_CLOEXEC);
if (fd < 0) {
qCDebug(logDmabuf) << "Could not open render node" << renderNodeStorage.c_str() << ":"
<< qt_error_string(fd);
return nullptr;
}
if (--iter->refcount == 0) {
auto fd = gbm_device_get_fd(iter->device);
gbm_device_destroy(iter->device);
auto* device = gbm_create_device(fd);
if (!device) {
qCDebug(logDmabuf) << "Failed to create GBM device from render node"
<< renderNodeStorage.c_str();
close(fd);
this->gbmDevices.erase(iter);
qCDebug(logDmabuf) << "Destroyed GBM device" << device;
return nullptr;
}
}
GbmDeviceHandle LinuxDmabufManager::dupHandle(const GbmDeviceHandle& handle) {
if (!handle) return GbmDeviceHandle();
qCDebug(logDmabuf) << "Created GBM device on render node" << renderNodeStorage.c_str();
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);
auto shared = std::make_shared<GbmDevice>(handle, std::move(renderNodeStorage), device);
this->gbmDevices.push_back(shared);
return shared;
}
WlBuffer* LinuxDmabufManager::createDmabuf(const WlBufferRequest& request) {
@@ -400,16 +648,67 @@ WlBuffer* LinuxDmabufManager::createDmabuf(const WlBufferRequest& request) {
}
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);
for (const auto& requestFormat: request.dmabuf.formats) {
for (const auto& [renderFormat, renderFormatModifiers]: this->renderFormats.formats) {
if (renderFormat != requestFormat.format) continue;
if (!requestFormat.modifiers.isEmpty()) {
LinuxDmabufModifiers mods;
mods.implicit = requestFormat.implicit && renderFormatModifiers.implicit;
for (auto mod: requestFormat.modifiers) {
for (auto renderMod: renderFormatModifiers.modifiers) {
if (mod != renderMod) continue;
mods.modifiers.push(mod);
break;
}
}
if (mods.implicit || !mods.modifiers.isEmpty()) {
formats.formats.push(qMakePair(requestFormat.format, mods));
}
} else {
for (const auto& [trancheFormat, trancheMods]: tranche.formats.formats) {
if (trancheFormat != requestFormat.format) continue;
LinuxDmabufModifiers mods;
mods.implicit = trancheMods.implicit && renderFormatModifiers.implicit;
for (auto mod: trancheMods.modifiers) {
for (auto renderMod: renderFormatModifiers.modifiers) {
if (mod != renderMod) continue;
mods.modifiers.push(mod);
break;
}
}
if (mods.implicit || !mods.modifiers.isEmpty()) {
formats.formats.push(qMakePair(trancheFormat, mods));
}
break;
}
}
break;
}
}
if (logDmabuf().isDebugEnabled()) {
qCDebug(logDmabuf) << "Usable formats on device" << tranche.device;
// will be sorted on first use otherwise
formats.ensureSorted();
for (auto& [format, modifiers]: 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);
}
}
}
@@ -438,7 +737,7 @@ WlBuffer* LinuxDmabufManager::createDmabuf(const WlBufferRequest& request) {
}
WlBuffer* LinuxDmabufManager::createDmabuf(
GbmDeviceHandle& device,
const std::shared_ptr<GbmDevice>& device,
uint32_t format,
const LinuxDmabufModifiers& modifiers,
uint32_t width,
@@ -457,15 +756,22 @@ WlBuffer* LinuxDmabufManager::createDmabuf(
return nullptr;
}
if (device->renderNode != this->renderNode) {
qCritical(logDmabuf) << "Failed to create gbm_bo: format supports only implicit modifier "
"which does not work across GPUs.";
return nullptr;
}
qCDebug(logDmabuf) << "Creating gbm_bo without modifiers...";
bo = gbm_bo_create(*device, width, height, format, flags);
buffer->usedImplicitModifier = true;
bo = gbm_bo_create(device->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,
device->device,
width,
height,
format,
@@ -514,7 +820,7 @@ WlBuffer* LinuxDmabufManager::createDmabuf(
params.create_immed(static_cast<int32_t>(width), static_cast<int32_t>(height), format, 0);
params.destroy();
buffer->device = this->dupHandle(device);
buffer->device = device;
buffer->width = width;
buffer->height = height;
buffer->format = format;
@@ -561,8 +867,9 @@ 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, [this](const auto& format) {
if (format.format != this->format) return false;
return format.format == this->format
&& (format.modifiers.isEmpty()
&& (format.modifiers.isEmpty() || (format.implicit && this->usedImplicitModifier)
|| std::ranges::find(format.modifiers, this->modifier) != format.modifiers.end());
});
+22 -34
View File
@@ -2,6 +2,7 @@
#include <array>
#include <cstdint>
#include <memory>
#include <EGL/egl.h>
#include <gbm.h>
@@ -72,28 +73,16 @@ private:
QDebug& operator<<(QDebug& debug, const FourCCStr& fourcc);
QDebug& operator<<(QDebug& debug, const FourCCModStr& fourcc);
class GbmDeviceHandle {
public:
GbmDeviceHandle() = default;
GbmDeviceHandle(gbm_device* device): device(device) {}
struct GbmDevice {
GbmDevice(dev_t handle, std::string renderNode, gbm_device* device)
: handle(handle)
, renderNode(std::move(renderNode))
, device(device) {}
~GbmDevice();
Q_DISABLE_COPY_MOVE(GbmDevice);
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:
dev_t handle = 0;
std::string renderNode;
gbm_device* device = nullptr;
};
@@ -172,7 +161,7 @@ private:
uint32_t stride = 0;
};
GbmDeviceHandle device;
std::shared_ptr<GbmDevice> device;
gbm_bo* bo = nullptr;
wl_buffer* mBuffer = nullptr;
int planeCount = 0;
@@ -181,6 +170,7 @@ private:
uint64_t modifier = 0;
uint32_t width = 0;
uint32_t height = 0;
bool usedImplicitModifier = false;
friend class LinuxDmabufManager;
friend QDebug& operator<<(QDebug& debug, const WlDmaBuffer* buffer);
@@ -239,33 +229,31 @@ public:
[[nodiscard]] WlBuffer* createDmabuf(const WlBufferRequest& request);
[[nodiscard]] WlBuffer* createDmabuf(
GbmDeviceHandle& device,
const std::shared_ptr<GbmDevice>& device,
uint32_t format,
const LinuxDmabufModifiers& modifiers,
uint32_t width,
uint32_t height
);
bool initRenderFormats(QQuickWindow* window);
private:
struct SharedGbmDevice {
dev_t handle = 0;
std::string renderNode;
gbm_device* device = nullptr;
qsizetype refcount = 0;
};
bool initRenderFormatsGl(QQuickWindow* window);
bool initRenderFormatsVk(QQuickWindow* window);
void feedbackDone();
GbmDeviceHandle getGbmDevice(dev_t handle);
void unrefGbmDevice(gbm_device* device);
GbmDeviceHandle dupHandle(const GbmDeviceHandle& handle);
std::shared_ptr<GbmDevice> getGbmDevice(dev_t handle);
LinuxDmabufFormatSelection renderFormats;
std::string renderNode;
QList<LinuxDmabufTranche> tranches;
QList<SharedGbmDevice> gbmDevices;
QList<std::weak_ptr<GbmDevice>> gbmDevices;
WlBufferManagerPrivate* manager;
friend class LinuxDmabufFeedback;
friend class GbmDeviceHandle;
friend struct GbmDevice;
};
} // namespace qs::wayland::buffer::dmabuf
+59 -10
View File
@@ -1,5 +1,8 @@
#include "manager.hpp"
#include "manager.hpp"
#include <cstdint>
#include <drm_fourcc.h>
#include <qdebug.h>
#include <qlogging.h>
#include <qloggingcategory.h>
@@ -11,6 +14,7 @@
#include <qvectornd.h>
#include "../../core/logcat.hpp"
#include "../../window/proxywindow.hpp"
#include "dmabuf.hpp"
#include "manager_p.hpp"
#include "qsg.hpp"
@@ -22,12 +26,25 @@ namespace {
QS_LOGGING_CATEGORY(logBuffer, "quickshell.wayland.buffer", QtWarningMsg);
}
void WlBufferRequest::DmaFormat::pushMod(uint64_t mod) {
if (mod == DRM_FORMAT_MOD_INVALID) {
this->implicit = true;
} else {
this->modifiers.push(mod);
}
}
bool WlBufferRequest::DmaFormat::modsDefined() const {
return this->implicit || !this->modifiers.isEmpty();
}
void WlBufferRequest::reset() { *this = WlBufferRequest(); }
WlBuffer* WlBufferSwapchain::createBackbuffer(const WlBufferRequest& request, bool* newBuffer) {
static const bool noReuse = qEnvironmentVariableIsSet("QS_NO_BUFFER_REUSE");
auto& buffer = this->presentSecondBuffer ? this->buffer1 : this->buffer2;
if (!buffer || !buffer->isCompatible(request)) {
if (!buffer || !buffer->isCompatible(request) || noReuse) {
buffer.reset(WlBufferManager::instance()->createBuffer(request));
if (newBuffer) *newBuffer = true;
}
@@ -44,7 +61,34 @@ WlBufferManager* WlBufferManager::instance() {
return instance;
}
bool WlBufferManager::isReady() const { return this->p->mReady; }
void WlBufferManager::initWindow(QQuickWindow* window) {
if (this->p->mRenderFormatsReady) return;
qCDebug(logBuffer) << "Initializing buffer manager";
static bool initWaiting = false;
if (!window || !window->isSceneGraphInitialized()) {
if (initWaiting) return;
initWaiting = true;
qCDebug(logBuffer) << "Waiting for scene graph to come online";
ProxiedWindow::callOnScenegraphInit([this](QQuickWindow* w) { this->initWindow(w); });
return;
}
qCDebug(logBuffer) << "Initializing render formats";
auto success = this->p->dmabuf.initRenderFormats(window);
if (!success) {
qCWarning(logBuffer) << "Render format initialization failed. All buffers will fall back to SHM.";
this->p->mRenderFormatsFailed = true;
}
this->p->mRenderFormatsReady = true;
if (this->p->mDmabufFormatsReady) emit this->ready();
}
bool WlBufferManager::isReady() const {
return this->p->mDmabufFormatsReady && this->p->mRenderFormatsReady;
}
[[nodiscard]] WlBuffer* WlBufferManager::createBuffer(const WlBufferRequest& request) {
static const bool dmabufDisabled = qEnvironmentVariableIsSet("QS_DISABLE_DMABUF");
@@ -54,11 +98,16 @@ bool WlBufferManager::isReady() const { return this->p->mReady; }
qCDebug(logBuffer).nospace() << " Dmabuf requests on device " << request.dmabuf.device
<< " (disabled: " << dmabufDisabled << ')';
for (const auto& [format, modifiers]: request.dmabuf.formats) {
qCDebug(logBuffer).nospace() << " Format " << dmabuf::FourCCStr(format)
<< (modifiers.length() == 0 ? " (No modifiers specified)" : "");
for (const auto& format: request.dmabuf.formats) {
qCDebug(logBuffer).nospace() << " Format " << dmabuf::FourCCStr(format.format)
<< (format.modifiers.length() == 0 ? " (No modifiers specified)"
: "");
for (const auto& modifier: modifiers) {
if (format.implicit) {
qCDebug(logBuffer) << " Implicit Modifier";
}
for (const auto& modifier: format.modifiers) {
qCDebug(logBuffer) << " Explicit Modifier" << dmabuf::FourCCModStr(modifier);
}
}
@@ -74,7 +123,7 @@ bool WlBufferManager::isReady() const { return this->p->mReady; }
return nullptr;
}
if (!dmabufDisabled) {
if (!dmabufDisabled && !this->p->mRenderFormatsFailed) {
if (auto* buf = this->p->dmabuf.createDmabuf(request)) return buf;
qCWarning(logBuffer) << "DMA buffer creation failed, falling back to SHM.";
}
@@ -87,8 +136,8 @@ WlBufferManagerPrivate::WlBufferManagerPrivate(WlBufferManager* manager)
, dmabuf(this) {}
void WlBufferManagerPrivate::dmabufReady() {
this->mReady = true;
emit this->manager->ready();
this->mDmabufFormatsReady = true;
if (this->mRenderFormatsReady) emit this->manager->ready();
}
WlBufferQSGDisplayNode::WlBufferQSGDisplayNode(QQuickWindow* window)
+4
View File
@@ -55,9 +55,12 @@ struct WlBufferRequest {
struct DmaFormat {
DmaFormat() = default;
DmaFormat(uint32_t format): format(format) {}
void pushMod(uint64_t mod);
[[nodiscard]] bool modsDefined() const;
uint32_t format = 0;
StackList<uint64_t, 10> modifiers;
bool implicit = false;
};
struct {
@@ -123,6 +126,7 @@ public:
static WlBufferManager* instance();
void initWindow(QQuickWindow* window);
[[nodiscard]] bool isReady() const;
[[nodiscard]] WlBuffer* createBuffer(const WlBufferRequest& request);
+3 -1
View File
@@ -14,7 +14,9 @@ public:
WlBufferManager* manager;
dmabuf::LinuxDmabufManager dmabuf;
bool mReady = false;
bool mDmabufFormatsReady = false;
bool mRenderFormatsReady = false;
bool mRenderFormatsFailed = false;
};
} // namespace qs::wayland::buffer
@@ -85,7 +85,7 @@ void IccScreencopyContext::ext_image_copy_capture_session_v1_dmabuf_format(
auto reqFormat = buffer::WlBufferRequest::DmaFormat(format);
for (uint16_t i = 0; i != modifierCount; i++) {
reqFormat.modifiers.push(modifierArray[i]); // NOLINT
reqFormat.pushMod(modifierArray[i]); // NOLINT
}
this->request.dmabuf.formats.push(reqFormat);
+2
View File
@@ -141,6 +141,8 @@ void ScreencopyView::componentComplete() {
this,
&ScreencopyView::onBuffersReady
);
bufManager->initWindow(this->window());
} else {
this->onBuffersReady();
}
+28
View File
@@ -1,4 +1,5 @@
#include "proxywindow.hpp"
#include <functional>
#include <private/qquickwindow_p.h>
#include <qcontainerfwd.h>
@@ -611,6 +612,33 @@ void ProxyWindowAttached::setWindow(ProxyWindowBase* window) {
emit this->windowChanged();
}
ProxiedWindow::ProxiedWindow(ProxyWindowBase* proxy, QWindow* parent)
: QQuickWindow(parent)
, mProxy(proxy) {
QObject::connect(
this,
&QQuickWindow::sceneGraphInitialized,
this,
&ProxiedWindow::onSceneGraphInitialized
);
}
namespace {
QList<std::function<void(QQuickWindow*)>> SCENEGRAPH_INIT_CALLBACKS; // NOLINT
}
void ProxiedWindow::callOnScenegraphInit(std::function<void(QQuickWindow*)> cb) { // NOLINT
SCENEGRAPH_INIT_CALLBACKS.emplaceBack(cb);
}
void ProxiedWindow::onSceneGraphInitialized() {
for (auto& cb: SCENEGRAPH_INIT_CALLBACKS) {
cb(this);
}
SCENEGRAPH_INIT_CALLBACKS.clear();
}
bool ProxiedWindow::event(QEvent* event) {
if (event->type() == QEvent::DevicePixelRatioChange) {
emit this->devicePixelRatioChanged();
+8 -3
View File
@@ -1,5 +1,7 @@
#pragma once
#include <functional>
#include <qcolor.h>
#include <qcontainerfwd.h>
#include <qevent.h>
@@ -254,17 +256,20 @@ class ProxiedWindow: public QQuickWindow {
Q_OBJECT;
public:
explicit ProxiedWindow(ProxyWindowBase* proxy, QWindow* parent = nullptr)
: QQuickWindow(parent)
, mProxy(proxy) {}
explicit ProxiedWindow(ProxyWindowBase* proxy, QWindow* parent = nullptr);
[[nodiscard]] ProxyWindowBase* proxy() const { return this->mProxy; }
void setProxy(ProxyWindowBase* proxy) { this->mProxy = proxy; }
static void callOnScenegraphInit(std::function<void(QQuickWindow*)> cb);
signals:
void exposed();
void devicePixelRatioChanged();
private slots:
void onSceneGraphInitialized();
protected:
bool event(QEvent* event) override;
void exposeEvent(QExposeEvent* event) override;