core: fix background screenshot on nvidia (#656)

Fixes DMA buffer screencopy on nvidia cards. Additionally adds shm screencopy as an option
This commit is contained in:
Maximilian Seidler 2025-01-24 13:25:37 +00:00 committed by GitHub
parent 742eb98c6a
commit 07b5e1b4cd
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
13 changed files with 615 additions and 273 deletions

1
.gitignore vendored
View file

@ -11,3 +11,4 @@ compile_commands.json
protocols/*.cpp
protocols/*.hpp
*.kdev4
.gdb_history

View file

@ -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"});

View file

@ -286,6 +286,8 @@ void CHyprlock::run() {
else if (IFACE == zwlr_screencopy_manager_v1_interface.name)
m_sWaylandState.screencopy =
makeShared<CCZwlrScreencopyManagerV1>((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<CCWlShm>((wl_proxy*)wl_registry_bind((wl_registry*)r->resource(), name, &wl_shm_interface, 1));
else
return;
@ -834,6 +836,10 @@ SP<CCZwlrScreencopyManagerV1> CHyprlock::getScreencopy() {
return m_sWaylandState.screencopy;
}
SP<CCWlShm> CHyprlock::getShm() {
return m_sWaylandState.shm;
}
void CHyprlock::attemptRestoreOnDeath() {
if (m_bTerminate || m_sCurrentDesktop != "Hyprland")
return;

View file

@ -79,6 +79,7 @@ class CHyprlock {
SP<CCWpFractionalScaleManagerV1> getFractionalMgr();
SP<CCWpViewporter> getViewporter();
SP<CCZwlrScreencopyManagerV1> getScreencopy();
SP<CCWlShm> getShm();
int32_t m_iKeebRepeatRate = 25;
int32_t m_iKeebRepeatDelay = 600;
@ -130,6 +131,7 @@ class CHyprlock {
SP<CCWpFractionalScaleManagerV1> fractional = nullptr;
SP<CCWpViewporter> viewporter = nullptr;
SP<CCZwlrScreencopyManagerV1> screencopy = nullptr;
SP<CCWlShm> shm = nullptr;
} m_sWaylandState;
void addDmabufListener();

View file

@ -1,8 +1,11 @@
#include <filesystem>
#include <algorithm>
#include <cmath>
#include <fcntl.h>
#include "MiscFunctions.hpp"
#include "Log.hpp"
#include <hyprutils/string/String.hpp>
#include <unistd.h>
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;
}

View file

@ -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);

View file

@ -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<CDMAFrame>(PMONITOR));
scframes.emplace_back(std::make_unique<CScreencopyFrame>(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<int>(rq.props.at("font_size")) : 16;
const CHyprColor FONTCOLOR = rq.props.contains("color") ? std::any_cast<CHyprColor>(rq.props.at("color")) : CHyprColor(1.0, 1.0, 1.0, 1.0);
const CHyprColor FONTCOLOR = rq.props.contains("color") ? std::any_cast<CHyprColor>(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<std::string>(rq.props.at("font_family")) : "Sans";
const bool ISCMD = rq.props.contains("cmd") ? std::any_cast<bool>(rq.props.at("cmd")) : false;

View file

@ -1,6 +1,6 @@
#pragma once
#include "DMAFrame.hpp"
#include "Screencopy.hpp"
#include <thread>
#include <atomic>
#include <vector>
@ -75,7 +75,7 @@ class CAsyncResourceGatherer {
Vector2D size;
};
std::vector<std::unique_ptr<CDMAFrame>> dmas;
std::vector<std::unique_ptr<CScreencopyFrame>> scframes;
std::vector<SPreloadTarget> preloadTargets;
std::mutex preloadTargetsMutex;
@ -83,5 +83,5 @@ class CAsyncResourceGatherer {
std::unordered_map<std::string, SPreloadedAsset> assets;
void gather();
void enqueueDMAFrames();
void enqueueScreencopyFrames();
};

View file

@ -1,216 +0,0 @@
#include "DMAFrame.hpp"
#include "../helpers/Log.hpp"
#include "../core/hyprlock.hpp"
#include "../core/Egl.hpp"
#include <EGL/eglext.h>
#include <libdrm/drm_fourcc.h>
#include <GLES3/gl32.h>
#include <GLES3/gl3ext.h>
#include <GLES2/gl2ext.h>
#include <unistd.h>
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<CCZwlrScreencopyFrameV1>(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<uint64_t> mods;
mods.resize(64);
std::vector<EGLBoolean> 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<uint64_t> 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<CCZwpLinuxBufferParamsV1>(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<CCWlBuffer>(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;
}

View file

@ -1,43 +0,0 @@
#pragma once
#include "../defines.hpp"
#include "../core/Output.hpp"
#include <gbm.h>
#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<CCWlBuffer> 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<CCZwlrScreencopyFrameV1> frameCb = nullptr;
int w = 0, h = 0;
uint32_t fmt = 0;
size_t frameSize = 0;
size_t frameStride = 0;
EGLImage image = nullptr;
};

View file

@ -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 <GLES3/gl32.h>
#include <GLES3/gl3ext.h>
#include <GLES2/gl2ext.h>
#include <algorithm>
#include "widgets/PasswordInputField.hpp"
#include "widgets/Background.hpp"
@ -416,7 +417,7 @@ std::vector<std::unique_ptr<IWidget>>* 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) {

456
src/renderer/Screencopy.cpp Normal file
View file

@ -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 <EGL/egl.h>
#include <EGL/eglext.h>
#include <cstring>
#include <array>
#include <cstdint>
#include <gbm.h>
#include <unistd.h>
#include <sys/mman.h>
#include <libdrm/drm_fourcc.h>
#include <GLES3/gl32.h>
#include <GLES3/gl3ext.h>
#include <GLES2/gl2ext.h>
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<CSCSHMFrame>(m_sc);
else
m_frame = std::make_unique<CSCDMAFrame>(m_sc);
}
void CScreencopyFrame::captureOutput() {
m_sc = makeShared<CCZwlrScreencopyFrameV1>(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<CCZwlrScreencopyFrameV1> 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<uint64_t, 64> mods;
std::array<EGLBoolean, 64> 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<uint64_t> 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<CCZwpLinuxBufferParamsV1>(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<CCWlBuffer>(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<EGLAttrib> 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<CCZwlrScreencopyFrameV1> 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<CCWlShmPool>(g_pHyprlock->getShm()->sendCreatePool(FD, SIZE));
m_wlBuffer = makeShared<CCWlBuffer>(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;
}

View file

@ -0,0 +1,94 @@
#pragma once
#include "../defines.hpp"
#include "../core/Output.hpp"
#include <cstdint>
#include <gbm.h>
#include <memory>
#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<CCWlBuffer> m_wlBuffer = nullptr;
};
class CScreencopyFrame {
public:
static std::string getResourceId(COutput* output);
CScreencopyFrame(COutput* mon);
~CScreencopyFrame() = default;
void captureOutput();
SP<CCZwlrScreencopyFrameV1> m_sc = nullptr;
std::string m_resourceID;
SPreloadedAsset m_asset;
private:
COutput* m_output = nullptr;
std::unique_ptr<ISCFrame> m_frame = nullptr;
bool m_dmaFailed = false;
};
// Uses a gpu buffer created via gbm_bo
class CSCDMAFrame : public ISCFrame {
public:
CSCDMAFrame(SP<CCZwlrScreencopyFrameV1> 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<CCZwlrScreencopyFrameV1> 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<CCZwlrScreencopyFrameV1> 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<CCZwlrScreencopyFrameV1> m_sc = nullptr;
uint32_t m_shmFmt = 0;
void* m_shmData = nullptr;
void* m_convBuffer = nullptr;
};