mirror of
https://git.outfoxxed.me/quickshell/quickshell.git
synced 2026-04-10 06:11:54 +10:00
Compare commits
7 commits
e7cd1e9982
...
cddb4f061b
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cddb4f061b | ||
|
|
6e17efab83 | ||
|
|
36517a2c10 | ||
|
|
c3c3e2ca25 | ||
|
|
2cf57f43d5 | ||
|
|
a99519c3ad | ||
|
|
158db16b93 |
18 changed files with 554 additions and 92 deletions
1
.github/workflows/build.yml
vendored
1
.github/workflows/build.yml
vendored
|
|
@ -50,6 +50,7 @@ jobs:
|
||||||
wayland-protocols \
|
wayland-protocols \
|
||||||
wayland \
|
wayland \
|
||||||
libdrm \
|
libdrm \
|
||||||
|
vulkan-headers \
|
||||||
libxcb \
|
libxcb \
|
||||||
libpipewire \
|
libpipewire \
|
||||||
cli11 \
|
cli11 \
|
||||||
|
|
|
||||||
1
BUILD.md
1
BUILD.md
|
|
@ -146,6 +146,7 @@ To disable: `-DSCREENCOPY=OFF`
|
||||||
Dependencies:
|
Dependencies:
|
||||||
- `libdrm`
|
- `libdrm`
|
||||||
- `libgbm`
|
- `libgbm`
|
||||||
|
- `vulkan-headers` (build-time)
|
||||||
|
|
||||||
Specific protocols can also be disabled:
|
Specific protocols can also be disabled:
|
||||||
- `DSCREENCOPY_ICC=OFF` - Disable screencopy via [ext-image-copy-capture-v1]
|
- `DSCREENCOPY_ICC=OFF` - Disable screencopy via [ext-image-copy-capture-v1]
|
||||||
|
|
|
||||||
2
Justfile
2
Justfile
|
|
@ -13,7 +13,7 @@ lint-changed:
|
||||||
git diff --name-only HEAD | grep "^.*\.cpp\$" | parallel -j$(nproc) --no-notice --will-cite --tty --bar clang-tidy --load={{ env_var("TIDYFOX") }}
|
git diff --name-only HEAD | grep "^.*\.cpp\$" | parallel -j$(nproc) --no-notice --will-cite --tty --bar clang-tidy --load={{ env_var("TIDYFOX") }}
|
||||||
|
|
||||||
lint-staged:
|
lint-staged:
|
||||||
git diff --staged --name-only HEAD | grep "^.*\.cpp\$" | parallel -j$(nproc) --no-notice --will-cite --tty --bar clang-tidy --load={{ env_var("TIDYFOX") }}
|
git diff --staged --name-only --diff-filter=d HEAD | grep "^.*\.cpp\$" | parallel -j$(nproc) --no-notice --will-cite --tty --bar clang-tidy --load={{ env_var("TIDYFOX") }}
|
||||||
|
|
||||||
configure target='debug' *FLAGS='':
|
configure target='debug' *FLAGS='':
|
||||||
cmake -GNinja -B {{builddir}} \
|
cmake -GNinja -B {{builddir}} \
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,7 @@ set shell id.
|
||||||
- Added support for IPC signal listeners.
|
- Added support for IPC signal listeners.
|
||||||
- Added Quickshell version checking and version gated preprocessing.
|
- Added Quickshell version checking and version gated preprocessing.
|
||||||
- Added a way to detect if an icon is from the system icon theme or not.
|
- Added a way to detect if an icon is from the system icon theme or not.
|
||||||
|
- Added vulkan support to screencopy.
|
||||||
|
|
||||||
## Other Changes
|
## Other Changes
|
||||||
|
|
||||||
|
|
@ -46,7 +47,10 @@ set shell id.
|
||||||
- Fixed asynchronous loaders not working before window creation.
|
- Fixed asynchronous loaders not working before window creation.
|
||||||
- Fixed memory leak in IPC handlers.
|
- Fixed memory leak in IPC handlers.
|
||||||
- Fixed ClippingRectangle related crashes.
|
- Fixed ClippingRectangle related crashes.
|
||||||
|
- Fixed crashes when monitors are unplugged.
|
||||||
|
- Fixed crashes when default pipewire devices are lost.
|
||||||
|
|
||||||
## Packaging Changes
|
## Packaging Changes
|
||||||
|
|
||||||
`glib` and `polkit` have been added as dependencies when compiling with polkit agent support.
|
`glib` and `polkit` have been added as dependencies when compiling with polkit agent support.
|
||||||
|
`vulkan-headers` has been added as a build-time dependency for screencopy (Vulkan backend support).
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,7 @@
|
||||||
xorg,
|
xorg,
|
||||||
libdrm,
|
libdrm,
|
||||||
libgbm ? null,
|
libgbm ? null,
|
||||||
|
vulkan-headers,
|
||||||
pipewire,
|
pipewire,
|
||||||
pam,
|
pam,
|
||||||
polkit,
|
polkit,
|
||||||
|
|
@ -77,7 +78,7 @@
|
||||||
++ lib.optional withJemalloc jemalloc
|
++ lib.optional withJemalloc jemalloc
|
||||||
++ lib.optional (withWayland && lib.strings.compareVersions qt6.qtbase.version "6.10.0" == -1) qt6.qtwayland
|
++ lib.optional (withWayland && lib.strings.compareVersions qt6.qtbase.version "6.10.0" == -1) qt6.qtwayland
|
||||||
++ lib.optionals withWayland [ wayland wayland-protocols ]
|
++ lib.optionals withWayland [ wayland wayland-protocols ]
|
||||||
++ lib.optionals (withWayland && libgbm != null) [ libdrm libgbm ]
|
++ lib.optionals (withWayland && libgbm != null) [ libdrm libgbm vulkan-headers ]
|
||||||
++ lib.optional withX11 xorg.libxcb
|
++ lib.optional withX11 xorg.libxcb
|
||||||
++ lib.optional withPam pam
|
++ lib.optional withPam pam
|
||||||
++ lib.optional withPipewire pipewire
|
++ lib.optional withPipewire pipewire
|
||||||
|
|
|
||||||
|
|
@ -251,37 +251,6 @@ public:
|
||||||
GuardedEmitBlocker block() { return GuardedEmitBlocker(&this->blocked); }
|
GuardedEmitBlocker block() { return GuardedEmitBlocker(&this->blocked); }
|
||||||
};
|
};
|
||||||
|
|
||||||
template <auto member, auto destroyedSlot, auto changedSignal>
|
|
||||||
class SimpleObjectHandleOps {
|
|
||||||
using Traits = MemberPointerTraits<decltype(member)>;
|
|
||||||
|
|
||||||
public:
|
|
||||||
static bool setObject(Traits::Class* parent, Traits::Type value) {
|
|
||||||
if (value == parent->*member) return false;
|
|
||||||
|
|
||||||
if (parent->*member != nullptr) {
|
|
||||||
QObject::disconnect(parent->*member, &QObject::destroyed, parent, destroyedSlot);
|
|
||||||
}
|
|
||||||
|
|
||||||
parent->*member = value;
|
|
||||||
|
|
||||||
if (value != nullptr) {
|
|
||||||
QObject::connect(parent->*member, &QObject::destroyed, parent, destroyedSlot);
|
|
||||||
}
|
|
||||||
|
|
||||||
if constexpr (changedSignal != nullptr) {
|
|
||||||
emit(parent->*changedSignal)();
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
template <auto member, auto destroyedSlot, auto changedSignal = nullptr>
|
|
||||||
bool setSimpleObjectHandle(auto* parent, auto* value) {
|
|
||||||
return SimpleObjectHandleOps<member, destroyedSlot, changedSignal>::setObject(parent, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
template <auto methodPtr>
|
template <auto methodPtr>
|
||||||
class MethodFunctor {
|
class MethodFunctor {
|
||||||
using PtrMeta = MemberPointerTraits<decltype(methodPtr)>;
|
using PtrMeta = MemberPointerTraits<decltype(methodPtr)>;
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,6 @@
|
||||||
#include <spa/utils/json.h>
|
#include <spa/utils/json.h>
|
||||||
|
|
||||||
#include "../../core/logcat.hpp"
|
#include "../../core/logcat.hpp"
|
||||||
#include "../../core/util.hpp"
|
|
||||||
#include "metadata.hpp"
|
#include "metadata.hpp"
|
||||||
#include "node.hpp"
|
#include "node.hpp"
|
||||||
#include "registry.hpp"
|
#include "registry.hpp"
|
||||||
|
|
@ -138,32 +137,6 @@ void PwDefaultTracker::onNodeAdded(PwNode* node) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void PwDefaultTracker::onNodeDestroyed(QObject* node) {
|
|
||||||
if (node == this->mDefaultSink) {
|
|
||||||
qCInfo(logDefaults) << "Default sink destroyed.";
|
|
||||||
this->mDefaultSink = nullptr;
|
|
||||||
emit this->defaultSinkChanged();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (node == this->mDefaultSource) {
|
|
||||||
qCInfo(logDefaults) << "Default source destroyed.";
|
|
||||||
this->mDefaultSource = nullptr;
|
|
||||||
emit this->defaultSourceChanged();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (node == this->mDefaultConfiguredSink) {
|
|
||||||
qCInfo(logDefaults) << "Default configured sink destroyed.";
|
|
||||||
this->mDefaultConfiguredSink = nullptr;
|
|
||||||
emit this->defaultConfiguredSinkChanged();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (node == this->mDefaultConfiguredSource) {
|
|
||||||
qCInfo(logDefaults) << "Default configured source destroyed.";
|
|
||||||
this->mDefaultConfiguredSource = nullptr;
|
|
||||||
emit this->defaultConfiguredSourceChanged();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void PwDefaultTracker::changeConfiguredSink(PwNode* node) {
|
void PwDefaultTracker::changeConfiguredSink(PwNode* node) {
|
||||||
if (node != nullptr) {
|
if (node != nullptr) {
|
||||||
if (!node->type.testFlags(PwNodeType::AudioSink)) {
|
if (!node->type.testFlags(PwNodeType::AudioSink)) {
|
||||||
|
|
@ -240,10 +213,23 @@ void PwDefaultTracker::setDefaultSink(PwNode* node) {
|
||||||
if (node == this->mDefaultSink) return;
|
if (node == this->mDefaultSink) return;
|
||||||
qCInfo(logDefaults) << "Default sink changed to" << node;
|
qCInfo(logDefaults) << "Default sink changed to" << node;
|
||||||
|
|
||||||
setSimpleObjectHandle<
|
if (this->mDefaultSink != nullptr) {
|
||||||
&PwDefaultTracker::mDefaultSink,
|
QObject::disconnect(this->mDefaultSink, nullptr, this, nullptr);
|
||||||
&PwDefaultTracker::onNodeDestroyed,
|
}
|
||||||
&PwDefaultTracker::defaultSinkChanged>(this, node);
|
|
||||||
|
this->mDefaultSink = node;
|
||||||
|
|
||||||
|
if (node != nullptr) {
|
||||||
|
QObject::connect(node, &QObject::destroyed, this, &PwDefaultTracker::onDefaultSinkDestroyed);
|
||||||
|
}
|
||||||
|
|
||||||
|
emit this->defaultSinkChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
void PwDefaultTracker::onDefaultSinkDestroyed() {
|
||||||
|
qCInfo(logDefaults) << "Default sink destroyed.";
|
||||||
|
this->mDefaultSink = nullptr;
|
||||||
|
emit this->defaultSinkChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
void PwDefaultTracker::setDefaultSinkName(const QString& name) {
|
void PwDefaultTracker::setDefaultSinkName(const QString& name) {
|
||||||
|
|
@ -257,10 +243,23 @@ void PwDefaultTracker::setDefaultSource(PwNode* node) {
|
||||||
if (node == this->mDefaultSource) return;
|
if (node == this->mDefaultSource) return;
|
||||||
qCInfo(logDefaults) << "Default source changed to" << node;
|
qCInfo(logDefaults) << "Default source changed to" << node;
|
||||||
|
|
||||||
setSimpleObjectHandle<
|
if (this->mDefaultSource != nullptr) {
|
||||||
&PwDefaultTracker::mDefaultSource,
|
QObject::disconnect(this->mDefaultSource, nullptr, this, nullptr);
|
||||||
&PwDefaultTracker::onNodeDestroyed,
|
}
|
||||||
&PwDefaultTracker::defaultSourceChanged>(this, node);
|
|
||||||
|
this->mDefaultSource = node;
|
||||||
|
|
||||||
|
if (node != nullptr) {
|
||||||
|
QObject::connect(node, &QObject::destroyed, this, &PwDefaultTracker::onDefaultSourceDestroyed);
|
||||||
|
}
|
||||||
|
|
||||||
|
emit this->defaultSourceChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
void PwDefaultTracker::onDefaultSourceDestroyed() {
|
||||||
|
qCInfo(logDefaults) << "Default source destroyed.";
|
||||||
|
this->mDefaultSource = nullptr;
|
||||||
|
emit this->defaultSourceChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
void PwDefaultTracker::setDefaultSourceName(const QString& name) {
|
void PwDefaultTracker::setDefaultSourceName(const QString& name) {
|
||||||
|
|
@ -274,10 +273,28 @@ void PwDefaultTracker::setDefaultConfiguredSink(PwNode* node) {
|
||||||
if (node == this->mDefaultConfiguredSink) return;
|
if (node == this->mDefaultConfiguredSink) return;
|
||||||
qCInfo(logDefaults) << "Default configured sink changed to" << node;
|
qCInfo(logDefaults) << "Default configured sink changed to" << node;
|
||||||
|
|
||||||
setSimpleObjectHandle<
|
if (this->mDefaultConfiguredSink != nullptr) {
|
||||||
&PwDefaultTracker::mDefaultConfiguredSink,
|
QObject::disconnect(this->mDefaultConfiguredSink, nullptr, this, nullptr);
|
||||||
&PwDefaultTracker::onNodeDestroyed,
|
}
|
||||||
&PwDefaultTracker::defaultConfiguredSinkChanged>(this, node);
|
|
||||||
|
this->mDefaultConfiguredSink = node;
|
||||||
|
|
||||||
|
if (node != nullptr) {
|
||||||
|
QObject::connect(
|
||||||
|
node,
|
||||||
|
&QObject::destroyed,
|
||||||
|
this,
|
||||||
|
&PwDefaultTracker::onDefaultConfiguredSinkDestroyed
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
emit this->defaultConfiguredSinkChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
void PwDefaultTracker::onDefaultConfiguredSinkDestroyed() {
|
||||||
|
qCInfo(logDefaults) << "Default configured sink destroyed.";
|
||||||
|
this->mDefaultConfiguredSink = nullptr;
|
||||||
|
emit this->defaultConfiguredSinkChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
void PwDefaultTracker::setDefaultConfiguredSinkName(const QString& name) {
|
void PwDefaultTracker::setDefaultConfiguredSinkName(const QString& name) {
|
||||||
|
|
@ -291,10 +308,28 @@ void PwDefaultTracker::setDefaultConfiguredSource(PwNode* node) {
|
||||||
if (node == this->mDefaultConfiguredSource) return;
|
if (node == this->mDefaultConfiguredSource) return;
|
||||||
qCInfo(logDefaults) << "Default configured source changed to" << node;
|
qCInfo(logDefaults) << "Default configured source changed to" << node;
|
||||||
|
|
||||||
setSimpleObjectHandle<
|
if (this->mDefaultConfiguredSource != nullptr) {
|
||||||
&PwDefaultTracker::mDefaultConfiguredSource,
|
QObject::disconnect(this->mDefaultConfiguredSource, nullptr, this, nullptr);
|
||||||
&PwDefaultTracker::onNodeDestroyed,
|
}
|
||||||
&PwDefaultTracker::defaultConfiguredSourceChanged>(this, node);
|
|
||||||
|
this->mDefaultConfiguredSource = node;
|
||||||
|
|
||||||
|
if (node != nullptr) {
|
||||||
|
QObject::connect(
|
||||||
|
node,
|
||||||
|
&QObject::destroyed,
|
||||||
|
this,
|
||||||
|
&PwDefaultTracker::onDefaultConfiguredSourceDestroyed
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
emit this->defaultConfiguredSourceChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
void PwDefaultTracker::onDefaultConfiguredSourceDestroyed() {
|
||||||
|
qCInfo(logDefaults) << "Default configured source destroyed.";
|
||||||
|
this->mDefaultConfiguredSource = nullptr;
|
||||||
|
emit this->defaultConfiguredSourceChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
void PwDefaultTracker::setDefaultConfiguredSourceName(const QString& name) {
|
void PwDefaultTracker::setDefaultConfiguredSourceName(const QString& name) {
|
||||||
|
|
|
||||||
|
|
@ -44,7 +44,10 @@ private slots:
|
||||||
void onMetadataAdded(PwMetadata* metadata);
|
void onMetadataAdded(PwMetadata* metadata);
|
||||||
void onMetadataProperty(const char* key, const char* type, const char* value);
|
void onMetadataProperty(const char* key, const char* type, const char* value);
|
||||||
void onNodeAdded(PwNode* node);
|
void onNodeAdded(PwNode* node);
|
||||||
void onNodeDestroyed(QObject* node);
|
void onDefaultSinkDestroyed();
|
||||||
|
void onDefaultSourceDestroyed();
|
||||||
|
void onDefaultConfiguredSinkDestroyed();
|
||||||
|
void onDefaultConfiguredSourceDestroyed();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void setDefaultSink(PwNode* node);
|
void setDefaultSink(PwNode* node);
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,8 @@
|
||||||
find_package(PkgConfig REQUIRED)
|
find_package(PkgConfig REQUIRED)
|
||||||
pkg_check_modules(dmabuf-deps REQUIRED IMPORTED_TARGET libdrm gbm egl)
|
pkg_check_modules(dmabuf-deps REQUIRED IMPORTED_TARGET libdrm gbm egl)
|
||||||
|
|
||||||
|
find_package(VulkanHeaders REQUIRED)
|
||||||
|
|
||||||
qt_add_library(quickshell-wayland-buffer STATIC
|
qt_add_library(quickshell-wayland-buffer STATIC
|
||||||
manager.cpp
|
manager.cpp
|
||||||
dmabuf.cpp
|
dmabuf.cpp
|
||||||
|
|
@ -10,9 +12,10 @@ qt_add_library(quickshell-wayland-buffer STATIC
|
||||||
wl_proto(wlp-linux-dmabuf linux-dmabuf-v1 "${WAYLAND_PROTOCOLS}/stable/linux-dmabuf")
|
wl_proto(wlp-linux-dmabuf linux-dmabuf-v1 "${WAYLAND_PROTOCOLS}/stable/linux-dmabuf")
|
||||||
|
|
||||||
target_link_libraries(quickshell-wayland-buffer PRIVATE
|
target_link_libraries(quickshell-wayland-buffer PRIVATE
|
||||||
Qt::Quick Qt::WaylandClient Qt::WaylandClientPrivate wayland-client
|
Qt::Quick Qt::QuickPrivate Qt::GuiPrivate Qt::WaylandClient Qt::WaylandClientPrivate wayland-client
|
||||||
PkgConfig::dmabuf-deps
|
PkgConfig::dmabuf-deps
|
||||||
wlp-linux-dmabuf
|
wlp-linux-dmabuf
|
||||||
|
Vulkan::Headers
|
||||||
)
|
)
|
||||||
|
|
||||||
qs_pch(quickshell-wayland-buffer SET large)
|
qs_pch(quickshell-wayland-buffer SET large)
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,8 @@
|
||||||
#include <fcntl.h>
|
#include <fcntl.h>
|
||||||
#include <gbm.h>
|
#include <gbm.h>
|
||||||
#include <libdrm/drm_fourcc.h>
|
#include <libdrm/drm_fourcc.h>
|
||||||
|
#include <private/qquickwindow_p.h>
|
||||||
|
#include <private/qrhivulkan_p.h>
|
||||||
#include <qcontainerfwd.h>
|
#include <qcontainerfwd.h>
|
||||||
#include <qdebug.h>
|
#include <qdebug.h>
|
||||||
#include <qlist.h>
|
#include <qlist.h>
|
||||||
|
|
@ -24,12 +26,17 @@
|
||||||
#include <qpair.h>
|
#include <qpair.h>
|
||||||
#include <qquickwindow.h>
|
#include <qquickwindow.h>
|
||||||
#include <qscopedpointer.h>
|
#include <qscopedpointer.h>
|
||||||
|
#include <qsgrendererinterface.h>
|
||||||
#include <qsgtexture_platform.h>
|
#include <qsgtexture_platform.h>
|
||||||
|
#include <qtypes.h>
|
||||||
|
#include <qvulkanfunctions.h>
|
||||||
|
#include <qvulkaninstance.h>
|
||||||
#include <qwayland-linux-dmabuf-v1.h>
|
#include <qwayland-linux-dmabuf-v1.h>
|
||||||
#include <qwaylandclientextension.h>
|
#include <qwaylandclientextension.h>
|
||||||
#include <sys/mman.h>
|
#include <sys/mman.h>
|
||||||
#include <sys/types.h>
|
#include <sys/types.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
|
#include <vulkan/vulkan_core.h>
|
||||||
#include <wayland-client-protocol.h>
|
#include <wayland-client-protocol.h>
|
||||||
#include <wayland-linux-dmabuf-v1-client-protocol.h>
|
#include <wayland-linux-dmabuf-v1-client-protocol.h>
|
||||||
#include <wayland-util.h>
|
#include <wayland-util.h>
|
||||||
|
|
@ -48,6 +55,36 @@ QS_LOGGING_CATEGORY(logDmabuf, "quickshell.wayland.buffer.dmabuf", QtWarningMsg)
|
||||||
|
|
||||||
LinuxDmabufManager* MANAGER = nullptr; // NOLINT
|
LinuxDmabufManager* MANAGER = nullptr; // NOLINT
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
bool drmFormatHasAlpha(uint32_t drmFormat) {
|
||||||
|
switch (drmFormat) {
|
||||||
|
case DRM_FORMAT_ARGB8888:
|
||||||
|
case DRM_FORMAT_ABGR8888:
|
||||||
|
case DRM_FORMAT_ARGB2101010:
|
||||||
|
case DRM_FORMAT_ABGR2101010:
|
||||||
|
case DRM_FORMAT_ABGR16161616F: return true;
|
||||||
|
default: return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
QDebug& operator<<(QDebug& debug, const FourCCStr& fourcc) {
|
QDebug& operator<<(QDebug& debug, const FourCCStr& fourcc) {
|
||||||
|
|
@ -81,25 +118,27 @@ GbmDeviceHandle::~GbmDeviceHandle() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// This will definitely backfire later
|
// Prefer ARGB over XRGB: XRGB has undefined alpha bytes which cause
|
||||||
|
// transparency artifacts on Vulkan (notably Intel GPUs) since Vulkan
|
||||||
|
// doesn't auto-fill alpha=1.0 for X formats like EGL does.
|
||||||
void LinuxDmabufFormatSelection::ensureSorted() {
|
void LinuxDmabufFormatSelection::ensureSorted() {
|
||||||
if (this->sorted) return;
|
if (this->sorted) return;
|
||||||
auto beginIter = this->formats.begin();
|
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) {
|
auto argbIter = std::ranges::find_if(this->formats, [](const auto& format) {
|
||||||
return format.first == DRM_FORMAT_ARGB8888;
|
return format.first == DRM_FORMAT_ARGB8888;
|
||||||
});
|
});
|
||||||
|
|
||||||
if (argbIter != this->formats.end()) std::swap(*beginIter, *argbIter);
|
if (argbIter != this->formats.end()) {
|
||||||
|
std::swap(*beginIter, *argbIter);
|
||||||
|
++beginIter;
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
this->sorted = true;
|
this->sorted = true;
|
||||||
}
|
}
|
||||||
|
|
@ -532,6 +571,15 @@ bool WlDmaBuffer::isCompatible(const WlBufferRequest& request) const {
|
||||||
}
|
}
|
||||||
|
|
||||||
WlBufferQSGTexture* WlDmaBuffer::createQsgTexture(QQuickWindow* window) const {
|
WlBufferQSGTexture* WlDmaBuffer::createQsgTexture(QQuickWindow* window) const {
|
||||||
|
auto* ri = window->rendererInterface();
|
||||||
|
if (ri && ri->graphicsApi() == QSGRendererInterface::Vulkan) {
|
||||||
|
return this->createQsgTextureVulkan(window);
|
||||||
|
}
|
||||||
|
|
||||||
|
return this->createQsgTextureGl(window);
|
||||||
|
}
|
||||||
|
|
||||||
|
WlBufferQSGTexture* WlDmaBuffer::createQsgTextureGl(QQuickWindow* window) const {
|
||||||
static auto* glEGLImageTargetTexture2DOES = []() {
|
static auto* glEGLImageTargetTexture2DOES = []() {
|
||||||
auto* fn = reinterpret_cast<PFNGLEGLIMAGETARGETTEXTURE2DOESPROC>(
|
auto* fn = reinterpret_cast<PFNGLEGLIMAGETARGETTEXTURE2DOESPROC>(
|
||||||
eglGetProcAddress("glEGLImageTargetTexture2DOES")
|
eglGetProcAddress("glEGLImageTargetTexture2DOES")
|
||||||
|
|
@ -662,6 +710,313 @@ WlBufferQSGTexture* WlDmaBuffer::createQsgTexture(QQuickWindow* window) const {
|
||||||
return tex;
|
return tex;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
WlBufferQSGTexture* WlDmaBuffer::createQsgTextureVulkan(QQuickWindow* window) const {
|
||||||
|
auto* ri = window->rendererInterface();
|
||||||
|
auto* vkInst = window->vulkanInstance();
|
||||||
|
|
||||||
|
if (!vkInst) {
|
||||||
|
qCWarning(logDmabuf) << "Failed to create Vulkan QSG texture: no QVulkanInstance.";
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto* vkDevicePtr =
|
||||||
|
static_cast<VkDevice*>(ri->getResource(window, QSGRendererInterface::DeviceResource));
|
||||||
|
auto* vkPhysDevicePtr = static_cast<VkPhysicalDevice*>(
|
||||||
|
ri->getResource(window, QSGRendererInterface::PhysicalDeviceResource)
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!vkDevicePtr || !vkPhysDevicePtr) {
|
||||||
|
qCWarning(logDmabuf) << "Failed to create Vulkan QSG texture: could not get Vulkan device.";
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
VkDevice device = *vkDevicePtr;
|
||||||
|
VkPhysicalDevice physDevice = *vkPhysDevicePtr;
|
||||||
|
|
||||||
|
auto* devFuncs = vkInst->deviceFunctions(device);
|
||||||
|
auto* instFuncs = vkInst->functions();
|
||||||
|
|
||||||
|
if (!devFuncs || !instFuncs) {
|
||||||
|
qCWarning(logDmabuf) << "Failed to create Vulkan QSG texture: "
|
||||||
|
"could not get Vulkan functions.";
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto getMemoryFdPropertiesKHR = reinterpret_cast<PFN_vkGetMemoryFdPropertiesKHR>(
|
||||||
|
instFuncs->vkGetDeviceProcAddr(device, "vkGetMemoryFdPropertiesKHR")
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!getMemoryFdPropertiesKHR) {
|
||||||
|
qCWarning(logDmabuf) << "Failed to create Vulkan QSG texture: "
|
||||||
|
"vkGetMemoryFdPropertiesKHR not available. "
|
||||||
|
"Missing VK_KHR_external_memory_fd extension.";
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
const VkFormat vkFormat = drmFormatToVkFormat(this->format);
|
||||||
|
if (vkFormat == VK_FORMAT_UNDEFINED) {
|
||||||
|
qCWarning(logDmabuf) << "Failed to create Vulkan QSG texture: unsupported DRM format"
|
||||||
|
<< FourCCStr(this->format);
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this->planeCount > 4) {
|
||||||
|
qCWarning(logDmabuf) << "Failed to create Vulkan QSG texture: too many planes"
|
||||||
|
<< this->planeCount;
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::array<VkSubresourceLayout, 4> planeLayouts = {};
|
||||||
|
for (int i = 0; i < this->planeCount; ++i) {
|
||||||
|
planeLayouts[i].offset = this->planes[i].offset; // NOLINT
|
||||||
|
planeLayouts[i].rowPitch = this->planes[i].stride; // NOLINT
|
||||||
|
planeLayouts[i].size = 0;
|
||||||
|
planeLayouts[i].arrayPitch = 0;
|
||||||
|
planeLayouts[i].depthPitch = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
const bool useModifier = this->modifier != DRM_FORMAT_MOD_INVALID;
|
||||||
|
|
||||||
|
VkExternalMemoryImageCreateInfo externalInfo = {};
|
||||||
|
externalInfo.sType = VK_STRUCTURE_TYPE_EXTERNAL_MEMORY_IMAGE_CREATE_INFO;
|
||||||
|
externalInfo.handleTypes = VK_EXTERNAL_MEMORY_HANDLE_TYPE_DMA_BUF_BIT_EXT;
|
||||||
|
|
||||||
|
VkImageDrmFormatModifierExplicitCreateInfoEXT modifierInfo = {};
|
||||||
|
modifierInfo.sType = VK_STRUCTURE_TYPE_IMAGE_DRM_FORMAT_MODIFIER_EXPLICIT_CREATE_INFO_EXT;
|
||||||
|
modifierInfo.drmFormatModifier = this->modifier;
|
||||||
|
modifierInfo.drmFormatModifierPlaneCount = static_cast<uint32_t>(this->planeCount);
|
||||||
|
modifierInfo.pPlaneLayouts = planeLayouts.data();
|
||||||
|
|
||||||
|
if (useModifier) {
|
||||||
|
externalInfo.pNext = &modifierInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
VkImageCreateInfo imageInfo = {};
|
||||||
|
imageInfo.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO;
|
||||||
|
imageInfo.pNext = &externalInfo;
|
||||||
|
imageInfo.imageType = VK_IMAGE_TYPE_2D;
|
||||||
|
imageInfo.format = vkFormat;
|
||||||
|
imageInfo.extent = {.width = this->width, .height = this->height, .depth = 1};
|
||||||
|
imageInfo.mipLevels = 1;
|
||||||
|
imageInfo.arrayLayers = 1;
|
||||||
|
imageInfo.samples = VK_SAMPLE_COUNT_1_BIT;
|
||||||
|
imageInfo.tiling = useModifier ? VK_IMAGE_TILING_DRM_FORMAT_MODIFIER_EXT : VK_IMAGE_TILING_LINEAR;
|
||||||
|
imageInfo.usage = VK_IMAGE_USAGE_SAMPLED_BIT;
|
||||||
|
imageInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
|
||||||
|
imageInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
|
||||||
|
|
||||||
|
VkImage image = VK_NULL_HANDLE;
|
||||||
|
VkResult result = devFuncs->vkCreateImage(device, &imageInfo, nullptr, &image);
|
||||||
|
if (result != VK_SUCCESS) {
|
||||||
|
qCWarning(logDmabuf) << "Failed to create VkImage for DMA-BUF import, result:" << result;
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
VkDeviceMemory memory = VK_NULL_HANDLE;
|
||||||
|
|
||||||
|
// dup() is required because vkAllocateMemory with VK_EXTERNAL_MEMORY_HANDLE_TYPE_DMA_BUF_BIT_EXT
|
||||||
|
// takes ownership of the fd on succcess. Without dup, WlDmaBuffer would double-close.
|
||||||
|
const int dupFd =
|
||||||
|
dup(this->planes[0].fd); // NOLINT(cppcoreguidelines-pro-bounds-pointer-arithmetic)
|
||||||
|
if (dupFd < 0) {
|
||||||
|
qCWarning(logDmabuf) << "Failed to dup() fd for DMA-BUF import";
|
||||||
|
goto cleanup_fail; // NOLINT
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
VkMemoryRequirements memReqs = {};
|
||||||
|
devFuncs->vkGetImageMemoryRequirements(device, image, &memReqs);
|
||||||
|
|
||||||
|
VkMemoryFdPropertiesKHR fdProps = {};
|
||||||
|
fdProps.sType = VK_STRUCTURE_TYPE_MEMORY_FD_PROPERTIES_KHR;
|
||||||
|
|
||||||
|
result = getMemoryFdPropertiesKHR(
|
||||||
|
device,
|
||||||
|
VK_EXTERNAL_MEMORY_HANDLE_TYPE_DMA_BUF_BIT_EXT,
|
||||||
|
dupFd,
|
||||||
|
&fdProps
|
||||||
|
);
|
||||||
|
|
||||||
|
if (result != VK_SUCCESS) {
|
||||||
|
close(dupFd);
|
||||||
|
qCWarning(logDmabuf) << "vkGetMemoryFdPropertiesKHR failed, result:" << result;
|
||||||
|
goto cleanup_fail; // NOLINT
|
||||||
|
}
|
||||||
|
|
||||||
|
const uint32_t memTypeBits = memReqs.memoryTypeBits & fdProps.memoryTypeBits;
|
||||||
|
|
||||||
|
VkPhysicalDeviceMemoryProperties memProps = {};
|
||||||
|
instFuncs->vkGetPhysicalDeviceMemoryProperties(physDevice, &memProps);
|
||||||
|
|
||||||
|
uint32_t memTypeIndex = UINT32_MAX;
|
||||||
|
for (uint32_t j = 0; j < memProps.memoryTypeCount; ++j) {
|
||||||
|
if (memTypeBits & (1u << j)) {
|
||||||
|
memTypeIndex = j;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (memTypeIndex == UINT32_MAX) {
|
||||||
|
close(dupFd);
|
||||||
|
qCWarning(logDmabuf) << "No compatible memory type for DMA-BUF import";
|
||||||
|
goto cleanup_fail; // NOLINT
|
||||||
|
}
|
||||||
|
|
||||||
|
VkImportMemoryFdInfoKHR importInfo = {};
|
||||||
|
importInfo.sType = VK_STRUCTURE_TYPE_IMPORT_MEMORY_FD_INFO_KHR;
|
||||||
|
importInfo.handleType = VK_EXTERNAL_MEMORY_HANDLE_TYPE_DMA_BUF_BIT_EXT;
|
||||||
|
importInfo.fd = dupFd;
|
||||||
|
|
||||||
|
VkMemoryDedicatedAllocateInfo dedicatedInfo = {};
|
||||||
|
dedicatedInfo.sType = VK_STRUCTURE_TYPE_MEMORY_DEDICATED_ALLOCATE_INFO;
|
||||||
|
dedicatedInfo.image = image;
|
||||||
|
dedicatedInfo.pNext = &importInfo;
|
||||||
|
|
||||||
|
VkMemoryAllocateInfo allocInfo = {};
|
||||||
|
allocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
|
||||||
|
allocInfo.pNext = &dedicatedInfo;
|
||||||
|
allocInfo.allocationSize = memReqs.size;
|
||||||
|
allocInfo.memoryTypeIndex = memTypeIndex;
|
||||||
|
|
||||||
|
result = devFuncs->vkAllocateMemory(device, &allocInfo, nullptr, &memory);
|
||||||
|
if (result != VK_SUCCESS) {
|
||||||
|
close(dupFd);
|
||||||
|
qCWarning(logDmabuf) << "vkAllocateMemory failed, result:" << result;
|
||||||
|
goto cleanup_fail; // NOLINT
|
||||||
|
}
|
||||||
|
|
||||||
|
result = devFuncs->vkBindImageMemory(device, image, memory, 0);
|
||||||
|
if (result != VK_SUCCESS) {
|
||||||
|
qCWarning(logDmabuf) << "vkBindImageMemory failed, result:" << result;
|
||||||
|
goto cleanup_fail; // NOLINT
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
// acquire the DMA-BUF from the foreign (compositor) queue and transition
|
||||||
|
// to shader-read layout. oldLayout must be GENERAL (not UNDEFINED) to
|
||||||
|
// preserve the DMA-BUF contents written by the external producer. Hopefully.
|
||||||
|
window->beginExternalCommands();
|
||||||
|
|
||||||
|
auto* cmdBufPtr = static_cast<VkCommandBuffer*>(
|
||||||
|
ri->getResource(window, QSGRendererInterface::CommandListResource)
|
||||||
|
);
|
||||||
|
|
||||||
|
if (cmdBufPtr && *cmdBufPtr) {
|
||||||
|
VkCommandBuffer cmdBuf = *cmdBufPtr;
|
||||||
|
|
||||||
|
// find the graphics queue family index for the ownrship transfer.
|
||||||
|
uint32_t graphicsQueueFamily = 0;
|
||||||
|
uint32_t queueFamilyCount = 0;
|
||||||
|
instFuncs->vkGetPhysicalDeviceQueueFamilyProperties(physDevice, &queueFamilyCount, nullptr);
|
||||||
|
std::vector<VkQueueFamilyProperties> queueFamilies(queueFamilyCount);
|
||||||
|
instFuncs->vkGetPhysicalDeviceQueueFamilyProperties(
|
||||||
|
physDevice,
|
||||||
|
&queueFamilyCount,
|
||||||
|
queueFamilies.data()
|
||||||
|
);
|
||||||
|
for (uint32_t i = 0; i < queueFamilyCount; ++i) {
|
||||||
|
if (queueFamilies[i].queueFlags & VK_QUEUE_GRAPHICS_BIT) {
|
||||||
|
graphicsQueueFamily = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
VkImageMemoryBarrier barrier = {};
|
||||||
|
barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
|
||||||
|
barrier.oldLayout = VK_IMAGE_LAYOUT_GENERAL;
|
||||||
|
barrier.newLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
|
||||||
|
barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_FOREIGN_EXT;
|
||||||
|
barrier.dstQueueFamilyIndex = graphicsQueueFamily;
|
||||||
|
barrier.image = image;
|
||||||
|
barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
|
||||||
|
barrier.subresourceRange.baseMipLevel = 0;
|
||||||
|
barrier.subresourceRange.levelCount = 1;
|
||||||
|
barrier.subresourceRange.baseArrayLayer = 0;
|
||||||
|
barrier.subresourceRange.layerCount = 1;
|
||||||
|
barrier.srcAccessMask = 0;
|
||||||
|
barrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT;
|
||||||
|
|
||||||
|
devFuncs->vkCmdPipelineBarrier(
|
||||||
|
cmdBuf,
|
||||||
|
VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT,
|
||||||
|
VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
nullptr,
|
||||||
|
0,
|
||||||
|
nullptr,
|
||||||
|
1,
|
||||||
|
&barrier
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
window->endExternalCommands();
|
||||||
|
|
||||||
|
auto* qsgTexture = QQuickWindowPrivate::get(window)->createTextureFromNativeTexture(
|
||||||
|
reinterpret_cast<quint64>(image),
|
||||||
|
VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL,
|
||||||
|
static_cast<uint>(vkFormat),
|
||||||
|
QSize(static_cast<int>(this->width), static_cast<int>(this->height)),
|
||||||
|
{}
|
||||||
|
);
|
||||||
|
|
||||||
|
// For opaque DRM formats (XRGB, XBGR, etc.), the alpha bytes are underfined.
|
||||||
|
// EGL silently forces alpha=1.0 for these, but Vulkan doesn't. Replace Qt's
|
||||||
|
// default identity-swizzle VkImageView with one that maps alpha to ONE.
|
||||||
|
if (!drmFormatHasAlpha(this->format)) {
|
||||||
|
auto* vkTexture = static_cast<QVkTexture*>(qsgTexture->rhiTexture()); // NOLINT
|
||||||
|
|
||||||
|
devFuncs->vkDestroyImageView(device, vkTexture->imageView, nullptr);
|
||||||
|
|
||||||
|
VkImageViewCreateInfo viewInfo = {};
|
||||||
|
viewInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
|
||||||
|
viewInfo.image = image;
|
||||||
|
viewInfo.viewType = VK_IMAGE_VIEW_TYPE_2D;
|
||||||
|
viewInfo.format = vkFormat;
|
||||||
|
viewInfo.components.r = VK_COMPONENT_SWIZZLE_IDENTITY;
|
||||||
|
viewInfo.components.g = VK_COMPONENT_SWIZZLE_IDENTITY;
|
||||||
|
viewInfo.components.b = VK_COMPONENT_SWIZZLE_IDENTITY;
|
||||||
|
viewInfo.components.a = VK_COMPONENT_SWIZZLE_ONE;
|
||||||
|
viewInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
|
||||||
|
viewInfo.subresourceRange.levelCount = 1;
|
||||||
|
viewInfo.subresourceRange.layerCount = 1;
|
||||||
|
|
||||||
|
result = devFuncs->vkCreateImageView(device, &viewInfo, nullptr, &vkTexture->imageView);
|
||||||
|
if (result != VK_SUCCESS) {
|
||||||
|
qCWarning(logDmabuf) << "Failed to create alpha-swizzled VkImageView, result:" << result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
auto* tex = new WlDmaBufferVulkanQSGTexture(devFuncs, device, image, memory, qsgTexture);
|
||||||
|
qCDebug(logDmabuf) << "Created WlDmaBufferVulkanQSGTexture" << tex << "from" << this;
|
||||||
|
return tex;
|
||||||
|
}
|
||||||
|
|
||||||
|
cleanup_fail:
|
||||||
|
if (image != VK_NULL_HANDLE) {
|
||||||
|
devFuncs->vkDestroyImage(device, image, nullptr);
|
||||||
|
}
|
||||||
|
if (memory != VK_NULL_HANDLE) {
|
||||||
|
devFuncs->vkFreeMemory(device, memory, nullptr);
|
||||||
|
}
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
WlDmaBufferVulkanQSGTexture::~WlDmaBufferVulkanQSGTexture() {
|
||||||
|
delete this->qsgTexture;
|
||||||
|
|
||||||
|
if (this->image != VK_NULL_HANDLE) {
|
||||||
|
this->devFuncs->vkDestroyImage(this->device, this->image, nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this->memory != VK_NULL_HANDLE) {
|
||||||
|
this->devFuncs->vkFreeMemory(this->device, this->memory, nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
qCDebug(logDmabuf) << "WlDmaBufferVulkanQSGTexture" << this << "destroyed.";
|
||||||
|
}
|
||||||
|
|
||||||
WlDmaBufferQSGTexture::~WlDmaBufferQSGTexture() {
|
WlDmaBufferQSGTexture::~WlDmaBufferQSGTexture() {
|
||||||
auto* context = QOpenGLContext::currentContext();
|
auto* context = QOpenGLContext::currentContext();
|
||||||
auto* display = context->nativeInterface<QNativeInterface::QEGLContext>()->display();
|
auto* display = context->nativeInterface<QNativeInterface::QEGLContext>()->display();
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <array>
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
|
|
||||||
#include <EGL/egl.h>
|
#include <EGL/egl.h>
|
||||||
|
|
@ -12,9 +13,11 @@
|
||||||
#include <qsize.h>
|
#include <qsize.h>
|
||||||
#include <qtclasshelpermacros.h>
|
#include <qtclasshelpermacros.h>
|
||||||
#include <qtypes.h>
|
#include <qtypes.h>
|
||||||
|
#include <qvulkanfunctions.h>
|
||||||
#include <qwayland-linux-dmabuf-v1.h>
|
#include <qwayland-linux-dmabuf-v1.h>
|
||||||
#include <qwaylandclientextension.h>
|
#include <qwaylandclientextension.h>
|
||||||
#include <sys/types.h>
|
#include <sys/types.h>
|
||||||
|
#include <vulkan/vulkan_core.h>
|
||||||
#include <wayland-linux-dmabuf-v1-client-protocol.h>
|
#include <wayland-linux-dmabuf-v1-client-protocol.h>
|
||||||
#include <wayland-util.h>
|
#include <wayland-util.h>
|
||||||
#include <xf86drm.h>
|
#include <xf86drm.h>
|
||||||
|
|
@ -114,6 +117,36 @@ private:
|
||||||
friend class WlDmaBuffer;
|
friend class WlDmaBuffer;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class WlDmaBufferVulkanQSGTexture: public WlBufferQSGTexture {
|
||||||
|
public:
|
||||||
|
~WlDmaBufferVulkanQSGTexture() override;
|
||||||
|
Q_DISABLE_COPY_MOVE(WlDmaBufferVulkanQSGTexture);
|
||||||
|
|
||||||
|
[[nodiscard]] QSGTexture* texture() const override { return this->qsgTexture; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
WlDmaBufferVulkanQSGTexture(
|
||||||
|
QVulkanDeviceFunctions* devFuncs,
|
||||||
|
VkDevice device,
|
||||||
|
VkImage image,
|
||||||
|
VkDeviceMemory memory,
|
||||||
|
QSGTexture* qsgTexture
|
||||||
|
)
|
||||||
|
: devFuncs(devFuncs)
|
||||||
|
, device(device)
|
||||||
|
, image(image)
|
||||||
|
, memory(memory)
|
||||||
|
, qsgTexture(qsgTexture) {}
|
||||||
|
|
||||||
|
QVulkanDeviceFunctions* devFuncs = nullptr;
|
||||||
|
VkDevice device = VK_NULL_HANDLE;
|
||||||
|
VkImage image = VK_NULL_HANDLE;
|
||||||
|
VkDeviceMemory memory = VK_NULL_HANDLE;
|
||||||
|
QSGTexture* qsgTexture = nullptr;
|
||||||
|
|
||||||
|
friend class WlDmaBuffer;
|
||||||
|
};
|
||||||
|
|
||||||
class WlDmaBuffer: public WlBuffer {
|
class WlDmaBuffer: public WlBuffer {
|
||||||
public:
|
public:
|
||||||
~WlDmaBuffer() override;
|
~WlDmaBuffer() override;
|
||||||
|
|
@ -151,6 +184,9 @@ private:
|
||||||
|
|
||||||
friend class LinuxDmabufManager;
|
friend class LinuxDmabufManager;
|
||||||
friend QDebug& operator<<(QDebug& debug, const WlDmaBuffer* buffer);
|
friend QDebug& operator<<(QDebug& debug, const WlDmaBuffer* buffer);
|
||||||
|
|
||||||
|
[[nodiscard]] WlBufferQSGTexture* createQsgTextureGl(QQuickWindow* window) const;
|
||||||
|
[[nodiscard]] WlBufferQSGTexture* createQsgTextureVulkan(QQuickWindow* window) const;
|
||||||
};
|
};
|
||||||
|
|
||||||
QDebug& operator<<(QDebug& debug, const WlDmaBuffer* buffer);
|
QDebug& operator<<(QDebug& debug, const WlDmaBuffer* buffer);
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@
|
||||||
#include <qqmlcomponent.h>
|
#include <qqmlcomponent.h>
|
||||||
#include <qqmlengine.h>
|
#include <qqmlengine.h>
|
||||||
#include <qqmllist.h>
|
#include <qqmllist.h>
|
||||||
|
#include <qquickgraphicsconfiguration.h>
|
||||||
#include <qquickitem.h>
|
#include <qquickitem.h>
|
||||||
#include <qquickwindow.h>
|
#include <qquickwindow.h>
|
||||||
#include <qscreen.h>
|
#include <qscreen.h>
|
||||||
|
|
@ -216,6 +217,15 @@ void WlSessionLockSurface::onReload(QObject* oldInstance) {
|
||||||
|
|
||||||
if (this->window == nullptr) {
|
if (this->window == nullptr) {
|
||||||
this->window = new QQuickWindow();
|
this->window = new QQuickWindow();
|
||||||
|
|
||||||
|
// needed for vulkan dmabuf import, qt ignores these if not applicable
|
||||||
|
auto graphicsConfig = this->window->graphicsConfiguration();
|
||||||
|
graphicsConfig.setDeviceExtensions({
|
||||||
|
"VK_KHR_external_memory_fd",
|
||||||
|
"VK_EXT_external_memory_dma_buf",
|
||||||
|
"VK_EXT_image_drm_format_modifier",
|
||||||
|
});
|
||||||
|
this->window->setGraphicsConfiguration(graphicsConfig);
|
||||||
}
|
}
|
||||||
|
|
||||||
this->mContentItem->setParentItem(this->window->contentItem());
|
this->mContentItem->setParentItem(this->window->contentItem());
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,7 @@ QSWaylandSessionLockSurface::QSWaylandSessionLockSurface(QtWaylandClient::QWayla
|
||||||
wl_output* output = nullptr; // NOLINT (include)
|
wl_output* output = nullptr; // NOLINT (include)
|
||||||
auto* waylandScreen = dynamic_cast<QtWaylandClient::QWaylandScreen*>(qwindow->screen()->handle());
|
auto* waylandScreen = dynamic_cast<QtWaylandClient::QWaylandScreen*>(qwindow->screen()->handle());
|
||||||
|
|
||||||
if (waylandScreen != nullptr) {
|
if (waylandScreen != nullptr && !waylandScreen->isPlaceholder() && waylandScreen->output()) {
|
||||||
output = waylandScreen->output();
|
output = waylandScreen->output();
|
||||||
} else {
|
} else {
|
||||||
qFatal() << "Session lock screen does not corrospond to a real screen. Force closing window";
|
qFatal() << "Session lock screen does not corrospond to a real screen. Force closing window";
|
||||||
|
|
|
||||||
|
|
@ -143,11 +143,11 @@ LayerSurface::LayerSurface(LayerShellIntegration* shell, QtWaylandClient::QWayla
|
||||||
auto* waylandScreen =
|
auto* waylandScreen =
|
||||||
dynamic_cast<QtWaylandClient::QWaylandScreen*>(qwindow->screen()->handle());
|
dynamic_cast<QtWaylandClient::QWaylandScreen*>(qwindow->screen()->handle());
|
||||||
|
|
||||||
if (waylandScreen != nullptr) {
|
if (waylandScreen != nullptr && !waylandScreen->isPlaceholder() && waylandScreen->output()) {
|
||||||
output = waylandScreen->output();
|
output = waylandScreen->output();
|
||||||
} else {
|
} else {
|
||||||
qWarning()
|
qWarning()
|
||||||
<< "Layershell screen does not corrospond to a real screen. Letting the compositor pick.";
|
<< "Layershell screen does not correspond to a real screen. Letting the compositor pick.";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,7 @@
|
||||||
#include <qqmlengine.h>
|
#include <qqmlengine.h>
|
||||||
#include <qqmlinfo.h>
|
#include <qqmlinfo.h>
|
||||||
#include <qqmllist.h>
|
#include <qqmllist.h>
|
||||||
|
#include <qquickgraphicsconfiguration.h>
|
||||||
#include <qquickitem.h>
|
#include <qquickitem.h>
|
||||||
#include <qquickwindow.h>
|
#include <qquickwindow.h>
|
||||||
#include <qregion.h>
|
#include <qregion.h>
|
||||||
|
|
@ -147,6 +148,15 @@ void ProxyWindowBase::ensureQWindow() {
|
||||||
this->window = nullptr; // createQQuickWindow may indirectly reference this->window
|
this->window = nullptr; // createQQuickWindow may indirectly reference this->window
|
||||||
this->window = this->createQQuickWindow();
|
this->window = this->createQQuickWindow();
|
||||||
this->window->setFormat(format);
|
this->window->setFormat(format);
|
||||||
|
|
||||||
|
// needed for vulkan dmabuf import, qt ignores these if not applicable
|
||||||
|
auto graphicsConfig = this->window->graphicsConfiguration();
|
||||||
|
graphicsConfig.setDeviceExtensions({
|
||||||
|
"VK_KHR_external_memory_fd",
|
||||||
|
"VK_EXT_external_memory_dma_buf",
|
||||||
|
"VK_EXT_image_drm_format_modifier",
|
||||||
|
});
|
||||||
|
this->window->setGraphicsConfiguration(graphicsConfig);
|
||||||
}
|
}
|
||||||
|
|
||||||
void ProxyWindowBase::createWindow() {
|
void ProxyWindowBase::createWindow() {
|
||||||
|
|
@ -213,6 +223,7 @@ void ProxyWindowBase::completeWindow() {
|
||||||
this->trySetHeight(this->implicitHeight());
|
this->trySetHeight(this->implicitHeight());
|
||||||
this->setColor(this->mColor);
|
this->setColor(this->mColor);
|
||||||
this->updateMask();
|
this->updateMask();
|
||||||
|
QQuickWindowPrivate::get(this->window)->updatesEnabled = this->mUpdatesEnabled;
|
||||||
|
|
||||||
// notify initial / post-connection geometry
|
// notify initial / post-connection geometry
|
||||||
emit this->xChanged();
|
emit this->xChanged();
|
||||||
|
|
@ -469,6 +480,19 @@ void ProxyWindowBase::setSurfaceFormat(QsSurfaceFormat format) {
|
||||||
emit this->surfaceFormatChanged();
|
emit this->surfaceFormatChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool ProxyWindowBase::updatesEnabled() const { return this->mUpdatesEnabled; }
|
||||||
|
|
||||||
|
void ProxyWindowBase::setUpdatesEnabled(bool updatesEnabled) {
|
||||||
|
if (updatesEnabled == this->mUpdatesEnabled) return;
|
||||||
|
this->mUpdatesEnabled = updatesEnabled;
|
||||||
|
|
||||||
|
if (this->window != nullptr) {
|
||||||
|
QQuickWindowPrivate::get(this->window)->updatesEnabled = updatesEnabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
emit this->updatesEnabledChanged();
|
||||||
|
}
|
||||||
|
|
||||||
qreal ProxyWindowBase::devicePixelRatio() const {
|
qreal ProxyWindowBase::devicePixelRatio() const {
|
||||||
if (this->window != nullptr) return this->window->devicePixelRatio();
|
if (this->window != nullptr) return this->window->devicePixelRatio();
|
||||||
if (this->mScreen != nullptr) return this->mScreen->devicePixelRatio();
|
if (this->mScreen != nullptr) return this->mScreen->devicePixelRatio();
|
||||||
|
|
|
||||||
|
|
@ -57,6 +57,7 @@ class ProxyWindowBase: public Reloadable {
|
||||||
Q_PROPERTY(QObject* windowTransform READ windowTransform NOTIFY windowTransformChanged);
|
Q_PROPERTY(QObject* windowTransform READ windowTransform NOTIFY windowTransformChanged);
|
||||||
Q_PROPERTY(bool backingWindowVisible READ isVisibleDirect NOTIFY backerVisibilityChanged);
|
Q_PROPERTY(bool backingWindowVisible READ isVisibleDirect NOTIFY backerVisibilityChanged);
|
||||||
Q_PROPERTY(QsSurfaceFormat surfaceFormat READ surfaceFormat WRITE setSurfaceFormat NOTIFY surfaceFormatChanged);
|
Q_PROPERTY(QsSurfaceFormat surfaceFormat READ surfaceFormat WRITE setSurfaceFormat NOTIFY surfaceFormatChanged);
|
||||||
|
Q_PROPERTY(bool updatesEnabled READ updatesEnabled WRITE setUpdatesEnabled NOTIFY updatesEnabledChanged);
|
||||||
Q_PROPERTY(QQmlListProperty<QObject> data READ data);
|
Q_PROPERTY(QQmlListProperty<QObject> data READ data);
|
||||||
// clang-format on
|
// clang-format on
|
||||||
Q_CLASSINFO("DefaultProperty", "data");
|
Q_CLASSINFO("DefaultProperty", "data");
|
||||||
|
|
@ -140,6 +141,9 @@ public:
|
||||||
[[nodiscard]] QsSurfaceFormat surfaceFormat() const { return this->qsSurfaceFormat; }
|
[[nodiscard]] QsSurfaceFormat surfaceFormat() const { return this->qsSurfaceFormat; }
|
||||||
void setSurfaceFormat(QsSurfaceFormat format);
|
void setSurfaceFormat(QsSurfaceFormat format);
|
||||||
|
|
||||||
|
[[nodiscard]] bool updatesEnabled() const;
|
||||||
|
void setUpdatesEnabled(bool updatesEnabled);
|
||||||
|
|
||||||
[[nodiscard]] QObject* windowTransform() const { return nullptr; } // NOLINT
|
[[nodiscard]] QObject* windowTransform() const { return nullptr; } // NOLINT
|
||||||
|
|
||||||
[[nodiscard]] QQmlListProperty<QObject> data();
|
[[nodiscard]] QQmlListProperty<QObject> data();
|
||||||
|
|
@ -163,6 +167,7 @@ signals:
|
||||||
void colorChanged();
|
void colorChanged();
|
||||||
void maskChanged();
|
void maskChanged();
|
||||||
void surfaceFormatChanged();
|
void surfaceFormatChanged();
|
||||||
|
void updatesEnabledChanged();
|
||||||
void polished();
|
void polished();
|
||||||
|
|
||||||
protected slots:
|
protected slots:
|
||||||
|
|
@ -187,6 +192,7 @@ protected:
|
||||||
ProxyWindowContentItem* mContentItem = nullptr;
|
ProxyWindowContentItem* mContentItem = nullptr;
|
||||||
bool reloadComplete = false;
|
bool reloadComplete = false;
|
||||||
bool ranLints = false;
|
bool ranLints = false;
|
||||||
|
bool mUpdatesEnabled = true;
|
||||||
QsSurfaceFormat qsSurfaceFormat;
|
QsSurfaceFormat qsSurfaceFormat;
|
||||||
QSurfaceFormat mSurfaceFormat;
|
QSurfaceFormat mSurfaceFormat;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -127,6 +127,9 @@ void WindowInterface::setMask(PendingRegion* mask) const { this->proxyWindow()->
|
||||||
QsSurfaceFormat WindowInterface::surfaceFormat() const { return this->proxyWindow()->surfaceFormat(); };
|
QsSurfaceFormat WindowInterface::surfaceFormat() const { return this->proxyWindow()->surfaceFormat(); };
|
||||||
void WindowInterface::setSurfaceFormat(QsSurfaceFormat format) const { this->proxyWindow()->setSurfaceFormat(format); };
|
void WindowInterface::setSurfaceFormat(QsSurfaceFormat format) const { this->proxyWindow()->setSurfaceFormat(format); };
|
||||||
|
|
||||||
|
bool WindowInterface::updatesEnabled() const { return this->proxyWindow()->updatesEnabled(); };
|
||||||
|
void WindowInterface::setUpdatesEnabled(bool updatesEnabled) const { this->proxyWindow()->setUpdatesEnabled(updatesEnabled); };
|
||||||
|
|
||||||
QQmlListProperty<QObject> WindowInterface::data() const { return this->proxyWindow()->data(); };
|
QQmlListProperty<QObject> WindowInterface::data() const { return this->proxyWindow()->data(); };
|
||||||
// clang-format on
|
// clang-format on
|
||||||
|
|
||||||
|
|
@ -148,6 +151,7 @@ void WindowInterface::connectSignals() const {
|
||||||
QObject::connect(window, &ProxyWindowBase::colorChanged, this, &WindowInterface::colorChanged);
|
QObject::connect(window, &ProxyWindowBase::colorChanged, this, &WindowInterface::colorChanged);
|
||||||
QObject::connect(window, &ProxyWindowBase::maskChanged, this, &WindowInterface::maskChanged);
|
QObject::connect(window, &ProxyWindowBase::maskChanged, this, &WindowInterface::maskChanged);
|
||||||
QObject::connect(window, &ProxyWindowBase::surfaceFormatChanged, this, &WindowInterface::surfaceFormatChanged);
|
QObject::connect(window, &ProxyWindowBase::surfaceFormatChanged, this, &WindowInterface::surfaceFormatChanged);
|
||||||
|
QObject::connect(window, &ProxyWindowBase::updatesEnabledChanged, this, &WindowInterface::updatesEnabledChanged);
|
||||||
// clang-format on
|
// clang-format on
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -143,6 +143,12 @@ class WindowInterface: public Reloadable {
|
||||||
///
|
///
|
||||||
/// > [!NOTE] The surface format cannot be changed after the window is created.
|
/// > [!NOTE] The surface format cannot be changed after the window is created.
|
||||||
Q_PROPERTY(QsSurfaceFormat surfaceFormat READ surfaceFormat WRITE setSurfaceFormat NOTIFY surfaceFormatChanged);
|
Q_PROPERTY(QsSurfaceFormat surfaceFormat READ surfaceFormat WRITE setSurfaceFormat NOTIFY surfaceFormatChanged);
|
||||||
|
/// If the window should receive render updates. Defaults to true.
|
||||||
|
///
|
||||||
|
/// When set to false, the window will not re-render in response to animations
|
||||||
|
/// or other visual updates from other windows. This is useful for static windows
|
||||||
|
/// such as wallpapers that do not need to update frequently, saving GPU cycles.
|
||||||
|
Q_PROPERTY(bool updatesEnabled READ updatesEnabled WRITE setUpdatesEnabled NOTIFY updatesEnabledChanged);
|
||||||
Q_PROPERTY(QQmlListProperty<QObject> data READ data);
|
Q_PROPERTY(QQmlListProperty<QObject> data READ data);
|
||||||
// clang-format on
|
// clang-format on
|
||||||
Q_CLASSINFO("DefaultProperty", "data");
|
Q_CLASSINFO("DefaultProperty", "data");
|
||||||
|
|
@ -231,6 +237,9 @@ public:
|
||||||
[[nodiscard]] QsSurfaceFormat surfaceFormat() const;
|
[[nodiscard]] QsSurfaceFormat surfaceFormat() const;
|
||||||
void setSurfaceFormat(QsSurfaceFormat format) const;
|
void setSurfaceFormat(QsSurfaceFormat format) const;
|
||||||
|
|
||||||
|
[[nodiscard]] bool updatesEnabled() const;
|
||||||
|
void setUpdatesEnabled(bool updatesEnabled) const;
|
||||||
|
|
||||||
[[nodiscard]] QQmlListProperty<QObject> data() const;
|
[[nodiscard]] QQmlListProperty<QObject> data() const;
|
||||||
|
|
||||||
static QsWindowAttached* qmlAttachedProperties(QObject* object);
|
static QsWindowAttached* qmlAttachedProperties(QObject* object);
|
||||||
|
|
@ -258,6 +267,7 @@ signals:
|
||||||
void colorChanged();
|
void colorChanged();
|
||||||
void maskChanged();
|
void maskChanged();
|
||||||
void surfaceFormatChanged();
|
void surfaceFormatChanged();
|
||||||
|
void updatesEnabledChanged();
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void connectSignals() const;
|
void connectSignals() const;
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue