diff --git a/CMakeLists.txt b/CMakeLists.txt index 1a06be0..411871f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -29,7 +29,7 @@ add_subdirectory(subprojects/sdbus-cpp) find_package(Threads REQUIRED) find_package(PkgConfig REQUIRED) -pkg_check_modules(deps REQUIRED IMPORTED_TARGET wayland-client wayland-protocols cairo pango pangocairo libjpeg libpipewire-0.3 libspa-0.2) +pkg_check_modules(deps REQUIRED IMPORTED_TARGET wayland-client wayland-protocols cairo pango pangocairo libjpeg libpipewire-0.3 libspa-0.2 libdrm gbm) file(GLOB_RECURSE SRCFILES CONFIGURE_DEPENDS "src/*.cpp") add_executable(xdg-desktop-portal-hyprland ${SRCFILES}) diff --git a/src/core/PortalManager.cpp b/src/core/PortalManager.cpp index 7a9e760..b4a6243 100644 --- a/src/core/PortalManager.cpp +++ b/src/core/PortalManager.cpp @@ -9,6 +9,8 @@ #include #include +#include +#include #include @@ -78,6 +80,117 @@ inline const zwp_linux_dmabuf_v1_listener dmabufListener = { .modifier = handleDMABUFModifier, }; +static void dmabufFeedbackMainDevice(void* data, zwp_linux_dmabuf_feedback_v1* feedback, wl_array* device_arr) { + Debug::log(LOG, "[core] dmabufFeedbackMainDevice"); + + RASSERT(!g_pPortalManager->m_sWaylandConnection.gbm, "double dmabuf feedback"); + + dev_t device; + assert(device_arr->size == sizeof(device)); + memcpy(&device, device_arr->data, sizeof(device)); + + drmDevice* drmDev; + if (drmGetDeviceFromDevId(device, /* flags */ 0, &drmDev) != 0) { + Debug::log(WARN, "[dmabuf] unable to open main device?"); + exit(1); + } + + g_pPortalManager->m_sWaylandConnection.gbmDevice = g_pPortalManager->createGBMDevice(drmDev); +} + +static void dmabufFeedbackFormatTable(void* data, zwp_linux_dmabuf_feedback_v1* feedback, int fd, uint32_t size) { + Debug::log(TRACE, "[core] dmabufFeedbackFormatTable"); + + g_pPortalManager->m_vDMABUFMods.clear(); + + g_pPortalManager->m_sWaylandConnection.dma.formatTable = mmap(NULL, size, PROT_READ, MAP_PRIVATE, fd, 0); + + if (g_pPortalManager->m_sWaylandConnection.dma.formatTable == MAP_FAILED) { + Debug::log(ERR, "[core] format table failed to mmap"); + g_pPortalManager->m_sWaylandConnection.dma.formatTable = nullptr; + g_pPortalManager->m_sWaylandConnection.dma.formatTableSize = 0; + return; + } + + g_pPortalManager->m_sWaylandConnection.dma.formatTableSize = size; +} + +static void dmabufFeedbackDone(void* data, zwp_linux_dmabuf_feedback_v1* feedback) { + Debug::log(TRACE, "[core] dmabufFeedbackDone"); + + if (g_pPortalManager->m_sWaylandConnection.dma.formatTable) + munmap(g_pPortalManager->m_sWaylandConnection.dma.formatTable, g_pPortalManager->m_sWaylandConnection.dma.formatTableSize); + + g_pPortalManager->m_sWaylandConnection.dma.formatTable = nullptr; + g_pPortalManager->m_sWaylandConnection.dma.formatTableSize = 0; +} + +static void dmabufFeedbackTrancheTargetDevice(void* data, zwp_linux_dmabuf_feedback_v1* feedback, wl_array* device_arr) { + Debug::log(TRACE, "[core] dmabufFeedbackTrancheTargetDevice"); + + dev_t device; + assert(device_arr->size == sizeof(device)); + memcpy(&device, device_arr->data, sizeof(device)); + + drmDevice* drmDev; + if (drmGetDeviceFromDevId(device, /* flags */ 0, &drmDev) != 0) + return; + + if (g_pPortalManager->m_sWaylandConnection.gbmDevice) { + drmDevice* drmDevRenderer = NULL; + drmGetDevice2(gbm_device_get_fd(g_pPortalManager->m_sWaylandConnection.gbmDevice), /* flags */ 0, &drmDevRenderer); + g_pPortalManager->m_sWaylandConnection.dma.deviceUsed = drmDevicesEqual(drmDevRenderer, drmDev); + } else { + g_pPortalManager->m_sWaylandConnection.gbmDevice = g_pPortalManager->createGBMDevice(drmDev); + g_pPortalManager->m_sWaylandConnection.dma.deviceUsed = g_pPortalManager->m_sWaylandConnection.gbm; + } +} + +static void dmabufFeedbackTrancheFlags(void* data, zwp_linux_dmabuf_feedback_v1* feedback, uint32_t flags) { + ; +} + +static void dmabufFeedbackTrancheFormats(void* data, zwp_linux_dmabuf_feedback_v1* feedback, wl_array* indices) { + Debug::log(TRACE, "[core] dmabufFeedbackTrancheFormats"); + + if (!g_pPortalManager->m_sWaylandConnection.dma.deviceUsed || !g_pPortalManager->m_sWaylandConnection.dma.formatTable) + return; + + struct fm_entry { + uint32_t format; + uint32_t padding; + uint64_t modifier; + }; + // An entry in the table has to be 16 bytes long + assert(sizeof(struct fm_entry) == 16); + + uint32_t n_modifiers = g_pPortalManager->m_sWaylandConnection.dma.formatTableSize / sizeof(struct fm_entry); + fm_entry* fm_entry = (struct fm_entry*)g_pPortalManager->m_sWaylandConnection.dma.formatTable; + uint16_t* idx; + wl_array_for_each(idx, indices) { + if (*idx >= n_modifiers) + continue; + + g_pPortalManager->m_vDMABUFMods.push_back({(fm_entry + *idx)->format, (fm_entry + *idx)->modifier}); + } +} + +static void dmabufFeedbackTrancheDone(void* data, struct zwp_linux_dmabuf_feedback_v1* zwp_linux_dmabuf_feedback_v1) { + Debug::log(TRACE, "[core] dmabufFeedbackTrancheDone"); + + g_pPortalManager->m_sWaylandConnection.dma.deviceUsed = false; +} + +inline const zwp_linux_dmabuf_feedback_v1_listener dmabufFeedbackListener = { + .done = dmabufFeedbackDone, + .format_table = dmabufFeedbackFormatTable, + .main_device = dmabufFeedbackMainDevice, + .tranche_done = dmabufFeedbackTrancheDone, + .tranche_target_device = dmabufFeedbackTrancheTargetDevice, + .tranche_formats = dmabufFeedbackTrancheFormats, + .tranche_flags = dmabufFeedbackTrancheFlags, +}; + // void CPortalManager::onGlobal(void* data, struct wl_registry* registry, uint32_t name, const char* interface, uint32_t version) { @@ -106,7 +219,7 @@ void CPortalManager::onGlobal(void* data, struct wl_registry* registry, uint32_t m_sWaylandConnection.linuxDmabuf = wl_registry_bind(registry, name, &zwp_linux_dmabuf_v1_interface, version); m_sWaylandConnection.linuxDmabufFeedback = zwp_linux_dmabuf_v1_get_default_feedback((zwp_linux_dmabuf_v1*)m_sWaylandConnection.linuxDmabuf); - // TODO: dmabuf + zwp_linux_dmabuf_feedback_v1_add_listener((zwp_linux_dmabuf_feedback_v1*)m_sWaylandConnection.linuxDmabufFeedback, &dmabufFeedbackListener, nullptr); } else if (INTERFACE == wl_shm_interface.name) @@ -211,4 +324,46 @@ SOutput* CPortalManager::getOutputFromName(const std::string& name) { return o.get(); } return nullptr; -} \ No newline at end of file +} + +static char* gbm_find_render_node(drmDevice* device) { + drmDevice* devices[64]; + char* render_node = NULL; + + int n = drmGetDevices2(0, devices, sizeof(devices) / sizeof(devices[0])); + for (int i = 0; i < n; ++i) { + drmDevice* dev = devices[i]; + if (device && !drmDevicesEqual(device, dev)) { + continue; + } + if (!(dev->available_nodes & (1 << DRM_NODE_RENDER))) + continue; + + render_node = strdup(dev->nodes[DRM_NODE_RENDER]); + break; + } + + drmFreeDevices(devices, n); + return render_node; +} + +gbm_device* CPortalManager::createGBMDevice(drmDevice* dev) { + char* renderNode = gbm_find_render_node(dev); + + if (!renderNode) { + Debug::log(ERR, "[core] Couldn't find a render node"); + return nullptr; + } + + Debug::log(TRACE, "[core] createGBMDevice: render node {}", renderNode); + + int fd = open(renderNode, O_RDWR | O_CLOEXEC); + if (fd < 0) { + Debug::log(ERR, "[core] couldn't open render node"); + free(renderNode); + return NULL; + } + + free(renderNode); + return gbm_create_device(fd); +} diff --git a/src/core/PortalManager.hpp b/src/core/PortalManager.hpp index 14434c8..4e7f527 100644 --- a/src/core/PortalManager.hpp +++ b/src/core/PortalManager.hpp @@ -6,6 +6,8 @@ #include "../portals/Screencopy.hpp" #include "../helpers/Timer.hpp" +#include +#include #include @@ -46,12 +48,21 @@ class CPortalManager { void* linuxDmabuf = nullptr; void* linuxDmabufFeedback = nullptr; wl_shm* shm = nullptr; + gbm_bo* gbm; + gbm_device* gbmDevice; + struct { + void* formatTable = nullptr; + size_t formatTableSize = 0; + bool deviceUsed = false; + } dma; } m_sWaylandConnection; std::vector m_vDMABUFMods; std::vector> m_vTimers; + gbm_device* createGBMDevice(drmDevice* dev); + private: std::unique_ptr m_pConnection; std::vector> m_vOutputs; diff --git a/src/helpers/Log.hpp b/src/helpers/Log.hpp index 21ab904..7aa0c25 100644 --- a/src/helpers/Log.hpp +++ b/src/helpers/Log.hpp @@ -13,6 +13,17 @@ enum eLogLevel CRIT }; +#define RASSERT(expr, reason, ...) \ + if (!(expr)) { \ + Debug::log(CRIT, "\n==========================================================================================\nASSERTION FAILED! \n\n{}\n\nat: line {} in {}", \ + std::format(reason, ##__VA_ARGS__), __LINE__, \ + ([]() constexpr->std::string { return std::string(__FILE__).substr(std::string(__FILE__).find_last_of('/') + 1); })().c_str()); \ + printf("Assertion failed! See the log in /tmp/hypr/hyprland.log for more info."); \ + *((int*)nullptr) = 1; /* so that we crash and get a coredump */ \ + } + +#define ASSERT(expr) RASSERT(expr, "?") + namespace Debug { template void log(eLogLevel level, const std::string& fmt, Args&&... args) { diff --git a/src/portals/Screencopy.cpp b/src/portals/Screencopy.cpp index 272d91c..c3787ab 100644 --- a/src/portals/Screencopy.cpp +++ b/src/portals/Screencopy.cpp @@ -5,6 +5,7 @@ #include #include +#include // --------------- Wayland Protocol Handlers --------------- // @@ -45,7 +46,7 @@ static void wlrOnReady(void* data, struct zwlr_screencopy_frame_v1* frame, uint3 g_pPortalManager->m_sPortals.screencopy->m_pPipewire->enqueue(PSESSION); - g_pPortalManager->m_vTimers.emplace_back(std::make_unique(1000.0 / FRAMERATE, [PSESSION]() { g_pPortalManager->m_sPortals.screencopy->startFrameCopy(PSESSION); })); + g_pPortalManager->m_sPortals.screencopy->queueNextShareFrame(PSESSION); zwlr_screencopy_frame_v1_destroy(frame); PSESSION->sharingData.frameCallback = nullptr; @@ -94,6 +95,17 @@ static void wlrOnBufferDone(void* data, struct zwlr_screencopy_frame_v1* frame) Debug::log(TRACE, "[sc] pw format {} size {}x{}", (int)PSTREAM->pwVideoInfo.format, PSTREAM->pwVideoInfo.size.width, PSTREAM->pwVideoInfo.size.height); Debug::log(TRACE, "[sc] wlr format {} size {}x{}", (int)PSESSION->sharingData.frameInfoSHM.fmt, PSESSION->sharingData.frameInfoSHM.w, PSESSION->sharingData.frameInfoSHM.h); + const auto FMT = PSTREAM->isDMA ? PSESSION->sharingData.frameInfoDMA.fmt : PSESSION->sharingData.frameInfoSHM.fmt; + if ((PSTREAM->pwVideoInfo.format != pwFromDrmFourcc(FMT) && PSTREAM->pwVideoInfo.format != pwStripAlpha(pwFromDrmFourcc(FMT))) || + (PSTREAM->pwVideoInfo.size.width != PSESSION->sharingData.frameInfoDMA.w || PSTREAM->pwVideoInfo.size.height != PSESSION->sharingData.frameInfoDMA.h)) { + Debug::log(LOG, "[sc] Incompatible formats, renegotiate stream"); + PSESSION->sharingData.status = FRAME_RENEG; + zwlr_screencopy_frame_v1_destroy(frame); + g_pPortalManager->m_sPortals.screencopy->m_pPipewire->updateStreamParam(PSTREAM); + g_pPortalManager->m_sPortals.screencopy->queueNextShareFrame(PSESSION); + return; + } + if (!PSTREAM->currentPWBuffer) { Debug::log(TRACE, "[sc] wlrOnBufferDone: dequeue, no current buffer"); g_pPortalManager->m_sPortals.screencopy->m_pPipewire->dequeue(PSESSION); @@ -351,8 +363,8 @@ void CScreencopyPortal::startSharing(CScreencopyPortal::SSession* pSession) { wl_display_dispatch(g_pPortalManager->m_sWaylandConnection.display); wl_display_roundtrip(g_pPortalManager->m_sWaylandConnection.display); - if (pSession->sharingData.frameInfoSHM.fmt == DRM_FORMAT_INVALID) { - Debug::log(ERR, "[screencopy] Couldn't obtain a format from shm"); + if (pSession->sharingData.frameInfoDMA.fmt == DRM_FORMAT_INVALID) { + Debug::log(ERR, "[screencopy] Couldn't obtain a format from dma"); return; } @@ -368,9 +380,9 @@ void CScreencopyPortal::startSharing(CScreencopyPortal::SSession* pSession) { Debug::log(LOG, "[screencopy] Sharing initialized"); - g_pPortalManager->m_vTimers.emplace_back(std::make_unique(1000.0 / FRAMERATE, [pSession]() { g_pPortalManager->m_sPortals.screencopy->startFrameCopy(pSession); })); + g_pPortalManager->m_sPortals.screencopy->queueNextShareFrame(pSession); - Debug::log(TRACE, "[sc] queued frame in {}ms", 1000.0 / FRAMERATE); + Debug::log(TRACE, "[sc] queued frame in {}ms", 1000.0 / pSession->sharingData.framerate); } void CScreencopyPortal::startFrameCopy(CScreencopyPortal::SSession* pSession) { @@ -408,6 +420,11 @@ void CScreencopyPortal::startFrameCopy(CScreencopyPortal::SSession* pSession) { Debug::log(LOG, "[screencopy] frame callbacks initialized"); } +void CScreencopyPortal::queueNextShareFrame(CScreencopyPortal::SSession* pSession) { + g_pPortalManager->m_vTimers.emplace_back( + std::make_unique(1000.0 / pSession->sharingData.framerate, [pSession]() { g_pPortalManager->m_sPortals.screencopy->startFrameCopy(pSession); })); +} + CScreencopyPortal::SSession* CScreencopyPortal::getSession(sdbus::ObjectPath& path) { for (auto& s : m_vSessions) { if (s->sessionHandle == path) @@ -517,12 +534,82 @@ static void pwStreamParamChanged(void* data, uint32_t id, const spa_pod* param) spa_pod_dynamic_builder_init(&dynBuilder[2], params_buffer[2], sizeof(params_buffer[2]), 2048); spa_format_video_raw_parse(param, &PSTREAM->pwVideoInfo); - // todo: framerate + PSTREAM->pSession->sharingData.framerate = PSTREAM->pwVideoInfo.max_framerate.num / PSTREAM->pwVideoInfo.max_framerate.denom; + + uint32_t data_type = 1 << SPA_DATA_MemFd; const struct spa_pod_prop* prop_modifier; - if ((prop_modifier = spa_pod_find_prop(param, NULL, SPA_FORMAT_VIDEO_modifier)) != NULL) { - Debug::log(ERR, "[pipewire] pw requested dmabuf"); - return; + if ((prop_modifier = spa_pod_find_prop(param, nullptr, SPA_FORMAT_VIDEO_modifier))) { + Debug::log(TRACE, "[pipewire] pw requested dmabuf"); + PSTREAM->isDMA = true; + data_type = 1 << SPA_DATA_DmaBuf; + + RASSERT(PSTREAM->pwVideoInfo.format == pwFromDrmFourcc(PSTREAM->pSession->sharingData.frameInfoDMA.fmt), "invalid format in dma pw param change"); + + if ((prop_modifier->flags & SPA_POD_PROP_FLAG_DONT_FIXATE) > 0) { + Debug::log(TRACE, "[pw] don't fixate"); + const spa_pod* pod_modifier = &prop_modifier->value; + + uint32_t n_modifiers = SPA_POD_CHOICE_N_VALUES(pod_modifier) - 1; + uint64_t* modifiers = SPA_POD_CHOICE_VALUES(pod_modifier); + modifiers++; + uint32_t flags = GBM_BO_USE_RENDERING; + uint64_t modifier; + uint32_t n_params; + spa_pod_builder* builder[2] = {&dynBuilder[0].b, &dynBuilder[1].b}; + + gbm_bo* bo = + gbm_bo_create_with_modifiers2(g_pPortalManager->m_sWaylandConnection.gbmDevice, PSTREAM->pSession->sharingData.frameInfoDMA.w, + PSTREAM->pSession->sharingData.frameInfoDMA.h, PSTREAM->pSession->sharingData.frameInfoDMA.fmt, modifiers, n_modifiers, flags); + if (bo) { + modifier = gbm_bo_get_modifier(bo); + gbm_bo_destroy(bo); + goto fixate_format; + } + + Debug::log(TRACE, "[pw] unable to allocate a dmabuf with modifiers. Falling back to the old api"); + for (uint32_t i = 0; i < n_modifiers; i++) { + switch (modifiers[i]) { + case DRM_FORMAT_MOD_INVALID: + flags = + GBM_BO_USE_RENDERING; // ;cast->ctx->state->config->screencast_conf.force_mod_linear ? GBM_BO_USE_RENDERING | GBM_BO_USE_LINEAR : GBM_BO_USE_RENDERING; + break; + case DRM_FORMAT_MOD_LINEAR: flags = GBM_BO_USE_RENDERING | GBM_BO_USE_LINEAR; break; + default: continue; + } + bo = gbm_bo_create(g_pPortalManager->m_sWaylandConnection.gbmDevice, PSTREAM->pSession->sharingData.frameInfoDMA.w, PSTREAM->pSession->sharingData.frameInfoDMA.h, + PSTREAM->pSession->sharingData.frameInfoDMA.fmt, flags); + if (bo) { + modifier = gbm_bo_get_modifier(bo); + gbm_bo_destroy(bo); + goto fixate_format; + } + } + + Debug::log(ERR, "[pw] failed to alloc dma"); + return; + + fixate_format: + params[0] = fixate_format(&dynBuilder[2].b, pwFromDrmFourcc(PSTREAM->pSession->sharingData.frameInfoDMA.fmt), PSTREAM->pSession->sharingData.frameInfoDMA.w, + PSTREAM->pSession->sharingData.frameInfoDMA.h, PSTREAM->pSession->sharingData.framerate, &modifier); + + n_params = g_pPortalManager->m_sPortals.screencopy->m_pPipewire->buildFormatsFor(builder, ¶ms[1], PSTREAM); + n_params++; + + pw_stream_update_params(PSTREAM->stream, params, n_params); + spa_pod_dynamic_builder_clean(&dynBuilder[0]); + spa_pod_dynamic_builder_clean(&dynBuilder[1]); + spa_pod_dynamic_builder_clean(&dynBuilder[2]); + + Debug::log(TRACE, "[pw] Format renegotiated:"); + Debug::log(TRACE, "[pw] | buffer_type {}", "DMA"); + Debug::log(TRACE, "[pw] | format: {}", (int)PSTREAM->pwVideoInfo.format); + Debug::log(TRACE, "[pw] | modifier: {}", PSTREAM->pwVideoInfo.modifier); + Debug::log(TRACE, "[pw] | size: {}x{}", PSTREAM->pwVideoInfo.size.width, PSTREAM->pwVideoInfo.size.height); + Debug::log(TRACE, "[pw] | framerate {}", PSTREAM->pSession->sharingData.framerate); + + return; + } } Debug::log(TRACE, "[pw] Format renegotiated:"); @@ -530,10 +617,9 @@ static void pwStreamParamChanged(void* data, uint32_t id, const spa_pod* param) Debug::log(TRACE, "[pw] | format: {}", (int)PSTREAM->pwVideoInfo.format); Debug::log(TRACE, "[pw] | modifier: {}", PSTREAM->pwVideoInfo.modifier); Debug::log(TRACE, "[pw] | size: {}x{}", PSTREAM->pwVideoInfo.size.width, PSTREAM->pwVideoInfo.size.height); - Debug::log(TRACE, "[pw] | framerate {}", FRAMERATE); + Debug::log(TRACE, "[pw] | framerate {}", PSTREAM->pSession->sharingData.framerate); - uint32_t blocks = 1; - uint32_t data_type = 1 << SPA_DATA_MemFd; + uint32_t blocks = 1; params[0] = build_buffer(&dynBuilder[0].b, blocks, PSTREAM->pSession->sharingData.frameInfoSHM.size, PSTREAM->pSession->sharingData.frameInfoSHM.stride, data_type); @@ -556,6 +642,7 @@ static void pwStreamAddBuffer(void* data, pw_buffer* buffer) { if ((spaData[0].type & (1u << SPA_DATA_MemFd)) > 0) { type = SPA_DATA_MemFd; + Debug::log(WARN, "[pipewire] Asked for a wl_shm buffer which is legacy."); } else if ((spaData[0].type & (1u << SPA_DATA_DmaBuf)) > 0) { type = SPA_DATA_DmaBuf; } else { @@ -679,15 +766,73 @@ void CPipewireConnection::destroyStream(CScreencopyPortal::SSession* pSession) { std::erase_if(m_vStreams, [&](const auto& other) { return other.get() == PSTREAM; }); } +static bool wlr_query_dmabuf_modifiers(uint32_t drm_format, uint32_t num_modifiers, uint64_t* modifiers, uint32_t* max_modifiers) { + if (g_pPortalManager->m_vDMABUFMods.empty()) + return false; + + if (num_modifiers == 0) { + *max_modifiers = 0; + for (auto& mod : g_pPortalManager->m_vDMABUFMods) { + if (mod.fourcc == drm_format && + (mod.mod == DRM_FORMAT_MOD_INVALID || gbm_device_get_format_modifier_plane_count(g_pPortalManager->m_sWaylandConnection.gbmDevice, mod.fourcc, mod.mod) > 0)) + (*max_modifiers)++; + } + return true; + } + + for (size_t i = 0; i < g_pPortalManager->m_vDMABUFMods.size(); ++i) { + if (i >= num_modifiers) + break; + + const auto& mod = g_pPortalManager->m_vDMABUFMods[i]; + + if (mod.fourcc == drm_format && + (mod.mod == DRM_FORMAT_MOD_INVALID || gbm_device_get_format_modifier_plane_count(g_pPortalManager->m_sWaylandConnection.gbmDevice, mod.fourcc, mod.mod) > 0)) + modifiers[i] = mod.mod; + } + + *max_modifiers = num_modifiers; + return true; +} + +static bool build_modifierlist(CPipewireConnection::SPWStream* stream, uint32_t drm_format, uint64_t** modifiers, uint32_t* modifier_count) { + if (!wlr_query_dmabuf_modifiers(drm_format, 0, nullptr, modifier_count)) { + *modifiers = NULL; + *modifier_count = 0; + return false; + } + if (*modifier_count == 0) { + Debug::log(ERR, "[pw] build_modifierlist: no mods"); + *modifiers = NULL; + return true; + } + *modifiers = (uint64_t*)calloc(*modifier_count, sizeof(uint64_t)); + bool ret = wlr_query_dmabuf_modifiers(drm_format, *modifier_count, *modifiers, modifier_count); + Debug::log(TRACE, "[pw] build_modifierlist: count {}", *modifier_count); + return ret; +} + uint32_t CPipewireConnection::buildFormatsFor(spa_pod_builder* b[2], const spa_pod* params[2], CPipewireConnection::SPWStream* stream) { - uint32_t paramCount = 0; + uint32_t paramCount = 0; + uint32_t modCount = 0; + uint64_t* modifiers = nullptr; - if (/*TODO: dmabuf*/ false) { + if (build_modifierlist(stream, stream->pSession->sharingData.frameInfoDMA.fmt, &modifiers, &modCount) && modCount > 0) { + Debug::log(LOG, "[pw] Building modifiers for dma"); + paramCount = 2; + params[0] = build_format(b[0], pwFromDrmFourcc(stream->pSession->sharingData.frameInfoDMA.fmt), stream->pSession->sharingData.frameInfoDMA.w, + stream->pSession->sharingData.frameInfoDMA.h, stream->pSession->sharingData.framerate, modifiers, modCount); + assert(params[0] != NULL); + params[1] = build_format(b[1], pwFromDrmFourcc(stream->pSession->sharingData.frameInfoSHM.fmt), stream->pSession->sharingData.frameInfoSHM.w, + stream->pSession->sharingData.frameInfoSHM.h, stream->pSession->sharingData.framerate, NULL, 0); + assert(params[1] != NULL); } else { + Debug::log(LOG, "[pw] Building modifiers for shm"); + paramCount = 1; params[0] = build_format(b[0], pwFromDrmFourcc(stream->pSession->sharingData.frameInfoSHM.fmt), stream->pSession->sharingData.frameInfoSHM.w, - stream->pSession->sharingData.frameInfoSHM.h, FRAMERATE /*TODO: FRAMERATE*/, NULL, 0); + stream->pSession->sharingData.frameInfoSHM.h, stream->pSession->sharingData.framerate, NULL, 0); } return paramCount; @@ -716,16 +861,16 @@ void CPipewireConnection::enqueue(CScreencopyPortal::SSession* pSession) { } spa_buffer* spaBuf = PSTREAM->currentPWBuffer->pwBuffer->buffer; - bool corrupt = PSTREAM->pSession->sharingData.status != FRAME_READY; - if (corrupt) + const bool CORRUPT = PSTREAM->pSession->sharingData.status != FRAME_READY; + if (CORRUPT) Debug::log(TRACE, "[pw] buffer corrupt"); Debug::log(TRACE, "[pw] Enqueue data:"); - spa_meta_header* header; - if ((header = (spa_meta_header*)spa_buffer_find_meta_data(spaBuf, SPA_META_Header, sizeof(*header)))) { + spa_meta_header* header = (spa_meta_header*)spa_buffer_find_meta_data(spaBuf, SPA_META_Header, sizeof(*header)); + if (header) { header->pts = PSTREAM->pSession->sharingData.tvTimestampNs; - header->flags = corrupt ? SPA_META_HEADER_FLAG_CORRUPTED : 0; + header->flags = CORRUPT ? SPA_META_HEADER_FLAG_CORRUPTED : 0; header->seq = PSTREAM->seq++; header->dts_offset = 0; Debug::log(TRACE, "[pw] | seq {}", header->seq); @@ -734,10 +879,10 @@ void CPipewireConnection::enqueue(CScreencopyPortal::SSession* pSession) { spa_data* datas = spaBuf->datas; - Debug::log(TRACE, "[pw] | size {}x{}", PSTREAM->pSession->sharingData.frameInfoSHM.w, PSTREAM->pSession->sharingData.frameInfoSHM.h); + Debug::log(TRACE, "[pw] | size {}x{}", PSTREAM->pSession->sharingData.frameInfoDMA.w, PSTREAM->pSession->sharingData.frameInfoDMA.h); for (uint32_t plane = 0; plane < spaBuf->n_datas; plane++) { - datas[plane].chunk->flags = corrupt ? SPA_CHUNK_FLAG_CORRUPTED : SPA_CHUNK_FLAG_NONE; + datas[plane].chunk->flags = CORRUPT ? SPA_CHUNK_FLAG_CORRUPTED : SPA_CHUNK_FLAG_NONE; Debug::log(TRACE, "[pw] | plane {}", plane); Debug::log(TRACE, "[pw] | fd {}", datas[plane].fd); @@ -776,15 +921,76 @@ void CPipewireConnection::dequeue(CScreencopyPortal::SSession* pSession) { std::unique_ptr CPipewireConnection::createBuffer(CPipewireConnection::SPWStream* pStream, bool dmabuf) { std::unique_ptr pBuffer = std::make_unique(); - pBuffer->w = pStream->pSession->sharingData.frameInfoSHM.w; - pBuffer->h = pStream->pSession->sharingData.frameInfoSHM.h; - pBuffer->fmt = pStream->pSession->sharingData.frameInfoSHM.fmt; pBuffer->isDMABUF = dmabuf; + Debug::log(TRACE, "[pw] createBuffer: type {}", dmabuf ? "dma" : "shm"); + if (dmabuf) { - // todo + pBuffer->w = pStream->pSession->sharingData.frameInfoDMA.w; + pBuffer->h = pStream->pSession->sharingData.frameInfoDMA.h; + pBuffer->fmt = pStream->pSession->sharingData.frameInfoDMA.fmt; + + uint32_t flags = GBM_BO_USE_RENDERING; + + if (pStream->pwVideoInfo.modifier != DRM_FORMAT_MOD_INVALID) { + uint64_t* mods = (uint64_t*)&pStream->pwVideoInfo.modifier; + pBuffer->bo = gbm_bo_create_with_modifiers2(g_pPortalManager->m_sWaylandConnection.gbmDevice, pBuffer->w, pBuffer->h, pBuffer->fmt, mods, 1, flags); + } else { + pBuffer->bo = gbm_bo_create(g_pPortalManager->m_sWaylandConnection.gbmDevice, pBuffer->w, pBuffer->h, pBuffer->fmt, flags); + } + + if (!pBuffer->bo) { + Debug::log(ERR, "[pw] Couldn't create a drm buffer"); + return nullptr; + } + + pBuffer->planeCount = gbm_bo_get_plane_count(pBuffer->bo); + + zwp_linux_buffer_params_v1* params = zwp_linux_dmabuf_v1_create_params((zwp_linux_dmabuf_v1*)g_pPortalManager->m_sWaylandConnection.linuxDmabuf); + if (!params) { + Debug::log(ERR, "[pw] zwp_linux_dmabuf_v1_create_params failed"); + gbm_bo_destroy(pBuffer->bo); + return nullptr; + } + + for (size_t plane = 0; plane < (size_t)pBuffer->planeCount; plane++) { + pBuffer->size[plane] = 0; + pBuffer->stride[plane] = gbm_bo_get_stride_for_plane(pBuffer->bo, plane); + pBuffer->offset[plane] = gbm_bo_get_offset(pBuffer->bo, plane); + uint64_t mod = gbm_bo_get_modifier(pBuffer->bo); + pBuffer->fd[plane] = gbm_bo_get_fd_for_plane(pBuffer->bo, plane); + + if (pBuffer->fd[plane] < 0) { + Debug::log(ERR, "[pw] gbm_bo_get_fd_for_plane failed"); + zwp_linux_buffer_params_v1_destroy(params); + gbm_bo_destroy(pBuffer->bo); + for (size_t plane_tmp = 0; plane_tmp < plane; plane_tmp++) { + close(pBuffer->fd[plane_tmp]); + } + return NULL; + } + + zwp_linux_buffer_params_v1_add(params, pBuffer->fd[plane], plane, pBuffer->offset[plane], pBuffer->stride[plane], mod >> 32, mod & 0xffffffff); + } + + pBuffer->wlBuffer = zwp_linux_buffer_params_v1_create_immed(params, pBuffer->w, pBuffer->h, pBuffer->fmt, /* flags */ 0); + zwp_linux_buffer_params_v1_destroy(params); + + if (!pBuffer->wlBuffer) { + Debug::log(ERR, "[pw] zwp_linux_buffer_params_v1_create_immed failed"); + gbm_bo_destroy(pBuffer->bo); + for (size_t plane = 0; plane < (size_t)pBuffer->planeCount; plane++) { + close(pBuffer->fd[plane]); + } + + return nullptr; + } } else { + pBuffer->w = pStream->pSession->sharingData.frameInfoSHM.w; + pBuffer->h = pStream->pSession->sharingData.frameInfoSHM.h; + pBuffer->fmt = pStream->pSession->sharingData.frameInfoSHM.fmt; + pBuffer->planeCount = 1; pBuffer->size[0] = pStream->pSession->sharingData.frameInfoSHM.size; pBuffer->stride[0] = pStream->pSession->sharingData.frameInfoSHM.stride; @@ -811,3 +1017,20 @@ std::unique_ptr CPipewireConnection::createBuffer(CPipewireConnection:: return pBuffer; } + +void CPipewireConnection::updateStreamParam(SPWStream* pStream) { + Debug::log(TRACE, "[pw] update stream params"); + + uint8_t paramsBuf[2][1024]; + spa_pod_dynamic_builder dynBuilder[2]; + spa_pod_dynamic_builder_init(&dynBuilder[0], paramsBuf[0], sizeof(paramsBuf[0]), 2048); + spa_pod_dynamic_builder_init(&dynBuilder[1], paramsBuf[1], sizeof(paramsBuf[1]), 2048); + const spa_pod* params[2]; + + spa_pod_builder* builder[2] = {&dynBuilder[0].b, &dynBuilder[1].b}; + uint32_t n_params = buildFormatsFor(builder, params, pStream); + + pw_stream_update_params(pStream->stream, params, n_params); + spa_pod_dynamic_builder_clean(&dynBuilder[0]); + spa_pod_dynamic_builder_clean(&dynBuilder[1]); +} diff --git a/src/portals/Screencopy.hpp b/src/portals/Screencopy.hpp index 33c8013..c743d5e 100644 --- a/src/portals/Screencopy.hpp +++ b/src/portals/Screencopy.hpp @@ -6,8 +6,6 @@ #include "../shared/ScreencopyShared.hpp" #include -#define FRAMERATE 60 - enum cursorModes { HIDDEN = 1, @@ -28,6 +26,7 @@ enum frameStatus FRAME_QUEUED, FRAME_READY, FRAME_FAILED, + FRAME_RENEG, }; struct pw_context; @@ -78,6 +77,7 @@ class CScreencopyPortal { uint32_t tvNsec = 0; uint64_t tvTimestampNs = 0; uint32_t nodeID = 0; + uint32_t framerate = 60; struct { uint32_t w = 0, h = 0, size = 0, stride = 0, fmt = 0; @@ -93,6 +93,7 @@ class CScreencopyPortal { }; void startFrameCopy(SSession* pSession); + void queueNextShareFrame(SSession* pSession); std::unique_ptr m_pPipewire; @@ -133,18 +134,20 @@ class CPipewireConnection { spa_hook streamListener; SBuffer* currentPWBuffer = nullptr; spa_video_info_raw pwVideoInfo; - uint32_t seq = 0; + uint32_t seq = 0; + bool isDMA = false; std::vector> buffers; }; std::unique_ptr createBuffer(SPWStream* pStream, bool dmabuf); SPWStream* streamFromSession(CScreencopyPortal::SSession* pSession); + uint32_t buildFormatsFor(spa_pod_builder* b[2], const spa_pod* params[2], SPWStream* stream); + void updateStreamParam(SPWStream* pStream); private: std::vector> m_vStreams; - uint32_t buildFormatsFor(spa_pod_builder* b[2], const spa_pod* params[2], SPWStream* stream); bool buildModListFor(SPWStream* stream, uint32_t drmFmt, uint64_t** mods, uint32_t* modCount); pw_context* m_pContext = nullptr; diff --git a/src/shared/ScreencopyShared.cpp b/src/shared/ScreencopyShared.cpp index e5fccb2..109d36c 100644 --- a/src/shared/ScreencopyShared.cpp +++ b/src/shared/ScreencopyShared.cpp @@ -118,7 +118,7 @@ std::string getRandName(std::string prefix) { (int)(std::rand() % 10)); } -spa_video_format xdph_format_pw_strip_alpha(spa_video_format format) { +spa_video_format pwStripAlpha(spa_video_format format) { switch (format) { case SPA_VIDEO_FORMAT_BGRA: return SPA_VIDEO_FORMAT_BGRx; case SPA_VIDEO_FORMAT_ABGR: return SPA_VIDEO_FORMAT_xBGR; @@ -154,7 +154,7 @@ spa_pod* build_buffer(spa_pod_builder* b, uint32_t blocks, uint32_t size, uint32 spa_pod* fixate_format(spa_pod_builder* b, spa_video_format format, uint32_t width, uint32_t height, uint32_t framerate, uint64_t* modifier) { spa_pod_frame f[1]; - spa_video_format format_without_alpha = xdph_format_pw_strip_alpha(format); + spa_video_format format_without_alpha = pwStripAlpha(format); spa_pod_builder_push_object(b, &f[0], SPA_TYPE_OBJECT_Format, SPA_PARAM_EnumFormat); spa_pod_builder_add(b, SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_video), 0); @@ -182,7 +182,7 @@ spa_pod* build_format(spa_pod_builder* b, spa_video_format format, uint32_t widt spa_pod_frame f[2]; int i, c; - spa_video_format format_without_alpha = xdph_format_pw_strip_alpha(format); + spa_video_format format_without_alpha = pwStripAlpha(format); spa_pod_builder_push_object(b, &f[0], SPA_TYPE_OBJECT_Format, SPA_PARAM_EnumFormat); spa_pod_builder_add(b, SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_video), 0); diff --git a/src/shared/ScreencopyShared.hpp b/src/shared/ScreencopyShared.hpp index 5bb0580..4145f58 100644 --- a/src/shared/ScreencopyShared.hpp +++ b/src/shared/ScreencopyShared.hpp @@ -41,6 +41,7 @@ SSelectionData promptForScreencopySelection(); uint32_t drmFourccFromSHM(wl_shm_format format); spa_video_format pwFromDrmFourcc(uint32_t format); wl_shm_format wlSHMFromDrmFourcc(uint32_t format); +spa_video_format pwStripAlpha(spa_video_format format); std::string getRandName(std::string prefix); spa_pod* build_format(spa_pod_builder* b, spa_video_format format, uint32_t width, uint32_t height, uint32_t framerate, uint64_t* modifiers, int modifier_count); spa_pod* fixate_format(spa_pod_builder* b, spa_video_format format, uint32_t width, uint32_t height, uint32_t framerate, uint64_t* modifier);