From dda5e546d1be8d07970d3e2acc0d1d89bd2628ff Mon Sep 17 00:00:00 2001 From: Awawa Date: Sat, 29 Oct 2022 21:51:58 +0200 Subject: [PATCH] Pipewire DMA & EGL hardware support --- CMakeLists.txt | 18 +- HyperhdrConfig.h.in | 3 + include/base/Grabber.h | 2 + include/base/SystemWrapper.h | 1 + include/grabber/PipewireGrabber.h | 12 +- include/grabber/PipewireWrapper.h | 1 + include/grabber/smartPipewire.h | 15 +- sources/base/Grabber.cpp | 5 + sources/base/HyperHdrInstance.cpp | 2 +- sources/base/SystemControl.cpp | 3 + sources/base/SystemWrapper.cpp | 10 + sources/grabber/pipewire/CMakeLists.txt | 2 +- sources/grabber/pipewire/PipewireGrabber.cpp | 127 +-- sources/grabber/pipewire/PipewireHandler.cpp | 820 ++++++++++++++++--- sources/grabber/pipewire/PipewireHandler.h | 126 ++- sources/grabber/pipewire/PipewireWrapper.cpp | 5 + sources/grabber/pipewire/smartPipewire.cpp | 24 +- 17 files changed, 968 insertions(+), 208 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index c862770f9..3d049059f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -30,6 +30,7 @@ SET ( DEFAULT_MAC_SYSTEM OFF ) SET ( DEFAULT_V4L2 OFF ) SET ( DEFAULT_X11 OFF ) SET ( DEFAULT_PIPEWIRE OFF ) +SET ( DEFAULT_PIPEWIRE_EGL OFF ) SET ( DEFAULT_FRAMEBUFFER OFF ) SET ( DEFAULT_SOUNDCAPWINDOWS OFF ) SET ( DEFAULT_SOUNDCAPMACOS OFF ) @@ -252,13 +253,21 @@ if (DEFAULT_PIPEWIRE) message( WARNING "QT dbus library is required for PipeWire/Portal support" ) SET ( DEFAULT_PIPEWIRE OFF ) else() - pkg_check_modules(PIPEWIRE libpipewire-0.3) if(NOT PIPEWIRE_FOUND OR NOT PIPEWIRE_INCLUDE_DIRS OR NOT PIPEWIRE_LIBRARIES) message( WARNING "Pipewire >= 3.0 not found (did you install libpipewire-0.3-dev?). Disabling support for PipeWire software grabber.") SET ( DEFAULT_PIPEWIRE OFF ) + else() + if(POLICY CMP0072) + cmake_policy(SET CMP0072 NEW) + endif() + find_package(OpenGL) + if (NOT OpenGL_OpenGL_FOUND OR NOT OpenGL_EGL_FOUND) + message( WARNING "OpenGL/EGL not found. Disabling DMA buffers for Pipewire.") + else() + SET ( DEFAULT_PIPEWIRE_EGL ON ) + endif() endif() - endif() endif() @@ -340,6 +349,11 @@ colorMe("ENABLE_MAC_SYSTEM = " ${ENABLE_MAC_SYSTEM}) option(ENABLE_PIPEWIRE "Enable the pipewire/portal Linux system grabber" ${DEFAULT_PIPEWIRE}) colorMe("ENABLE_PIPEWIRE = " ${ENABLE_PIPEWIRE}) +option(ENABLE_PIPEWIRE_EGL "Enable the pipewire EGL extension" ${DEFAULT_PIPEWIRE_EGL}) +if (DEFAULT_PIPEWIRE) + colorMe("ENABLE_PIPEWIRE_EGL = " ${ENABLE_PIPEWIRE_EGL}) +endif() + option(ENABLE_X11 "Enable the X11 Linux system grabber" ${DEFAULT_X11}) colorMe("ENABLE_X11 = " ${ENABLE_X11}) diff --git a/HyperhdrConfig.h.in b/HyperhdrConfig.h.in index eda5f7a8c..7dee5cc56 100644 --- a/HyperhdrConfig.h.in +++ b/HyperhdrConfig.h.in @@ -21,6 +21,9 @@ // PipeWire system grabber #cmakedefine ENABLE_PIPEWIRE +// PipeWire EGL extension +#cmakedefine ENABLE_PIPEWIRE_EGL + // Define to enable boblight server #cmakedefine ENABLE_BOBLIGHT diff --git a/include/base/Grabber.h b/include/base/Grabber.h index 9673ec3f6..f9b4d3eb4 100644 --- a/include/base/Grabber.h +++ b/include/base/Grabber.h @@ -139,6 +139,8 @@ class Grabber : public DetectionAutomatic, public DetectionManual }; public slots: + virtual bool isRunning(); + virtual bool start() = 0; virtual void stop() = 0; diff --git a/include/base/SystemWrapper.h b/include/base/SystemWrapper.h index fb255c1f7..7e7dd77ff 100644 --- a/include/base/SystemWrapper.h +++ b/include/base/SystemWrapper.h @@ -58,6 +58,7 @@ public slots: void setHdrToneMappingEnabled(int mode); void handleSettingsUpdate(settings::type type, const QJsonDocument& config); virtual void stateChanged(bool state); + bool isRunning(); protected: virtual QString getGrabberInfo(); diff --git a/include/grabber/PipewireGrabber.h b/include/grabber/PipewireGrabber.h index 395c91b58..7ca3228cb 100644 --- a/include/grabber/PipewireGrabber.h +++ b/include/grabber/PipewireGrabber.h @@ -18,6 +18,7 @@ #include #include #include +#include // general JPEG decoder includes #include @@ -42,12 +43,12 @@ class PipewireGrabber : public Grabber void stateChanged(bool state); -private slots: - - void grabFrame(); + static void callbackFunction(const PipewireImage& frame); public slots: + void grabFrame(const PipewireImage& data); + bool start() override; void stop() override; @@ -56,6 +57,8 @@ public slots: void newWorkerFrameError(unsigned int workerIndex, QString error, quint64 sourceCount) override {}; + bool isRunning() override; + private: QString GetSharedLut(); @@ -75,12 +78,11 @@ public slots: private: QString _configurationPath; - QTimer _timer; - QSemaphore _semaphore; void* _library; int _actualDisplay; bool _isActive; bool _storedToken; bool _versionCheck; + bool _hasFrame; }; diff --git a/include/grabber/PipewireWrapper.h b/include/grabber/PipewireWrapper.h index 5b83948ab..88f88baae 100644 --- a/include/grabber/PipewireWrapper.h +++ b/include/grabber/PipewireWrapper.h @@ -13,6 +13,7 @@ class PipewireWrapper : public SystemWrapper public slots: void stateChanged(bool state) override; + void processFrame(const PipewireImage& frame); protected: QString getGrabberInfo() override; diff --git a/include/grabber/smartPipewire.h b/include/grabber/smartPipewire.h index 5805af384..5c98e8ca4 100644 --- a/include/grabber/smartPipewire.h +++ b/include/grabber/smartPipewire.h @@ -1,16 +1,21 @@ +#pragma once + +#include + struct PipewireImage { int version; bool isError; - int width, height; + int width, height, stride; bool isOrderRgb; - unsigned char* data; + uint8_t* data; }; + +typedef int (*pipewire_callback_func)(const PipewireImage& frame); + extern "C" const char* getPipewireToken(); extern "C" const char* getPipewireError(); extern "C" bool hasPipewire(); -extern "C" void initPipewireDisplay(const char* restorationToken); +extern "C" void initPipewireDisplay(const char* restorationToken, uint32_t requestedFPS, pipewire_callback_func callback); extern "C" void uniniPipewireDisplay(); -extern "C" PipewireImage getFramePipewire(); -extern "C" void releaseFramePipewire(); diff --git a/sources/base/Grabber.cpp b/sources/base/Grabber.cpp index 5df0d88c2..f1c30f086 100644 --- a/sources/base/Grabber.cpp +++ b/sources/base/Grabber.cpp @@ -910,3 +910,8 @@ QJsonObject Grabber::getJsonInfo() void Grabber::alternativeCaching(bool alternative) { } + +bool Grabber::isRunning() +{ + return false; +} diff --git a/sources/base/HyperHdrInstance.cpp b/sources/base/HyperHdrInstance.cpp index 66e650b69..54cd1ea6d 100644 --- a/sources/base/HyperHdrInstance.cpp +++ b/sources/base/HyperHdrInstance.cpp @@ -665,7 +665,7 @@ void HyperHdrInstance::updateResult(std::vector _ledBuffer) else if (prevToken != (_computeStats.token = PerformanceCounters::currentToken())) { - if (diff >= 59000 && diff <= 65000) + if (diff >= 59000) emit PerformanceCounters::getInstance()->newCounter( PerformanceReport(static_cast(PerformanceReportType::INSTANCE), _computeStats.token, _name, _computeStats.total / qMax(diff/1000.0, 1.0), _computeStats.total, 0, 0, getInstanceIndex())); diff --git a/sources/base/SystemControl.cpp b/sources/base/SystemControl.cpp index ce9f13572..61a0b1dee 100644 --- a/sources/base/SystemControl.cpp +++ b/sources/base/SystemControl.cpp @@ -137,6 +137,9 @@ void SystemControl::handleCompStateChangeRequest(hyperhdr::Components component, void SystemControl::setSysInactive() { + if (SystemWrapper::getInstance() != nullptr && SystemWrapper::getInstance()->isRunning()) + return; + if (!_alive) _hyperhdr->setInputInactive(_sysCaptPrio); diff --git a/sources/base/SystemWrapper.cpp b/sources/base/SystemWrapper.cpp index feca521ab..20a0e4d1d 100644 --- a/sources/base/SystemWrapper.cpp +++ b/sources/base/SystemWrapper.cpp @@ -237,3 +237,13 @@ QJsonObject SystemWrapper::getJsonInfo() return systemDevice; } + +bool SystemWrapper::isRunning() +{ + if (_grabber != NULL) + { + return _grabber->isRunning(); + } + + return false; +} diff --git a/sources/grabber/pipewire/CMakeLists.txt b/sources/grabber/pipewire/CMakeLists.txt index 5c7ec8666..0fb1d13a4 100644 --- a/sources/grabber/pipewire/CMakeLists.txt +++ b/sources/grabber/pipewire/CMakeLists.txt @@ -8,7 +8,7 @@ add_library(smartPipewire SHARED ${SMARTPIPEWIRE_SOURCES} ) set_target_properties(smartPipewire PROPERTIES VERSION 1) # Pipewire -target_include_directories(smartPipewire PUBLIC ${CMAKE_CURRENT_BINARY_DIR} ${PIPEWIRE_INCLUDE_DIRS} ) +target_include_directories(smartPipewire PUBLIC ${CMAKE_CURRENT_BINARY_DIR} ${PIPEWIRE_INCLUDE_DIRS} ${OPENGL_INCLUDE_DIR} ${OPENGL_EGL_INCLUDE_DIRS} ) target_link_libraries(smartPipewire PUBLIC ${PIPEWIRE_LIBRARIES} Qt${Qt_VERSION}::Core Qt${Qt_VERSION}::DBus ) # Grabber diff --git a/sources/grabber/pipewire/PipewireGrabber.cpp b/sources/grabber/pipewire/PipewireGrabber.cpp index b8223e8fb..8c6ae1b55 100644 --- a/sources/grabber/pipewire/PipewireGrabber.cpp +++ b/sources/grabber/pipewire/PipewireGrabber.cpp @@ -47,31 +47,27 @@ #include #include -#include +#include +#include #include #include bool (*_hasPipewire)() = nullptr; const char* (*_getPipewireError)() = nullptr; -void (*_initPipewireDisplay)(const char* restorationToken) = nullptr; +void (*_initPipewireDisplay)(const char* restorationToken, uint32_t requestedFPS, pipewire_callback_func callback) = nullptr; void (*_uninitPipewireDisplay)() = nullptr; -PipewireImage (*_getFramePipewire)() = nullptr; -void (*_releaseFramePipewire)() = nullptr; const char* (*_getPipewireToken)() = nullptr; PipewireGrabber::PipewireGrabber(const QString& device, const QString& configurationPath) : Grabber("PIPEWIRE_SYSTEM:" + device.left(14)) , _configurationPath(configurationPath) - , _semaphore(1) , _library(nullptr) , _actualDisplay(0) , _isActive(false) , _storedToken(false) , _versionCheck(false) + , _hasFrame(false) { - _timer.setTimerType(Qt::PreciseTimer); - connect(&_timer, &QTimer::timeout, this, &PipewireGrabber::grabFrame); - // Load library _library = dlopen("libsmartPipewire.so", RTLD_NOW); @@ -80,15 +76,13 @@ PipewireGrabber::PipewireGrabber(const QString& device, const QString& configura _getPipewireToken = (const char* (*)()) dlsym(_library, "getPipewireToken"); _getPipewireError = (const char* (*)()) dlsym(_library, "getPipewireError"); _hasPipewire = (bool (*)()) dlsym(_library, "hasPipewire"); - _initPipewireDisplay = (void (*)(const char*)) dlsym(_library, "initPipewireDisplay"); + _initPipewireDisplay = (void (*)(const char*, uint32_t, pipewire_callback_func)) dlsym(_library, "initPipewireDisplay"); _uninitPipewireDisplay = (void (*)()) dlsym(_library, "uniniPipewireDisplay"); - _getFramePipewire = (PipewireImage (*)()) dlsym(_library, "getFramePipewire"); - _releaseFramePipewire = (void (*)()) dlsym(_library, "releaseFramePipewire"); } else Error(_log, "Could not load Pipewire proxy library. Error: %s", dlerror()); - if (_library && (_getPipewireToken == nullptr || _hasPipewire == nullptr || _releaseFramePipewire == nullptr || _initPipewireDisplay == nullptr || _uninitPipewireDisplay == nullptr || _getFramePipewire == nullptr)) + if (_library && (_getPipewireToken == nullptr || _hasPipewire == nullptr || _initPipewireDisplay == nullptr || _uninitPipewireDisplay == nullptr )) { Error(_log, "Could not load Pipewire proxy library definition. Error: %s", dlerror()); @@ -153,7 +147,6 @@ void PipewireGrabber::uninit() Debug(_log, "Uninit grabber: %s", QSTRING_CSTR(_deviceName)); } - _initialized = false; } @@ -161,6 +154,7 @@ bool PipewireGrabber::init() { Debug(_log, "init"); + _hasFrame = false; if (!_initialized) { @@ -236,8 +230,6 @@ bool PipewireGrabber::start() { if (init()) { - _timer.setInterval(1000/_fps); - _timer.start(); Info(_log, "Started"); return true; } @@ -254,14 +246,11 @@ void PipewireGrabber::stop() { if (_initialized) { - _semaphore.acquire(); - _timer.stop(); - _uninitPipewireDisplay(); _isActive = false; _initialized = false; + _hasFrame = false; - _semaphore.release(); Info(_log, "Stopped"); } } @@ -279,7 +268,7 @@ bool PipewireGrabber::init_device(int _display) token = ""; else Info(_log, "Loading restoration token: %s", QSTRING_CSTR(maskToken(token))); - _initPipewireDisplay(token.toLatin1().constData()); + _initPipewireDisplay(token.toLatin1().constData(), _fps, (pipewire_callback_func) &PipewireGrabber::callbackFunction); _isActive = true; @@ -313,67 +302,60 @@ void PipewireGrabber::stateChanged(bool state) } } -void PipewireGrabber::grabFrame() +void PipewireGrabber::grabFrame(const PipewireImage& data) { bool stopNow = false; - if (_semaphore.tryAcquire()) + if (_initialized && _isActive) { - if (_initialized && _isActive) + if (!_versionCheck) { - PipewireImage data = _getFramePipewire(); + if (data.version >= 4) + Info(_log, "Portal protocol version: %i", data.version); + else + Warning(_log, "Legacy portal protocol version: %i. To enjoy persistant autorization since version 4, you should update xdg-desktop-portal at least to version 1.12.1 *AND* provide backend that can implement it (for example newest xdg-desktop-portal-gnome).", data.version); - if (!_versionCheck) - { - if (data.version >= 4) - Info(_log, "Portal protocol version: %i", data.version); - else - Warning(_log, "Legacy portal protocol version: %i. To enjoy persistant autorization since version 4, you should update xdg-desktop-portal at least to version 1.12.1 *AND* provide backend that can implement it (for example newest xdg-desktop-portal-gnome).", data.version); + _versionCheck = true; + } - _versionCheck = true; - } + if (!_storedToken && !data.isError) + { + QString token = QString("%1").arg(_getPipewireToken()); - if (!_storedToken && !data.isError) + if (!token.isEmpty()) { - QString token = QString("%1").arg(_getPipewireToken()); + AuthManager* instance = AuthManager::getInstance(); - if (!token.isEmpty()) - { - AuthManager* instance = AuthManager::getInstance(); + Info(_log, "Saving restoration token: %s", QSTRING_CSTR(maskToken(token))); - Info(_log, "Saving restoration token: %s", QSTRING_CSTR(maskToken(token))); + instance->savePipewire(token); - instance->savePipewire(token); - - _storedToken = true; - } - } + _storedToken = true; + } + } - if (data.data == nullptr) + if (data.data == nullptr) + { + if (data.isError) { - if (data.isError) - { - QString err = QString("%1").arg(_getPipewireError()); - Error(_log, "Could not capture pipewire frame: %s", QSTRING_CSTR(err)); - stopNow = true; - } + QString err = QString("%1").arg(_getPipewireError()); + Error(_log, "Could not capture pipewire frame: %s", QSTRING_CSTR(err)); + stopNow = true; } - else - { - _actualWidth = data.width; - _actualHeight = data.height; - - if (data.isOrderRgb) - processSystemFrameRGBA(data.data); - else - processSystemFrameBGRA(data.data); + } + else + { + _hasFrame = true; + _actualWidth = data.width; + _actualHeight = data.height; - _releaseFramePipewire(); - } + if (data.isOrderRgb) + processSystemFrameRGBA(data.data, data.stride); + else + processSystemFrameBGRA(data.data, data.stride); } - _semaphore.release(); } if (stopNow) @@ -390,3 +372,24 @@ void PipewireGrabber::setCropping(unsigned cropLeft, unsigned cropRight, unsigne _cropTop = cropTop; _cropBottom = cropBottom; } + +void PipewireGrabber::callbackFunction(const PipewireImage& frame) +{ + if (SystemWrapper::getInstance() == nullptr) + return; + + PipewireWrapper* wrapper = dynamic_cast(SystemWrapper::getInstance()); + + if (wrapper != NULL) + { + if (QThread::currentThread() == wrapper->thread()) + wrapper->processFrame(frame); + else + QMetaObject::invokeMethod(wrapper, "processFrame", Qt::ConnectionType::BlockingQueuedConnection, Q_ARG(const PipewireImage&, frame)); + } +} + +bool PipewireGrabber::isRunning() +{ + return _initialized && _hasFrame; +} diff --git a/sources/grabber/pipewire/PipewireHandler.cpp b/sources/grabber/pipewire/PipewireHandler.cpp index ee3b42c45..85c8c4627 100644 --- a/sources/grabber/pipewire/PipewireHandler.cpp +++ b/sources/grabber/pipewire/PipewireHandler.cpp @@ -58,6 +58,7 @@ #include #include #include +#include #include #include "PipewireHandler.h" @@ -79,12 +80,18 @@ const QString PORTAL_RESPONSE = QStringLiteral("Response"); const QString REQUEST_TEMPLATE = QStringLiteral("/org/freedesktop/portal/desktop/request/%1/%2"); -PipewireHandler::PipewireHandler() : _sessionHandle(""), _restorationToken(""), _errorMessage(""), _portalStatus(false), _isError(false), _version(0), _streamNodeId(0), +PipewireHandler::PipewireHandler() : + _callback(nullptr), _sessionHandle(""), _restorationToken(""), _errorMessage(""), _portalStatus(false), + _isError(false), _version(0), _streamNodeId(0), _sender(""), _replySessionPath(""), _sourceReplyPath(""), _startReplyPath(""), _pwMainThreadLoop(nullptr), _pwNewContext(nullptr), _pwContextConnection(nullptr), _pwStream(nullptr), - _backupFrame(nullptr), _workingFrame(nullptr), - _frameWidth(0),_frameHeight(0),_frameOrderRgb(false), _framePaused(false) -{ + _frameWidth(0),_frameHeight(0),_frameOrderRgb(false), _framePaused(false), _requestedFPS(10), _hasFrame(false), + _infoUpdated(false), _initEGL(false), _libEglHandle(NULL), _libGlHandle(NULL), + _frameDrmFormat(DRM_FORMAT_MOD_INVALID), _frameDrmModifier(DRM_FORMAT_MOD_INVALID) +{ + _timer.setTimerType(Qt::PreciseTimer); + connect(&_timer, &QTimer::timeout, this, &PipewireHandler::grabFrame); + _pwStreamListener = {}; _pwCoreListener = {}; @@ -115,10 +122,23 @@ QString PipewireHandler::getError() PipewireHandler::~PipewireHandler() { closeSession(); + + if (_libEglHandle != NULL) + { + dlclose(_libEglHandle); + _libEglHandle = NULL; + } + + if (_libGlHandle != NULL) + { + dlclose(_libGlHandle); + _libGlHandle = NULL; + } } void PipewireHandler::closeSession() { + _timer.stop(); if (_pwMainThreadLoop != nullptr) { @@ -127,19 +147,7 @@ void PipewireHandler::closeSession() } if (_pwStream != nullptr) - { - if (_backupFrame != nullptr) - { - pw_stream_queue_buffer(_pwStream, _backupFrame); - _backupFrame = nullptr; - } - - if (_workingFrame != nullptr) - { - pw_stream_queue_buffer(_pwStream, _workingFrame); - _workingFrame = nullptr; - } - + { pw_stream_destroy(_pwStream); _pwStream = nullptr; } @@ -207,9 +215,33 @@ void PipewireHandler::closeSession() _frameWidth = 0; _frameHeight = 0; _frameOrderRgb = false; - _backupFrame = nullptr; - _workingFrame = nullptr; _framePaused = false; + _callback = nullptr; + _requestedFPS = 10; + _hasFrame = false; + _infoUpdated = false; + _frameDrmFormat = DRM_FORMAT_MOD_INVALID; + _frameDrmModifier = DRM_FORMAT_MOD_INVALID; + +#ifdef ENABLE_PIPEWIRE_EGL + if (contextEgl != EGL_NO_CONTEXT) + { + eglDestroyContext(displayEgl, contextEgl); + contextEgl = EGL_NO_CONTEXT; + } + + if (displayEgl != EGL_NO_DISPLAY) + { + printf("PipewireEGL: terminate the display\n"); + eglTerminate(displayEgl); + displayEgl = EGL_NO_DISPLAY; + } + + _initEGL = false; +#endif + + for (supportedDmaFormat& supVal : _supportedDmaFormatsList) + supVal.hasDma = false; if (_version > 0) { @@ -260,12 +292,24 @@ int PipewireHandler::readVersion() return version; } -void PipewireHandler::startSession(QString restorationToken) +void PipewireHandler::startSession(QString restorationToken, uint32_t requestedFPS, pipewire_callback_func callback) { std::cout << "Pipewire: initialization invoked. Cleaning up first..." << std::endl; closeSession(); + if (callback == nullptr) + { + reportError("Pipewire: missing callback."); + return; + } + + if (requestedFPS < 1 || requestedFPS > 60) + { + reportError("Pipewire: invalid capture rate."); + return; + } + _restorationToken = QString("%1").arg(restorationToken); _version = PipewireHandler::readVersion(); @@ -276,6 +320,9 @@ void PipewireHandler::startSession(QString restorationToken) return; } + _requestedFPS = requestedFPS; + _callback = callback; + _sender = QString("%1").arg(QDBusConnection::sessionBus().baseService()).replace('.','_'); if (_sender.length() > 0 && _sender[0] == ':') _sender = _sender.right(_sender.length()-1); @@ -304,6 +351,7 @@ void PipewireHandler::startSession(QString restorationToken) reportError(QString("Pipewire: Couldn't get reply for session create. Error: %1").arg(replySession.error().message())); } + std::cout << "Requested FPS: " << _requestedFPS << std::endl; std::cout << "Pipewire: CreateSession finished" << std::endl; } @@ -516,6 +564,9 @@ void PipewireHandler::startResponse(uint response, const QVariantMap& results) return; } + _timer.setInterval(1000 / _requestedFPS); + _timer.start(); + pw_thread_loop_unlock(_pwMainThreadLoop); } @@ -523,19 +574,7 @@ void PipewireHandler::startResponse(uint response, const QVariantMap& results) void PipewireHandler::onStateChanged(pw_stream_state old, pw_stream_state state, const char* error) { if (state != PW_STREAM_STATE_STREAMING && _pwStream != nullptr) - { - if (_backupFrame != nullptr) - { - pw_stream_queue_buffer(_pwStream, _backupFrame); - _backupFrame = nullptr; - } - - if (_workingFrame != nullptr) - { - pw_stream_queue_buffer(_pwStream, _workingFrame); - _workingFrame = nullptr; - } - + { _framePaused = true; } @@ -551,9 +590,10 @@ void PipewireHandler::onStateChanged(pw_stream_state old, pw_stream_state state, } if (state == PW_STREAM_STATE_UNCONNECTED) printf("Pipewire: state UNCONNECTED (%d, %d)\n", (int)state, (int)old); - if (state == PW_STREAM_STATE_CONNECTING) printf("Pipewire: state CONNECTING (%d, %d)\n", (int)state, (int)old); - if (state == PW_STREAM_STATE_PAUSED) printf("Pipewire: state PAUSED (%d, %d)\n", (int)state, (int)old); - if (state == PW_STREAM_STATE_STREAMING) printf("Pipewire: state STREAMING (%d, %d)\n", (int)state, (int)old); + else if (state == PW_STREAM_STATE_CONNECTING) printf("Pipewire: state CONNECTING (%d, %d)\n", (int)state, (int)old); + else if (state == PW_STREAM_STATE_PAUSED) printf("Pipewire: state PAUSED (%d, %d)\n", (int)state, (int)old); + else if (state == PW_STREAM_STATE_STREAMING) printf("Pipewire: state STREAMING (%d, %d)\n", (int)state, (int)old); + else printf("Pipewire: state UNKNOWN (%d, %d)\n", (int)state, (int)old); } void PipewireHandler::onParamsChanged(uint32_t id, const struct spa_pod* param) @@ -576,33 +616,264 @@ void PipewireHandler::onParamsChanged(uint32_t id, const struct spa_pod* param) _frameWidth = format.info.raw.size.width; _frameHeight = format.info.raw.size.height; + _frameDrmModifier = format.info.raw.modifier; _frameOrderRgb = (format.info.raw.format == SPA_VIDEO_FORMAT_RGBx || format.info.raw.format == SPA_VIDEO_FORMAT_RGBA); + for (const supportedDmaFormat& val : _supportedDmaFormatsList) + if (val.spaFormat == format.info.raw.format) + { + _frameDrmFormat = val.drmFormat; + break; + } + printf("Pipewire: video format = %d (%s)\n", format.info.raw.format, spa_debug_type_find_name(spa_type_video_format, format.info.raw.format)); printf("Pipewire: video size = %dx%d (RGB order = %s)\n", _frameWidth, _frameHeight, (_frameOrderRgb) ? "true" : "false"); - printf("Pipewire: framerate = %d/%d\n", format.info.raw.framerate.num, format.info.raw.framerate.denom); + printf("Pipewire: framerate = %d/%d\n", format.info.raw.framerate.num, format.info.raw.framerate.denom); + + uint8_t updateBuffer[1000]; + struct spa_pod_builder updateBufferBuilder = SPA_POD_BUILDER_INIT(updateBuffer, sizeof(updateBuffer)); + const struct spa_pod* updatedParams[1]; + const uint32_t stride = SPA_ROUND_UP_N(_frameWidth * 4, 4); + const int32_t size = _frameHeight * stride; + + const auto bufferTypes = spa_pod_find_prop(param, nullptr, SPA_FORMAT_VIDEO_modifier) + ? (1 << SPA_DATA_DmaBuf) | (1 << SPA_DATA_MemFd) | (1 << SPA_DATA_MemPtr) + : (1 << SPA_DATA_MemFd) | (1 << SPA_DATA_MemPtr); + + if (bufferTypes & (1 << SPA_DATA_DmaBuf)) + printf("Pipewire: DMA buffer available. Format: %s. Modifier: %s.\n", + fourCCtoString(_frameDrmFormat).toLocal8Bit().constData(), + fourCCtoString(_frameDrmModifier).toLocal8Bit().constData()); + if (bufferTypes & (1 << SPA_DATA_MemFd)) + printf("Pipewire: MemFD buffer available\n"); + if (bufferTypes & (1 << SPA_DATA_MemPtr)) + printf("Pipewire: MemPTR buffer available\n"); + + updatedParams[0] = (spa_pod*)(spa_pod*)spa_pod_builder_add_object(&updateBufferBuilder, + SPA_TYPE_OBJECT_ParamBuffers, SPA_PARAM_Buffers, + SPA_PARAM_BUFFERS_buffers, SPA_POD_CHOICE_RANGE_Int(2, 2, 16), + SPA_PARAM_BUFFERS_blocks, SPA_POD_Int(1), + SPA_PARAM_BUFFERS_size, SPA_POD_Int(size), + SPA_PARAM_BUFFERS_stride, SPA_POD_CHOICE_RANGE_Int(stride, stride, INT32_MAX), + SPA_PARAM_BUFFERS_align, SPA_POD_Int(16), + SPA_PARAM_BUFFERS_dataType, SPA_POD_CHOICE_FLAGS_Int(bufferTypes)); + + printf("Pipewire: updated parameters %d\n", pw_stream_update_params(_pwStream, updatedParams, 1)); + + _infoUpdated = false; }; void PipewireHandler::onProcessFrame() { - struct pw_buffer* newFrame; - - if ((newFrame = pw_stream_dequeue_buffer(_pwStream)) == nullptr) + if (_hasFrame) { - std::cout << "Pipewire: out of buffers" << std::endl; + struct pw_buffer* newFrame; + if ((newFrame = pw_stream_dequeue_buffer(_pwStream)) != nullptr) + pw_stream_queue_buffer(_pwStream, newFrame); + } + + _hasFrame = true; +}; + +void PipewireHandler::grabFrame() +{ + struct pw_buffer* newFrame = nullptr; + struct pw_buffer* dequeueFrame = nullptr; + uint8_t* mappedMemory = nullptr; + uint8_t* frameBuffer = nullptr; + PipewireImage image; + + if (_pwStream == nullptr) return; + + _hasFrame = false; + + image.width = _frameWidth; + image.height = _frameHeight; + image.isOrderRgb = _frameOrderRgb; + image.version = getVersion(); + image.isError = hasError(); + image.stride = 0; + image.data = nullptr; + + while ((dequeueFrame = pw_stream_dequeue_buffer(_pwStream)) != nullptr) + { + if (newFrame != nullptr) + pw_stream_queue_buffer(_pwStream, newFrame); + newFrame = dequeueFrame; } - if (newFrame->buffer->datas[0].data == nullptr) + if (newFrame != nullptr) { - std::cout << "Pipewire: empty buffer" << std::endl; - return; + if (!_infoUpdated) + { + if (newFrame->buffer->datas->type == SPA_DATA_MemFd) + printf("Pipewire: Using MemFD frame type. The hardware acceleration is DISABLED.\n"); + else if (newFrame->buffer->datas->type == SPA_DATA_DmaBuf) + printf("Pipewire: Using DmaBuf frame type. The hardware acceleration is ENABLED.\n"); + else if (newFrame->buffer->datas->type == SPA_DATA_MemPtr) + printf("Pipewire: Using MemPTR frame type. The hardware acceleration is DISABLED.\n"); + } + +#ifdef ENABLE_PIPEWIRE_EGL + if (newFrame->buffer->datas->type == SPA_DATA_DmaBuf) + { + if (!_initEGL) + { + printf("PipewireEGL: EGL is not initialized\n"); + } + else if (newFrame->buffer->n_datas == 0 || newFrame->buffer->n_datas > 4) + { + printf("PipewireEGL: unexpected plane number\n"); + } + else if (!eglMakeCurrent(displayEgl, EGL_NO_SURFACE, EGL_NO_SURFACE, contextEgl)) + { + printf("PipewireEGL: failed to make a current context (reason = '%s')\n", eglErrorToString(eglGetError())); + } + else + { + QVector attribs; + + attribs << EGL_WIDTH << _frameWidth << EGL_HEIGHT << _frameHeight << EGL_LINUX_DRM_FOURCC_EXT << EGLint(_frameDrmFormat); + + EGLint EGL_DMA_BUF_PLANE_FD_EXT[] = { EGL_DMA_BUF_PLANE0_FD_EXT, EGL_DMA_BUF_PLANE1_FD_EXT, EGL_DMA_BUF_PLANE2_FD_EXT, EGL_DMA_BUF_PLANE3_FD_EXT }; + EGLint EGL_DMA_BUF_PLANE_OFFSET_EXT[] = { EGL_DMA_BUF_PLANE0_OFFSET_EXT, EGL_DMA_BUF_PLANE1_OFFSET_EXT, EGL_DMA_BUF_PLANE2_OFFSET_EXT, EGL_DMA_BUF_PLANE3_OFFSET_EXT }; + EGLint EGL_DMA_BUF_PLANE_PITCH_EXT[] = { EGL_DMA_BUF_PLANE0_PITCH_EXT, EGL_DMA_BUF_PLANE1_PITCH_EXT, EGL_DMA_BUF_PLANE2_PITCH_EXT, EGL_DMA_BUF_PLANE3_PITCH_EXT }; + EGLint EGL_DMA_BUF_PLANE_MODIFIER_LO_EXT[] = { EGL_DMA_BUF_PLANE0_MODIFIER_LO_EXT, EGL_DMA_BUF_PLANE1_MODIFIER_LO_EXT, EGL_DMA_BUF_PLANE2_MODIFIER_LO_EXT, EGL_DMA_BUF_PLANE3_MODIFIER_LO_EXT }; + EGLint EGL_DMA_BUF_PLANE_MODIFIER_HI_EXT[] = { EGL_DMA_BUF_PLANE0_MODIFIER_HI_EXT, EGL_DMA_BUF_PLANE1_MODIFIER_HI_EXT, EGL_DMA_BUF_PLANE2_MODIFIER_HI_EXT, EGL_DMA_BUF_PLANE3_MODIFIER_HI_EXT }; + + for(uint32_t i = 0; i < newFrame->buffer->n_datas && i < 4; i++) + { + auto planes = newFrame->buffer->datas[i]; + + attribs << EGL_DMA_BUF_PLANE_FD_EXT[i] << EGLint(planes.fd) << EGL_DMA_BUF_PLANE_OFFSET_EXT[i] << EGLint(planes.chunk->offset) + << EGL_DMA_BUF_PLANE_PITCH_EXT[i] << EGLint(planes.chunk->stride); + + if (_frameDrmModifier != DRM_FORMAT_MOD_INVALID) + { + attribs << EGL_DMA_BUF_PLANE_MODIFIER_LO_EXT[i] << EGLint(_frameDrmModifier & 0xffffffff) + << EGL_DMA_BUF_PLANE_MODIFIER_HI_EXT[i] << EGLint(_frameDrmModifier >> 32); + } + } + + attribs << EGL_IMAGE_PRESERVED_KHR << EGL_TRUE << EGL_NONE; + + EGLImage eglImage = eglCreateImageKHR(displayEgl, EGL_NO_CONTEXT, EGL_LINUX_DMA_BUF_EXT, (EGLClientBuffer) nullptr, attribs.data()); + + if (eglImage == EGL_NO_IMAGE_KHR) + { + printf("PipewireEGL: failed to create a texture (reason = '%s')\n", eglErrorToString(eglGetError())); + } + else + { + if (!_infoUpdated) + printf("PipewireEGL: got the texture\n"); + + GLenum glRes; + GLuint texture; + + glGenTextures(1, &texture); + glRes = glGetError(); + + if (glRes != GL_NO_ERROR) + { + printf("PipewireGL: could not render create a texture (%s)", glErrorToString(glRes)); + } + else + { + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + glBindTexture(GL_TEXTURE_2D, texture); + + glEGLImageTargetTexture2DOES(GL_TEXTURE_2D, eglImage); + glRes = glGetError(); + + if (glRes != GL_NO_ERROR) + { + printf("PipewireGL: glEGLImageTargetTexture2DOES failed (%s)\n", glErrorToString(glRes)); + } + else + { + frameBuffer = (uint8_t*) malloc(newFrame->buffer->datas[0].chunk->stride * _frameHeight); + + for (supportedDmaFormat& supVal : _supportedDmaFormatsList) + if (_frameDrmFormat == supVal.drmFormat) + { + glGetTexImage(GL_TEXTURE_2D, 0, supVal.glFormat, GL_UNSIGNED_BYTE, frameBuffer); + glRes = glGetError(); + + if (glRes != GL_NO_ERROR) + { + printf("PipewireGL: could not render the DMA texture (%s)\n", glErrorToString(glRes)); + } + else + { + if (!_infoUpdated) + printf("PipewireEGL: succesfully rendered the DMA texture\n"); + + image.data = frameBuffer; + } + + break; + } + } + + glDeleteTextures(1, &texture); + } + eglDestroyImageKHR(displayEgl, eglImage); + } + } + } + else +#endif + if (newFrame->buffer->datas[0].data == nullptr) + { + printf("Pipewire: empty buffer\n"); + } + else + { + image.stride = newFrame->buffer->datas->chunk->stride; + + if (newFrame->buffer->datas->type == SPA_DATA_MemFd) + { + mappedMemory = static_cast(mmap(nullptr, + newFrame->buffer->datas->maxsize + newFrame->buffer->datas->mapoffset, + PROT_READ, MAP_PRIVATE, newFrame->buffer->datas->fd, 0)); + + if (mappedMemory == MAP_FAILED) + { + printf("Pipewire: Failed to mmap the memory: %s ", strerror(errno)); + mappedMemory = nullptr; + } + else + { + image.data = mappedMemory; + } + } + else if (newFrame->buffer->datas->type == SPA_DATA_MemPtr) + { + image.data = static_cast(newFrame->buffer->datas[0].data); + } + } } - if (_backupFrame != nullptr) - pw_stream_queue_buffer(_pwStream, _backupFrame); + // forward the frame + _callback(image); + + // clean up + if (mappedMemory != nullptr) + munmap(mappedMemory, newFrame->buffer->datas->maxsize + newFrame->buffer->datas->mapoffset); - _backupFrame = newFrame; + if (frameBuffer != nullptr) + free(frameBuffer); + + if (newFrame != nullptr) + pw_stream_queue_buffer(_pwStream, newFrame); + + // goodbye + _infoUpdated = true; }; void PipewireHandler::onCoreError(uint32_t id, int seq, int res, const char *message) @@ -610,6 +881,350 @@ void PipewireHandler::onCoreError(uint32_t id, int seq, int res, const char *mes reportError(QString("Pipewire: core reports error '%1'").arg(message)); } +#ifdef ENABLE_PIPEWIRE_EGL +const char* PipewireHandler::glErrorToString(GLenum errorType) +{ + switch (errorType) { + case GL_NO_ERROR: + return "No error"; + break; + case GL_INVALID_ENUM: + return "An unacceptable value is specified for an enumerated argument (GL_INVALID_ENUM)"; + break; + case GL_INVALID_VALUE: + return "A numeric argument is out of range (GL_INVALID_VALUE)"; + break; + case GL_INVALID_OPERATION: + return "The specified operation is not allowed in the current state (GL_INVALID_OPERATION)"; + break; + case GL_STACK_OVERFLOW: + return "This command would cause a stack overflow (GL_STACK_OVERFLOW)"; + break; + case GL_STACK_UNDERFLOW: + return "This command would cause a stack underflow (GL_STACK_UNDERFLOW)"; + break; + case GL_OUT_OF_MEMORY: + return "There is not enough memory left to execute the command (GL_OUT_OF_MEMORY)"; + break; + case GL_TABLE_TOO_LARGE: + return "The specified table exceeds the implementation's maximum supported table size (GL_TABLE_TOO_LARGE)"; + break; + default: + return "An unknown error"; + break; + } +} + +const char* PipewireHandler::eglErrorToString(EGLint error_number) +{ + switch (error_number) { + case EGL_SUCCESS: + return "No error"; + break; + case EGL_NOT_INITIALIZED: + return "EGL is not initialized, or could not be initialized"; + break; + case EGL_BAD_ACCESS: + return "EGL cannot access a requested resource (thread context exception?)"; + break; + case EGL_BAD_ALLOC: + return "EGL failed to allocate resources"; + break; + case EGL_BAD_ATTRIBUTE: + return "An unrecognized attribute or value in the attribute list"; + break; + case EGL_BAD_CONTEXT: + return "An invalid EGLContext"; + break; + case EGL_BAD_CONFIG: + return "An invalid EGL frame buffer configuration for EGLConfig"; + break; + case EGL_BAD_CURRENT_SURFACE: + return "The current surface is no longer valid"; + break; + case EGL_BAD_DISPLAY: + return "An invalid EGL display connection"; + break; + case EGL_BAD_SURFACE: + return "The surface argument is nor properly configured for GL rendering"; + break; + case EGL_BAD_MATCH: + return "Arguments are inconsistent"; + break; + case EGL_BAD_PARAMETER: + return "Bad arguments"; + break; + case EGL_BAD_NATIVE_PIXMAP: + return "A NativePixmapType is invalid"; + break; + case EGL_BAD_NATIVE_WINDOW: + return "A NativeWindowType is invalid"; + break; + case EGL_CONTEXT_LOST: + return "The context is lost (a power management event has occurred?)"; + break; + default: + return "An unknown error"; + break; + } +} + +void PipewireHandler::initEGL() +{ + if (_initEGL) + return; + + if (_libEglHandle == NULL && (_libEglHandle = dlopen("libEGL.so.1", RTLD_NOW | RTLD_GLOBAL)) == NULL) + { + printf("PipewireEGL: HyperHDR could not open EGL library\n"); + return; + } + + if (_libGlHandle == NULL && ((_libGlHandle = dlopen("libGL.so.1", RTLD_NOW | RTLD_GLOBAL)) == NULL) + && ((_libGlHandle = dlopen("libGL.so", RTLD_NOW | RTLD_GLOBAL)) == NULL)) + { + printf("PipewireEGL: HyperHDR could not open GL library\n"); + return; + } + + if ((eglGetProcAddress = (eglGetProcAddressFun)dlsym(_libEglHandle, "eglGetProcAddress")) == nullptr) + { + printf("PipewireEGL: failed to get eglGetProcAddress\n"); + return; + } + + if ((eglGetPlatformDisplay = (eglGetPlatformDisplayFun)eglGetProcAddress("eglGetPlatformDisplay")) == nullptr) + { + printf("PipewireEGL: failed to get eglGetPlatformDisplay\n"); + return; + } + + if ((eglTerminate = (eglTerminateFun)eglGetProcAddress("eglTerminate")) == nullptr) + { + printf("PipewireEGL: failed to get eglTerminate\n"); + return; + } + + if ((eglInitialize = (eglInitializeFun)eglGetProcAddress("eglInitialize")) == nullptr) + { + printf("PipewireEGL: failed to get eglInitialize\n"); + return; + } + + if ((eglQueryDmaBufFormatsEXT = (eglQueryDmaBufFormatsEXTFun)eglGetProcAddress("eglQueryDmaBufFormatsEXT")) == nullptr) + { + printf("PipewireEGL: failed to get eglQueryDmaBufFormatsEXT\n"); + return; + } + + if ((eglQueryDmaBufModifiersEXT = (eglQueryDmaBufModifiersEXTFun)eglGetProcAddress("eglQueryDmaBufModifiersEXT")) == nullptr) + { + printf("PipewireEGL: failed to get eglQueryDmaBufModifiersEXT\n"); + return; + } + + if ((eglCreateImageKHR = (eglCreateImageKHRFun)eglGetProcAddress("eglCreateImageKHR")) == nullptr) + { + printf("PipewireEGL: failed to get eglCreateImageKHR\n"); + return; + } + + if ((eglDestroyImageKHR = (eglDestroyImageKHRFun)eglGetProcAddress("eglDestroyImageKHR")) == nullptr) + { + printf("PipewireEGL: failed to get eglDestroyImageKHR\n"); + return; + } + + if ((eglCreateContext = (eglCreateContextFun)eglGetProcAddress("eglCreateContext")) == nullptr) + { + printf("PipewireEGL: failed to get eglCreateContext\n"); + return; + } + + if ((eglDestroyContext = (eglDestroyContextFun)eglGetProcAddress("eglDestroyContext")) == nullptr) + { + printf("PipewireEGL: failed to get eglDestroyContext\n"); + return; + } + + if ((eglMakeCurrent = (eglMakeCurrentFun)eglGetProcAddress("eglMakeCurrent")) == nullptr) + { + printf("PipewireEGL: failed to get eglMakeCurrent\n"); + return; + } + + if ((eglGetError = (eglGetErrorFun)eglGetProcAddress("eglGetError")) == nullptr) + { + printf("PipewireEGL: failed to get eglGetError\n"); + return; + } + + if ((glEGLImageTargetTexture2DOES = (glEGLImageTargetTexture2DOESFun)eglGetProcAddress("glEGLImageTargetTexture2DOES")) == nullptr) + { + printf("PipewireEGL: failed to get glEGLImageTargetTexture2DOES\n"); + return; + } + + if ((eglBindAPI = (eglBindAPIFun)eglGetProcAddress("eglBindAPI")) == nullptr) + { + printf("PipewireEGL: failed to get eglBindAPI\n"); + return; + } + + if ((glXGetProcAddressARB = (glXGetProcAddressARBFun)dlsym(_libGlHandle, "glXGetProcAddressARB")) == nullptr) + { + printf("PipewireGL: failed to get glXGetProcAddressARB\n"); + return; + } + + if ((glBindTexture = (glBindTextureFun)glXGetProcAddressARB("glBindTexture")) == nullptr) + { + printf("PipewireGL: failed to get glBindTexture\n"); + return; + } + + if ((glDeleteTextures = (glDeleteTexturesFun)glXGetProcAddressARB("glDeleteTextures")) == nullptr) + { + printf("PipewireGL: failed to get glDeleteTextures\n"); + return; + } + + if ((glGenTextures = (glGenTexturesFun)glXGetProcAddressARB("glGenTextures")) == nullptr) + { + printf("PipewireGL: failed to get glGenTextures\n"); + return; + } + + if ((glGetError = (glGetErrorFun)glXGetProcAddressARB("glGetError")) == nullptr) + { + printf("PipewireGL: failed to get glGetError\n"); + return; + } + + if ((glGetTexImage = (glGetTexImageFun)glXGetProcAddressARB("glGetTexImage")) == nullptr) + { + printf("PipewireGL: failed to get glGetTexImage\n"); + return; + } + + if ((glTexParameteri = (glTexParameteriFun)glXGetProcAddressARB("glTexParameteri")) == nullptr) + { + printf("PipewireGL: failed to get glTexParameteri\n"); + return; + } + + if (displayEgl == EGL_NO_DISPLAY) + { + if (((displayEgl = eglGetPlatformDisplay(EGL_PLATFORM_WAYLAND_KHR, (void*)EGL_DEFAULT_DISPLAY, nullptr)) == EGL_NO_DISPLAY) && + ((displayEgl = eglGetPlatformDisplay(EGL_PLATFORM_X11_KHR, (void*)EGL_DEFAULT_DISPLAY, nullptr)) == EGL_NO_DISPLAY)) + { + printf("PipewireEGL: no EGL display\n"); + return; + } + else + { + EGLint major, minor; + if (eglInitialize(displayEgl, &major, &minor)) + { + printf("PipewireEGL: EGL initialized for HyperHDR. Version: %d.%d\n", major, minor); + } + else + { + printf("PipewireEGL: failed to init the display\n"); + return; + } + } + } + + if (eglBindAPI(EGL_OPENGL_API) == EGL_FALSE) + { + printf("PipewireEGL: could not bing OpenGL API (reason = '%s')\n", eglErrorToString(eglGetError())); + return; + } + + if (contextEgl == EGL_NO_CONTEXT) + { + if ((contextEgl = eglCreateContext(displayEgl, nullptr, EGL_NO_CONTEXT, nullptr)) == EGL_NO_CONTEXT) + { + printf("PipewireEGL: Failed to create a context (reason = '%s')\n", eglErrorToString(eglGetError())); + return; + } + } + + EGLint dmaCount = 0; + + if (!eglQueryDmaBufFormatsEXT(displayEgl, 0, nullptr, &dmaCount)) + { + printf("PipewireEGL: Failed to query DMA-BUF format count (count = %d, reason = '%s')\n", dmaCount, eglErrorToString(eglGetError())); + return; + } + else if (dmaCount > 0) + { + printf("PipewireEGL: Found %d DMA-BUF formats\n", dmaCount); + } + + + QVector dmaFormats(dmaCount); + + if (dmaCount > 0 && eglQueryDmaBufFormatsEXT(displayEgl, dmaCount, (EGLint*)dmaFormats.data(), &dmaCount)) + { + printf("PipewireEGL: got DMA format list (count = %d)\n", dmaCount); + } + else + { + printf("PipewireEGL: Failed to get DMA-BUF formats (reason = '%s')\n", eglErrorToString(eglGetError())); + return; + } + + + foreach(const uint32_t& val, dmaFormats) + { + bool found = false; + + for(supportedDmaFormat& supVal : _supportedDmaFormatsList) + if (val == supVal.drmFormat) + { + EGLint modCount = 0; + if (eglQueryDmaBufModifiersEXT(displayEgl, supVal.drmFormat, 0, nullptr, nullptr, &modCount) && modCount > 0) + { + supVal.modifiers = QVector(modCount); + if (eglQueryDmaBufModifiersEXT(displayEgl, supVal.drmFormat, modCount, supVal.modifiers.data(), nullptr, &modCount)) + { + printf("PipewireEGL: Found %s DMA format (%s)\n", supVal.friendlyName, fourCCtoString(val).toLocal8Bit().constData()); + supVal.hasDma = true; + found = true; + _initEGL = true; + break; + } + } + } + + if (!found) + { + printf("PipewireEGL: Found unsupported by HyperHDR '%s' DMA format\n", fourCCtoString(val).toLocal8Bit().constData()); + } + } +} +#endif + +const QString PipewireHandler::fourCCtoString(int64_t val) +{ + QString buf; + if (val == DRM_FORMAT_MOD_INVALID) + { + return QString("None"); + } + else for (int i = 0; i < 4; i++) + { + char c = (val >> (i * 8)) & 0xff; + if (isascii(c) && isprint(c)) + buf.append(QChar(c)); + else + buf.append(QChar('?')); + } + return buf; +} + pw_stream* PipewireHandler::createCapturingStream() { static const pw_stream_events pwStreamEvents = { @@ -641,7 +1256,7 @@ pw_stream* PipewireHandler::createCapturingStream() spa_rectangle pwScreenBoundsMax = SPA_RECTANGLE(8192, 8192); spa_fraction pwFramerateMin = SPA_FRACTION(0, 1); - spa_fraction pwFramerateDefault = SPA_FRACTION(25, 1); + spa_fraction pwFramerateDefault = SPA_FRACTION(_requestedFPS, 1); spa_fraction pwFramerateMax = SPA_FRACTION(60, 1); pw_properties* reuseProps = pw_properties_new_string("pipewire.client.reuse=1"); @@ -652,30 +1267,71 @@ pw_stream* PipewireHandler::createCapturingStream() if (stream != nullptr) { - const int spaBufferSize = 1024; - const spa_pod* streamParams[1]; + const int spaBufferSize = 2048; + const spa_pod* streamParams[(sizeof(_supportedDmaFormatsList) / sizeof(supportedDmaFormat)) + 1]; + int streamParamsIndex = 0; uint8_t* spaBuffer = static_cast(calloc(spaBufferSize, 1)); - auto spaBuilder = SPA_POD_BUILDER_INIT(spaBuffer, spaBufferSize); - streamParams[0] = reinterpret_cast (spa_pod_builder_add_object(&spaBuilder, - SPA_TYPE_OBJECT_Format, SPA_PARAM_EnumFormat, - SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_video), - SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_raw), - SPA_FORMAT_VIDEO_format, SPA_POD_CHOICE_ENUM_Id(5, - SPA_VIDEO_FORMAT_BGRx, SPA_VIDEO_FORMAT_BGRx, SPA_VIDEO_FORMAT_BGRA, - SPA_VIDEO_FORMAT_RGBx, SPA_VIDEO_FORMAT_RGBA), - SPA_FORMAT_VIDEO_size, SPA_POD_CHOICE_RANGE_Rectangle( &pwScreenBoundsDefault, &pwScreenBoundsMin, &pwScreenBoundsMax), - SPA_FORMAT_VIDEO_framerate, SPA_POD_CHOICE_RANGE_Fraction( &pwFramerateDefault, &pwFramerateMin, &pwFramerateMax))); + #ifdef ENABLE_PIPEWIRE_EGL + + initEGL(); + + for (const supportedDmaFormat& val : _supportedDmaFormatsList) + if (val.hasDma) + { + spa_pod_frame frameDMA[2]; + spa_pod_builder_push_object(&spaBuilder, &frameDMA[0], SPA_TYPE_OBJECT_Format, SPA_PARAM_EnumFormat); + spa_pod_builder_add(&spaBuilder, SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_video), 0); + spa_pod_builder_add(&spaBuilder, SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_raw), 0); + spa_pod_builder_add(&spaBuilder, SPA_FORMAT_VIDEO_format, SPA_POD_Id(val.spaFormat), 0); + + if (val.modifiers.count() == 1 && val.modifiers[0] == DRM_FORMAT_MOD_INVALID) + { + spa_pod_builder_prop(&spaBuilder, SPA_FORMAT_VIDEO_modifier, SPA_POD_PROP_FLAG_MANDATORY); + spa_pod_builder_long(&spaBuilder, val.modifiers[0]); + } + else if (val.modifiers.count() > 0) + { + spa_pod_builder_prop(&spaBuilder, SPA_FORMAT_VIDEO_modifier, SPA_POD_PROP_FLAG_MANDATORY | SPA_POD_PROP_FLAG_DONT_FIXATE); + spa_pod_builder_push_choice(&spaBuilder, &frameDMA[1], SPA_CHOICE_Enum, 0); + spa_pod_builder_long(&spaBuilder, val.modifiers[0]); + for (auto modVal : val.modifiers) + { + spa_pod_builder_long(&spaBuilder, modVal); + } + spa_pod_builder_pop(&spaBuilder, &frameDMA[1]); + } + + spa_pod_builder_add(&spaBuilder, SPA_FORMAT_VIDEO_size, SPA_POD_CHOICE_RANGE_Rectangle(&pwScreenBoundsDefault, &pwScreenBoundsMin, &pwScreenBoundsMax), 0); + spa_pod_builder_add(&spaBuilder, SPA_FORMAT_VIDEO_framerate, SPA_POD_CHOICE_RANGE_Fraction(&pwFramerateDefault, &pwFramerateMin, &pwFramerateMax), 0); + streamParams[streamParamsIndex++] = reinterpret_cast (spa_pod_builder_pop(&spaBuilder, &frameDMA[0])); + } + #endif + + streamParams[streamParamsIndex++] = reinterpret_cast (spa_pod_builder_add_object(&spaBuilder, + SPA_TYPE_OBJECT_Format, SPA_PARAM_EnumFormat, + SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_video), + SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_raw), + SPA_FORMAT_VIDEO_format, SPA_POD_CHOICE_ENUM_Id(5, + SPA_VIDEO_FORMAT_BGRx, SPA_VIDEO_FORMAT_BGRx, SPA_VIDEO_FORMAT_BGRA, + SPA_VIDEO_FORMAT_RGBx, SPA_VIDEO_FORMAT_RGBA), + SPA_FORMAT_VIDEO_size, SPA_POD_CHOICE_RANGE_Rectangle(&pwScreenBoundsDefault, &pwScreenBoundsMin, &pwScreenBoundsMax), + SPA_FORMAT_VIDEO_framerate, SPA_POD_CHOICE_RANGE_Fraction(&pwFramerateDefault, &pwFramerateMin, &pwFramerateMax))); pw_stream_add_listener(stream, &_pwStreamListener, &pwStreamEvents, this); - if (pw_stream_connect(stream, PW_DIRECTION_INPUT, _streamNodeId, static_cast(PW_STREAM_FLAG_AUTOCONNECT | PW_STREAM_FLAG_MAP_BUFFERS), streamParams, 1) != 0) + auto res = pw_stream_connect(stream, PW_DIRECTION_INPUT, _streamNodeId, static_cast(PW_STREAM_FLAG_AUTOCONNECT | PW_STREAM_FLAG_MAP_BUFFERS), streamParams, streamParamsIndex); + + if ( res != 0) { + printf("Pipewire: could not connect to the stream. Error code: %d\n", res); pw_stream_destroy(stream); stream = nullptr; } + else + printf("Pipewire: the stream is connected\n"); free(spaBuffer); } @@ -683,45 +1339,3 @@ pw_stream* PipewireHandler::createCapturingStream() return stream; } -void PipewireHandler::getImage(PipewireImage* image) -{ - image->version = getVersion(); - image->isError = hasError(); - image->data = nullptr; - if (_workingFrame == nullptr && _backupFrame != nullptr) - { - if (static_cast(_backupFrame->buffer->datas[0].chunk->size) == (_frameWidth * _frameHeight * 4)) - { - _workingFrame = _backupFrame; - _backupFrame = nullptr; - - image->width = _frameWidth; - image->height = _frameHeight; - image->isOrderRgb = _frameOrderRgb; - - image->data = static_cast(_workingFrame->buffer->datas[0].data); - } - else - printf("Pipewire: unexpected frame size. Got: %d, expected: %d\n", _backupFrame->buffer->datas[0].chunk->size, (_frameWidth * _frameHeight * 4)); - } - - if (_framePaused && _pwStream != nullptr) - pw_stream_set_active(_pwStream, true); -} - -void PipewireHandler::releaseWorkingFrame() -{ - if (_pwStream == nullptr || _workingFrame == nullptr) - return; - - if (_backupFrame == nullptr) - { - _backupFrame = _workingFrame; - } - else - { - pw_stream_queue_buffer(_pwStream, _workingFrame); - } - - _workingFrame = nullptr; -} diff --git a/sources/grabber/pipewire/PipewireHandler.h b/sources/grabber/pipewire/PipewireHandler.h index 1831a1647..2fe01b9d7 100644 --- a/sources/grabber/pipewire/PipewireHandler.h +++ b/sources/grabber/pipewire/PipewireHandler.h @@ -1,13 +1,67 @@ +#pragma once + #include +#include #include +#include #include #include #include #include #include #include - -struct PipewireImage; +#include +#include +#include + +#if !PW_CHECK_VERSION(0, 3, 29) +#define SPA_POD_PROP_FLAG_MANDATORY (1u << 3) +#endif +#if !PW_CHECK_VERSION(0, 3, 33) +#define SPA_POD_PROP_FLAG_DONT_FIXATE (1u << 4) +#endif + +#ifdef ENABLE_PIPEWIRE_EGL +#include +#include +#include + +typedef void* (*eglGetProcAddressFun)(const char*); +typedef EGLDisplay(*eglGetPlatformDisplayFun)(EGLenum platform, void* native_display, const EGLAttrib* attrib_list); +typedef EGLBoolean(*eglTerminateFun)(EGLDisplay display); +typedef EGLBoolean(*eglInitializeFun)(EGLDisplay dpy, EGLint* major, EGLint* minor); +typedef EGLint(*eglGetErrorFun)(void); +typedef EGLBoolean (*eglQueryDmaBufFormatsEXTFun)(EGLDisplay dpy, EGLint max_formats, EGLint* formats, EGLint* num_formats); +typedef EGLBoolean (*eglQueryDmaBufModifiersEXTFun)(EGLDisplay dpy, EGLint format, EGLint max_modifiers, EGLuint64KHR* modifiers, EGLBoolean* external_only, EGLint* num_modifiers); +typedef EGLImageKHR (*eglCreateImageKHRFun)(EGLDisplay dpy, EGLContext ctx, EGLenum target, EGLClientBuffer buffer, const EGLint* attrib_list); +typedef EGLBoolean (*eglDestroyImageKHRFun)(EGLDisplay dpy, EGLImageKHR image); +typedef EGLContext(*eglCreateContextFun)(EGLDisplay dpy, EGLConfig config, EGLContext share_context, const EGLint* attrib_list); +typedef EGLBoolean(*eglDestroyContextFun)(EGLDisplay display, EGLContext context); +typedef EGLBoolean(*eglMakeCurrentFun)(EGLDisplay dpy, EGLSurface draw, EGLSurface read, EGLContext ctx); +typedef EGLBoolean(*eglBindAPIFun)(EGLenum api); +typedef void (*glEGLImageTargetTexture2DOESFun)(GLenum target, GLeglImageOES image); + +typedef void* (*glXGetProcAddressARBFun)(const char*); +typedef void (*glBindTextureFun)(GLenum target, GLuint texture); +typedef void (*glDeleteTexturesFun)(GLsizei n, const GLuint* textures); +typedef void (*glGenTexturesFun)(GLsizei n, GLuint* textures); +typedef GLenum (*glGetErrorFun)(void); +typedef void (*glGetTexImageFun)(GLenum target, GLint level, GLenum format, GLenum type, void* pixels); +typedef void (*glTexParameteriFun)(GLenum target, GLenum pname, GLint param); + +#define fourcc_code(a, b, c, d) ((__u32)(a) | ((__u32)(b) << 8) | \ + ((__u32)(c) << 16) | ((__u32)(d) << 24)) +#define fourcc_mod_code(vendor, val) \ + ((((__u64)DRM_FORMAT_MOD_VENDOR_## vendor) << 56) | ((val) & 0x00ffffffffffffffULL)) +#define DRM_FORMAT_MOD_VENDOR_NONE 0 +#define DRM_FORMAT_RESERVED ((1ULL << 56) - 1) +#define DRM_FORMAT_MOD_INVALID fourcc_mod_code(NONE, DRM_FORMAT_RESERVED) + +#define DRM_FORMAT_XRGB8888 fourcc_code('X', 'R', '2', '4') +#define DRM_FORMAT_XBGR8888 fourcc_code('X', 'B', '2', '4') +#define DRM_FORMAT_ARGB8888 fourcc_code('A', 'R', '2', '4') +#define DRM_FORMAT_ABGR8888 fourcc_code('A', 'B', '2', '4') +#endif class PipewireHandler : public QObject { @@ -17,7 +71,7 @@ class PipewireHandler : public QObject PipewireHandler(); ~PipewireHandler(); - void startSession(QString restorationToken); + void startSession(QString restorationToken, uint32_t requestedFPS, pipewire_callback_func callback); void closeSession(); bool hasError(); @@ -25,9 +79,6 @@ class PipewireHandler : public QObject QString getToken(); QString getError(); - void getImage(PipewireImage* image); - void releaseWorkingFrame(); - static int readVersion(); struct PipewireStructure @@ -38,7 +89,7 @@ class PipewireHandler : public QObject }; public Q_SLOTS: - + void grabFrame(); void createSessionResponse(uint response, const QVariantMap& results); void selectSourcesResponse(uint response, const QVariantMap& results); void startResponse(uint response, const QVariantMap& results); @@ -56,11 +107,13 @@ public Q_SLOTS: private: void reportError(const QString& input); + const QString fourCCtoString(int64_t val); pw_stream* createCapturingStream(); QString getSessionToken(); QString getRequestToken(); + pipewire_callback_func _callback; QString _sessionHandle; QString _restorationToken; QString _errorMessage; @@ -87,4 +140,63 @@ public Q_SLOTS: int _frameHeight; bool _frameOrderRgb; bool _framePaused; + uint32_t _requestedFPS; + bool _hasFrame; + bool _infoUpdated; + bool _initEGL; + void* _libEglHandle; + void* _libGlHandle; + int64_t _frameDrmFormat; + int64_t _frameDrmModifier; + QTimer _timer; + +#ifdef ENABLE_PIPEWIRE_EGL + eglGetProcAddressFun eglGetProcAddress = nullptr; + eglInitializeFun eglInitialize = nullptr; + eglTerminateFun eglTerminate = nullptr; + eglGetPlatformDisplayFun eglGetPlatformDisplay = nullptr; + eglGetErrorFun eglGetError = nullptr; + eglQueryDmaBufFormatsEXTFun eglQueryDmaBufFormatsEXT = nullptr; + eglQueryDmaBufModifiersEXTFun eglQueryDmaBufModifiersEXT = nullptr; + eglCreateImageKHRFun eglCreateImageKHR = nullptr; + eglDestroyImageKHRFun eglDestroyImageKHR = nullptr; + eglCreateContextFun eglCreateContext = nullptr; + eglDestroyContextFun eglDestroyContext = nullptr; + eglMakeCurrentFun eglMakeCurrent = nullptr; + glEGLImageTargetTexture2DOESFun glEGLImageTargetTexture2DOES = nullptr; + eglBindAPIFun eglBindAPI = nullptr; + + glXGetProcAddressARBFun glXGetProcAddressARB = nullptr; + glBindTextureFun glBindTexture = nullptr; + glDeleteTexturesFun glDeleteTextures = nullptr; + glGenTexturesFun glGenTextures = nullptr; + glGetErrorFun glGetError = nullptr; + glGetTexImageFun glGetTexImage = nullptr; + glTexParameteriFun glTexParameteri = nullptr; + + EGLDisplay displayEgl = EGL_NO_DISPLAY; + EGLContext contextEgl = EGL_NO_CONTEXT; + + const char* eglErrorToString(EGLint error_number); + const char* glErrorToString(GLenum errorType); + void initEGL(); + + struct supportedDmaFormat { + int64_t drmFormat; + uint32_t spaFormat; + GLenum glFormat; + const char* friendlyName; + bool hasDma; + QVector modifiers; + }; + + supportedDmaFormat _supportedDmaFormatsList[4] = { + { DRM_FORMAT_XRGB8888, SPA_VIDEO_FORMAT_BGRx, GL_BGRA, "DRM_FORMAT_XRGB8888", false}, + { DRM_FORMAT_ARGB8888, SPA_VIDEO_FORMAT_BGRA, GL_BGRA, "DRM_FORMAT_ARGB8888", false}, + { DRM_FORMAT_XBGR8888, SPA_VIDEO_FORMAT_RGBx, GL_RGBA, "DRM_FORMAT_XBGR8888", false}, + { DRM_FORMAT_ABGR8888, SPA_VIDEO_FORMAT_RGBA, GL_RGBA, "DRM_FORMAT_ABGR8888", false} + }; + +#endif + }; diff --git a/sources/grabber/pipewire/PipewireWrapper.cpp b/sources/grabber/pipewire/PipewireWrapper.cpp index 6473376fe..b86b129af 100644 --- a/sources/grabber/pipewire/PipewireWrapper.cpp +++ b/sources/grabber/pipewire/PipewireWrapper.cpp @@ -54,3 +54,8 @@ void PipewireWrapper::stateChanged(bool state) _grabber.stateChanged(state); } +void PipewireWrapper::processFrame(const PipewireImage& frame) +{ + _grabber.grabFrame(frame); +} + diff --git a/sources/grabber/pipewire/smartPipewire.cpp b/sources/grabber/pipewire/smartPipewire.cpp index c85e2c4ec..1bcb6ba0e 100644 --- a/sources/grabber/pipewire/smartPipewire.cpp +++ b/sources/grabber/pipewire/smartPipewire.cpp @@ -51,16 +51,11 @@ std::unique_ptr _pipewireHandler(nullptr); -void initPipewireDisplay(const char* restorationToken) +void initPipewireDisplay(const char* restorationToken, uint32_t requestedFPS, pipewire_callback_func callback) { QString qRestorationToken = QString("%1").arg(restorationToken); _pipewireHandler = std::unique_ptr(new PipewireHandler()); - _pipewireHandler->startSession(qRestorationToken); -} - -void releaseFramePipewire() -{ - _pipewireHandler->releaseWorkingFrame(); + _pipewireHandler->startSession(qRestorationToken, requestedFPS, callback); } const char* getPipewireToken() @@ -130,18 +125,3 @@ bool hasPipewire() } return false; } - -PipewireImage getFramePipewire() -{ - PipewireImage retVal; - - if (_pipewireHandler != nullptr) - { - releaseFramePipewire(); - - _pipewireHandler->getImage(&retVal); - } - - return retVal; -} -