From 07b5e1b4cd28518fcb54f59e9e69d593ab2fc1c0 Mon Sep 17 00:00:00 2001 From: Maximilian Seidler <78690852+PaideiaDilemma@users.noreply.github.com> Date: Fri, 24 Jan 2025 13:25:37 +0000 Subject: [PATCH] core: fix background screenshot on nvidia (#656) Fixes DMA buffer screencopy on nvidia cards. Additionally adds shm screencopy as an option --- .gitignore | 1 + src/config/ConfigManager.cpp | 1 + src/core/hyprlock.cpp | 6 + src/core/hyprlock.hpp | 2 + src/helpers/MiscFunctions.cpp | 41 ++- src/helpers/MiscFunctions.hpp | 1 + src/renderer/AsyncResourceGatherer.cpp | 16 +- src/renderer/AsyncResourceGatherer.hpp | 6 +- src/renderer/DMAFrame.cpp | 216 ------------ src/renderer/DMAFrame.hpp | 43 --- src/renderer/Renderer.cpp | 5 +- src/renderer/Screencopy.cpp | 456 +++++++++++++++++++++++++ src/renderer/Screencopy.hpp | 94 +++++ 13 files changed, 615 insertions(+), 273 deletions(-) delete mode 100644 src/renderer/DMAFrame.cpp delete mode 100644 src/renderer/DMAFrame.hpp create mode 100644 src/renderer/Screencopy.cpp create mode 100644 src/renderer/Screencopy.hpp diff --git a/.gitignore b/.gitignore index e12d18d..6c52687 100644 --- a/.gitignore +++ b/.gitignore @@ -11,3 +11,4 @@ compile_commands.json protocols/*.cpp protocols/*.hpp *.kdev4 +.gdb_history diff --git a/src/config/ConfigManager.cpp b/src/config/ConfigManager.cpp index bda91ef..2ce133a 100644 --- a/src/config/ConfigManager.cpp +++ b/src/config/ConfigManager.cpp @@ -217,6 +217,7 @@ void CConfigManager::init() { m_config.addConfigValue("general:ignore_empty_input", Hyprlang::INT{0}); m_config.addConfigValue("general:immediate_render", Hyprlang::INT{0}); m_config.addConfigValue("general:fractional_scaling", Hyprlang::INT{2}); + m_config.addConfigValue("general:screencopy_mode", Hyprlang::INT{0}); m_config.addConfigValue("auth:pam:enabled", Hyprlang::INT{1}); m_config.addConfigValue("auth:pam:module", Hyprlang::STRING{"hyprlock"}); diff --git a/src/core/hyprlock.cpp b/src/core/hyprlock.cpp index 50eef94..1adbc02 100644 --- a/src/core/hyprlock.cpp +++ b/src/core/hyprlock.cpp @@ -286,6 +286,8 @@ void CHyprlock::run() { else if (IFACE == zwlr_screencopy_manager_v1_interface.name) m_sWaylandState.screencopy = makeShared((wl_proxy*)wl_registry_bind((wl_registry*)r->resource(), name, &zwlr_screencopy_manager_v1_interface, 3)); + else if (IFACE == wl_shm_interface.name) + m_sWaylandState.shm = makeShared((wl_proxy*)wl_registry_bind((wl_registry*)r->resource(), name, &wl_shm_interface, 1)); else return; @@ -834,6 +836,10 @@ SP CHyprlock::getScreencopy() { return m_sWaylandState.screencopy; } +SP CHyprlock::getShm() { + return m_sWaylandState.shm; +} + void CHyprlock::attemptRestoreOnDeath() { if (m_bTerminate || m_sCurrentDesktop != "Hyprland") return; diff --git a/src/core/hyprlock.hpp b/src/core/hyprlock.hpp index 98608f0..862348e 100644 --- a/src/core/hyprlock.hpp +++ b/src/core/hyprlock.hpp @@ -79,6 +79,7 @@ class CHyprlock { SP getFractionalMgr(); SP getViewporter(); SP getScreencopy(); + SP getShm(); int32_t m_iKeebRepeatRate = 25; int32_t m_iKeebRepeatDelay = 600; @@ -130,6 +131,7 @@ class CHyprlock { SP fractional = nullptr; SP viewporter = nullptr; SP screencopy = nullptr; + SP shm = nullptr; } m_sWaylandState; void addDmabufListener(); diff --git a/src/helpers/MiscFunctions.cpp b/src/helpers/MiscFunctions.cpp index 2add0ed..bbdc13b 100644 --- a/src/helpers/MiscFunctions.cpp +++ b/src/helpers/MiscFunctions.cpp @@ -1,8 +1,11 @@ #include #include #include +#include #include "MiscFunctions.hpp" +#include "Log.hpp" #include +#include using namespace Hyprutils::String; @@ -12,7 +15,7 @@ std::string absolutePath(const std::string& rawpath, const std::string& currentD // Handling where rawpath starts with '~' if (!rawpath.empty() && rawpath[0] == '~') { static const char* const ENVHOME = getenv("HOME"); - path = std::filesystem::path(ENVHOME) / path.relative_path().string().substr(2); + path = std::filesystem::path(ENVHOME) / path.relative_path().string().substr(2); } // Handling e.g. ./, ../ @@ -97,4 +100,40 @@ int64_t configStringToInt(const std::string& VALUE) { } catch (std::exception& e) { throw std::invalid_argument(std::string{"stoll threw: "} + e.what()); } return 0; +} + +int createPoolFile(size_t size, std::string& name) { + const auto XDGRUNTIMEDIR = getenv("XDG_RUNTIME_DIR"); + if (!XDGRUNTIMEDIR) { + Debug::log(CRIT, "XDG_RUNTIME_DIR not set!"); + return -1; + } + + name = std::string(XDGRUNTIMEDIR) + "/.hyprlock_sc_XXXXXX"; + + const auto FD = mkstemp((char*)name.c_str()); + if (FD < 0) { + Debug::log(CRIT, "createPoolFile: fd < 0"); + return -1; + } + // set cloexec + long flags = fcntl(FD, F_GETFD); + if (flags == -1) { + close(FD); + return -1; + } + + if (fcntl(FD, F_SETFD, flags | FD_CLOEXEC) == -1) { + close(FD); + Debug::log(CRIT, "createPoolFile: fcntl < 0"); + return -1; + } + + if (ftruncate(FD, size) < 0) { + close(FD); + Debug::log(CRIT, "createPoolFile: ftruncate < 0"); + return -1; + } + + return FD; } \ No newline at end of file diff --git a/src/helpers/MiscFunctions.hpp b/src/helpers/MiscFunctions.hpp index 7441a16..c2fb126 100644 --- a/src/helpers/MiscFunctions.hpp +++ b/src/helpers/MiscFunctions.hpp @@ -6,3 +6,4 @@ std::string absolutePath(const std::string&, const std::string&); int64_t configStringToInt(const std::string& VALUE); +int createPoolFile(size_t size, std::string& name); diff --git a/src/renderer/AsyncResourceGatherer.cpp b/src/renderer/AsyncResourceGatherer.cpp index 4cd1653..a8d1515 100644 --- a/src/renderer/AsyncResourceGatherer.cpp +++ b/src/renderer/AsyncResourceGatherer.cpp @@ -15,13 +15,13 @@ using namespace Hyprgraphics; CAsyncResourceGatherer::CAsyncResourceGatherer() { if (g_pHyprlock->getScreencopy()) - enqueueDMAFrames(); + enqueueScreencopyFrames(); initialGatherThread = std::thread([this]() { this->gather(); }); asyncLoopThread = std::thread([this]() { this->asyncAssetSpinLock(); }); } -void CAsyncResourceGatherer::enqueueDMAFrames() { +void CAsyncResourceGatherer::enqueueScreencopyFrames() { // some things can't be done async :( // gather background textures when needed @@ -56,7 +56,7 @@ void CAsyncResourceGatherer::enqueueDMAFrames() { const auto PMONITOR = MON->get(); - dmas.emplace_back(std::make_unique(PMONITOR)); + scframes.emplace_back(std::make_unique(PMONITOR)); } } @@ -73,9 +73,9 @@ SPreloadedAsset* CAsyncResourceGatherer::getAssetByID(const std::string& id) { } }; - for (auto& dma : dmas) { - if (id == dma->resourceID) - return dma->asset.ready ? &dma->asset : nullptr; + for (auto& frame : scframes) { + if (id == frame->m_resourceID) + return frame->m_asset.ready ? &frame->m_asset : nullptr; } return nullptr; @@ -130,7 +130,7 @@ void CAsyncResourceGatherer::gather() { } } - while (!g_pHyprlock->m_bTerminate && std::any_of(dmas.begin(), dmas.end(), [](const auto& d) { return !d->asset.ready; })) { + while (!g_pHyprlock->m_bTerminate && std::any_of(scframes.begin(), scframes.end(), [](const auto& d) { return !d->m_asset.ready; })) { std::this_thread::sleep_for(std::chrono::milliseconds(1)); } @@ -216,7 +216,7 @@ void CAsyncResourceGatherer::renderText(const SPreloadRequest& rq) { target.id = rq.id; const int FONTSIZE = rq.props.contains("font_size") ? std::any_cast(rq.props.at("font_size")) : 16; - const CHyprColor FONTCOLOR = rq.props.contains("color") ? std::any_cast(rq.props.at("color")) : CHyprColor(1.0, 1.0, 1.0, 1.0); + const CHyprColor FONTCOLOR = rq.props.contains("color") ? std::any_cast(rq.props.at("color")) : CHyprColor(1.0, 1.0, 1.0, 1.0); const std::string FONTFAMILY = rq.props.contains("font_family") ? std::any_cast(rq.props.at("font_family")) : "Sans"; const bool ISCMD = rq.props.contains("cmd") ? std::any_cast(rq.props.at("cmd")) : false; diff --git a/src/renderer/AsyncResourceGatherer.hpp b/src/renderer/AsyncResourceGatherer.hpp index ae82380..d6aa13a 100644 --- a/src/renderer/AsyncResourceGatherer.hpp +++ b/src/renderer/AsyncResourceGatherer.hpp @@ -1,6 +1,6 @@ #pragma once -#include "DMAFrame.hpp" +#include "Screencopy.hpp" #include #include #include @@ -75,7 +75,7 @@ class CAsyncResourceGatherer { Vector2D size; }; - std::vector> dmas; + std::vector> scframes; std::vector preloadTargets; std::mutex preloadTargetsMutex; @@ -83,5 +83,5 @@ class CAsyncResourceGatherer { std::unordered_map assets; void gather(); - void enqueueDMAFrames(); + void enqueueScreencopyFrames(); }; diff --git a/src/renderer/DMAFrame.cpp b/src/renderer/DMAFrame.cpp deleted file mode 100644 index d7572c7..0000000 --- a/src/renderer/DMAFrame.cpp +++ /dev/null @@ -1,216 +0,0 @@ -#include "DMAFrame.hpp" -#include "../helpers/Log.hpp" -#include "../core/hyprlock.hpp" -#include "../core/Egl.hpp" -#include -#include -#include -#include -#include -#include - -static PFNGLEGLIMAGETARGETTEXTURE2DOESPROC glEGLImageTargetTexture2DOES = nullptr; -static PFNEGLQUERYDMABUFMODIFIERSEXTPROC eglQueryDmaBufModifiersEXT = nullptr; - -// -std::string CDMAFrame::getResourceId(COutput* output) { - return std::format("dma:{}-{}x{}", output->stringPort, output->size.x, output->size.y); -} - -CDMAFrame::CDMAFrame(COutput* output_) { - resourceID = getResourceId(output_); - - if (!glEGLImageTargetTexture2DOES) { - glEGLImageTargetTexture2DOES = (PFNGLEGLIMAGETARGETTEXTURE2DOESPROC)eglGetProcAddress("glEGLImageTargetTexture2DOES"); - if (!glEGLImageTargetTexture2DOES) { - Debug::log(ERR, "No glEGLImageTargetTexture2DOES??"); - return; - } - } - - if (!eglQueryDmaBufModifiersEXT) - eglQueryDmaBufModifiersEXT = (PFNEGLQUERYDMABUFMODIFIERSEXTPROC)eglGetProcAddress("eglQueryDmaBufModifiersEXT"); - - // firstly, plant a listener for the frame - frameCb = makeShared(g_pHyprlock->getScreencopy()->sendCaptureOutput(false, output_->output->resource())); - - frameCb->setBufferDone([this](CCZwlrScreencopyFrameV1* r) { - Debug::log(TRACE, "[sc] wlrOnBufferDone for {}", (void*)this); - - if (!onBufferDone()) { - Debug::log(ERR, "onBufferDone failed"); - return; - } - - frameCb->sendCopy(wlBuffer->resource()); - - Debug::log(TRACE, "[sc] wlr frame copied"); - }); - - frameCb->setLinuxDmabuf([this](CCZwlrScreencopyFrameV1* r, uint32_t format, uint32_t width, uint32_t height) { - Debug::log(TRACE, "[sc] wlrOnDmabuf for {}", (void*)this); - - w = width; - h = height; - fmt = format; - - Debug::log(TRACE, "[sc] DMABUF format reported: {:x}", format); - }); - - frameCb->setReady([this](CCZwlrScreencopyFrameV1* r, uint32_t, uint32_t, uint32_t) { - Debug::log(TRACE, "[sc] wlrOnReady for {}", (void*)this); - - if (!onBufferReady()) { - Debug::log(ERR, "onBufferReady failed"); - return; - } - - frameCb.reset(); - }); - - frameCb->setBuffer([this](CCZwlrScreencopyFrameV1* r, uint32_t format, uint32_t width, uint32_t height, uint32_t stride) { - Debug::log(TRACE, "[sc] wlrOnBuffer for {}", (void*)this); - - frameSize = stride * height; - frameStride = stride; - }); -} - -CDMAFrame::~CDMAFrame() { - if (g_pEGL) - eglDestroyImage(g_pEGL->eglDisplay, image); - - // leaks bo and stuff but lives throughout so for now who cares -} - -bool CDMAFrame::onBufferDone() { - uint32_t flags = GBM_BO_USE_RENDERING; - - if (!eglQueryDmaBufModifiersEXT) { - Debug::log(WARN, "Querying modifiers without eglQueryDmaBufModifiersEXT support"); - bo = gbm_bo_create(g_pHyprlock->dma.gbmDevice, w, h, fmt, flags); - } else { - std::vector mods; - mods.resize(64); - std::vector externalOnly; - externalOnly.resize(64); - int num = 0; - if (!eglQueryDmaBufModifiersEXT(g_pEGL->eglDisplay, fmt, 64, mods.data(), externalOnly.data(), &num) || num == 0) { - Debug::log(WARN, "eglQueryDmaBufModifiersEXT failed, falling back to regular bo"); - bo = gbm_bo_create(g_pHyprlock->dma.gbmDevice, w, h, fmt, flags); - } else { - Debug::log(LOG, "eglQueryDmaBufModifiersEXT found {} mods", num); - std::vector goodMods; - for (int i = 0; i < num; ++i) { - if (externalOnly[i]) { - Debug::log(TRACE, "Modifier {:x} failed test", mods[i]); - continue; - } - - Debug::log(TRACE, "Modifier {:x} passed test", mods[i]); - goodMods.push_back(mods[i]); - } - - uint64_t zero = 0; - bool hasLinear = std::find(goodMods.begin(), goodMods.end(), 0) != goodMods.end(); - - bo = gbm_bo_create_with_modifiers2(g_pHyprlock->dma.gbmDevice, w, h, fmt, hasLinear ? &zero : goodMods.data(), hasLinear ? 1 : goodMods.size(), flags); - } - } - - if (!bo) { - Debug::log(ERR, "Couldn't create a drm buffer"); - return false; - } - - planes = gbm_bo_get_plane_count(bo); - - uint64_t mod = gbm_bo_get_modifier(bo); - Debug::log(LOG, "bo chose modifier {:x}", mod); - - auto params = makeShared(g_pHyprlock->dma.linuxDmabuf->sendCreateParams()); - if (!params) { - Debug::log(ERR, "zwp_linux_dmabuf_v1_create_params failed"); - gbm_bo_destroy(bo); - return false; - } - - for (size_t plane = 0; plane < (size_t)planes; plane++) { - size[plane] = 0; - stride[plane] = gbm_bo_get_stride_for_plane(bo, plane); - offset[plane] = gbm_bo_get_offset(bo, plane); - fd[plane] = gbm_bo_get_fd_for_plane(bo, plane); - - if (fd[plane] < 0) { - Debug::log(ERR, "gbm_bo_get_fd_for_plane failed"); - params.reset(); - gbm_bo_destroy(bo); - for (size_t plane_tmp = 0; plane_tmp < plane; plane_tmp++) { - close(fd[plane_tmp]); - } - return false; - } - - params->sendAdd(fd[plane], plane, offset[plane], stride[plane], mod >> 32, mod & 0xffffffff); - } - - wlBuffer = makeShared(params->sendCreateImmed(w, h, fmt, (zwpLinuxBufferParamsV1Flags)0)); - params.reset(); - - if (!wlBuffer) { - Debug::log(ERR, "[pw] zwp_linux_buffer_params_v1_create_immed failed"); - gbm_bo_destroy(bo); - for (size_t plane = 0; plane < (size_t)planes; plane++) - close(fd[plane]); - - return false; - } - - return true; -} - -bool CDMAFrame::onBufferReady() { - static const int general_attribs = 3; - static const int plane_attribs = 5; - static const int entries_per_attrib = 2; - EGLAttrib attribs[(general_attribs + plane_attribs * 4) * entries_per_attrib + 1]; - int attr = 0; - Vector2D size{w, h}; - - attribs[attr++] = EGL_WIDTH; - attribs[attr++] = size.x; - attribs[attr++] = EGL_HEIGHT; - attribs[attr++] = size.y; - attribs[attr++] = EGL_LINUX_DRM_FOURCC_EXT; - attribs[attr++] = fmt; - attribs[attr++] = EGL_DMA_BUF_PLANE0_FD_EXT; - attribs[attr++] = fd[0]; - attribs[attr++] = EGL_DMA_BUF_PLANE0_OFFSET_EXT; - attribs[attr++] = offset[0]; - attribs[attr++] = EGL_DMA_BUF_PLANE0_PITCH_EXT; - attribs[attr++] = stride[0]; - attribs[attr] = EGL_NONE; - - image = eglCreateImage(g_pEGL->eglDisplay, EGL_NO_CONTEXT, EGL_LINUX_DMA_BUF_EXT, NULL, attribs); - - if (image == EGL_NO_IMAGE) { - Debug::log(ERR, "failed creating an egl image"); - return false; - } - - asset.texture.allocate(); - asset.texture.m_vSize = size; - glBindTexture(GL_TEXTURE_2D, asset.texture.m_iTexID); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); - glEGLImageTargetTexture2DOES(GL_TEXTURE_2D, image); - glBindTexture(GL_TEXTURE_2D, 0); - - Debug::log(LOG, "Got dma frame with size {}", size); - - asset.ready = true; - - return true; -} \ No newline at end of file diff --git a/src/renderer/DMAFrame.hpp b/src/renderer/DMAFrame.hpp deleted file mode 100644 index 0b26abd..0000000 --- a/src/renderer/DMAFrame.hpp +++ /dev/null @@ -1,43 +0,0 @@ -#pragma once - -#include "../defines.hpp" -#include "../core/Output.hpp" -#include -#include "Shared.hpp" -#include "linux-dmabuf-v1.hpp" -#include "wlr-screencopy-unstable-v1.hpp" - -class CDMAFrame; - -class CDMAFrame { - public: - static std::string getResourceId(COutput* output); - - CDMAFrame(COutput* mon); - ~CDMAFrame(); - - bool onBufferDone(); - bool onBufferReady(); - - SP wlBuffer = nullptr; - - std::string resourceID; - - SPreloadedAsset asset; - - private: - gbm_bo* bo = nullptr; - - int planes = 0; - - int fd[4]; - uint32_t size[4], stride[4], offset[4]; - - SP frameCb = nullptr; - int w = 0, h = 0; - uint32_t fmt = 0; - size_t frameSize = 0; - size_t frameStride = 0; - - EGLImage image = nullptr; -}; \ No newline at end of file diff --git a/src/renderer/Renderer.cpp b/src/renderer/Renderer.cpp index 288ef7d..d6261b5 100644 --- a/src/renderer/Renderer.cpp +++ b/src/renderer/Renderer.cpp @@ -1,5 +1,6 @@ #include "Renderer.hpp" #include "Shaders.hpp" +#include "Screencopy.hpp" #include "../config/ConfigManager.hpp" #include "../core/AnimationManager.hpp" #include "../core/Egl.hpp" @@ -7,9 +8,9 @@ #include "../core/hyprlock.hpp" #include "../helpers/Color.hpp" #include "../helpers/Log.hpp" -#include "../renderer/DMAFrame.hpp" #include #include +#include #include #include "widgets/PasswordInputField.hpp" #include "widgets/Background.hpp" @@ -416,7 +417,7 @@ std::vector>* CRenderer::getOrCreateWidgetsFor(const CS std::string resourceID = ""; if (PATH == "screenshot") { - resourceID = CDMAFrame::getResourceId(surf->output); + resourceID = CScreencopyFrame::getResourceId(surf->output); // When the initial gather of the asyncResourceGatherer is completed (ready), all DMAFrames are available. // Dynamic ones are tricky, because a screencopy would copy hyprlock itself. if (asyncResourceGatherer->gathered) { diff --git a/src/renderer/Screencopy.cpp b/src/renderer/Screencopy.cpp new file mode 100644 index 0000000..db9b415 --- /dev/null +++ b/src/renderer/Screencopy.cpp @@ -0,0 +1,456 @@ +#include "Screencopy.hpp" +#include "../helpers/Log.hpp" +#include "../helpers/MiscFunctions.hpp" +#include "../core/hyprlock.hpp" +#include "../core/Egl.hpp" +#include "../config/ConfigManager.hpp" +#include "wlr-screencopy-unstable-v1.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static PFNGLEGLIMAGETARGETTEXTURE2DOESPROC glEGLImageTargetTexture2DOES = nullptr; +static PFNEGLQUERYDMABUFMODIFIERSEXTPROC eglQueryDmaBufModifiersEXT = nullptr; + +// +std::string CScreencopyFrame::getResourceId(COutput* output) { + return std::format("screencopy:{}-{}x{}", output->stringPort, output->size.x, output->size.y); +} + +CScreencopyFrame::CScreencopyFrame(COutput* output) : m_output(output) { + m_resourceID = getResourceId(m_output); + + captureOutput(); + + static auto* const PSCMODE = (Hyprlang::INT* const*)g_pConfigManager->getValuePtr("general:screencopy_mode"); + if (**PSCMODE == 1) + m_frame = std::make_unique(m_sc); + else + m_frame = std::make_unique(m_sc); +} + +void CScreencopyFrame::captureOutput() { + m_sc = makeShared(g_pHyprlock->getScreencopy()->sendCaptureOutput(false, m_output->output->resource())); + + m_sc->setBufferDone([this](CCZwlrScreencopyFrameV1* r) { + Debug::log(TRACE, "[sc] wlrOnBufferDone for {}", (void*)this); + + if (!m_frame || !m_frame->onBufferDone() || !m_frame->m_wlBuffer) { + Debug::log(ERR, "[sc] Failed to create a wayland buffer for the screencopy frame"); + return; + } + + m_sc->sendCopy(m_frame->m_wlBuffer->resource()); + + Debug::log(TRACE, "[sc] wlr frame copied"); + }); + + m_sc->setFailed([this](CCZwlrScreencopyFrameV1* r) { + Debug::log(ERR, "[sc] wlrOnFailed for {}", (void*)r); + + m_frame.reset(); + }); + + m_sc->setReady([this](CCZwlrScreencopyFrameV1* r, uint32_t, uint32_t, uint32_t) { + Debug::log(TRACE, "[sc] wlrOnReady for {}", (void*)this); + + if (!m_frame || !m_frame->onBufferReady(m_asset)) { + Debug::log(ERR, "[sc] Failed to bind the screencopy buffer to a texture"); + return; + } + + m_sc.reset(); + }); +} + +CSCDMAFrame::CSCDMAFrame(SP sc) : m_sc(sc) { + if (!glEGLImageTargetTexture2DOES) { + glEGLImageTargetTexture2DOES = (PFNGLEGLIMAGETARGETTEXTURE2DOESPROC)eglGetProcAddress("glEGLImageTargetTexture2DOES"); + if (!glEGLImageTargetTexture2DOES) { + Debug::log(ERR, "No glEGLImageTargetTexture2DOES??"); + return; + } + } + + if (!eglQueryDmaBufModifiersEXT) + eglQueryDmaBufModifiersEXT = (PFNEGLQUERYDMABUFMODIFIERSEXTPROC)eglGetProcAddress("eglQueryDmaBufModifiersEXT"); + + m_sc->setLinuxDmabuf([this](CCZwlrScreencopyFrameV1* r, uint32_t format, uint32_t width, uint32_t height) { + Debug::log(TRACE, "[sc] wlrOnDmabuf for {}", (void*)this); + + m_w = width; + m_h = height; + m_fmt = format; + + Debug::log(TRACE, "[sc] DMABUF format reported: {:x}", format); + }); + + m_sc->setBuffer([](CCZwlrScreencopyFrameV1* r, uint32_t format, uint32_t width, uint32_t height, uint32_t stride) { + ; // unused by dma + }); +} + +CSCDMAFrame::~CSCDMAFrame() { + if (g_pEGL) + eglDestroyImage(g_pEGL->eglDisplay, m_image); + + // leaks bo and stuff but lives throughout so for now who cares +} + +bool CSCDMAFrame::onBufferDone() { + uint32_t flags = GBM_BO_USE_RENDERING; + + if (!eglQueryDmaBufModifiersEXT) { + Debug::log(WARN, "Querying modifiers without eglQueryDmaBufModifiersEXT support"); + m_bo = gbm_bo_create(g_pHyprlock->dma.gbmDevice, m_w, m_h, m_fmt, flags); + } else { + std::array mods; + std::array externalOnly; + int num = 0; + if (!eglQueryDmaBufModifiersEXT(g_pEGL->eglDisplay, m_fmt, 64, mods.data(), externalOnly.data(), &num) || num == 0) { + Debug::log(WARN, "eglQueryDmaBufModifiersEXT failed, falling back to regular bo"); + m_bo = gbm_bo_create(g_pHyprlock->dma.gbmDevice, m_w, m_h, m_fmt, flags); + } else { + Debug::log(LOG, "eglQueryDmaBufModifiersEXT found {} mods", num); + std::vector goodMods; + for (int i = 0; i < num; ++i) { + if (externalOnly[i]) { + Debug::log(TRACE, "Modifier {:x} failed test", mods[i]); + continue; + } + + Debug::log(TRACE, "Modifier {:x} passed test", mods[i]); + goodMods.emplace_back(mods[i]); + } + + m_bo = gbm_bo_create_with_modifiers2(g_pHyprlock->dma.gbmDevice, m_w, m_h, m_fmt, goodMods.data(), goodMods.size(), flags); + } + } + + if (!m_bo) { + Debug::log(ERR, "[bo] Couldn't create a drm buffer"); + return false; + } + + m_planes = gbm_bo_get_plane_count(m_bo); + Debug::log(LOG, "[bo] has {} plane(s)", m_planes); + + m_mod = gbm_bo_get_modifier(m_bo); + Debug::log(LOG, "[bo] chose modifier {:x}", m_mod); + + auto params = makeShared(g_pHyprlock->dma.linuxDmabuf->sendCreateParams()); + if (!params) { + Debug::log(ERR, "zwp_linux_dmabuf_v1_create_params failed"); + gbm_bo_destroy(m_bo); + return false; + } + + for (size_t plane = 0; plane < (size_t)m_planes; plane++) { + m_stride[plane] = gbm_bo_get_stride_for_plane(m_bo, plane); + m_offset[plane] = gbm_bo_get_offset(m_bo, plane); + m_fd[plane] = gbm_bo_get_fd_for_plane(m_bo, plane); + + if (m_fd[plane] < 0) { + Debug::log(ERR, "gbm_m_bo_get_fd_for_plane failed"); + params.reset(); + gbm_bo_destroy(m_bo); + for (size_t plane_tmp = 0; plane_tmp < plane; plane_tmp++) { + close(m_fd[plane_tmp]); + } + return false; + } + + params->sendAdd(m_fd[plane], plane, m_offset[plane], m_stride[plane], m_mod >> 32, m_mod & 0xffffffff); + } + + m_wlBuffer = makeShared(params->sendCreateImmed(m_w, m_h, m_fmt, (zwpLinuxBufferParamsV1Flags)0)); + params.reset(); + + if (!m_wlBuffer) { + Debug::log(ERR, "[pw] zwp_linux_buffer_params_v1_create_immed failed"); + gbm_bo_destroy(m_bo); + for (size_t plane = 0; plane < (size_t)m_planes; plane++) + close(m_fd[plane]); + + return false; + } + + return true; +} + +bool CSCDMAFrame::onBufferReady(SPreloadedAsset& asset) { + static constexpr struct { + EGLAttrib fd; + EGLAttrib offset; + EGLAttrib pitch; + EGLAttrib modlo; + EGLAttrib modhi; + } attrNames[4] = { + {EGL_DMA_BUF_PLANE0_FD_EXT, EGL_DMA_BUF_PLANE0_OFFSET_EXT, EGL_DMA_BUF_PLANE0_PITCH_EXT, EGL_DMA_BUF_PLANE0_MODIFIER_LO_EXT, EGL_DMA_BUF_PLANE0_MODIFIER_HI_EXT}, + {EGL_DMA_BUF_PLANE1_FD_EXT, EGL_DMA_BUF_PLANE1_OFFSET_EXT, EGL_DMA_BUF_PLANE1_PITCH_EXT, EGL_DMA_BUF_PLANE1_MODIFIER_LO_EXT, EGL_DMA_BUF_PLANE1_MODIFIER_HI_EXT}, + {EGL_DMA_BUF_PLANE2_FD_EXT, EGL_DMA_BUF_PLANE2_OFFSET_EXT, EGL_DMA_BUF_PLANE2_PITCH_EXT, EGL_DMA_BUF_PLANE2_MODIFIER_LO_EXT, EGL_DMA_BUF_PLANE2_MODIFIER_HI_EXT}, + {EGL_DMA_BUF_PLANE3_FD_EXT, EGL_DMA_BUF_PLANE3_OFFSET_EXT, EGL_DMA_BUF_PLANE3_PITCH_EXT, EGL_DMA_BUF_PLANE3_MODIFIER_LO_EXT, EGL_DMA_BUF_PLANE3_MODIFIER_HI_EXT}}; + + std::vector attribs = { + EGL_WIDTH, m_w, EGL_HEIGHT, m_h, EGL_LINUX_DRM_FOURCC_EXT, m_fmt, + }; + for (int i = 0; i < m_planes; i++) { + attribs.emplace_back(attrNames[i].fd); + attribs.emplace_back(m_fd[i]); + attribs.emplace_back(attrNames[i].offset); + attribs.emplace_back(m_offset[i]); + attribs.emplace_back(attrNames[i].pitch); + attribs.emplace_back(m_stride[i]); + if (m_mod != DRM_FORMAT_MOD_INVALID) { + attribs.emplace_back(attrNames[i].modlo); + attribs.emplace_back(m_mod & 0xFFFFFFFF); + attribs.emplace_back(attrNames[i].modhi); + attribs.emplace_back(m_mod >> 32); + } + } + attribs.emplace_back(EGL_NONE); + + m_image = eglCreateImage(g_pEGL->eglDisplay, EGL_NO_CONTEXT, EGL_LINUX_DMA_BUF_EXT, nullptr, attribs.data()); + + if (m_image == EGL_NO_IMAGE) { + Debug::log(ERR, "Failed creating an egl image"); + return false; + } + + asset.texture.allocate(); + asset.texture.m_vSize = {m_w, m_h}; + glBindTexture(GL_TEXTURE_2D, asset.texture.m_iTexID); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glEGLImageTargetTexture2DOES(GL_TEXTURE_2D, m_image); + glBindTexture(GL_TEXTURE_2D, 0); + + Debug::log(LOG, "Got dma frame with size {}", asset.texture.m_vSize); + + asset.ready = true; + + return true; +} + +CSCSHMFrame::CSCSHMFrame(SP sc) : m_sc(sc) { + Debug::log(TRACE, "[sc] [shm] Creating a SHM frame"); + + m_sc->setBuffer([this](CCZwlrScreencopyFrameV1* r, uint32_t format, uint32_t width, uint32_t height, uint32_t stride) { + Debug::log(TRACE, "[sc] [shm] wlrOnBuffer for {}", (void*)this); + + const auto SIZE = stride * height; + m_shmFmt = format; + m_w = width; + m_h = height; + m_stride = stride; + + // Create a shm pool with format and size + std::string shmPoolFile; + const auto FD = createPoolFile(SIZE, shmPoolFile); + + if (FD < 0) { + Debug::log(ERR, "[sc] [shm] failed to create a pool file"); + return; + } + + m_shmData = mmap(nullptr, SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, FD, 0); + if (m_shmData == MAP_FAILED) { + Debug::log(ERR, "[sc] [shm] failed to (errno {})", strerror(errno)); + close(FD); + m_ok = false; + return; + } + + if (!g_pHyprlock->getShm()) { + Debug::log(ERR, "[sc] [shm] Failed to get WLShm global"); + close(FD); + m_ok = false; + return; + } + + auto pShmPool = makeShared(g_pHyprlock->getShm()->sendCreatePool(FD, SIZE)); + m_wlBuffer = makeShared(pShmPool->sendCreateBuffer(0, width, height, stride, m_shmFmt)); + + pShmPool.reset(); + + close(FD); + }); + + m_sc->setLinuxDmabuf([](CCZwlrScreencopyFrameV1* r, uint32_t, uint32_t, uint32_t) { + ; // unused by scshm + }); +} + +CSCSHMFrame::~CSCSHMFrame() { + if (m_convBuffer) + free(m_convBuffer); + if (m_shmData) + munmap(m_shmData, m_stride * m_h); +} + +void CSCSHMFrame::convertBuffer() { + const auto BYTESPERPX = m_stride / m_w; + if (BYTESPERPX == 4) { + switch (m_shmFmt) { + case WL_SHM_FORMAT_ARGB8888: + case WL_SHM_FORMAT_XRGB8888: { + Debug::log(LOG, "[sc] [shm] Converting ARGB to RGBA"); + uint8_t* data = (uint8_t*)m_shmData; + + for (uint32_t y = 0; y < m_h; ++y) { + for (uint32_t x = 0; x < m_w; ++x) { + struct pixel { + // little-endian ARGB + unsigned char blue; + unsigned char green; + unsigned char red; + unsigned char alpha; + }* px = (struct pixel*)(data + y * m_w * 4 + x * 4); + + // RGBA + *px = {px->red, px->green, px->blue, px->alpha}; + } + } + } break; + case WL_SHM_FORMAT_ABGR8888: + case WL_SHM_FORMAT_XBGR8888: { + Debug::log(LOG, "[sc] [shm] Converting ABGR to RGBA"); + uint8_t* data = (uint8_t*)m_shmData; + + for (uint32_t y = 0; y < m_h; ++y) { + for (uint32_t x = 0; x < m_w; ++x) { + struct pixel { + // little-endian ARGB + unsigned char blue; + unsigned char green; + unsigned char red; + unsigned char alpha; + }* px = (struct pixel*)(data + y * m_w * 4 + x * 4); + + // RGBA + *px = {px->blue, px->green, px->red, px->alpha}; + } + } + } break; + case WL_SHM_FORMAT_ABGR2101010: + case WL_SHM_FORMAT_ARGB2101010: + case WL_SHM_FORMAT_XRGB2101010: + case WL_SHM_FORMAT_XBGR2101010: { + Debug::log(LOG, "[sc] [shm] Converting 10-bit channels to 8-bit"); + uint8_t* data = (uint8_t*)m_shmData; + + const bool FLIP = m_shmFmt != WL_SHM_FORMAT_XBGR2101010; + + for (uint32_t y = 0; y < m_h; ++y) { + for (uint32_t x = 0; x < m_w; ++x) { + uint32_t* px = (uint32_t*)(data + y * m_w * 4 + x * 4); + + // conv to 8 bit + uint8_t R = (uint8_t)std::round((255.0 * (((*px) & 0b00000000000000000000001111111111) >> 0) / 1023.0)); + uint8_t G = (uint8_t)std::round((255.0 * (((*px) & 0b00000000000011111111110000000000) >> 10) / 1023.0)); + uint8_t B = (uint8_t)std::round((255.0 * (((*px) & 0b00111111111100000000000000000000) >> 20) / 1023.0)); + uint8_t A = (uint8_t)std::round((255.0 * (((*px) & 0b11000000000000000000000000000000) >> 30) / 3.0)); + + // write 8-bit values + *px = ((FLIP ? B : R) << 0) + (G << 8) + ((FLIP ? R : B) << 16) + (A << 24); + } + } + } break; + default: { + Debug::log(WARN, "[sc] [shm] Unsupported format {}", m_shmFmt); + } + } + } else if (BYTESPERPX == 3) { + Debug::log(LOG, "[sc] [shm] Converting 24 bit to 32 bit"); + m_convBuffer = malloc(m_w * m_h * 4); + const int NEWSTRIDE = m_w * 4; + RASSERT(m_convBuffer, "malloc failed"); + + switch (m_shmFmt) { + case WL_SHM_FORMAT_BGR888: { + Debug::log(LOG, "[sc] [shm] Converting BGR to RGBA"); + for (uint32_t y = 0; y < m_h; ++y) { + for (uint32_t x = 0; x < m_w; ++x) { + struct pixel3 { + // little-endian RGB + unsigned char blue; + unsigned char green; + unsigned char red; + }* srcPx = (struct pixel3*)((char*)m_shmData + y * m_stride + x * 3); + struct pixel4 { + // little-endian ARGB + unsigned char blue; + unsigned char green; + unsigned char red; + unsigned char alpha; + }* dstPx = (struct pixel4*)((char*)m_convBuffer + y * NEWSTRIDE + x * 4); + *dstPx = {srcPx->blue, srcPx->green, srcPx->red, 0xFF}; + } + } + } break; + case WL_SHM_FORMAT_RGB888: { + Debug::log(LOG, "[sc] [shm] Converting RGB to RGBA"); + for (uint32_t y = 0; y < m_h; ++y) { + for (uint32_t x = 0; x < m_w; ++x) { + struct pixel3 { + // big-endian RGB + unsigned char red; + unsigned char green; + unsigned char blue; + }* srcPx = (struct pixel3*)((char*)m_shmData + y * m_stride + x * 3); + struct pixel4 { + // big-endian ARGB + unsigned char alpha; + unsigned char red; + unsigned char green; + unsigned char blue; + }* dstPx = (struct pixel4*)((char*)m_convBuffer + y * NEWSTRIDE + x * 4); + *dstPx = {srcPx->red, srcPx->green, srcPx->blue, 0xFF}; + } + } + } break; + default: { + Debug::log(ERR, "[sc] [shm] Unsupported format for 24bit buffer {}", m_shmFmt); + } + } + + } else { + Debug::log(ERR, "[sc] [shm] Unsupported bytes per pixel {}", BYTESPERPX); + } +} + +bool CSCSHMFrame::onBufferReady(SPreloadedAsset& asset) { + convertBuffer(); + + asset.texture.allocate(); + asset.texture.m_vSize.x = m_w; + asset.texture.m_vSize.y = m_h; + + glBindTexture(GL_TEXTURE_2D, asset.texture.m_iTexID); + + void* buffer = m_convBuffer ? m_convBuffer : m_shmData; + + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, m_w, m_h, 0, GL_RGBA, GL_UNSIGNED_BYTE, buffer); + glBindTexture(GL_TEXTURE_2D, 0); + + Debug::log(LOG, "[sc] [shm] Got screenshot with size {}", asset.texture.m_vSize); + + asset.ready = true; + + return true; +} diff --git a/src/renderer/Screencopy.hpp b/src/renderer/Screencopy.hpp new file mode 100644 index 0000000..8a707a2 --- /dev/null +++ b/src/renderer/Screencopy.hpp @@ -0,0 +1,94 @@ +#pragma once + +#include "../defines.hpp" +#include "../core/Output.hpp" +#include +#include +#include +#include "Shared.hpp" +#include "linux-dmabuf-v1.hpp" +#include "wlr-screencopy-unstable-v1.hpp" + +class ISCFrame { + public: + ISCFrame() = default; + virtual ~ISCFrame() = default; + + virtual bool onBufferDone() = 0; + virtual bool onBufferReady(SPreloadedAsset& asset) = 0; + + SP m_wlBuffer = nullptr; +}; + +class CScreencopyFrame { + public: + static std::string getResourceId(COutput* output); + + CScreencopyFrame(COutput* mon); + ~CScreencopyFrame() = default; + + void captureOutput(); + + SP m_sc = nullptr; + + std::string m_resourceID; + SPreloadedAsset m_asset; + + private: + COutput* m_output = nullptr; + std::unique_ptr m_frame = nullptr; + + bool m_dmaFailed = false; +}; + +// Uses a gpu buffer created via gbm_bo +class CSCDMAFrame : public ISCFrame { + public: + CSCDMAFrame(SP sc); + virtual ~CSCDMAFrame(); + + virtual bool onBufferReady(SPreloadedAsset& asset); + virtual bool onBufferDone(); + + private: + gbm_bo* m_bo = nullptr; + + int m_planes = 0; + uint64_t m_mod = 0; + + int m_fd[4]; + uint32_t m_stride[4], m_offset[4]; + + int m_w = 0, m_h = 0; + uint32_t m_fmt = 0; + + SP m_sc = nullptr; + + EGLImage m_image = nullptr; +}; + +// Uses a shm buffer - is slow and needs ugly format conversion +// Used as a fallback just in case. +class CSCSHMFrame : public ISCFrame { + public: + CSCSHMFrame(SP sc); + virtual ~CSCSHMFrame(); + + virtual bool onBufferDone() { + return m_ok; + } + virtual bool onBufferReady(SPreloadedAsset& asset); + void convertBuffer(); + + private: + bool m_ok = true; + + uint32_t m_w = 0, m_h = 0; + uint32_t m_stride = 0; + + SP m_sc = nullptr; + + uint32_t m_shmFmt = 0; + void* m_shmData = nullptr; + void* m_convBuffer = nullptr; +};