basic gpu stuff

This commit is contained in:
Vaxry 2024-07-19 14:20:54 +02:00
parent 87791c0a99
commit c788a59dac
20 changed files with 1312 additions and 332 deletions

View file

@ -50,6 +50,9 @@ pkg_check_modules(
deps
REQUIRED
IMPORTED_TARGET
gbm
egl
libdrm
wayland-client
wayland-protocols
cairo

View file

@ -6,6 +6,7 @@ Hyprpaper is a blazing fast wallpaper utility for Hyprland with the ability to d
- Per-output wallpapers
- fill or contain modes
- fractional scaling support
- gpu acceleration
- IPC for blazing fast wallpaper switches
- preloading targets into memory

View file

@ -1,8 +1,17 @@
#include "Hyprpaper.hpp"
#include "render/Renderer.hpp"
#include <filesystem>
#include <fstream>
#include <signal.h>
#include <sys/types.h>
#include "render/Egl.hpp"
#include "protocols/wayland.hpp"
#include "protocols/linux-dmabuf-v1.hpp"
#include "protocols/wlr-layer-shell-unstable-v1.hpp"
#include "protocols/fractional-scale-v1.hpp"
#include "protocols/viewporter.hpp"
#include "protocols/cursor-shape-v1.hpp"
CHyprpaper::CHyprpaper() = default;
@ -33,6 +42,18 @@ static void handleGlobal(CCWlRegistry* registry, uint32_t name, const char* inte
} else if (strcmp(interface, wp_cursor_shape_manager_v1_interface.name) == 0) {
g_pHyprpaper->m_pCursorShape =
makeShared<CCWpCursorShapeManagerV1>((wl_proxy*)wl_registry_bind((wl_registry*)registry->resource(), name, &wp_cursor_shape_manager_v1_interface, 1));
} else if (strcmp(interface, zwp_linux_dmabuf_v1_interface.name) == 0) {
g_pHyprpaper->m_pLinuxDmabuf = makeShared<CCZwpLinuxDmabufV1>((wl_proxy*)wl_registry_bind((wl_registry*)registry->resource(), name, &zwp_linux_dmabuf_v1_interface, 3));
g_pHyprpaper->m_pLinuxDmabuf->setModifier([](CCZwpLinuxDmabufV1* r, uint32_t fmt, uint32_t modHi, uint32_t modLo) {
g_pHyprpaper->m_vDmabufFormats.emplace_back(SDMABUFFormat{
.format = fmt,
.modifier = (((uint64_t)modLo) << 32) | (uint64_t)modLo,
});
});
wl_display_roundtrip(g_pHyprpaper->m_sDisplay);
g_pHyprpaper->m_pLinuxDmabuf = makeShared<CCZwpLinuxDmabufV1>((wl_proxy*)wl_registry_bind((wl_registry*)registry->resource(), name, &zwp_linux_dmabuf_v1_interface, 4));
}
}
@ -70,6 +91,25 @@ void CHyprpaper::init() {
wl_display_roundtrip(m_sDisplay);
g_pRenderer = std::make_unique<CRenderer>(!m_bNoGpu);
if (!m_bNoGpu) {
try {
if (m_vDmabufFormats.empty()) {
Debug::log(ERR, "No dmabuf support, using cpu rendering");
m_bNoGpu = true;
}
} catch (std::exception& e) {
std::cerr << "Failed to create a gpu context: " << e.what() << ", falling back to cpu\n";
m_bNoGpu = true;
}
}
if (m_bNoGpu && (g_pEGL || g_pRenderer->gbmDevice)) {
g_pEGL.reset();
g_pRenderer = std::make_unique<CRenderer>(!m_bNoGpu);
}
while (m_vMonitors.size() < 1 || m_vMonitors[0]->name.empty()) {
wl_display_dispatch(m_sDisplay);
}
@ -99,7 +139,6 @@ void CHyprpaper::tick(bool force) {
return;
preloadAllWallpapersFromConfig();
ensurePoolBuffersPresent();
recheckAllMonitors();
}
@ -129,25 +168,6 @@ void CHyprpaper::unloadWallpaper(const std::string& path) {
return;
}
// clean buffers
for (auto it = m_vBuffers.begin(); it != m_vBuffers.end();) {
if (it->get()->target != path) {
it++;
continue;
}
const auto PRELOADPATH = it->get()->name;
Debug::log(LOG, "Unloading target %s, preload path %s", path.c_str(), PRELOADPATH.c_str());
std::filesystem::remove(PRELOADPATH);
destroyBuffer(it->get());
it = m_vBuffers.erase(it);
}
m_mWallpaperTargets.erase(path); // will free the cairo surface
}
@ -224,7 +244,7 @@ void CHyprpaper::recheckMonitor(SMonitor* pMonitor) {
if (pMonitor->wantsReload) {
pMonitor->wantsReload = false;
renderWallpaperForMonitor(pMonitor);
g_pRenderer->renderWallpaperForMonitor(pMonitor);
}
}
@ -272,46 +292,6 @@ SMonitor* CHyprpaper::getMonitorFromName(const std::string& monname) {
return nullptr;
}
void CHyprpaper::ensurePoolBuffersPresent() {
bool anyNewBuffers = false;
for (auto& [file, wt] : m_mWallpaperTargets) {
for (auto& m : m_vMonitors) {
if (m->size == Vector2D())
continue;
auto it = std::find_if(m_vBuffers.begin(), m_vBuffers.end(), [wt = &wt, &m](const std::unique_ptr<SPoolBuffer>& el) {
auto scale = std::round((m->pCurrentLayerSurface && m->pCurrentLayerSurface->pFractionalScaleInfo ? m->pCurrentLayerSurface->fScale : m->scale) * 120.0) / 120.0;
return el->target == wt->m_szPath && vectorDeltaLessThan(el->pixelSize, m->size * scale, 1);
});
if (it == m_vBuffers.end()) {
// create
const auto PBUFFER = m_vBuffers.emplace_back(std::make_unique<SPoolBuffer>()).get();
auto scale = std::round((m->pCurrentLayerSurface && m->pCurrentLayerSurface->pFractionalScaleInfo ? m->pCurrentLayerSurface->fScale : m->scale) * 120.0) / 120.0;
createBuffer(PBUFFER, m->size.x * scale, m->size.y * scale, WL_SHM_FORMAT_ARGB8888);
PBUFFER->target = wt.m_szPath;
Debug::log(LOG, "Buffer created for target %s, Shared Memory usage: %.1fMB", wt.m_szPath.c_str(), PBUFFER->size / 1000000.f);
anyNewBuffers = true;
}
}
}
if (anyNewBuffers) {
uint64_t bytesUsed = 0;
for (auto& bf : m_vBuffers) {
bytesUsed += bf->size;
}
Debug::log(LOG, "Total SM usage for all buffers: %.1fMB", bytesUsed / 1000000.f);
}
}
void CHyprpaper::clearWallpaperFromMonitor(const std::string& monname) {
const auto PMONITOR = getMonitorFromName(monname);
@ -411,227 +391,6 @@ void CHyprpaper::createLSForMonitor(SMonitor* pMonitor) {
pMonitor->pCurrentLayerSurface = pMonitor->layerSurfaces.emplace_back(std::make_unique<CLayerSurface>(pMonitor)).get();
}
bool CHyprpaper::setCloexec(const int& FD) {
long flags = fcntl(FD, F_GETFD);
if (flags == -1) {
return false;
}
if (fcntl(FD, F_SETFD, flags | FD_CLOEXEC) == -1) {
return false;
}
return true;
}
int CHyprpaper::createPoolFile(size_t size, std::string& name) {
const auto XDGRUNTIMEDIR = getenv("XDG_RUNTIME_DIR");
if (!XDGRUNTIMEDIR) {
Debug::log(CRIT, "XDG_RUNTIME_DIR not set!");
exit(1);
}
name = std::string(XDGRUNTIMEDIR) + "/.hyprpaper_XXXXXX";
const auto FD = mkstemp((char*)name.c_str());
if (FD < 0) {
Debug::log(CRIT, "createPoolFile: fd < 0");
exit(1);
}
if (!setCloexec(FD)) {
close(FD);
Debug::log(CRIT, "createPoolFile: !setCloexec");
exit(1);
}
if (ftruncate(FD, size) < 0) {
close(FD);
Debug::log(CRIT, "createPoolFile: ftruncate < 0");
exit(1);
}
return FD;
}
void CHyprpaper::createBuffer(SPoolBuffer* pBuffer, int32_t w, int32_t h, uint32_t format) {
const size_t STRIDE = w * 4;
const size_t SIZE = STRIDE * h;
std::string name;
const auto FD = createPoolFile(SIZE, name);
if (FD == -1) {
Debug::log(CRIT, "Unable to create pool file!");
exit(1);
}
const auto DATA = mmap(nullptr, SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, FD, 0);
auto POOL = makeShared<CCWlShmPool>(g_pHyprpaper->m_pSHM->sendCreatePool(FD, SIZE));
pBuffer->buffer = makeShared<CCWlBuffer>(POOL->sendCreateBuffer(0, w, h, STRIDE, format));
POOL.reset();
close(FD);
pBuffer->size = SIZE;
pBuffer->data = DATA;
pBuffer->surface = cairo_image_surface_create_for_data((unsigned char*)DATA, CAIRO_FORMAT_ARGB32, w, h, STRIDE);
pBuffer->cairo = cairo_create(pBuffer->surface);
pBuffer->pixelSize = Vector2D(w, h);
pBuffer->name = name;
}
void CHyprpaper::destroyBuffer(SPoolBuffer* pBuffer) {
pBuffer->buffer.reset();
cairo_destroy(pBuffer->cairo);
cairo_surface_destroy(pBuffer->surface);
munmap(pBuffer->data, pBuffer->size);
pBuffer->buffer = nullptr;
}
SPoolBuffer* CHyprpaper::getPoolBuffer(SMonitor* pMonitor, CWallpaperTarget* pWallpaperTarget) {
const auto IT = std::find_if(m_vBuffers.begin(), m_vBuffers.end(), [&](const std::unique_ptr<SPoolBuffer>& el) {
auto scale =
std::round((pMonitor->pCurrentLayerSurface && pMonitor->pCurrentLayerSurface->pFractionalScaleInfo ? pMonitor->pCurrentLayerSurface->fScale : pMonitor->scale) *
120.0) /
120.0;
return el->target == pWallpaperTarget->m_szPath && vectorDeltaLessThan(el->pixelSize, pMonitor->size * scale, 1);
});
if (IT == m_vBuffers.end())
return nullptr;
return IT->get();
}
void CHyprpaper::renderWallpaperForMonitor(SMonitor* pMonitor) {
static auto* const PRENDERSPLASH = reinterpret_cast<Hyprlang::INT* const*>(g_pConfigManager->config->getConfigValuePtr("splash")->getDataStaticPtr());
static auto* const PSPLASHOFFSET = reinterpret_cast<Hyprlang::FLOAT* const*>(g_pConfigManager->config->getConfigValuePtr("splash_offset")->getDataStaticPtr());
if (!m_mMonitorActiveWallpaperTargets[pMonitor])
recheckMonitor(pMonitor);
const auto PWALLPAPERTARGET = m_mMonitorActiveWallpaperTargets[pMonitor];
const auto CONTAIN = m_mMonitorWallpaperRenderData[pMonitor->name].contain;
if (!PWALLPAPERTARGET) {
Debug::log(CRIT, "wallpaper target null in render??");
exit(1);
}
auto* PBUFFER = getPoolBuffer(pMonitor, PWALLPAPERTARGET);
if (!PBUFFER) {
Debug::log(LOG, "Pool buffer missing for available target??");
ensurePoolBuffersPresent();
PBUFFER = getPoolBuffer(pMonitor, PWALLPAPERTARGET);
if (!PBUFFER) {
Debug::log(LOG, "Pool buffer failed #2. Ignoring WP.");
return;
}
}
const double SURFACESCALE = pMonitor->pCurrentLayerSurface && pMonitor->pCurrentLayerSurface->pFractionalScaleInfo ? pMonitor->pCurrentLayerSurface->fScale : pMonitor->scale;
const Vector2D DIMENSIONS = Vector2D{std::round(pMonitor->size.x * SURFACESCALE), std::round(pMonitor->size.y * SURFACESCALE)};
const auto PCAIRO = PBUFFER->cairo;
cairo_save(PCAIRO);
cairo_set_operator(PCAIRO, CAIRO_OPERATOR_CLEAR);
cairo_paint(PCAIRO);
cairo_restore(PCAIRO);
// always draw a black background behind the wallpaper
cairo_set_source_rgb(PCAIRO, 0, 0, 0);
cairo_rectangle(PCAIRO, 0, 0, DIMENSIONS.x, DIMENSIONS.y);
cairo_fill(PCAIRO);
cairo_surface_flush(PBUFFER->surface);
// get scale
// we always do cover
double scale;
Vector2D origin;
const bool LOWASPECTRATIO = pMonitor->size.x / pMonitor->size.y > PWALLPAPERTARGET->m_vSize.x / PWALLPAPERTARGET->m_vSize.y;
if ((CONTAIN && !LOWASPECTRATIO) || (!CONTAIN && LOWASPECTRATIO)) {
scale = DIMENSIONS.x / PWALLPAPERTARGET->m_vSize.x;
origin.y = -(PWALLPAPERTARGET->m_vSize.y * scale - DIMENSIONS.y) / 2.0 / scale;
} else {
scale = DIMENSIONS.y / PWALLPAPERTARGET->m_vSize.y;
origin.x = -(PWALLPAPERTARGET->m_vSize.x * scale - DIMENSIONS.x) / 2.0 / scale;
}
Debug::log(LOG, "Image data for %s: %s at [%.2f, %.2f], scale: %.2f (original image size: [%i, %i])", pMonitor->name.c_str(), PWALLPAPERTARGET->m_szPath.c_str(), origin.x,
origin.y, scale, (int)PWALLPAPERTARGET->m_vSize.x, (int)PWALLPAPERTARGET->m_vSize.y);
cairo_scale(PCAIRO, scale, scale);
cairo_set_source_surface(PCAIRO, PWALLPAPERTARGET->m_pCairoSurface, origin.x, origin.y);
cairo_paint(PCAIRO);
if (**PRENDERSPLASH && getenv("HYPRLAND_INSTANCE_SIGNATURE")) {
auto SPLASH = execAndGet("hyprctl splash");
SPLASH.pop_back();
Debug::log(LOG, "Rendering splash: %s", SPLASH.c_str());
cairo_select_font_face(PCAIRO, "Sans", CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_NORMAL);
const auto FONTSIZE = (int)(DIMENSIONS.y / 76.0 / scale);
cairo_set_font_size(PCAIRO, FONTSIZE);
static auto* const PSPLASHCOLOR = reinterpret_cast<Hyprlang::INT* const*>(g_pConfigManager->config->getConfigValuePtr("splash_color")->getDataStaticPtr());
Debug::log(LOG, "Splash color: %x", **PSPLASHCOLOR);
cairo_set_source_rgba(PCAIRO, ((**PSPLASHCOLOR >> 16) & 0xFF) / 255.0, ((**PSPLASHCOLOR >> 8) & 0xFF) / 255.0, (**PSPLASHCOLOR & 0xFF) / 255.0,
((**PSPLASHCOLOR >> 24) & 0xFF) / 255.0);
cairo_text_extents_t textExtents;
cairo_text_extents(PCAIRO, SPLASH.c_str(), &textExtents);
cairo_move_to(PCAIRO, ((DIMENSIONS.x - textExtents.width * scale) / 2.0) / scale, ((DIMENSIONS.y * (100 - **PSPLASHOFFSET)) / 100 - textExtents.height * scale) / scale);
Debug::log(LOG, "Splash font size: %d, pos: %.2f, %.2f", FONTSIZE, (DIMENSIONS.x - textExtents.width) / 2.0 / scale,
((DIMENSIONS.y * (100 - **PSPLASHOFFSET)) / 100 - textExtents.height * scale) / scale);
cairo_show_text(PCAIRO, SPLASH.c_str());
cairo_surface_flush(PWALLPAPERTARGET->m_pCairoSurface);
}
cairo_restore(PCAIRO);
if (pMonitor->pCurrentLayerSurface) {
pMonitor->pCurrentLayerSurface->pSurface->sendAttach(PBUFFER->buffer.get(), 0, 0);
pMonitor->pCurrentLayerSurface->pSurface->sendSetBufferScale(pMonitor->pCurrentLayerSurface->pFractionalScaleInfo ? 1 : pMonitor->scale);
pMonitor->pCurrentLayerSurface->pSurface->sendDamageBuffer(0, 0, 0xFFFF, 0xFFFF);
// our wps are always opaque
auto opaqueRegion = makeShared<CCWlRegion>(g_pHyprpaper->m_pCompositor->sendCreateRegion());
opaqueRegion->sendAdd(0, 0, PBUFFER->pixelSize.x, PBUFFER->pixelSize.y);
pMonitor->pCurrentLayerSurface->pSurface->sendSetOpaqueRegion(opaqueRegion.get());
if (pMonitor->pCurrentLayerSurface->pFractionalScaleInfo) {
Debug::log(LOG, "Submitting viewport dest size %ix%i for %x", static_cast<int>(std::round(pMonitor->size.x)), static_cast<int>(std::round(pMonitor->size.y)),
pMonitor->pCurrentLayerSurface);
pMonitor->pCurrentLayerSurface->pViewport->sendSetDestination(static_cast<int>(std::round(pMonitor->size.x)), static_cast<int>(std::round(pMonitor->size.y)));
}
pMonitor->pCurrentLayerSurface->pSurface->sendCommit();
}
// check if we dont need to remove a wallpaper
if (pMonitor->layerSurfaces.size() > 1) {
for (auto it = pMonitor->layerSurfaces.begin(); it != pMonitor->layerSurfaces.end(); it++) {
if (pMonitor->pCurrentLayerSurface != it->get()) {
pMonitor->layerSurfaces.erase(it);
break;
}
}
}
}
bool CHyprpaper::lockSingleInstance() {
const std::string XDG_RUNTIME_DIR = getenv("XDG_RUNTIME_DIR");

View file

@ -4,22 +4,31 @@
#include "defines.hpp"
#include "helpers/MiscFunctions.hpp"
#include "helpers/Monitor.hpp"
#include "helpers/PoolBuffer.hpp"
#include "ipc/Socket.hpp"
#include "render/WallpaperTarget.hpp"
#include <mutex>
#include "protocols/cursor-shape-v1.hpp"
#include "protocols/fractional-scale-v1.hpp"
#include "protocols/linux-dmabuf-v1.hpp"
#include "protocols/viewporter.hpp"
#include "protocols/wayland.hpp"
#include "protocols/wlr-layer-shell-unstable-v1.hpp"
#include <unordered_map>
struct SWallpaperRenderData {
bool contain = false;
};
struct SDMABUFFormat {
uint32_t format = 0; // invalid
uint64_t modifier = 0; // linear
};
class CCWlCompositor;
class CCWlShm;
class CCZwlrLayerShellV1;
class CCWpFractionalScaleManagerV1;
class CCWpViewporter;
class CCWlSeat;
class CCWlPointer;
class CCWpCursorShapeDeviceV1;
class CCWpCursorShapeManagerV1;
class CCZwpLinuxDmabufV1;
class CHyprpaper {
public:
// important
@ -33,6 +42,7 @@ class CHyprpaper {
SP<CCWlPointer> m_pSeatPointer;
SP<CCWpCursorShapeDeviceV1> m_pSeatCursorShapeDevice;
SP<CCWpCursorShapeManagerV1> m_pCursorShape;
SP<CCZwpLinuxDmabufV1> m_pLinuxDmabuf;
// init the utility
CHyprpaper();
@ -43,28 +53,22 @@ class CHyprpaper {
std::unordered_map<std::string, std::string> m_mMonitorActiveWallpapers;
std::unordered_map<std::string, SWallpaperRenderData> m_mMonitorWallpaperRenderData;
std::unordered_map<SMonitor*, CWallpaperTarget*> m_mMonitorActiveWallpaperTargets;
std::vector<std::unique_ptr<SPoolBuffer>> m_vBuffers;
std::vector<std::unique_ptr<SMonitor>> m_vMonitors;
std::vector<SDMABUFFormat> m_vDmabufFormats;
std::string m_szExplicitConfigPath;
bool m_bNoFractionalScale = false;
bool m_bNoGpu = false;
void removeOldHyprpaperImages();
void preloadAllWallpapersFromConfig();
void recheckAllMonitors();
void ensureMonitorHasActiveWallpaper(SMonitor*);
void createLSForMonitor(SMonitor*);
void renderWallpaperForMonitor(SMonitor*);
void createBuffer(SPoolBuffer*, int32_t, int32_t, uint32_t);
void destroyBuffer(SPoolBuffer*);
int createPoolFile(size_t, std::string&);
bool setCloexec(const int&);
void clearWallpaperFromMonitor(const std::string&);
SMonitor* getMonitorFromName(const std::string&);
bool isPreloaded(const std::string&);
void recheckMonitor(SMonitor*);
void ensurePoolBuffersPresent();
SPoolBuffer* getPoolBuffer(SMonitor*, CWallpaperTarget*);
void unloadWallpaper(const std::string&);
void createSeat(SP<CCWlSeat>);
bool lockSingleInstance(); // fails on multi-instance

View file

@ -1,6 +1,8 @@
#include "Monitor.hpp"
#include "../Hyprpaper.hpp"
#include "protocols/wayland.hpp"
void SMonitor::registerListeners() {
output->setMode([this](CCWlOutput* r, uint32_t flags, int32_t width, int32_t height, int32_t refresh) { size = Vector2D(width, height); });

View file

@ -2,8 +2,8 @@
#include "../defines.hpp"
#include "../render/LayerSurface.hpp"
#include "PoolBuffer.hpp"
#include "protocols/wayland.hpp"
class CCWlOutput;
struct SMonitor {
std::string name = "";
@ -19,7 +19,6 @@ struct SMonitor {
bool wildcard = true;
uint32_t configureSerial = 0;
SPoolBuffer buffer;
bool wantsReload = false;
bool wantsACK = false;

View file

@ -1,18 +0,0 @@
#pragma once
#include "../defines.hpp"
#include "protocols/wayland.hpp"
class CWallpaperTarget;
struct SPoolBuffer {
SP<CCWlBuffer> buffer = nullptr;
cairo_surface_t* surface = nullptr;
cairo_t* cairo = nullptr;
void* data = nullptr;
size_t size = 0;
std::string name = "";
std::string target = "";
Vector2D pixelSize;
};

View file

@ -7,7 +7,7 @@ int main(int argc, char** argv, char** envp) {
// parse some args
std::string configPath;
bool noFractional = false;
bool noFractional = false, noGpu = false;
for (int i = 1; i < argc; ++i) {
if ((!strcmp(argv[i], "-c") || !strcmp(argv[i], "--config")) && argc >= i + 2) {
configPath = std::string(argv[++i]);
@ -15,11 +15,15 @@ int main(int argc, char** argv, char** envp) {
} else if (!strcmp(argv[i], "--no-fractional") || !strcmp(argv[i], "-n")) {
noFractional = true;
Debug::log(LOG, "Disabling fractional scaling support!");
} else if (!strcmp(argv[i], "--no-gpu") || !strcmp(argv[i], "-g")) {
noGpu = true;
Debug::log(LOG, "Disabling gpu acceleration");
} else {
std::cout << "Hyprpaper usage: hyprpaper [arg [...]].\n\nArguments:\n"
<< "--help -h | Show this help message\n"
<< "--config -c | Specify config file to use\n"
<< "--no-fractional -n | Disable fractional scaling support\n";
<< "--no-fractional -n | Disable fractional scaling support\n"
<< "--no-gpu -g | Disable gpu acceleration\n";
return 1;
}
}
@ -28,6 +32,7 @@ int main(int argc, char** argv, char** envp) {
g_pHyprpaper = std::make_unique<CHyprpaper>();
g_pHyprpaper->m_szExplicitConfigPath = configPath;
g_pHyprpaper->m_bNoFractionalScale = noFractional;
g_pHyprpaper->m_bNoGpu = noGpu;
g_pHyprpaper->init();
return 0;

202
src/render/Buffer.cpp Normal file
View file

@ -0,0 +1,202 @@
#include "Buffer.hpp"
#include "../Hyprpaper.hpp"
#include <gbm.h>
#include <xf86drm.h>
#include <drm_fourcc.h>
#include "Renderer.hpp"
#include "Egl.hpp"
#include "protocols/wayland.hpp"
#include "protocols/linux-dmabuf-v1.hpp"
static bool setCloexec(const int& FD) {
long flags = fcntl(FD, F_GETFD);
if (flags == -1) {
return false;
}
if (fcntl(FD, F_SETFD, flags | FD_CLOEXEC) == -1) {
return false;
}
return true;
}
static 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!");
exit(1);
}
name = std::string(XDGRUNTIMEDIR) + "/.hyprpaper_XXXXXX";
const auto FD = mkstemp((char*)name.c_str());
if (FD < 0) {
Debug::log(CRIT, "createPoolFile: fd < 0");
exit(1);
}
if (!setCloexec(FD)) {
close(FD);
Debug::log(CRIT, "createPoolFile: !setCloexec");
exit(1);
}
if (ftruncate(FD, size) < 0) {
close(FD);
Debug::log(CRIT, "createPoolFile: ftruncate < 0");
exit(1);
}
return FD;
}
void CBuffer::createPool() {
const size_t STRIDE = pixelSize.x * 4;
const size_t SIZE = STRIDE * pixelSize.y;
std::string name;
const auto FD = createPoolFile(SIZE, name);
if (FD == -1) {
Debug::log(CRIT, "Unable to create pool file!");
exit(1);
}
const auto DATA = mmap(nullptr, SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, FD, 0);
auto POOL = makeShared<CCWlShmPool>(g_pHyprpaper->m_pSHM->sendCreatePool(FD, SIZE));
buffer = makeShared<CCWlBuffer>(POOL->sendCreateBuffer(0, pixelSize.x, pixelSize.y, STRIDE, WL_SHM_FORMAT_XRGB8888));
POOL.reset();
close(FD);
cpu.data = DATA;
cpu.size = SIZE;
cpu.surface = cairo_image_surface_create_for_data((unsigned char*)DATA, CAIRO_FORMAT_ARGB32, pixelSize.x, pixelSize.y, STRIDE);
cpu.cairo = cairo_create(cpu.surface);
cpu.name = name;
}
void CBuffer::destroyPool() {
cairo_destroy(cpu.cairo);
cairo_surface_destroy(cpu.surface);
munmap(cpu.data, cpu.size);
}
void CBuffer::createGpu() {
uint32_t format = 0;
std::vector<uint64_t> modifiers = {};
// try to find a 10b format+mod first
for (auto& [fmt, mod] : g_pHyprpaper->m_vDmabufFormats) {
if (fmt != DRM_FORMAT_XRGB2101010 && fmt != DRM_FORMAT_XBGR2101010)
continue;
if (mod == DRM_FORMAT_MOD_LINEAR || mod == DRM_FORMAT_MOD_INVALID)
continue;
if (format != 0 && fmt != format)
continue;
format = fmt;
modifiers.emplace_back(mod);
}
if (!format) {
Debug::log(WARN, "No 10-bit DMA format found, trying 8b");
for (auto& [fmt, mod] : g_pHyprpaper->m_vDmabufFormats) {
if (fmt != DRM_FORMAT_XRGB8888 && fmt != DRM_FORMAT_XBGR8888)
continue;
if (mod == DRM_FORMAT_MOD_LINEAR || mod == DRM_FORMAT_MOD_INVALID)
continue;
if (format != 0 && fmt != format)
continue;
format = fmt;
modifiers.emplace_back(mod);
}
}
if (!format) {
Debug::log(ERR, "Failed to find a dma format for gpu buffer");
return;
}
gpu.bo = gbm_bo_create_with_modifiers2(g_pRenderer->gbmDevice, pixelSize.x, pixelSize.y, format, modifiers.data(), modifiers.size(), GBM_BO_USE_RENDERING);
if (!gpu.bo) {
Debug::log(ERR, "Failed to get a bo for gpu buffer, retrying without mods");
gpu.bo = gbm_bo_create(g_pRenderer->gbmDevice, pixelSize.x, pixelSize.y, format, GBM_BO_USE_RENDERING);
if (!gpu.bo) {
Debug::log(ERR, "Failed to get a bo for gpu buffers");
return;
}
}
gpu.attrs.planes = gbm_bo_get_plane_count(gpu.bo);
gpu.attrs.modifier = gbm_bo_get_modifier(gpu.bo);
gpu.attrs.size = pixelSize;
gpu.attrs.format = format;
for (size_t i = 0; i < (size_t)gpu.attrs.planes; ++i) {
gpu.attrs.strides.at(i) = gbm_bo_get_stride_for_plane(gpu.bo, i);
gpu.attrs.offsets.at(i) = gbm_bo_get_offset(gpu.bo, i);
gpu.attrs.fds.at(i) = gbm_bo_get_fd_for_plane(gpu.bo, i);
if (gpu.attrs.fds.at(i) < 0) {
Debug::log(ERR, "GBM: Failed to query fd for plane %i", i);
for (size_t j = 0; j < i; ++j) {
close(gpu.attrs.fds.at(j));
}
gpu.attrs.planes = 0;
return;
}
}
gpu.attrs.success = true;
gpu.eglImage = g_pEGL->getEglImage(gpu.attrs);
if (!gpu.eglImage) {
Debug::log(ERR, "Failed to get an eglImage for gpu buffer");
return;
}
// send to compositor
auto PARAMS = makeShared<CCZwpLinuxBufferParamsV1>(g_pHyprpaper->m_pLinuxDmabuf->sendCreateParams());
for (size_t i = 0; i < (size_t)gpu.attrs.planes; ++i) {
PARAMS->sendAdd(gpu.attrs.fds.at(i), i, gpu.attrs.offsets.at(i), gpu.attrs.strides.at(i), gpu.attrs.modifier >> 32, gpu.attrs.modifier & 0xFFFFFFFF);
}
buffer = makeShared<CCWlBuffer>(PARAMS->sendCreateImmed(pixelSize.x, pixelSize.y, format, (zwpLinuxBufferParamsV1Flags)0));
}
void CBuffer::destroyGpu() {
g_pEGL->destroyEglImage(gpu.eglImage);
for (int i = 0; i < gpu.attrs.planes; ++i) {
close(gpu.attrs.fds.at(i));
}
gbm_bo_destroy(gpu.bo);
gpu.bo = nullptr;
}
CBuffer::CBuffer(const Vector2D& size) : pixelSize(size) {
if (g_pRenderer->gbmDevice)
createGpu();
else
createPool();
}
CBuffer::~CBuffer() {
buffer->sendDestroy();
if (gpu.bo)
destroyGpu();
else
destroyPool();
}

51
src/render/Buffer.hpp Normal file
View file

@ -0,0 +1,51 @@
#pragma once
#include "../defines.hpp"
#include <EGL/egl.h>
#include <gbm.h>
class CCWlBuffer;
struct SDMABUFAttrs {
bool success = false;
Hyprutils::Math::Vector2D size;
uint32_t format = 0; // fourcc
uint64_t modifier = 0;
int planes = 1;
std::array<uint32_t, 4> offsets = {0};
std::array<uint32_t, 4> strides = {0};
std::array<int, 4> fds = {-1, -1, -1, -1};
};
class CBuffer {
public:
CBuffer(const Vector2D& size);
~CBuffer();
struct {
cairo_surface_t* surface = nullptr;
cairo_t* cairo = nullptr;
void* data = nullptr;
size_t size = 0;
std::string name = "";
} cpu;
struct {
void* eglImage = nullptr;
gbm_bo* bo = nullptr;
SDMABUFAttrs attrs;
} gpu;
SP<CCWlBuffer> buffer = nullptr;
std::string target = "";
Vector2D pixelSize;
private:
void destroyPool();
void createPool();
void createGpu();
void destroyGpu();
};

162
src/render/Egl.cpp Normal file
View file

@ -0,0 +1,162 @@
#include "Egl.hpp"
#include "../debug/Log.hpp"
#include <EGL/eglext.h>
#include <xf86drm.h>
#include <drm_fourcc.h>
PFNEGLGETPLATFORMDISPLAYEXTPROC eglGetPlatformDisplayEXT;
const EGLint config_attribs[] = {
EGL_SURFACE_TYPE, EGL_WINDOW_BIT, EGL_RED_SIZE, 8, EGL_GREEN_SIZE, 8, EGL_BLUE_SIZE, 8, EGL_ALPHA_SIZE, 8, EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, EGL_NONE,
};
const EGLint context_attribs[] = {
EGL_CONTEXT_MAJOR_VERSION,
3,
EGL_CONTEXT_MINOR_VERSION,
2,
EGL_NONE,
};
CEGL::CEGL(gbm_device* device) {
const char* _EXTS = eglQueryString(EGL_NO_DISPLAY, EGL_EXTENSIONS);
if (!_EXTS) {
if (eglGetError() == EGL_BAD_DISPLAY)
throw std::runtime_error("EGL_EXT_client_extensions not supported");
else
throw std::runtime_error("Failed to query EGL client extensions");
}
std::string EXTS = _EXTS;
if (!EXTS.contains("EGL_EXT_platform_base"))
throw std::runtime_error("EGL_EXT_platform_base not supported");
if (!EXTS.contains("EGL_EXT_platform_wayland"))
throw std::runtime_error("EGL_EXT_platform_wayland not supported");
eglGetPlatformDisplayEXT = (PFNEGLGETPLATFORMDISPLAYEXTPROC)eglGetProcAddress("eglGetPlatformDisplayEXT");
if (eglGetPlatformDisplayEXT == NULL)
throw std::runtime_error("Failed to get eglGetPlatformDisplayEXT");
eglCreatePlatformWindowSurfaceEXT = (PFNEGLCREATEPLATFORMWINDOWSURFACEEXTPROC)eglGetProcAddress("eglCreatePlatformWindowSurfaceEXT");
if (eglCreatePlatformWindowSurfaceEXT == NULL)
throw std::runtime_error("Failed to get eglCreatePlatformWindowSurfaceEXT");
eglDisplay = eglGetPlatformDisplayEXT(EGL_PLATFORM_GBM_KHR, device, NULL);
EGLint matched = 0;
if (eglDisplay == EGL_NO_DISPLAY) {
Debug::log(CRIT, "Failed to create EGL display");
goto error;
}
if (eglInitialize(eglDisplay, NULL, NULL) == EGL_FALSE) {
Debug::log(CRIT, "Failed to initialize EGL");
goto error;
}
if (!eglChooseConfig(eglDisplay, config_attribs, &eglConfig, 1, &matched)) {
Debug::log(CRIT, "eglChooseConfig failed");
goto error;
}
if (matched == 0) {
Debug::log(CRIT, "Failed to match an EGL config");
goto error;
}
eglContext = eglCreateContext(eglDisplay, eglConfig, EGL_NO_CONTEXT, context_attribs);
if (eglContext == EGL_NO_CONTEXT) {
Debug::log(CRIT, "Failed to create EGL context");
goto error;
}
eglDestroyImageKHR = (PFNEGLDESTROYIMAGEKHRPROC)eglGetProcAddress("eglDestroyImageKHR");
if (eglDestroyImageKHR == NULL)
throw std::runtime_error("Failed to get eglDestroyImageKHR");
eglCreateImageKHR = (PFNEGLCREATEIMAGEKHRPROC)eglGetProcAddress("eglCreateImageKHR");
if (eglCreateImageKHR == NULL)
throw std::runtime_error("Failed to get eglCreateImageKHR");
glEGLImageTargetTexture2DOES = (PFNGLEGLIMAGETARGETTEXTURE2DOESPROC)eglGetProcAddress("glEGLImageTargetTexture2DOES");
if (glEGLImageTargetTexture2DOES == NULL)
throw std::runtime_error("Failed to get glEGLImageTargetTexture2DOES");
glEGLImageTargetRenderbufferStorageOES = (PFNGLEGLIMAGETARGETRENDERBUFFERSTORAGEOESPROC)eglGetProcAddress("glEGLImageTargetRenderbufferStorageOES");
if (glEGLImageTargetRenderbufferStorageOES == NULL)
throw std::runtime_error("Failed to get glEGLImageTargetRenderbufferStorageOES");
return;
error:
eglMakeCurrent(EGL_NO_DISPLAY, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
}
CEGL::~CEGL() {
if (eglContext != EGL_NO_CONTEXT)
eglDestroyContext(eglDisplay, eglContext);
if (eglDisplay)
eglTerminate(eglDisplay);
eglReleaseThread();
}
void CEGL::makeCurrent(EGLSurface surf) {
eglMakeCurrent(eglDisplay, surf, surf, eglContext);
}
void* CEGL::getEglImage(const SDMABUFAttrs& attrs) {
std::vector<uint32_t> attribs;
attribs.push_back(EGL_WIDTH);
attribs.push_back(attrs.size.x);
attribs.push_back(EGL_HEIGHT);
attribs.push_back(attrs.size.y);
attribs.push_back(EGL_LINUX_DRM_FOURCC_EXT);
attribs.push_back(attrs.format);
struct {
EGLint fd;
EGLint offset;
EGLint pitch;
EGLint modlo;
EGLint 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}};
for (int i = 0; i < attrs.planes; i++) {
attribs.push_back(attrNames[i].fd);
attribs.push_back(attrs.fds[i]);
attribs.push_back(attrNames[i].offset);
attribs.push_back(attrs.offsets[i]);
attribs.push_back(attrNames[i].pitch);
attribs.push_back(attrs.strides[i]);
if (attrs.modifier != DRM_FORMAT_MOD_INVALID) {
attribs.push_back(attrNames[i].modlo);
attribs.push_back(attrs.modifier & 0xFFFFFFFF);
attribs.push_back(attrNames[i].modhi);
attribs.push_back(attrs.modifier >> 32);
}
}
attribs.push_back(EGL_IMAGE_PRESERVED_KHR);
attribs.push_back(EGL_TRUE);
attribs.push_back(EGL_NONE);
EGLImageKHR image = eglCreateImageKHR(eglDisplay, EGL_NO_CONTEXT, EGL_LINUX_DMA_BUF_EXT, nullptr, (int*)attribs.data());
if (image == EGL_NO_IMAGE_KHR) {
Debug::log(ERR, "EGL: EGLCreateImageKHR failed with 0x%lx", eglGetError());
return EGL_NO_IMAGE_KHR;
}
return image;
}
void CEGL::destroyEglImage(EGLImageKHR image) {
eglDestroyImageKHR(eglDisplay, image);
}

38
src/render/Egl.hpp Normal file
View file

@ -0,0 +1,38 @@
#pragma once
#include <memory>
#include "Buffer.hpp"
#include <EGL/egl.h>
typedef void* EGLImageKHR;
typedef void* GLeglImageOES;
typedef EGLSurface(EGLAPIENTRYP PFNEGLCREATEPLATFORMWINDOWSURFACEEXTPROC)(EGLDisplay dpy, EGLConfig config, void* native_window, const EGLint* attrib_list);
typedef EGLImageKHR(EGLAPIENTRYP PFNEGLCREATEIMAGEKHRPROC)(EGLDisplay dpy, EGLContext ctx, EGLenum target, EGLClientBuffer buffer, const EGLint* attrib_list);
typedef EGLBoolean(EGLAPIENTRYP PFNEGLDESTROYIMAGEKHRPROC)(EGLDisplay dpy, EGLImageKHR image);
typedef void(GL_APIENTRYP PFNGLEGLIMAGETARGETTEXTURE2DOESPROC)(GLenum target, GLeglImageOES image);
typedef void(GL_APIENTRYP PFNGLEGLIMAGETARGETRENDERBUFFERSTORAGEOESPROC)(GLenum target, GLeglImageOES image);
struct gbm_device;
class CEGL {
public:
CEGL(gbm_device*);
~CEGL();
EGLDisplay eglDisplay = nullptr;
EGLConfig eglConfig = nullptr;
EGLContext eglContext = nullptr;
PFNEGLCREATEPLATFORMWINDOWSURFACEEXTPROC eglCreatePlatformWindowSurfaceEXT = nullptr;
PFNEGLCREATEIMAGEKHRPROC eglCreateImageKHR = nullptr;
PFNEGLDESTROYIMAGEKHRPROC eglDestroyImageKHR = nullptr;
PFNGLEGLIMAGETARGETTEXTURE2DOESPROC glEGLImageTargetTexture2DOES = nullptr;
PFNGLEGLIMAGETARGETRENDERBUFFERSTORAGEOESPROC glEGLImageTargetRenderbufferStorageOES = nullptr;
void makeCurrent(EGLSurface surf);
EGLImageKHR getEglImage(const SDMABUFAttrs& attrs);
void destroyEglImage(EGLImageKHR image);
};
inline std::unique_ptr<CEGL> g_pEGL;

View file

@ -2,6 +2,11 @@
#include "../Hyprpaper.hpp"
#include "protocols/wlr-layer-shell-unstable-v1.hpp"
#include "protocols/wayland.hpp"
#include "protocols/fractional-scale-v1.hpp"
#include "protocols/viewporter.hpp"
CLayerSurface::CLayerSurface(SMonitor* pMonitor) {
m_pMonitor = pMonitor;

View file

@ -1,10 +1,11 @@
#pragma once
#include "../defines.hpp"
#include "protocols/fractional-scale-v1.hpp"
#include "protocols/viewporter.hpp"
#include "protocols/wayland.hpp"
#include "protocols/wlr-layer-shell-unstable-v1.hpp"
class CCZwlrLayerSurfaceV1;
class CCWlSurface;
class CCWpFractionalScaleV1;
class CCWpViewport;
struct SMonitor;

207
src/render/Math.cpp Normal file
View file

@ -0,0 +1,207 @@
#include "Math.hpp"
#include <unordered_map>
#include <cstring>
#include <cmath>
void matrixIdentity(float mat[9]) {
static const float identity[9] = {
1.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f,
};
memcpy(mat, identity, sizeof(identity));
}
void matrixMultiply(float mat[9], const float a[9], const float b[9]) {
float product[9];
product[0] = a[0] * b[0] + a[1] * b[3] + a[2] * b[6];
product[1] = a[0] * b[1] + a[1] * b[4] + a[2] * b[7];
product[2] = a[0] * b[2] + a[1] * b[5] + a[2] * b[8];
product[3] = a[3] * b[0] + a[4] * b[3] + a[5] * b[6];
product[4] = a[3] * b[1] + a[4] * b[4] + a[5] * b[7];
product[5] = a[3] * b[2] + a[4] * b[5] + a[5] * b[8];
product[6] = a[6] * b[0] + a[7] * b[3] + a[8] * b[6];
product[7] = a[6] * b[1] + a[7] * b[4] + a[8] * b[7];
product[8] = a[6] * b[2] + a[7] * b[5] + a[8] * b[8];
memcpy(mat, product, sizeof(product));
}
void matrixTranspose(float mat[9], const float a[9]) {
float transposition[9] = {
a[0], a[3], a[6], a[1], a[4], a[7], a[2], a[5], a[8],
};
memcpy(mat, transposition, sizeof(transposition));
}
void matrixTranslate(float mat[9], float x, float y) {
float translate[9] = {
1.0f, 0.0f, x, 0.0f, 1.0f, y, 0.0f, 0.0f, 1.0f,
};
matrixMultiply(mat, mat, translate);
}
void matrixScale(float mat[9], float x, float y) {
float scale[9] = {
x, 0.0f, 0.0f, 0.0f, y, 0.0f, 0.0f, 0.0f, 1.0f,
};
matrixMultiply(mat, mat, scale);
}
void matrixRotate(float mat[9], float rad) {
float rotate[9] = {
(float)cos(rad), (float)-sin(rad), 0.0f, (float)sin(rad), (float)cos(rad), 0.0f, 0.0f, 0.0f, 1.0f,
};
matrixMultiply(mat, mat, rotate);
}
std::unordered_map<eTransform, std::array<float, 9>> transforms = {
{HYPRUTILS_TRANSFORM_NORMAL,
{
1.0f,
0.0f,
0.0f,
0.0f,
1.0f,
0.0f,
0.0f,
0.0f,
1.0f,
}},
{HYPRUTILS_TRANSFORM_90,
{
0.0f,
1.0f,
0.0f,
-1.0f,
0.0f,
0.0f,
0.0f,
0.0f,
1.0f,
}},
{HYPRUTILS_TRANSFORM_180,
{
-1.0f,
0.0f,
0.0f,
0.0f,
-1.0f,
0.0f,
0.0f,
0.0f,
1.0f,
}},
{HYPRUTILS_TRANSFORM_270,
{
0.0f,
-1.0f,
0.0f,
1.0f,
0.0f,
0.0f,
0.0f,
0.0f,
1.0f,
}},
{HYPRUTILS_TRANSFORM_FLIPPED,
{
-1.0f,
0.0f,
0.0f,
0.0f,
1.0f,
0.0f,
0.0f,
0.0f,
1.0f,
}},
{HYPRUTILS_TRANSFORM_FLIPPED_90,
{
0.0f,
1.0f,
0.0f,
1.0f,
0.0f,
0.0f,
0.0f,
0.0f,
1.0f,
}},
{HYPRUTILS_TRANSFORM_FLIPPED_180,
{
1.0f,
0.0f,
0.0f,
0.0f,
-1.0f,
0.0f,
0.0f,
0.0f,
1.0f,
}},
{HYPRUTILS_TRANSFORM_FLIPPED_270,
{
0.0f,
-1.0f,
0.0f,
-1.0f,
0.0f,
0.0f,
0.0f,
0.0f,
1.0f,
}},
};
void matrixTransform(float mat[9], eTransform transform) {
matrixMultiply(mat, mat, transforms.at(transform).data());
}
void matrixProjection(float mat[9], int width, int height, eTransform transform) {
memset(mat, 0, sizeof(*mat) * 9);
const float* t = transforms.at(transform).data();
float x = 2.0f / width;
float y = 2.0f / height;
// Rotation + reflection
mat[0] = x * t[0];
mat[1] = x * t[1];
mat[3] = y * -t[3];
mat[4] = y * -t[4];
// Translation
mat[2] = -copysign(1.0f, mat[0] + mat[1]);
mat[5] = -copysign(1.0f, mat[3] + mat[4]);
// Identity
mat[8] = 1.0f;
}
void projectBox(float mat[9], CBox& box, eTransform transform, float rotation, const float projection[9]) {
double x = box.x;
double y = box.y;
double width = box.width;
double height = box.height;
matrixIdentity(mat);
matrixTranslate(mat, x, y);
if (rotation != 0) {
matrixTranslate(mat, width / 2, height / 2);
matrixRotate(mat, rotation);
matrixTranslate(mat, -width / 2, -height / 2);
}
matrixScale(mat, width, height);
if (transform != HYPRUTILS_TRANSFORM_NORMAL) {
matrixTranslate(mat, 0.5, 0.5);
matrixTransform(mat, transform);
matrixTranslate(mat, -0.5, -0.5);
}
matrixMultiply(mat, projection, mat);
}

18
src/render/Math.hpp Normal file
View file

@ -0,0 +1,18 @@
#pragma once
// FIXME: migrate this to utils
// includes box and vector as well
#include <hyprutils/math/Region.hpp>
using namespace Hyprutils::Math;
void projectBox(float mat[9], CBox& box, eTransform transform, float rotation, const float projection[9]);
void matrixProjection(float mat[9], int width, int height, eTransform transform);
void matrixTransform(float mat[9], eTransform transform);
void matrixRotate(float mat[9], float rad);
void matrixScale(float mat[9], float x, float y);
void matrixTranslate(float mat[9], float x, float y);
void matrixTranspose(float mat[9], const float a[9]);
void matrixMultiply(float mat[9], const float a[9], const float b[9]);
void matrixIdentity(float mat[9]);

467
src/render/Renderer.cpp Normal file
View file

@ -0,0 +1,467 @@
#include "Renderer.hpp"
#include "protocols/linux-dmabuf-v1.hpp"
#include "protocols/wlr-layer-shell-unstable-v1.hpp"
#include "protocols/wayland.hpp"
#include "protocols/viewporter.hpp"
#include "../Hyprpaper.hpp"
#include <xf86drm.h>
#include "Buffer.hpp"
#include "LayerSurface.hpp"
#include "Egl.hpp"
#include <EGL/egl.h>
#include <GLES3/gl32.h>
#include <GLES3/gl3ext.h>
#include <GLES2/gl2ext.h>
#include <GLES2/gl2.h>
#include <hyprutils/math/Box.hpp>
#include "Math.hpp"
using namespace Hyprutils::Math;
// ------------------- shader utils
GLuint compileShader(const GLuint& type, std::string src) {
auto shader = glCreateShader(type);
auto shaderSource = src.c_str();
glShaderSource(shader, 1, (const GLchar**)&shaderSource, nullptr);
glCompileShader(shader);
GLint ok;
glGetShaderiv(shader, GL_COMPILE_STATUS, &ok);
if (ok == GL_FALSE)
return 0;
return shader;
}
GLuint createProgram(const std::string& vert, const std::string& frag) {
auto vertCompiled = compileShader(GL_VERTEX_SHADER, vert);
if (vertCompiled == 0)
return 0;
auto fragCompiled = compileShader(GL_FRAGMENT_SHADER, frag);
if (fragCompiled == 0)
return 0;
auto prog = glCreateProgram();
glAttachShader(prog, vertCompiled);
glAttachShader(prog, fragCompiled);
glLinkProgram(prog);
glDetachShader(prog, vertCompiled);
glDetachShader(prog, fragCompiled);
glDeleteShader(vertCompiled);
glDeleteShader(fragCompiled);
GLint ok;
glGetProgramiv(prog, GL_LINK_STATUS, &ok);
if (ok == GL_FALSE)
return 0;
return prog;
}
inline const std::string VERT_SRC = R"#(
uniform mat3 proj;
attribute vec2 pos;
attribute vec2 texcoord;
varying vec2 v_texcoord;
void main() {
gl_Position = vec4(proj * vec3(pos, 1.0), 1.0);
v_texcoord = texcoord;
})#";
inline const std::string FRAG_SRC = R"#(
precision highp float;
varying vec2 v_texcoord; // is in 0-1
uniform sampler2D tex;
void main() {
gl_FragColor = texture2D(tex, v_texcoord);
})#";
inline const std::string FRAG_SRC_EXT = R"#(
#extension GL_OES_EGL_image_external : require
precision highp float;
varying vec2 v_texcoord; // is in 0-1
uniform samplerExternalOES texture0;
void main() {
gl_FragColor = texture2D(texture0, v_texcoord);
})#";
// -------------------
CRenderer::CRenderer(bool gpu) {
if (gpu) {
dmabufFeedback = makeShared<CCZwpLinuxDmabufFeedbackV1>(g_pHyprpaper->m_pLinuxDmabuf->sendGetDefaultFeedback());
dmabufFeedback->setMainDevice([this](CCZwpLinuxDmabufFeedbackV1* r, wl_array* deviceArr) {
dev_t device;
if (deviceArr->size != sizeof(device))
abort();
memcpy(&device, deviceArr->data, sizeof(device));
drmDevice* drmDev;
if (drmGetDeviceFromDevId(device, /* flags */ 0, &drmDev) != 0) {
Debug::log(ERR, "zwp_linux_dmabuf_v1: drmGetDeviceFromDevId failed");
return;
}
const char* name = nullptr;
if (drmDev->available_nodes & (1 << DRM_NODE_RENDER))
name = drmDev->nodes[DRM_NODE_RENDER];
else {
// Likely a split display/render setup. Pick the primary node and hope
// Mesa will open the right render node under-the-hood.
if (!(drmDev->available_nodes & (1 << DRM_NODE_PRIMARY)))
abort();
name = drmDev->nodes[DRM_NODE_PRIMARY];
Debug::log(WARN, "zwp_linux_dmabuf_v1: DRM device has no render node, using primary");
}
if (!name) {
Debug::log(ERR, "zwp_linux_dmabuf_v1: no node name");
return;
}
nodeName = name;
drmFreeDevice(&drmDev);
Debug::log(LOG, "zwp_linux_dmabuf_v1: got node %s", nodeName.c_str());
});
wl_display_roundtrip(g_pHyprpaper->m_sDisplay);
if (!nodeName.empty()) {
nodeFD = open(nodeName.c_str(), O_RDWR | O_NONBLOCK | O_CLOEXEC);
if (nodeFD < 0) {
Debug::log(ERR, "zwp_linux_dmabuf_v1: failed to open node");
return;
}
Debug::log(LOG, "zwp_linux_dmabuf_v1: opened node %s with fd %i", nodeName.c_str(), nodeFD);
gbmDevice = gbm_create_device(nodeFD);
}
g_pEGL = std::make_unique<CEGL>(gbmDevice);
g_pEGL->makeCurrent(EGL_NO_SURFACE);
gl.shader.program = createProgram(VERT_SRC, FRAG_SRC);
if (gl.shader.program == 0) {
Debug::log(ERR, "renderer: failed to link shader");
return;
}
gl.shader.proj = glGetUniformLocation(gl.shader.program, "proj");
gl.shader.posAttrib = glGetAttribLocation(gl.shader.program, "pos");
gl.shader.texAttrib = glGetAttribLocation(gl.shader.program, "texcoord");
gl.shader.tex = glGetUniformLocation(gl.shader.program, "tex");
}
}
void CRenderer::renderWallpaperForMonitor(SMonitor* pMonitor) {
const auto PBUFFER = getOrCreateBuffer(pMonitor);
if (!g_pHyprpaper->m_mMonitorActiveWallpaperTargets[pMonitor])
g_pHyprpaper->recheckMonitor(pMonitor);
if (gbmDevice)
renderGpu(pMonitor, PBUFFER);
else
renderCpu(pMonitor, PBUFFER);
if (pMonitor->pCurrentLayerSurface) {
pMonitor->pCurrentLayerSurface->pSurface->sendAttach(PBUFFER->buffer.get(), 0, 0);
pMonitor->pCurrentLayerSurface->pSurface->sendSetBufferScale(pMonitor->pCurrentLayerSurface->pFractionalScaleInfo ? 1 : pMonitor->scale);
pMonitor->pCurrentLayerSurface->pSurface->sendDamageBuffer(0, 0, 0xFFFF, 0xFFFF);
// our wps are always opaque
auto opaqueRegion = makeShared<CCWlRegion>(g_pHyprpaper->m_pCompositor->sendCreateRegion());
opaqueRegion->sendAdd(0, 0, PBUFFER->pixelSize.x, PBUFFER->pixelSize.y);
pMonitor->pCurrentLayerSurface->pSurface->sendSetOpaqueRegion(opaqueRegion.get());
if (pMonitor->pCurrentLayerSurface->pFractionalScaleInfo) {
Debug::log(LOG, "Submitting viewport dest size %ix%i for %x", static_cast<int>(std::round(pMonitor->size.x)), static_cast<int>(std::round(pMonitor->size.y)),
pMonitor->pCurrentLayerSurface);
pMonitor->pCurrentLayerSurface->pViewport->sendSetDestination(static_cast<int>(std::round(pMonitor->size.x)), static_cast<int>(std::round(pMonitor->size.y)));
}
pMonitor->pCurrentLayerSurface->pSurface->sendCommit();
}
// check if we dont need to remove a wallpaper
if (pMonitor->layerSurfaces.size() > 1) {
for (auto it = pMonitor->layerSurfaces.begin(); it != pMonitor->layerSurfaces.end(); it++) {
if (pMonitor->pCurrentLayerSurface != it->get()) {
pMonitor->layerSurfaces.erase(it);
break;
}
}
}
}
SP<CBuffer> CRenderer::getOrCreateBuffer(SMonitor* pMonitor) {
Vector2D size = pMonitor->size;
if (pMonitor->pCurrentLayerSurface)
size = size * pMonitor->pCurrentLayerSurface->fScale;
if (monitorBuffers.contains(pMonitor) && monitorBuffers.at(pMonitor)->pixelSize == size)
return monitorBuffers.at(pMonitor);
auto buf = makeShared<CBuffer>(size);
monitorBuffers[pMonitor] = buf;
return buf;
}
void CRenderer::renderCpu(SMonitor* pMonitor, SP<CBuffer> buf) {
renderWallpaper(pMonitor, buf->cpu.surface, buf->cpu.cairo);
}
SGLTex CRenderer::glTex(void* cairoData, const Vector2D& size) {
SGLTex tex;
glGenTextures(1, &tex.texid);
glBindTexture(GL_TEXTURE_2D, tex.texid);
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_SWIZZLE_R, GL_BLUE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_B, GL_RED);
glPixelStorei(GL_UNPACK_ROW_LENGTH_EXT, (size.x * 4) / 4);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, size.x, size.y, 0, GL_RGBA, GL_UNSIGNED_BYTE, cairoData);
glPixelStorei(GL_UNPACK_ROW_LENGTH_EXT, 0);
glBindTexture(GL_TEXTURE_2D, 0);
return tex;
}
inline const float fullVerts[] = {
1, 0, // top right
0, 0, // top left
1, 1, // bottom right
0, 1, // bottom left
};
void CRenderer::renderGpu(SMonitor* pMonitor, SP<CBuffer> buf) {
// TODO: get the image in 10b
// TODO: actually render with the gpu
auto pCairoSurface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, buf->pixelSize.x, buf->pixelSize.y);
auto pCairo = cairo_create(pCairoSurface);
renderWallpaper(pMonitor, pCairoSurface, pCairo, true);
cairo_destroy(pCairo);
g_pEGL->makeCurrent(EGL_NO_SURFACE);
SGLTex fromTex = glTex(cairo_image_surface_get_data(pCairoSurface), buf->pixelSize);
GLuint rboID = 0, fboID = 0;
glGenRenderbuffers(1, &rboID);
glBindRenderbuffer(GL_RENDERBUFFER, rboID);
g_pEGL->glEGLImageTargetRenderbufferStorageOES(GL_RENDERBUFFER, (GLeglImageOES)buf->gpu.eglImage);
glBindRenderbuffer(GL_RENDERBUFFER, 0);
glGenFramebuffers(1, &fboID);
glBindFramebuffer(GL_FRAMEBUFFER, fboID);
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, rboID);
if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) {
Debug::log(ERR, "EGL: failed to create a rbo");
return;
}
glClearColor(0.77F, 0.F, 0.74F, 1.F);
glClear(GL_COLOR_BUFFER_BIT);
glEnable(GL_BLEND);
glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
// done, let's render the texture to the rbo
// first, render the bg
double scale = 1.0;
Vector2D origin;
const auto PWALLPAPERTARGET = g_pHyprpaper->m_mMonitorActiveWallpaperTargets[pMonitor];
const auto CONTAIN = g_pHyprpaper->m_mMonitorWallpaperRenderData[pMonitor->name].contain;
const double SURFACESCALE = pMonitor->pCurrentLayerSurface && pMonitor->pCurrentLayerSurface->pFractionalScaleInfo ? pMonitor->pCurrentLayerSurface->fScale : pMonitor->scale;
const Vector2D DIMENSIONS = Vector2D{std::round(pMonitor->size.x * SURFACESCALE), std::round(pMonitor->size.y * SURFACESCALE)};
const bool LOWASPECTRATIO = pMonitor->size.x / pMonitor->size.y > PWALLPAPERTARGET->m_vSize.x / PWALLPAPERTARGET->m_vSize.y;
if ((CONTAIN && !LOWASPECTRATIO) || (!CONTAIN && LOWASPECTRATIO)) {
scale = DIMENSIONS.x / PWALLPAPERTARGET->m_vSize.x;
origin.y = -(PWALLPAPERTARGET->m_vSize.y * scale - DIMENSIONS.y) / 2.0 / scale;
} else {
scale = DIMENSIONS.y / PWALLPAPERTARGET->m_vSize.y;
origin.x = -(PWALLPAPERTARGET->m_vSize.x * scale - DIMENSIONS.x) / 2.0 / scale;
}
renderTexture(PWALLPAPERTARGET->gpu.textureID, CBox{origin, PWALLPAPERTARGET->m_vSize * scale}, buf->pixelSize);
// then, any decoration we got from cairo
renderTexture(fromTex.texid, {{}, buf->pixelSize}, buf->pixelSize);
// rendered, cleanup
glFlush();
glBindFramebuffer(GL_FRAMEBUFFER, 0);
glBindRenderbuffer(GL_RENDERBUFFER, 0);
glDeleteFramebuffers(1, &fboID);
glDeleteRenderbuffers(1, &rboID);
glDeleteTextures(1, &fromTex.texid);
cairo_surface_destroy(pCairoSurface);
}
void CRenderer::renderTexture(GLuint texid, const CBox& box, const Vector2D& viewport) {
CBox renderBox = {{}, viewport};
float mtx[9];
float base[9];
float monitorProj[9];
matrixIdentity(base);
auto& SHADER = gl.shader;
// KMS uses flipped y, we have to do FLIPPED_180
matrixTranslate(base, viewport.x / 2.0, viewport.y / 2.0);
matrixTransform(base, HYPRUTILS_TRANSFORM_FLIPPED_180);
matrixTranslate(base, -viewport.x / 2.0, -viewport.y / 2.0);
projectBox(mtx, renderBox, HYPRUTILS_TRANSFORM_FLIPPED_180, 0, base);
matrixProjection(monitorProj, viewport.x, viewport.y, HYPRUTILS_TRANSFORM_FLIPPED_180);
float glMtx[9];
matrixMultiply(glMtx, monitorProj, mtx);
glViewport(0, 0, viewport.x, viewport.y);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, texid);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glUseProgram(SHADER.program);
glDisable(GL_SCISSOR_TEST);
matrixTranspose(glMtx, glMtx);
glUniformMatrix3fv(SHADER.proj, 1, GL_FALSE, glMtx);
glUniform1i(SHADER.tex, 0);
glVertexAttribPointer(SHADER.posAttrib, 2, GL_FLOAT, GL_FALSE, 0, fullVerts);
glVertexAttribPointer(SHADER.texAttrib, 2, GL_FLOAT, GL_FALSE, 0, fullVerts);
glEnableVertexAttribArray(SHADER.posAttrib);
glEnableVertexAttribArray(SHADER.texAttrib);
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
glDisableVertexAttribArray(SHADER.posAttrib);
glDisableVertexAttribArray(SHADER.texAttrib);
glBindTexture(GL_TEXTURE_2D, 0);
}
void CRenderer::renderWallpaper(SMonitor* pMonitor, cairo_surface_t* pCairoSurface, cairo_t* pCairo, bool splashOnly) {
static auto* const PRENDERSPLASH = reinterpret_cast<Hyprlang::INT* const*>(g_pConfigManager->config->getConfigValuePtr("splash")->getDataStaticPtr());
static auto* const PSPLASHOFFSET = reinterpret_cast<Hyprlang::FLOAT* const*>(g_pConfigManager->config->getConfigValuePtr("splash_offset")->getDataStaticPtr());
const auto PWALLPAPERTARGET = g_pHyprpaper->m_mMonitorActiveWallpaperTargets[pMonitor];
const auto CONTAIN = g_pHyprpaper->m_mMonitorWallpaperRenderData[pMonitor->name].contain;
if (!PWALLPAPERTARGET) {
Debug::log(CRIT, "wallpaper target null in render??");
exit(1);
}
const double SURFACESCALE = pMonitor->pCurrentLayerSurface && pMonitor->pCurrentLayerSurface->pFractionalScaleInfo ? pMonitor->pCurrentLayerSurface->fScale : pMonitor->scale;
const Vector2D DIMENSIONS = Vector2D{std::round(pMonitor->size.x * SURFACESCALE), std::round(pMonitor->size.y * SURFACESCALE)};
cairo_save(pCairo);
cairo_set_source_rgba(pCairo, 0, 0, 0, 0);
cairo_set_operator(pCairo, CAIRO_OPERATOR_SOURCE);
cairo_paint(pCairo);
cairo_restore(pCairo);
// always draw a black background behind the wallpaper
if (!splashOnly) {
cairo_set_source_rgb(pCairo, 0, 0, 0);
cairo_rectangle(pCairo, 0, 0, DIMENSIONS.x, DIMENSIONS.y);
cairo_fill(pCairo);
cairo_surface_flush(pCairoSurface);
}
// get scale
double scale;
Vector2D origin;
const bool LOWASPECTRATIO = pMonitor->size.x / pMonitor->size.y > PWALLPAPERTARGET->m_vSize.x / PWALLPAPERTARGET->m_vSize.y;
if ((CONTAIN && !LOWASPECTRATIO) || (!CONTAIN && LOWASPECTRATIO)) {
scale = DIMENSIONS.x / PWALLPAPERTARGET->m_vSize.x;
origin.y = -(PWALLPAPERTARGET->m_vSize.y * scale - DIMENSIONS.y) / 2.0 / scale;
} else {
scale = DIMENSIONS.y / PWALLPAPERTARGET->m_vSize.y;
origin.x = -(PWALLPAPERTARGET->m_vSize.x * scale - DIMENSIONS.x) / 2.0 / scale;
}
cairo_scale(pCairo, scale, scale);
if (!splashOnly) {
Debug::log(LOG, "Image data for %s: %s at [%.2f, %.2f], scale: %.2f (original image size: [%i, %i])", pMonitor->name.c_str(), PWALLPAPERTARGET->m_szPath.c_str(), origin.x,
origin.y, scale, (int)PWALLPAPERTARGET->m_vSize.x, (int)PWALLPAPERTARGET->m_vSize.y);
cairo_set_source_surface(pCairo, PWALLPAPERTARGET->cpu.cairoSurface, origin.x, origin.y);
cairo_paint(pCairo);
}
if (**PRENDERSPLASH && getenv("HYPRLAND_INSTANCE_SIGNATURE")) {
auto SPLASH = execAndGet("hyprctl splash");
SPLASH.pop_back();
Debug::log(LOG, "Rendering splash: %s", SPLASH.c_str());
cairo_select_font_face(pCairo, "Sans", CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_NORMAL);
const auto FONTSIZE = (int)(DIMENSIONS.y / 76.0 / scale);
cairo_set_font_size(pCairo, FONTSIZE);
static auto* const PSPLASHCOLOR = reinterpret_cast<Hyprlang::INT* const*>(g_pConfigManager->config->getConfigValuePtr("splash_color")->getDataStaticPtr());
Debug::log(LOG, "Splash color: %x", **PSPLASHCOLOR);
cairo_set_source_rgba(pCairo, ((**PSPLASHCOLOR >> 16) & 0xFF) / 255.0, ((**PSPLASHCOLOR >> 8) & 0xFF) / 255.0, (**PSPLASHCOLOR & 0xFF) / 255.0,
((**PSPLASHCOLOR >> 24) & 0xFF) / 255.0);
cairo_text_extents_t textExtents;
cairo_text_extents(pCairo, SPLASH.c_str(), &textExtents);
cairo_move_to(pCairo, ((DIMENSIONS.x - textExtents.width * scale) / 2.0) / scale, ((DIMENSIONS.y * (100 - **PSPLASHOFFSET)) / 100 - textExtents.height * scale) / scale);
Debug::log(LOG, "Splash font size: %d, pos: %.2f, %.2f", FONTSIZE, (DIMENSIONS.x - textExtents.width) / 2.0 / scale,
((DIMENSIONS.y * (100 - **PSPLASHOFFSET)) / 100 - textExtents.height * scale) / scale);
cairo_show_text(pCairo, SPLASH.c_str());
}
cairo_restore(pCairo);
cairo_surface_flush(pCairoSurface);
}

52
src/render/Renderer.hpp Normal file
View file

@ -0,0 +1,52 @@
#pragma once
#include "../defines.hpp"
#include <gbm.h>
#include <unordered_map>
#include <hyprutils/math/Box.hpp>
using namespace Hyprutils::Math;
struct SMonitor;
class CBuffer;
class CCZwpLinuxDmabufFeedbackV1;
struct SGLTex {
void* image = nullptr;
GLuint texid = 0;
GLuint target = GL_TEXTURE_2D;
};
class CRenderer {
public:
CRenderer(bool gpu);
gbm_device* gbmDevice = nullptr;
std::string nodeName = "";
int nodeFD = -1;
void renderWallpaperForMonitor(SMonitor* pMonitor);
SGLTex glTex(void* cairoData, const Vector2D& size);
// no need for double-buffering as we don't update the images
std::unordered_map<SMonitor*, SP<CBuffer>> monitorBuffers;
private:
SP<CCZwpLinuxDmabufFeedbackV1> dmabufFeedback;
void renderCpu(SMonitor* pMonitor, SP<CBuffer> buf);
void renderGpu(SMonitor* pMonitor, SP<CBuffer> buf);
void renderWallpaper(SMonitor* pMonitor, cairo_surface_t* pCairoSurface, cairo_t* pCairo, bool splashOnly = false);
void renderTexture(GLuint texid, const CBox& box, const Vector2D& viewport);
SP<CBuffer> getOrCreateBuffer(SMonitor* pMonitor);
struct {
struct SShader {
GLuint program = 0;
GLint proj = -1, tex = -1, posAttrib = -1, texAttrib = -1;
} shader;
} gl;
};
inline std::unique_ptr<CRenderer> g_pRenderer;

View file

@ -2,9 +2,14 @@
#include <chrono>
#include <magic.h>
#include "Egl.hpp"
#include "Renderer.hpp"
CWallpaperTarget::~CWallpaperTarget() {
cairo_surface_destroy(m_pCairoSurface);
if (cpu.cairoSurface)
cairo_surface_destroy(cpu.cairoSurface);
if (gpu.textureID)
glDeleteTextures(1, &gpu.textureID);
}
void CWallpaperTarget::create(const std::string& path) {
@ -57,5 +62,16 @@ void CWallpaperTarget::create(const std::string& path) {
Debug::log(LOG, "Preloaded target %s in %.2fms -> Pixel size: [%i, %i]", path.c_str(), MS, (int)m_vSize.x, (int)m_vSize.y);
m_pCairoSurface = CAIROSURFACE;
if (!g_pEGL) {
cpu.cairoSurface = CAIROSURFACE;
return;
}
Debug::log(LOG, "GPU mode, uploading the preloaded image into VRAM and deleting from RAM");
auto tex = g_pRenderer->glTex(cairo_image_surface_get_data(CAIROSURFACE), m_vSize);
gpu.textureID = tex.texid;
cairo_surface_destroy(CAIROSURFACE);
}

View file

@ -17,5 +17,11 @@ class CWallpaperTarget {
bool m_bHasAlpha = true;
cairo_surface_t* m_pCairoSurface;
struct {
cairo_surface_t* cairoSurface = nullptr;
} cpu;
struct {
uint32_t textureID = 0;
} gpu;
};