diff --git a/changelog/next.md b/changelog/next.md index 761161d..7e4f81b 100644 --- a/changelog/next.md +++ b/changelog/next.md @@ -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 diff --git a/src/wayland/buffer/dmabuf.cpp b/src/wayland/buffer/dmabuf.cpp index 47462fb..4ed335a 100644 --- a/src/wayland/buffer/dmabuf.cpp +++ b/src/wayland/buffer/dmabuf.cpp @@ -1,6 +1,7 @@ #include "dmabuf.hpp" #include #include +#include #include #include #include @@ -9,6 +10,7 @@ #include #include +#include #include #include #include @@ -17,6 +19,7 @@ #include #include #include +#include #include #include #include @@ -24,7 +27,7 @@ #include #include #include -#include +#include #include #include #include @@ -33,6 +36,8 @@ #include #include #include +#include +#include #include #include #include @@ -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 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 +T eglProc(const char* name) { + auto* proc = reinterpret_cast(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& 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( + ri->getResource(window, QSGRendererInterface::OpenGLContextResource) + ); + + if (!context) { + qCCritical(logDmabuf) << "Failed to query render formats: No GL context."; + return false; + } + + auto* qEglContext = context->nativeInterface(); + if (!qEglContext) { + qCCritical(logDmabuf) << "Failed to query render formats: No EGL context."; + return false; + } + + auto* display = qEglContext->display(); + + static auto* eglQueryDisplayAttribEXT = + eglProc("eglQueryDisplayAttribEXT"); + static auto* eglQueryDeviceStringEXT = + eglProc("eglQueryDeviceStringEXT"); + static auto* eglQueryDmaBufFormatsEXT = + eglProc("eglQueryDmaBufFormatsEXT"); + static auto* eglQueryDmaBufModifiersEXT = + eglProc("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(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(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(numModifiers); + auto externalOnly = std::vector(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(ri->getResource(window, QSGRendererInterface::DeviceResource)); + auto* vkPhysDevicePtr = static_cast( + 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(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 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(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(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& 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(width), static_cast(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()); }); diff --git a/src/wayland/buffer/dmabuf.hpp b/src/wayland/buffer/dmabuf.hpp index ffe5d02..9c14b84 100644 --- a/src/wayland/buffer/dmabuf.hpp +++ b/src/wayland/buffer/dmabuf.hpp @@ -2,6 +2,7 @@ #include #include +#include #include #include @@ -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 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& 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 getGbmDevice(dev_t handle); + LinuxDmabufFormatSelection renderFormats; + std::string renderNode; QList tranches; - QList gbmDevices; + QList> gbmDevices; WlBufferManagerPrivate* manager; friend class LinuxDmabufFeedback; - friend class GbmDeviceHandle; + friend struct GbmDevice; }; } // namespace qs::wayland::buffer::dmabuf diff --git a/src/wayland/buffer/manager.cpp b/src/wayland/buffer/manager.cpp index 9cf77fd..79f3030 100644 --- a/src/wayland/buffer/manager.cpp +++ b/src/wayland/buffer/manager.cpp @@ -1,5 +1,8 @@ -#include "manager.hpp" +#include "manager.hpp" +#include + +#include #include #include #include @@ -11,6 +14,7 @@ #include #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) diff --git a/src/wayland/buffer/manager.hpp b/src/wayland/buffer/manager.hpp index 8abc218..41f726a 100644 --- a/src/wayland/buffer/manager.hpp +++ b/src/wayland/buffer/manager.hpp @@ -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 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); diff --git a/src/wayland/buffer/manager_p.hpp b/src/wayland/buffer/manager_p.hpp index 55f5e66..84e81a2 100644 --- a/src/wayland/buffer/manager_p.hpp +++ b/src/wayland/buffer/manager_p.hpp @@ -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 diff --git a/src/wayland/screencopy/image_copy_capture/image_copy_capture.cpp b/src/wayland/screencopy/image_copy_capture/image_copy_capture.cpp index 13d1bc6..39bc3fc 100644 --- a/src/wayland/screencopy/image_copy_capture/image_copy_capture.cpp +++ b/src/wayland/screencopy/image_copy_capture/image_copy_capture.cpp @@ -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); diff --git a/src/wayland/screencopy/view.cpp b/src/wayland/screencopy/view.cpp index 7d10dc2..b456aac 100644 --- a/src/wayland/screencopy/view.cpp +++ b/src/wayland/screencopy/view.cpp @@ -141,6 +141,8 @@ void ScreencopyView::componentComplete() { this, &ScreencopyView::onBuffersReady ); + + bufManager->initWindow(this->window()); } else { this->onBuffersReady(); } diff --git a/src/window/proxywindow.cpp b/src/window/proxywindow.cpp index 8a20dfa..ce3df8a 100644 --- a/src/window/proxywindow.cpp +++ b/src/window/proxywindow.cpp @@ -1,4 +1,5 @@ #include "proxywindow.hpp" +#include #include #include @@ -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> SCENEGRAPH_INIT_CALLBACKS; // NOLINT +} + +void ProxiedWindow::callOnScenegraphInit(std::function 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(); diff --git a/src/window/proxywindow.hpp b/src/window/proxywindow.hpp index 9ff66c4..8adcefa 100644 --- a/src/window/proxywindow.hpp +++ b/src/window/proxywindow.hpp @@ -1,5 +1,7 @@ #pragma once +#include + #include #include #include @@ -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 cb); + signals: void exposed(); void devicePixelRatioChanged(); +private slots: + void onSceneGraphInitialized(); + protected: bool event(QEvent* event) override; void exposeEvent(QExposeEvent* event) override;